Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.99/116: Рейтинг темы: голосов - 116, средняя оценка - 4.99
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2

INPC (INotifyPropertyChanged) и получение данных из Модели [WPF, Элд Хасп]

27.01.2019, 17:51. Показов 23765. Ответов 63

Лучший ответ Сообщение было отмечено Элд Хасп как решение

Решение

Студворк — интернет-сервис помощи студентам
Тема из цикла Готовые решения, примеры и рекомендации начинающим на WPF [Элд Хасп]

MVVM состоит из трёх раздельных частей. "Знания" этих частей друг о другу ограничены. View знает только о VM, VM только Model, а Model знать никого не желает.

В такой схеме передача данных от View в Model проблем не вызывает. Ограничения есть только для передачи данных от View к VM. Для отправки данных View обращается к Set методу нужного свойства VM (к которому осуществлена привязка), поэтому привязывать свойства View можно только к публичным свойствам VM. Привязать к полям или методам VM невозможно.

Если взять VM из примера в WPF команды и MVVM. Часть 1. пост#15, то свойства First, Second можно упростить до автосвойств и на функционировании приложения это ни как не скажется (сброс свойства Result в данном случае можно опустить)
C#
9
10
public int First { get; set; }
public int Second { get; set; }
Получение же данных происходит иным образом. Так как у VM нет знаний о View, она сама не может "лезть" в View и менять там значения свойств. View сама запрашивает у VM нужные данные вызывая метод Get нужного свойства VM.

Но как определить View, что данные изменились и их надо заново запросить? Для этого VM должна известить View о том какие данные были изменены. Это извещение (событие) для WPF View должно происходить через реализацию интерфейсов INotifyPropertyChanged (INPC) для свойств и INotifyCollectionChanged (INCC) для коллекций (здесь рассматривать не буду).

Дефолтная реализация реализация интерфейса INPC состоит всего из одной строчки
C#
1
public event PropertyChangedEventHandler PropertyChanged;
Но для удобства использования обычно вводят ещё и метод для вызова этого события
C#
1
2
3
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName="") 
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Атрибут CallerMemberName введён с Framework 4.5. Он позволяет не указывать имя свойства, если вызов происходит из Set метода этого свойства. В указанном примере это используется в свойстве Result
C#
1
2
3
4
5
6
7
8
9
        public int? Result
        {
            get => _result;
            private set
            {
                _result = value;
                OnPropertyChanged();
            }
        }
Использование без параметра OnPropertyChanged() равносильно OnPropertyChanged("Result")

Но только такое использование INPC необязательно. Можно заменить Result на автосвойство и вызывать OnPropertyChanged при записи в него нового значения.
C#
1
2
3
4
5
6
7
8
9
        public int? Result { get; private set;  }
        private void OnCalculate(object parameter)
        {
            if (parameter is OperatorsEnum Operator)
                Result = model.Calculate(First, Operator, Second);
            else
                Result = null;
            OnPropertyChanged("Result");
        }
В указанном в WPF команды и MVVM. Часть 1. пост#15 примере кнопка по сути не нужна. Она была введена для демонстрации команд. Изменим пример - убрав кнопку и используя INPC для обновления Result.

