none
WPF MVVM двусторонний биндинг изменений в представлении и изменений в модели RRS feed

  • Вопрос

  • Чтобы работал биндинг (двусторонний и односторонний) на какое-либо открытое свойство экземпляра пользовательского класса, оно обязательно должно быть свойством зависимостей? К примеру, у меня есть класс Cars, я пишу какой-то UI на WPF, и мне понадобилось сделать контрол, всегда показывающий актуальное количество машин Count . Мне что, нужно весь класс Cars наследовать от DependencyObject и из Count делать свойство зависимостей?



    • Изменено Qwester33 9 ноября 2011 г. 14:04
    7 ноября 2011 г. 15:58

Ответы

  • > Чтобы работал биндинг (двусторонний и односторонний) на какое-либо открытое свойство экземпляра пользовательского класса, оно обязательно должно быть свойством зависимостей?


    нет, не обязательно. можно в классе реализовать INotifyPropertyChanged

    using System.Windows;
    using System.Windows.Controls;
    using System.ComponentModel;
    using System;
    
    namespace WpfApplication5
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new Test();
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                (this.DataContext as Test).Value = DateTime.Now.ToString();
            }
        }
    
    
        class Test : INotifyPropertyChanged
        {
            public string Value 
            {
                get { return _Value;}
                set 
                {
                    _Value = value;
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
                }
            }
            string _Value;
    
            public event PropertyChangedEventHandler PropertyChanged = delegate {};
        }
    }
    
    <Window x:Class="WpfApplication5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:WpfApplication5"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <Button Content="open" Click="Button_Click"  HorizontalAlignment="Left"  />
            <TextBox Text="{Binding Value}"></TextBox>
            <TextBlock Text="{Binding Value}"></TextBlock>
        </StackPanel>
    </Window>
    
    
    • Помечено в качестве ответа Qwester33 7 ноября 2011 г. 16:30
    7 ноября 2011 г. 16:15
  • уведомить UI об изменениях в объекте можно и без INotifyPropertyChanged.
    дело в том, что привязки работают через дескрипторы свойств.
    список дескрипторов можно сформировать, т.е. создать виртуальные свойства.
     
    пример "магии":
     

    <Window x:Class="WpfApplication5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:WpfApplication5"
            FontSize="14"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <Button Content="test" Click="Button_Click"  HorizontalAlignment="Left"  />
            <RichTextBox>
                <FlowDocument>
                    <Paragraph Margin="0,0,0,0">
                        <Run>Test.Text (virtual property): </Run>
                        <Run Text="{Binding Text}" Background="Yellow" />
                    </Paragraph>
                    <Paragraph Margin="0,0,0,0">
                        <Run>Test.Value: </Run>
                        <Run Text="{Binding Value}" Background="WhiteSmoke" />
                    </Paragraph>
                </FlowDocument>
            </RichTextBox>
            <TextBlock Text="{Binding Text}" Background="WhiteSmoke" />
            <TextBlock Text="{Binding Value}" Background="WhiteSmoke" Margin="0,5,0,0" />
        </StackPanel>
    </Window>
    
    using System.Windows;
    using System.Windows.Controls;
    using System.ComponentModel;
    using System;
    using System.Collections;
    
    namespace WpfApplication5
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                TypeDescriptor.AddProvider(new TestDescriptionProvider(), typeof(Test));
                this.DataContext = new Test() { Value = "hello" };
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                this.DataContext.SetValue("Text", DateTime.Now.ToString());
                (this.DataContext as Test).Value += "!";
            }
        }
    
        class Test
        {
            public string Value { get; set; }
        }
       
        public static class PropertyDescriptorHelper
        {
            public static void SetValue(this object o, string pd, object val)
            {
                var pdc = TypeDescriptor.GetProperties(o);
                pdc[pd].SetValue(o, val);
            }
        }
        class TestDescriptionProvider : TypeDescriptionProvider
        {
            ICustomTypeDescriptor _Desc;
            public TestDescriptionProvider()
            {
                _Desc = new Descriptor(
                    new Property<Test, string>("Text")
                );
            }
            public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { return _Desc; }
        }
        class Descriptor : CustomTypeDescriptor
        {
            PropertyDescriptorCollection _Props;
            public Descriptor(params PropertyDescriptor[] props) { _Props = new PropertyDescriptorCollection(props); }
            public override PropertyDescriptorCollection GetProperties() { return _Props; }
        }
        class Property<C, T> : PropertyDescriptor
        {
            public Property(string name, params Attribute[] attrs)
                : base(name, attrs)
            {
                _Values = new Hashtable();
            }
            public override bool SupportsChangeEvents { get { return true; } }
            public override Type PropertyType { get { return typeof(T); } }
            public override Type ComponentType { get { return typeof(C); } }
            public override bool IsReadOnly { get { return false; } }
            public override void ResetValue(object component) { }
            public override bool ShouldSerializeValue(object component) { return false; }
            public override bool CanResetValue(object component) { return false; }
            public override object GetValue(object component) { return _Values[component]; }
            public override void SetValue(object component, object value)
            {
                _Values[component] = value;
                this.OnValueChanged(component, EventArgs.Empty);
            }
            Hashtable _Values;
        }
    }
    

     

    • Изменено Malobukv 7 ноября 2011 г. 17:25
    • Помечено в качестве ответа Qwester33 8 ноября 2011 г. 8:00
    7 ноября 2011 г. 17:15
  • Вы увеличиваете Average у DataClass, а должны у ViewModel.

    Если же вы хотите, чтобы по изменению значения в DataClass ViewModel понимал, что что-то изменилось, DataClass должен кидать событие, что свойство поменялось, его будет перехватывать ViewModel и кидать свое событие о том, что такое-то свойство изменилось. Сейчас все логично работает.

    • Помечено в качестве ответа Qwester33 9 ноября 2011 г. 13:35
    9 ноября 2011 г. 9:11
    Отвечающий
  • > По клику должна увеличиваться цифра, но этого не происходит.


    UI привязан к ViewModel, которая транслирует изменения в DataClass.
    поэтому надо изменить ViewModel.

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        (DataContext as ViewModel).Average++;
    }
    

     
    код можно сократить. примерно так:

    using System.ComponentModel;
    using System.Windows;
    
    namespace WpfApplication0
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new ViewModel { Data = new DataClass { Average = 1 }};
            }
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                (this.DataContext as ViewModel).Average++;
            }
        }
        public class DataClass
        {
            public decimal Average { get; set; }
        }
        public class ViewModel : INotifyPropertyChanged
        {
            public DataClass Data;
            public decimal Average
            {
                get { return this.Data.Average; }
                set
                {
                    this.Data.Average = value;
                    OnPropertyChanged("Average");
                }
            }
            public event PropertyChangedEventHandler PropertyChanged = delegate {};
            protected void OnPropertyChanged(string propertyName)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    
    

    • Помечено в качестве ответа Qwester33 9 ноября 2011 г. 13:35
    9 ноября 2011 г. 11:23
  • Предполагается, что просто так у вас не изменится модель. Обычно обновления данных делаются во ViewModel. Например во ViewModel определяется сервис, к которому VM периодически обращается и проверяет изменились ли данные. Если пришли новые данные, VM об этом уже знает и заменяет сама нужные данные, посылая при этом сообщение вьюшке.

    Модель же как правило достаточно пассивна. В Entity Framework-е уже сделаны ивенты за вас. Вы можете просто подписаться на общий ивент во VM и по нему оправлять нотификацию дальше. Кстати, если вы напишите во VM OnPropertyChanged("") - это будет означать, что все поля обновились и их нужно повторно запросить.

    • Помечено в качестве ответа Qwester33 10 ноября 2011 г. 12:31
    9 ноября 2011 г. 14:50
    Отвечающий
  • > После запуска таймера значение меняется, но не отрисовывается. OnPropertyChanged("Average");

    чтобы ViewModel узнал об изменениях в DataClass можно в ViewModel
    подписаться на событие PropertyChanged. примерно так:
    using System.ComponentModel;
    using System.Windows;
    
    namespace WpfApplication0
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new ViewModel(new DataClass());
            }
        }
    
        public class DataClass : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged = delegate { };
            public decimal Average
            {
                get { return _Average; }
                set
                {
                    _Average = value;
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Average"));
                }
            }
            decimal _Average;
        }
        public class ViewModel : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged = delegate { };
            public ViewModel(DataClass dc)
            {
                Data = dc;
                dc.PropertyChanged += new PropertyChangedEventHandler(Data_PropertyChanged);
            }
            void Data_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (e.PropertyName == "Avarage")
                {
                    // здесь можно выполнить свой this.PropertyChanged(...
                }
            }
            public readonly DataClass Data;
        }
    }
    
    но лучше не дублировать код.

    • Изменено Malobukv 9 ноября 2011 г. 16:27
    • Помечено в качестве ответа Qwester33 10 ноября 2011 г. 12:31
    9 ноября 2011 г. 16:26
  • > ... для переменных. То есть, чтобы можно было указать ключевое слово или атрибут при ее объявлении, который бы обозначал автоматическую выдачу уведомлений при ее изменении.


    для этого надо использовать TypeDescriptionProvider. пример есть выше по теме. особенность в том, что уведомление посылается, только если изменение вносится через метод SetValue.

    другой способ - кодогенерация. пример здесь.
    можно генерировать класс-обертку, которая будет транслировать вызовы и посылать уведомления.

    но вообще для хранения и частичной обработки данных лучше/проще использовать DataSet или XElement. в них есть все необходимое для привязки данных и посылки уведомлений об изменениях.

    • Помечено в качестве ответа Qwester33 11 ноября 2011 г. 14:49
    10 ноября 2011 г. 20:45