В VM при изменении значений свойств First, Second и SelectOperator будем отправлять в View сообщение об изменении свойства
Result. А в свойстве Result пропишем обращение к свойствам Модели
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    /// <summary>Класс Модели Представления</summary>
    public class CalcViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        #region Секция свойств 
        private int _first;
        private int _second;
        private OperatorsEnum _selectOperator;
 
        public int First
        {
            get => _first;
            set
            {
                if (_first != value)
                {
                    _first = value;
                    OnPropertyChanged("Result");
                }
            }
        }
        public int Second
        {
            get => _second;
            set
            {
                if (_second != value)
                {
                    _second = value;
                    OnPropertyChanged("Result");
                }
            }
        }
 
        public OperatorsEnum SelectOperator
        {
            get => _selectOperator;
            set
            {
                _selectOperator = value;
                OnPropertyChanged("Result");
            }
        }
 
        public int? Result => model.IsCanExecuteCalculate(First, SelectOperator, Second)
            ? model.Calculate(First, SelectOperator, Second)
            : null;
 
        public IEnumerable<OperatorsEnum> Operators => model.GetOperators();
        #endregion
 
        CalcModel model = new CalcModel(); // Ссылка на модель
    }
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Window x:Class="INPCinModel.CalcWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:INPCinModel"
        mc:Ignorable="d"
        Title="Калькулятор (Calc)" SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <local:CalcViewModel/>
    </Window.DataContext>
    <StackPanel Orientation="Horizontal">
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding First, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5" MinWidth="80"/>
        <ComboBox VerticalAlignment="Center" 
                  ItemsSource="{Binding Operators}"
                  SelectedItem="{Binding SelectOperator}"
                  Margin="5" MinWidth="80"/>
        <TextBox Grid.Column="2" VerticalAlignment="Center" 
                 Text="{Binding Second, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5"  MinWidth="80"/>
        <TextBlock Margin="5" VerticalAlignment="Center" Text="="/>
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding Result, Mode=OneWay}" 
                 Margin="5" IsReadOnly="True" MinWidth="80"/>
    </StackPanel>
</Window>
Рассмотрим теперь полную схему получения данных в MVVM

Если для отправки извещения в View, VM должна реализовывать INPC или INCC, то модель этим не ограничена. Но использование этих интерфейсов в большинстве случаев удобно и позволяет сделать код более прозрачным и читаемым. Можно использовать INPC не совсем стандартно. По своей сути событие PropertyChanged пересылает просто текстовое сообщение (string), ни каких проверок на существование свойства с таким именем не производится. Поэтому не обязательно чтобы параметр метода OnPropertyChanged был названием свойства.
В примере выше, VM обращается к методам Модели сразу возвращающим данные, но такие методы не всегда есть у модели. И если значения свойств First, Second и SelectOperator сразу посылать в Модель, то VM не может знать изменится Result или нет. Для этого при изменении Result Модель должна создать событие извещающее об этом, а VM "прослушивать" это событие и по нему запросить данные.

Пример Модели. В Модели реализованы методы для получения данных от VM: First, Second и SelectOperator. Методы для возвращения данных в VM: GetResult и GetOperators. Для удобства введён дополнительный конструктор сразу подключающий "прослушку" события PropertyChanged.
Имена методов Модели намеренно сделаны не совпадающими с именами свойств VM для большей наглядности взаимодействия. Для этой же цели PropertyChanged сообщает VM об изменении несуществующего свойства ResultChange
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    /// <summary>Класс модели</summary>
    public class CalcINPCModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
 
        #region закрытые поля
        private int _first;
        private int _second;
        private OperatorsEnum _selectOperator;
        private int? _result = null;
        #endregion
 
        #region Методы получающие данные
        public void SetFirst(int value)
        {
            if (_first != value)
            {
                _first = value;
                OnResult();
            }
        }
        public void SetSecond(int value)
        {
            if (_second != value)
            {
                _second = value;
                OnResult();
            }
        }
 
        public void SetOperator(OperatorsEnum value)
        {
            if (_selectOperator != value)
            {
                _selectOperator = value;
                OnResult();
            }
        }
        #endregion
 
        #region Методы отправляющие данные
        public int? GetResult() => _result;
 
        /// <summary>Возвращает последовательность значений перечисления OperatorsEnum</summary>
        /// <returns></returns>
        public IEnumerable<OperatorsEnum> GetOperators()
            => Enum.GetValues(typeof(OperatorsEnum)).Cast<OperatorsEnum>();
        #endregion
 
        /// <summary>Обработка полученных данных</summary>
        private void OnResult()
        {
            int? value = null;
            if (_selectOperator != OperatorsEnum.Деление || _second != 0)
                switch (_selectOperator)
                {
                    case OperatorsEnum.Сложение: value = _first + _second; break;
                    case OperatorsEnum.Умножение: value = _first * _second; break;
                    case OperatorsEnum.Вычитание: value = _first - _second; break;
                    case OperatorsEnum.Деление: value = _first / _second; break;
                }
            if (_result != value)
            {
                _result = value;
                OnPropertyChanged("ResultChange");
            }
        }
 
        /// <summary>Безпараметрический конструктор</summary>
        public CalcINPCModel() { }
        /// <summary>Конструктор принимающий делегат для "прослушивания" события PropertyChanged</summary>
        /// <param name="propertyChanged">Делегат для прослушки</param>
        public CalcINPCModel(PropertyChangedEventHandler propertyChanged)
            => PropertyChanged += propertyChanged;
    }
В ViewModel свойства First, Second и SelectOperator ни где не хранят свои значения. При получении изменений от View они сразу отправляют их в методы Модели.
Инициализация Модели сделана через обращение к свойству для подключения прослушки чтобы не создавать конструктор ViewModel. В методе Model_PropertyChanged прослушивающем PropertyChanged проверяется имя свойства, если оно ResultChange, то отправляется извещение в View об изменении свойства Result. Пр обращении View к свойству Result оно получает своё значение от модели.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    /// <summary>Класс Модели Представления</summary>
    public class CalcINPCViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        #region Секция свойств 
        public int First { set => model.SetFirst(value); }
        public int Second { set => model.SetSecond(value); }
 
        public OperatorsEnum SelectOperator { set => model.SetOperator(value); }
 
        public int? Result => model.GetResult();
 
        public IEnumerable<OperatorsEnum> Operators => model.GetOperators();
        #endregion
 
        CalcINPCModel _model;
        CalcINPCModel model => _model ?? (_model = new CalcINPCModel(Model_PropertyChanged)); // Ссылка на модель
 
        private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ResultChange")
                OnPropertyChanged("Result");
        }
    }
XAML окна не изменился, но на всякий случай привожу его
Кликните здесь для просмотра всего текста
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Window x:Class="INPCinModel.CalcINPCWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:INPCinModel"
        mc:Ignorable="d"
        Title="Калькулятор (CalcINPC)" SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <local:CalcINPCViewModel/>
    </Window.DataContext>
    <StackPanel Orientation="Horizontal">
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding First, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5" MinWidth="80"/>
        <ComboBox VerticalAlignment="Center" 
                  ItemsSource="{Binding Operators}"
                  SelectedItem="{Binding SelectOperator}"
                  Margin="5" MinWidth="80"/>
        <TextBox Grid.Column="2" VerticalAlignment="Center" 
                 Text="{Binding Second, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5"  MinWidth="80"/>
        <TextBlock Margin="5" VerticalAlignment="Center" Text="="/>
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding Result, Mode=OneWay}" 
                 Margin="5" IsReadOnly="True" MinWidth="80"/>
    </StackPanel>
</Window>


Теперь к такой Модели можно подключить другие View и ViewModel

Модель и первые View+VM почти те же. Добавлена только возможность ссылки на модель через статическое поле.
Добавление в Модели
C#
1
2
        static CalcIWithResultModel _model;
        public static CalcIWithResultModel Model => _model ?? (_model = new CalcIWithResultModel());
Получение Модели в конструкторе VM
C#
1
2
3
4
5
6
        readonly CalcIWithResultModel model;
        public CalcIWithResultViewModel()
        {
            model = CalcIWithResultModel.Model;
            model.PropertyChanged += Model_PropertyChanged;
        }