Все ответы

  • > Чтобы работал биндинг (двусторонний и односторонний) на какое-либо открытое свойство экземпляра пользовательского класса, оно обязательно должно быть свойством зависимостей?


    нет, не обязательно. можно в классе реализовать INotifyPropertyChanged

    using System.Windows;
    using System.Windows.Controls;
    using System.ComponentModel;
    using System;
    
    namespace WpfApplication5
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new Test();
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                (this.DataContext as Test).Value = DateTime.Now.ToString();
            }
        }
    
    
        class Test : INotifyPropertyChanged
        {
            public string Value 
            {
                get { return _Value;}
                set 
                {
                    _Value = value;
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
                }
            }
            string _Value;
    
            public event PropertyChangedEventHandler PropertyChanged = delegate {};
        }
    }
    
    <Window x:Class="WpfApplication5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:WpfApplication5"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <Button Content="open" Click="Button_Click"  HorizontalAlignment="Left"  />
            <TextBox Text="{Binding Value}"></TextBox>
            <TextBlock Text="{Binding Value}"></TextBlock>
        </StackPanel>
    </Window>
    
    
    • Помечено в качестве ответа Qwester33 7 ноября 2011 г. 16:30
    7 ноября 2011 г. 16:15
  • уведомить UI об изменениях в объекте можно и без INotifyPropertyChanged.
    дело в том, что привязки работают через дескрипторы свойств.
    список дескрипторов можно сформировать, т.е. создать виртуальные свойства.
     
    пример "магии":
     

    <Window x:Class="WpfApplication5.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:WpfApplication5"
            FontSize="14"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <Button Content="test" Click="Button_Click"  HorizontalAlignment="Left"  />
            <RichTextBox>
                <FlowDocument>
                    <Paragraph Margin="0,0,0,0">
                        <Run>Test.Text (virtual property): </Run>
                        <Run Text="{Binding Text}" Background="Yellow" />
                    </Paragraph>
                    <Paragraph Margin="0,0,0,0">
                        <Run>Test.Value: </Run>
                        <Run Text="{Binding Value}" Background="WhiteSmoke" />
                    </Paragraph>
                </FlowDocument>
            </RichTextBox>
            <TextBlock Text="{Binding Text}" Background="WhiteSmoke" />
            <TextBlock Text="{Binding Value}" Background="WhiteSmoke" Margin="0,5,0,0" />
        </StackPanel>
    </Window>
    
    using System.Windows;
    using System.Windows.Controls;
    using System.ComponentModel;
    using System;
    using System.Collections;
    
    namespace WpfApplication5
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                TypeDescriptor.AddProvider(new TestDescriptionProvider(), typeof(Test));
                this.DataContext = new Test() { Value = "hello" };
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                this.DataContext.SetValue("Text", DateTime.Now.ToString());
                (this.DataContext as Test).Value += "!";
            }
        }
    
        class Test
        {
            public string Value { get; set; }
        }
       
        public static class PropertyDescriptorHelper
        {
            public static void SetValue(this object o, string pd, object val)
            {
                var pdc = TypeDescriptor.GetProperties(o);
                pdc[pd].SetValue(o, val);
            }
        }
        class TestDescriptionProvider : TypeDescriptionProvider
        {
            ICustomTypeDescriptor _Desc;
            public TestDescriptionProvider()
            {
                _Desc = new Descriptor(
                    new Property<Test, string>("Text")
                );
            }
            public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { return _Desc; }
        }
        class Descriptor : CustomTypeDescriptor
        {
            PropertyDescriptorCollection _Props;
            public Descriptor(params PropertyDescriptor[] props) { _Props = new PropertyDescriptorCollection(props); }
            public override PropertyDescriptorCollection GetProperties() { return _Props; }
        }
        class Property<C, T> : PropertyDescriptor
        {
            public Property(string name, params Attribute[] attrs)
                : base(name, attrs)
            {
                _Values = new Hashtable();
            }
            public override bool SupportsChangeEvents { get { return true; } }
            public override Type PropertyType { get { return typeof(T); } }
            public override Type ComponentType { get { return typeof(C); } }
            public override bool IsReadOnly { get { return false; } }
            public override void ResetValue(object component) { }
            public override bool ShouldSerializeValue(object component) { return false; }
            public override bool CanResetValue(object component) { return false; }
            public override object GetValue(object component) { return _Values[component]; }
            public override void SetValue(object component, object value)
            {
                _Values[component] = value;
                this.OnValueChanged(component, EventArgs.Empty);
            }
            Hashtable _Values;
        }
    }
    

     

    • Изменено Malobukv 7 ноября 2011 г. 17:25
    • Помечено в качестве ответа Qwester33 8 ноября 2011 г. 8:00
    7 ноября 2011 г. 17:15
  • У меня что-то не получается. Я реализовал INotifyPropertyChanged в классе-источнике данных Cars, соответственно описал Count. Меняю значения переменной Count, и вижу, что срабатывает поднятие события в коде этого класса. Следовательно, ошибка где-то дальше.

    Я пытаюсь использовать MVVM, поэтому класс Cars имеет Вьюмодель и биндинг  Представления сделан на нее. Переменная во Вьюмодели описана так:

     

            public int Count
            {
                get { return Cars.Count; }
                set
                {
                    Cars.Count= value;
                    OnPropertyChanged("Count");
                }
            }
    

     


     Вышеприведенный  код из Вьюмодели не выполняется, то есть при изменении Count у меня не сработали брекпойнты, поставленные в него. Брекпойнты срабатывают только при первом запуске, когда Контрол из Представления запрашивает и получает нулевые значения. Значение Count точно меняется, проверил по логу.

     

    Также, во Вьюмодели у меня описаны другие свойства, но они являются обсервб коллекциями, которые нормально отображаются и обновляются, к примеру:

            public ObservableDictionary<Order> Orders
            {
                get { return Cars.Orders; }
                set
                {
                    Cars.Orders = value;
                    OnPropertyChanged("Orders");
                }
            }
    
    То есть, сама Вьюмодель реализована правильно, ошибка либо в приведенной в первом коде реализации свойства Count (но оно полностью аналогично примерам из учебников и из инета), либо у меня принципиальная ошибка где-то во всей этой конструкции.

    Вообще, мне казалось, что при MVVM не надо в классе бизнес-логики (Cars) реализовывать никакие уведомления. Сейчас получается, что не работает ни с ними, ни без них.

     





    • Изменено Qwester33 8 ноября 2011 г. 10:29
    8 ноября 2011 г. 10:21
  • > Я реализовал INotifyPropertyChanged в классе-источнике данных Cars, соответственно
    описал Count. [...] get { return Cars.Count; } [...]
    либо у меня принципиальная ошибка
    где-то во всей этой конструкции.


    по фрагменту непонятен источник проблемы. приведите минимальный компилируемый код, воспроизводящий проблему.

    8 ноября 2011 г. 16:54
  • 1. разметка (представление)
    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
    
        <StackPanel >
            <Button Content="open" Click="Button_Click"  HorizontalAlignment="Left"  />
            <TextBox Text="{Binding Average}"></TextBox>
        </StackPanel>
    </Window>
    

    2. файл кода файла разметки
        public partial class MainWindow : Window
        {
            public DataClass T;
            public int w = 1;
    
            public MainWindow()
            {
                InitializeComponent();
                T = new DataClass();
    
                var viewModel = new ViewModel(T);
                DataContext = viewModel;
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                w += 1;
                T.Average = w;
            }
        }
    

    3. модель
        public class DataClass
        {
            public decimal Average  { get; set; }
    
            public DataClass()
            {
                Average = 1;
            }
        }

    4. вьюмодель
        public class ViewModel : INotifyPropertyChanged
        {
            public DataClass T;
    
            public ViewModel(DataClass unknown)
            {
                T = unknown;
            }
    
            public decimal Average
            {
                get { return T.Average; }
                set
                {
                    T.Average = value;
                    OnPropertyChanged("Average");
                }
            }
    
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected void OnPropertyChanged(string propertyName)
            {
                var handler = PropertyChanged;
    
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }
    


    По клику должна увеличиваться цифра, но этого не происходит.





    • Изменено Qwester33 9 ноября 2011 г. 7:29
    9 ноября 2011 г. 7:22
  • Вы увеличиваете Average у DataClass, а должны у ViewModel.

    Если же вы хотите, чтобы по изменению значения в DataClass ViewModel понимал, что что-то изменилось, DataClass должен кидать событие, что свойство поменялось, его будет перехватывать ViewModel и кидать свое событие о том, что такое-то свойство изменилось. Сейчас все логично работает.

    • Помечено в качестве ответа Qwester33 9 ноября 2011 г. 13:35
    9 ноября 2011 г. 9:11
    Отвечающий
  • > По клику должна увеличиваться цифра, но этого не происходит.


    UI привязан к ViewModel, которая транслирует изменения в DataClass.
    поэтому надо изменить ViewModel.

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        (DataContext as ViewModel).Average++;
    }
    

     
    код можно сократить. примерно так:

    using System.ComponentModel;
    using System.Windows;
    
    namespace WpfApplication0
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new ViewModel { Data = new DataClass { Average = 1 }};
            }
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                (this.DataContext as ViewModel).Average++;
            }
        }
        public class DataClass
        {
            public decimal Average { get; set; }
        }
        public class ViewModel : INotifyPropertyChanged
        {
            public DataClass Data;
            public decimal Average
            {
                get { return this.Data.Average; }
                set
                {
                    this.Data.Average = value;
                    OnPropertyChanged("Average");
                }
            }
            public event PropertyChangedEventHandler PropertyChanged = delegate {};
            protected void OnPropertyChanged(string propertyName)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    
    

    • Помечено в качестве ответа Qwester33 9 ноября 2011 г. 13:35
    9 ноября 2011 г. 11:23
  • Спасибо, ошибку при попытке изменений из Представления я понял. А вот в случае изменений в Модели, мне не удается добиться, чтобы менялось и в представлении:

    Я попытался реализовать в Модели так:

     

        public class DataClass : INotifyPropertyChanged
        {
            private static Timer _timer;
            private static double RefreshInterval = 500;
    
            private decimal _average;
            public decimal Average
            {
                get { return _average; }
                set
                {
                    _average = value;
                    OnPropertyChanged("Average");
                }
            }
    
            public DataClass()
            {
                Average = 1;
            }
    
            public void StartTimer()
            {
                _timer = new Timer { Interval = RefreshInterval };
                _timer.Elapsed += ((source, e) =>
                                       {
                                           Average++;
                                       });
                _timer.Start();
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected void OnPropertyChanged(string propertyName)
            {
                var handler = PropertyChanged;
    
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }
    

    Но так не работает. После запуска таймера значение меняется, но не отрисовывается. OnPropertyChanged("Average");

    выполняется, но представление (вернее, вьюмодель) об этом не узнает.

    Мне надо делать схему с событием по полной программе, с подпиской во Вьюмодели?

     Просто тогда получается, что шаблон MVVM позволяет автоматически отражать в Модели изменения со стороны Представления,

    но не наоборот. Наверное, это связано со свойствами зависимостей? Если бы DataClass.Average было свойством зависимостей, все бы работало? Вьюмодель отлавливала бы изменения в Модели?

    (сорри за кривое редактирование текста, что-то не могу понять почему так получается)








    • Изменено Qwester33 9 ноября 2011 г. 14:02
    9 ноября 2011 г. 13:45
  • Предполагается, что просто так у вас не изменится модель. Обычно обновления данных делаются во ViewModel. Например во ViewModel определяется сервис, к которому VM периодически обращается и проверяет изменились ли данные. Если пришли новые данные, VM об этом уже знает и заменяет сама нужные данные, посылая при этом сообщение вьюшке.

    Модель же как правило достаточно пассивна. В Entity Framework-е уже сделаны ивенты за вас. Вы можете просто подписаться на общий ивент во VM и по нему оправлять нотификацию дальше. Кстати, если вы напишите во VM OnPropertyChanged("") - это будет означать, что все поля обновились и их нужно повторно запросить.

    • Помечено в качестве ответа Qwester33 10 ноября 2011 г. 12:31
    9 ноября 2011 г. 14:50
    Отвечающий
  • > После запуска таймера значение меняется, но не отрисовывается. OnPropertyChanged("Average");

    чтобы ViewModel узнал об изменениях в DataClass можно в ViewModel
    подписаться на событие PropertyChanged. примерно так:
    using System.ComponentModel;
    using System.Windows;
    
    namespace WpfApplication0
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new ViewModel(new DataClass());
            }
        }
    
        public class DataClass : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged = delegate { };
            public decimal Average
            {
                get { return _Average; }
                set
                {
                    _Average = value;
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Average"));
                }
            }
            decimal _Average;
        }
        public class ViewModel : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged = delegate { };
            public ViewModel(DataClass dc)
            {
                Data = dc;
                dc.PropertyChanged += new PropertyChangedEventHandler(Data_PropertyChanged);
            }
            void Data_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (e.PropertyName == "Avarage")
                {
                    // здесь можно выполнить свой this.PropertyChanged(...
                }
            }
            public readonly DataClass Data;
        }
    }
    
    но лучше не дублировать код.

    • Изменено Malobukv 9 ноября 2011 г. 16:27
    • Помечено в качестве ответа Qwester33 10 ноября 2011 г. 12:31
    9 ноября 2011 г. 16:26
  • Большое спасибо вам обоим за исчерпывающие объяснения и код.

    Я думал, что вьюмодель по дефолту уже подписана на изменения в модели, типа как вью сразу же подписано на изменения вьюмодели, и достаточно выкинуть уведомление.

    А нет ли такой штуки, типа как ObservableCollection, только для переменных. То есть, чтобы можно было указать ключевое слово или атрибут при ее объявлении, который бы обозначал автоматическую выдачу уведомлений при ее изменении. В случае с коллекциями можно просто объявить коллекцию в модели как ObservableCollection, и не надо будет реализовывать INotifyPropertyChanged в модели, вручную организовывать уведомления со стороны модели, подписку на них во вьюмодели - все работает по дефолту.

    Или, к примеру, указывать как-то во вьюмодели, что она должна отслеживать изменения не только во вью, но и в модели. То есть, это вроде-бы очевидная и технически нужная вещь, но ради достижения такого простого функционала приходится писать довольно много дополнительного кода. А то выходит, что вместо MVVM проще в диспетчертаймере написать: контрол.Text = DataClass.Average и всех делов.

    • Изменено Qwester33 10 ноября 2011 г. 13:25
    10 ноября 2011 г. 12:33
  • > ... для переменных. То есть, чтобы можно было указать ключевое слово или атрибут при ее объявлении, который бы обозначал автоматическую выдачу уведомлений при ее изменении.


    для этого надо использовать TypeDescriptionProvider. пример есть выше по теме. особенность в том, что уведомление посылается, только если изменение вносится через метод SetValue.

    другой способ - кодогенерация. пример здесь.
    можно генерировать класс-обертку, которая будет транслировать вызовы и посылать уведомления.

    но вообще для хранения и частичной обработки данных лучше/проще использовать DataSet или XElement. в них есть все необходимое для привязки данных и посылки уведомлений об изменениях.

    • Помечено в качестве ответа Qwester33 11 ноября 2011 г. 14:49
    10 ноября 2011 г. 20:45