Целиком коды цепочки "Calc"
Кликните здесь для просмотра всего текста
Модель
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    /// <summary>Класс модели</summary>
    public class CalcIWithResultModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
        static CalcIWithResultModel _model;
        public static CalcIWithResultModel Model => _model ?? (_model = new CalcIWithResultModel());
 
        #region закрытые поля
        private int _first;
        private int _second;
        private OperatorsEnum _selectOperator;
        private int? _result = null;
        #endregion
 
        #region Методы получающие данные
        public void SetFirst(int value)
        {
            if (_first != value)
            {
                _first = value;
                OnResult();
            }
        }
        public void SetSecond(int value)
        {
            if (_second != value)
            {
                _second = value;
                OnResult();
            }
        }
 
        public void SetOperator(OperatorsEnum value)
        {
            if (_selectOperator != value)
            {
                _selectOperator = value;
                OnResult();
            }
        }
        #endregion
 
        #region Методы отправляющие данные
        public int? GetResult() => _result;
 
        /// <summary>Возвращает последовательность значений перечисления OperatorsEnum</summary>
        /// <returns></returns>
        public IEnumerable<OperatorsEnum> GetOperators()
            => Enum.GetValues(typeof(OperatorsEnum)).Cast<OperatorsEnum>();
        #endregion
 
        /// <summary>Обработка полученных данных</summary>
        private void OnResult()
        {
            int? value = null;
            if (_selectOperator != OperatorsEnum.Деление || _second != 0)
                switch (_selectOperator)
                {
                    case OperatorsEnum.Сложение: value = _first + _second; break;
                    case OperatorsEnum.Умножение: value = _first * _second; break;
                    case OperatorsEnum.Вычитание: value = _first - _second; break;
                    case OperatorsEnum.Деление: value = _first / _second; break;
                }
            if (_result != value)
            {
                _result = value;
                OnPropertyChanged("ResultChange");
            }
        }
 
        /// <summary>Безпараметрический конструктор</summary>
        public CalcIWithResultModel() { }
        /// <summary>Конструктор принимающий делегат для "прослушивания" события PropertyChanged</summary>
        /// <param name="propertyChanged">Делегат для прослушки</param>
    }
ViewModel
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    /// <summary>Класс Модели Представления</summary>
    public class CalcIWithResultViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
        #region Секция свойств 
        public int First { set => model.SetFirst(value); }
        public int Second { set => model.SetSecond(value); }
 
        public OperatorsEnum SelectOperator { set => model.SetOperator(value); }
 
        public int? Result => model.GetResult();
 
        public IEnumerable<OperatorsEnum> Operators => model.GetOperators();
        #endregion
 
 
        private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ResultChange")
                OnPropertyChanged("Result");
        }
 
        readonly CalcIWithResultModel model;
        public CalcIWithResultViewModel()
        {
            model = CalcIWithResultModel.Model;
            model.PropertyChanged += Model_PropertyChanged;
        }
    }
Окно
Кликните здесь для просмотра всего текста
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Window x:Class="INPCinModel.CalcIWithResultWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:INPCinModel"
        mc:Ignorable="d"
        Title="Калькулятор (CalcIWithResult)" SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <local:CalcIWithResultViewModel/>
    </Window.DataContext>
    <StackPanel Orientation="Horizontal">
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding First, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5" MinWidth="80"/>
        <ComboBox VerticalAlignment="Center" 
                  ItemsSource="{Binding Operators}"
                  SelectedItem="{Binding SelectOperator}"
                  Margin="5" MinWidth="80"/>
        <TextBox Grid.Column="2" VerticalAlignment="Center" 
                 Text="{Binding Second, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5"  MinWidth="80"/>
        <TextBlock Margin="5" VerticalAlignment="Center" Text="="/>
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding Result, Mode=OneWay}" 
                 Margin="5" IsReadOnly="True" MinWidth="80"/>
    </StackPanel>
</Window>

Во второй цепочке "Result" используется упрощённая VM и выводится только результат.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    /// <summary>Класс упрощённой ViewModel</summary>
    public class ResultViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
        public int? Result => model.GetResult();
 
        readonly CalcIWithResultModel model;
        public ResultViewModel()
        {
            model = CalcIWithResultModel.Model;
            model.PropertyChanged += Model_PropertyChanged;
        }
 
 
        private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ResultChange")
                OnPropertyChanged("Result");
        }
 
    }
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Window x:Class="INPCinModel.ResultWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:INPCinModel"
        mc:Ignorable="d"
        Title="ResultWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ResultViewModel/>
    </Window.DataContext>
    <Viewbox>
        <TextBlock Text="{Binding Result}"/>
    </Viewbox>
</Window>
И, конечно, для показа обоих окон надо добавить в App обработчик Startup
C#
1
2
3
4
5
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            (new CalcIWithResultWindow()).Show();
            (new ResultWindow()).Show();
        }
Архив с кодами приложен.
Вложения
Тип файла: zip INPCinModel.zip (100.7 Кб, 256 просмотров)
10
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
27.01.2019, 17:51
Ответы с готовыми решениями:

Библиотека элементов для реализации WPF MVVM Решений [WPF, Элд Хасп]
Решил собрать элементы используемые в темах в этом разделе. В библиотеку включаю элементы которые, на мой взгляд, имеют универсальное...

WPF команды и MVVM. Часть 1. [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Для использования и создания WPF команд в Net предусмотрен...

Передача данных между Окнами, между VM, Шина Сообщений, Локатор [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Использование статического класса. Передача...

63
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
27.01.2019, 18:57
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Атрибут CallerMemberName введён с Framework 4.5. Он позволяет не указывать имя свойства, если вызов происходит из Set метода этого свойства.
Говоря обобщенно, наличие этого атрибута присваивает значение параметру, к которому он применен, равное имени вызывающего метода. В случае геттеров и сеттеров свойств — это имя свойства.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
По своей сути событие PropertyChanged пересылает просто текстовое сообщение (string), ни каких проверок на существование свойства с таким именем не производится. Поэтому не обязательно чтобы параметр метода OnPropertyChanged был названием свойства.
Малоизвестный факт (среди тех, кто не читает документацию): использование в качестве имени свойства null или string.Empty уведомляет о том, что изменились все свойства объекта.
Порой бывает удобно уведомить интерфейс чтобы он обновил биндинги на всех свойствах — например, при реализации транзакций или обертки над моделью, где свойства — всего лишь обертка над вложенным объектом и этот объект поменялся на другой.

Из-за часто используемой реализации INPC с атрибутом CallerMemberName с этим могут возникнуть проблемы, т.к. передаваемое имя свойства автоматом подменяется на имя вызывающего метода.
Если планируется уведомлять интерфейс об изменении всех свойств, то метод можно малость изменить/расширить:
C#
1
2
3
4
5
private void OnPropertyChanged([CallerMemberName]string propertyName="", bool allProperties = false) =>
   PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(allProperties ? null : propertyName));
 
private void OnAllPropertiesChanged() =>
   OnPropertyChanged(allProperties: true);
5
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
27.01.2019, 19:24  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Малоизвестный факт (среди тех, кто не читает документацию): использование в качестве имени свойства null или string.Empty уведомляет о том, что изменились все свойства объекта.
Не знал.
Цитата Сообщение от kolorotur Посмотреть сообщение
Если планируется уведомлять интерфейс об изменении всех свойств, то метод можно малость изменить/расширить:
У меня в предыдущих темах есть подобная реализация, но через рефлексию.
Но в аспекте [CallerMemberName]string propertyName="" и использование в качестве имени свойства null... не будет такого же результата с OnPropertyChanged(null)?
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
27.01.2019, 20:09
Цитата Сообщение от Элд Хасп Посмотреть сообщение
использование в качестве имени свойства null... не будет такого же результата с OnPropertyChanged(null)?
Помню, у меня с этим были проблемы — при явной передаче null значение подменялось именем свойства.
Возможно, в поздних версиях фреймворка это пофиксили.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
27.01.2019, 20:23  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Помню, у меня с этим были проблемы — при явной передаче null значение подменялось именем свойства.
Возможно, в поздних версиях фреймворка это пофиксили.
На уровне работы CallerMemberName - передаёт правильно. При () замещает названием метода, при (null) остаётся null.
На обновление всех свойств не проверял. Но одно свойство обновляет нормаль - я в примере заменил OnPropertyChanged("Result"); на OnPropertyChanged(null);. Думаю для всех тоже будет работать нормально.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
30.01.2019, 05:05  [ТС]
kolorotur, начал дальше проверять PropertyChanged с null или Empty - не так всё просто! похоже обработка всех привязанных свойств это соглашение WPF. А само событие передаёт только параметр. Если, допустим, ловить в VM от Модели PropertyChanged, то там надо делать самому явную проверку на null или Empty и проверять все нужные свойства Модели.

Наверное WPF так и делает. Получив null или Empty проверяет весь список привязок к этому объекту.
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
30.01.2019, 08:35
Лучший ответ Сообщение было отмечено Элд Хасп как решение

Решение

Цитата Сообщение от Элд Хасп Посмотреть сообщение
похоже обработка всех привязанных свойств это соглашение WPF.
Нет, это задокументированное поведение:
The PropertyChanged event can indicate all properties on the object have changed by using either null or String.Empty as the property name in the PropertyChangedEventArgs.
WPF просто соблюдает это поведение

Цитата Сообщение от Элд Хасп Посмотреть сообщение
само событие передаёт только параметр. Если, допустим, ловить в VM от Модели PropertyChanged, то там надо делать самому явную проверку на null или Empty и проверять все нужные свойства Модели.
Вы, наверное, не так поняли работу этого события.
Передача null не означает, что событие будет вызвано 100500 раз — отдельно для каждого свойства и автоматом.
Оно лишь означает, что если вы получили null в обработчике, то значит объект уведомляет вас о том, что все его свойства изменились. А что делать с этой информацией — это уже ваше дело как получателя.

WPF, например, обновляет все привязки этого объекта.
2
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
30.01.2019, 09:59  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Нет, это задокументированное поведение:
Цитата Сообщение от kolorotur Посмотреть сообщение
WPF, например, обновляет все привязки этого объекта.
Следовательно, желательно аналогично реализовывать обработку PropertyChanged в кастомных классах.
Буду стараться это соблюдать.
0
 Аватар для Luca Brasi
892 / 204 / 55
Регистрация: 06.11.2015
Сообщений: 2,288
Записей в блоге: 2
25.08.2020, 16:52
Элд Хасп, в какой момень происходит инициализация PropertyChanged? У меня он почему-то всё время null.

C#
1
2
3
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName="") 
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
25.08.2020, 17:39  [ТС]
Цитата Сообщение от Luca Brasi Посмотреть сообщение
в какой момень происходит инициализация PropertyChanged? У меня он почему-то всё время null.
Как и любое событие, оно будет отлично от нуля только если есть подписанный на него метод.
0
 Аватар для Luca Brasi
892 / 204 / 55
Регистрация: 06.11.2015
Сообщений: 2,288
Записей в блоге: 2
25.08.2020, 21:28
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Как и любое событие, оно будет отлично от нуля только если есть подписанный на него метод.
да, согласен, но меня еще смущает факт, что если PropertyChanged является null. То при вызове метода OnPropertyChanged никакого NullReference не выкидывается.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
25.08.2020, 22:31  [ТС]
Цитата Сообщение от Luca Brasi Посмотреть сообщение
То при вызове метода OnPropertyChanged никакого NullReference не выкидывается.
Там же тернарный оператор и инвок.
Если записать по старому, то код равносилен такому:
C#
1
2
3
4
5
6
7
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName="") 
//            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        {
               if(PropertyChanged != null)
                     PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
         }
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
08.10.2020, 11:35  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Малоизвестный факт (среди тех, кто не читает документацию): использование в качестве имени свойства null или string.Empty уведомляет о том, что изменились все свойства объекта.
Наткнулся на старый пост, решил подправить для большей точности.
Это верно для WPF, но в UWP надо использовать только значение string.Empty.
В связи с чем метод лучше определять так:
C#
1
2
3
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName]string propertyName = null) 
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
08.10.2020, 13:25
Цитата Сообщение от Элд Хасп Посмотреть сообщение
В связи с чем метод лучше определять так
Тогда, наверное, так:
C#
1
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName ?? string.Empty));
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
08.10.2020, 13:34  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Тогда, наверное, так:
Возможно.
Но я не нашёл как именно в UWP интерпретируется null.
Может игнорируется, а может тоже что-то значит...

Всё что я видел:
Событие PropertyChanged может указывать, что все свойства объекта были изменены с помощью либо null, либо String.Empty качестве имени свойства в PropertyChangedEventArgs . Обратите внимание, что в приложении UWP необходимо использовать String.Empty, а не null
Надо включить UWP поэксперементировать, но надёжнее будет найти где-нибудь в документации.

Добавлено через 1 минуту
Кстати, стало сейчас любопытно: А что будет если sender=null ?
Надо проверить,... если не забуду.

Добавлено через 38 секунд
Или sender указывает на другой объект?
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
08.10.2020, 13:50
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Возможно.
Дык судя по документации — обязательно.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
я не нашёл как именно в UWP интерпретируется null.
Ну раз в документации сказано, что необходимо использовать string.Empty, а не null, то наверное имеет смысл не мудрить, а делать так, как просят

Цитата Сообщение от Элд Хасп Посмотреть сообщение
А что будет если sender=null ?
Даже в голову не приходило null отсылать.
Зачем?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
08.10.2020, 14:22  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Даже в голову не приходило null отсылать.
Зачем?
Тоже не приходило, но стало любопытно...
0
 Аватар для werymag
26 / 11 / 1
Регистрация: 20.05.2015
Сообщений: 216
01.08.2023, 13:25
Где если не здесь поспрашивать о том, а что есть Модель.

Допустим у меня есть десктопный клиент состоящий из View и класса VM,
VM состоит из всякой логики обрабатывающей данные, и ссылки на класс реализующий патерн UnitOfWork(обобщенные IRepository),
UnitOfWork(IRepository) взаимодействует с бекэнд сервером, где данные проверяются и отправляются в БД.
То есть как-то так:

View
ViewModel
IUnitOfWork(IRepository)
BackEndServer
DataBase

И возникает вопрос, что в этой цепочке описывается понятием модель?
0
 Аватар для Andrey-MSK
3308 / 2196 / 386
Регистрация: 14.08.2018
Сообщений: 7,390
Записей в блоге: 4
01.08.2023, 13:56
Цитата Сообщение от werymag Посмотреть сообщение
что в этой цепочке описывается понятием модель?
Я думаю вот это всё
Цитата Сообщение от werymag Посмотреть сообщение
IUnitOfWork(IRepository)
BackEndServer
DataBase
Если приложение работает только как показать/отредактировать данные в БД, то моделью является репозиторий с DTO и сама СУБД.
2
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16113 / 11234 / 2887
Регистрация: 21.04.2018
Сообщений: 33,035
Записей в блоге: 2
01.08.2023, 14:09  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Я думаю вот это всё
Угу.

Цитата Сообщение от werymag Посмотреть сообщение
а что есть Модель.
Слой приложения содержащий всю Бизнес (Доменную) логику.
В свою очередь он может разбиваться ещё на подслои, сервисы и т.д.
Репозиторий - этот подслой Модели.
BackEndServer - это часть реализации Репозитория.
DataBase - по сути, некое API для работы с физ. хранилищем.
Всё это входит "внутрь" Модели.

Очень часто под Моделью имеют ввиду не всю Модель целиком, а только ту её публичную часть (Интерфейс) через которую взаимодействуют с ней её потребители. Особенно это актуально в рамках реализации паттернов семейства MV*. Паттерны не рассматривают вопросы внутренней реализации слоёв, а регулируют только требуемый от них функционал и правила взаимодействия друг с другом.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
01.08.2023, 14:09
Помогаю со студенческими работами здесь

WPF конвертеры [Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html View получает данные от ViewModel, но часто бывают случаи...

Элемент геометрии «Стрелка» [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Реализация сделана по вопросу отDissolve в теме...

WPF vs WinForms (для начинающих) [Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Эту тему решил создать, так как очень часто сталкиваюсь с...

WPF команды и MVVM. Часть 2. Всплытие команд. Реализация команды для списка элементов [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html На практике часто встречаются случаи когда команда и кнопка...

Обсуждение темы "Библиотека элементов для реализации WPF MVVM Решений" [WPF, Элд Хасп]
Любое обсуждение, рекомендации, вопросы и т.п. по теме https://www.cyberforum.ru/wpf-silverlight/thread2738784.html В том числе по...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
Расскажи мне о Мире, бродяга
kumehtar 12.11.2025
— Расскажи мне о Мире, бродяга, Ты же видел моря и метели. Как сменялись короны и стяги, Как эпохи стрелою летели. - Этот мир — это крылья и горы, Снег и пламя, любовь и тревоги, И бескрайние. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru