Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.52/254: Рейтинг темы: голосов - 254, средняя оценка - 4.52
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
1

WPF команды и MVVM. Часть 1. [WPF, Элд Хасп]

18.01.2019, 19:56. Показов 48858. Ответов 92

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

Для использования и создания WPF команд в Net предусмотрен интерфейс IСommand и классы RoutedCommand и RoutedUICommand. Команда - это View компонента и ей присущи чисто View-ские свойства: можно привязать её, параметр передаваемый ей, целевой элемент; она может всплывать по дереву элементов; можно обработать её по по пути всплытия.

Основная проблема применения команд в MVVM, на мой взгляд, это отсутствие встроенной поддержки привязки к методам ViewModel. Дефолтно в Net обработка команд производится аналогично обработке событий элементов в CB окна. И уже в в этом обработчике можно вызвать методы ViewModel. Но тут возникает другая проблема. Как обработчику получить ViewModel? Придётся обращаться к DataContext приводить к нужному типу, походу могут возникнуть ещё другие нюансы. Самым удобным было бы привязка команд в XAML напрямую, сразу к свойствам ViewModel.

Для такой привязки в большинстве случаев создают дополнительный класс реализующий интерфейс IСommand. Почти всегда его называют RelayCommand. Реализации его могут быть разные, но они очень похожи. Одна из реализаций из темы Пример реализации WPF+MVVM приложения (с учётом замечания от Lexeq)
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
    public class RelayCommand : ICommand
    {
        private readonly Func<object, bool> _canExecute;
        private readonly Action<object> _onExecute;
 
        /// <summary>Событие извещающее об измении состояния команды</summary>
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
 
        /// <summary>Конструктор команды</summary>
        /// <param name="execute">Выполняемый метод команды</param>
        /// <param name="canExecute">Метод разрешающий выполнение команды</param>
        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            _onExecute = execute;
            _canExecute = canExecute;
        }
 
        /// <summary>Вызов разрешающего метода команды</summary>
        /// <param name="parameter">Параметр команды</param>
        /// <returns>True - если выполнение команды разрешено</returns>
        public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute.Invoke(parameter);
 
        /// <summary>Вызов выполняющего метода команды</summary>
        /// <param name="parameter">Параметр команды</param>
        public void Execute(object parameter) => _onExecute?.Invoke(parameter);
    }
Используя этот класс легко создать команду привязанную напрямую к ViewModel. Покажу это на простом примере суммирующем и вычитающем два числа.
VievModel
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
    public class PlusMinusViewModel : OnPropertyChangedClass
    {
        #region Секция свойств 
        private int _first;
        private int _second;
        private int? _result = null;
 
        public int First
        {
            get => _first;
            set
            {
                if (_first != value)
                {
                    Result = null;
                    _first = value;
                    OnPropertyChanged();
                }
            }
        }
        public int Second
        {
            get => _second;
            set
            {
                if (_second != value)
                {
                    Result = null;
                    _second = value;
                    OnPropertyChanged();
                }
            }
        }
        public int? Result { get => _result; private set { _result = value; OnPropertyChanged(); } }
        #endregion
 
        #region Секция команд
        private ICommand _sumCommand;
        public ICommand SumCommand => _sumCommand ?? (_sumCommand = new RelayCommand(OnSum));
        public ICommand DiffCommand { get; }
        #endregion
 
        #region Секция методов
        private void OnSum(object parameter) => Result = First + Second;
        private void OnDiff(object parameter) => Result = First - Second;
        #endregion
 
        public PlusViewModel()
        {
            DiffCommand = new RelayCommand(OnDiff);
        }
 
    }
Обратите внимание на инициализацию команд. Так как команды привязаны к экземпляру ViewModel, то инициализировать их напрямую при объявлении свойства не получится. Можно инициализировать при первом обращении к команде, как это сделано для свойства SumCommand. Можно инициализировать из конструктора ViewModel, как это сделано для свойства DiffCommand. В основном используется первый способ. При инициализации команды через класс RelayCommand надо передать ссылку на метод ViewModel, который будет обрабатывать эту команду.

И окно для PlusMinusViewModel
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
29
30
31
32
33
34
35
36
37
<Window x:Class="WpfCommands.PlusMinusWindow"
        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:WpfCommands"
        mc:Ignorable="d"
        Title="PlusMinusWindow" SizeToContent="Height">
    <Window.Resources>
        <Style TargetType="FrameworkElement" x:Key="BaseStyle">
            <Setter Property="Margin" Value="5"/>
        </Style>
        <Style TargetType="Button" BasedOn="{StaticResource BaseStyle}"/>
        <Style TargetType="TextBlock" BasedOn="{StaticResource BaseStyle}"/>
        <Style TargetType="TextBox" BasedOn="{StaticResource BaseStyle}"/>
    </Window.Resources>
    <Window.DataContext>
        <local:PlusMinusViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox Grid.RowSpan="2" VerticalAlignment="Center" Text="{Binding First}" />
        <TextBox Grid.Column="1" Grid.RowSpan="2" VerticalAlignment="Center" Text="{Binding Second}" />
        <Button Content="Сумма" Grid.Column="2" Command="{Binding SumCommand, Mode=OneWay}"/>
        <Button Content="Разница" Grid.Column="2" Grid.Row="1" Command="{Binding DiffCommand, Mode=OneWay}"/>
        <TextBlock Grid.Column="3" Grid.RowSpan="2" VerticalAlignment="Center" Text="{Binding Result, Mode=OneWay}"/>
    </Grid>
</Window>
Методы обрабатывающие команды принимают один параметр - это CommandParameter который можно привязать одновременно с привязкой в XAML команды, и его значение будет передано в метод.

Изменим немного ViewModel для демонстрации обработки CommandParameter.
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
    public class MultiOperatorsViewModel : OnPropertyChangedClass
    {
        #region Секция свойств 
        private int _first;
        private int _second;
        private int? _result = null;
 
        public int First
        {
            get => _first;
            set
            {
                if (_first != value)
                {
                    Result = null;
                    _first = value;
                    OnPropertyChanged();
                }
            }
        }
        public int Second
        {
            get => _second;
            set
            {
                if (_second != value)
                {
                    Result = null;
                    _second = value;
                    OnPropertyChanged();
                }
            }
        }
        public int? Result { get => _result; private set { _result = value; OnPropertyChanged(); } }
        public IReadOnlyCollection<string> Operators => new List<string>
        {
            "Сложение",
            "Умножение",
            "Вычитание",
            "Деление",
        };
        #endregion
 
        #region Секция команд
        private ICommand _calculateCommand;
        public ICommand CalculateCommand => _calculateCommand ?? (_calculateCommand = new RelayCommand(OnCalculate));
        #endregion
 
        #region Секция методов
        private void OnCalculate(object parameter)
        {
            if (parameter is string param)
            {
                switch (param)
                {
                    case "Сложение": Result = First + Second; break;
                    case "Умножение": Result = First * Second; break;
                    case "Вычитание": Result = First - Second; break;
                    case "Деление": Result = First / Second; break;
                }
            }
            
        }
        #endregion
    }
В данном случае используется общий метод для выполнения всех действий и в параметре надо передать название этого действия. Список допустимых названий формируется в свойстве Operators, к которому будет привязан ComboBox.

Окно для MultiOperatorsViewModel
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
29
    <Window.Resources>
        <Style TargetType="FrameworkElement" x:Key="BaseStyle">
            <Setter Property="Margin" Value="5"/>
        </Style>
        <Style TargetType="Button" BasedOn="{StaticResource BaseStyle}"/>
        <Style TargetType="TextBlock" BasedOn="{StaticResource BaseStyle}"/>
        <Style TargetType="TextBox" BasedOn="{StaticResource BaseStyle}"/>
        <Style TargetType="ComboBox" BasedOn="{StaticResource BaseStyle}"/>
    </Window.Resources>
    <Window.DataContext>
        <local:MultiOperatorsViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox VerticalAlignment="Center" Text="{Binding First}" />
        <TextBox Grid.Column="2" VerticalAlignment="Center" Text="{Binding Second}" />
        <ComboBox x:Name="comboBox" Grid.Column="1" VerticalAlignment="Center" ItemsSource="{Binding Operators}"/>
        <Button Content="Вычислить" Grid.Column="3" Command="{Binding CalculateCommand, Mode=OneWay}" CommandParameter="{Binding SelectedValue, ElementName=comboBox}"/>
        <TextBlock Grid.Column="4" VerticalAlignment="Center" Text="{Binding Result, Mode=OneWay}"/>
    </Grid>
Если сейчас попробовать вычислить деление при нуле во втором поле, то будет исключение. Для обработки таких ситуаций используется второй параметре в конструкторе RelayCommand в котором передаётся ссылка на метод возвращающий true если команда может выполняться.

Изменим 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
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
    public class CanExecuteMultiOperatorsViewModel : OnPropertyChangedClass
    {
        #region Секция свойств 
        private int _first;
        private int _second;
        private int? _result = null;
 
        public int First
        {
            get => _first;
            set
            {
                if (_first != value)
                {
                    Result = null;
                    _first = value;
                    OnPropertyChanged();
                }
            }
        }
        public int Second
        {
            get => _second;
            set
            {
                if (_second != value)
                {
                    Result = null;
                    _second = value;
                    OnPropertyChanged();
                }
            }
        }
        public int? Result { get => _result; private set { _result = value; OnPropertyChanged(); } }
        public IReadOnlyCollection<string> Operators => new List<string>
        {
            "Сложение",
            "Умножение",
            "Вычитание",
            "Деление",
        };
        #endregion
 
        #region Секция команд
        private ICommand _calculateCommand;
 
        public ICommand CalculateCommand => _calculateCommand ?? (_calculateCommand = new RelayCommand(OnCalculate, IsCanExecuteCalculate));
        #endregion
 
        #region Секция методов
        private void OnCalculate(object parameter)
        {
            if (parameter is string param)
            {
                switch (param)
                {
                    case "Сложение": Result = First + Second; break;
                    case "Умножение": Result = First * Second; break;
                    case "Вычитание": Result = First - Second; break;
                    case "Деление": Result = First / Second; break;
                }
            }
 
        }
        private bool IsCanExecuteCalculate(object parameter)
        {
            if (parameter is string param && param == "Деление" && Second == 0)
                return false;
            return true;
        }
        #endregion
    }
Окно
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
29
    <Window.Resources>
        <Style TargetType="FrameworkElement" x:Key="BaseStyle">
            <Setter Property="Margin" Value="5"/>
        </Style>
        <Style TargetType="Button" BasedOn="{StaticResource BaseStyle}"/>
        <Style TargetType="TextBlock" BasedOn="{StaticResource BaseStyle}"/>
        <Style TargetType="TextBox" BasedOn="{StaticResource BaseStyle}"/>
        <Style TargetType="ComboBox" BasedOn="{StaticResource BaseStyle}"/>
    </Window.Resources>
    <Window.DataContext>
        <local:CanExecuteMultiOperatorsViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox VerticalAlignment="Center" Text="{Binding First}" />
        <TextBox Grid.Column="2" VerticalAlignment="Center" Text="{Binding Second}" />
        <ComboBox x:Name="comboBox" Grid.Column="1" VerticalAlignment="Center" ItemsSource="{Binding Operators}"/>
        <Button Content="Вычислить" Grid.Column="3" Command="{Binding CalculateCommand, Mode=OneWay}" CommandParameter="{Binding SelectedValue, ElementName=comboBox}"/>
        <TextBlock Grid.Column="4" VerticalAlignment="Center" Text="{Binding Result, Mode=OneWay}"/>
    </Grid>
Теперь если выбрана операция деления и делитель равен нулю, то команда не может выполниться и кнопка, к которой привязана команда, становится неактивной.

Использование WPF команд в MVVM для простых случаев, надеюсь, объяснил подробно и понятно.

Но есть более сложные применения. Допустим, есть ListBox и в нём в шаблоне Item есть кнопка. Как к ней привязать WPF команду? Для этого надо вспомнить, что WPF команда это View компонента и одним и свойств WPF команды является её всплывание по дереву. Команду надо "поймать" во включающем ListBox контейнере и обработать.
Как это сделать напишу в следующей части.

Архив проекта с кодами приложен.

Ещё хороший пример простой для понимания предложил Lexeq в пост #10

В пост #15 финальный вариант с учётом замечаний от HF, Lexeq, Рядовой, kolorotur. Там же архив со всеми кодами из темы.

В пост #64 вариант реализации RelayCommand с исправленным (при помощи proa33 и kolorotur) методом Invalidate для работы в многопоточном приложении.
Вложения
Тип файла: zip WpfCommands.zip (82.5 Кб, 267 просмотров)
12
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
18.01.2019, 19:56
Ответы с готовыми решениями:

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

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

Обсуждение темы "Библиотека элементов для реализации WPF MVVM Решений" [WPF, Элд Хасп]
Любое обсуждение, рекомендации, вопросы и т.п. по теме...

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

92
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
13.02.2019, 14:11  [ТС] 61
Author24 — интернет-сервис помощи студентам
Цитата Сообщение от kolorotur Посмотреть сообщение
А какая задумка скрывается под словом "инициирование"?
Может я чё не так понимаю....
У меня одна Модель (из нескольких объектов). К ней подсоединены 2 ViewModel. При подсоединении первой VM она инициирует Model и они обмениваются данными. Через некоторое время (зависит от пользователя) создаётся вторая VM.

Понятно, что при изменении данных VM2 изменит значения свойств. Но до изменения данных VM2 тоже надо инициировать свои свойства текущими значениями Model.

Я поэтому так и сделал. После подсоединения прослушки, сразу посылается сигнал об изменении всех свойств. Автоматически VM2 свои свойства обновит. Если это убрать - тогда надо в коде VM2 прописать явное чтение всех свойств из Model после соединения. Получается дублирование кода. Обновление свойств и так уже записано в обработчике PropertyChange.

Добавлено через 5 минут
Даже, для первой VM. Она соpдаёт Model. После создания Model к ней подсоединяется прослушка. Но если при инициализации Model она изменит значения своих свойств, то VM об этом не узнает, так как прослушка подсоединится позже. И какое-то время, пока все свойства ещё раз не обновятся состояния Model и VM будут отличаться.

Придётся, опять таки, явно после подсоединения прослушки VM считывать значения всех свойств.

Хотя у WPF элементов такого не возникает. Там, интересно, как этот момент реализован?
0
Эксперт .NET
17685 / 12871 / 3365
Регистрация: 17.09.2011
Сообщений: 21,136
13.02.2019, 14:24 62
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Через некоторое время (зависит от пользователя) создаётся вторая VM.
Зачем? У вас же уже есть VM для того же самого объекта — зачем плодить лишние?

Цитата Сообщение от Элд Хасп Посмотреть сообщение
до изменения данных VM2 тоже надо инициировать свои свойства текущими значениями Model.
Ничего не понял.
Вы копируете значения свойств из модели в VM?

Цитата Сообщение от Элд Хасп Посмотреть сообщение
После подсоединения прослушки, сразу посылается сигнал об изменении всех свойств. Автоматически VM2 свои свойства обновит. Если это убрать - тогда надо в коде VM2 прописать явное чтение всех свойств из Model после соединения.
То есть вы не знаете в какой момент вы подписываетесь на PropertyChanged модели внутри кода VM?
А если знаете, то почему сразу после подписки в той же VM не вызвать OnAllPropertyChanged?

А то как-то странно получается: модель должна знать о том, что VM должна обновить свои свойства?
0
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
13.02.2019, 14:52  [ТС] 63
Цитата Сообщение от kolorotur Посмотреть сообщение
Зачем? У вас же уже есть VM для того же самого объекта — зачем плодить лишние?
В Модели несколько объектов и много свойств. Первая VM настраивает Модель: создаёт нужные объекты (не сама, конечно, а через методы Модели) и задаёт значения свойствам. Потом эти объекты и свойства не меняются - эта VM не нужна.

VM2 уже работает с другими свойствами и методами модели. Я думал в начале сделать общую VM..., но код получался какой-то монструозный, огромный. Поэтому разделил на две VM.
Цитата Сообщение от kolorotur Посмотреть сообщение
Ничего не понял.
Вы копируете значения свойств из модели в VM?
После создания VM ей же надо как-то отобразить состояние Модели на момент создания.... Или я опять не в те дебри полез....?
Цитата Сообщение от kolorotur Посмотреть сообщение
То есть вы не знаете в какой момент вы подписываетесь на PropertyChanged модели внутри кода VM?
А если знаете, то почему сразу после подписки в той же VM не вызвать OnAllPropertyChanged?
Опаньки!
А вот это мне в голову не пришло!
Так действительно, будет разумнее!

Добавлено через 4 минуты
kolorotur, а с Invalidate какое оптимальный подход будет?
И я так и не понял (в потоках токо-токо начинаю разбираться) из-за чего ошибка?
Ведь там только код, визуализации нет.... Откуда потребность в одном потоке?
0
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
30.04.2019, 22:39  [ТС] 64
Новая реализация RelayCommand с исправлениями от proa33 и kolorotur
Как выяснилось при многопоточной работе метод Invalidate (в реализации из поста #15) выдаёт ошибку.
Обсуждение ошибки в постах #35-#57.
proa33 и kolorotur нашли решение, но в полном виде я забыл его опубликовать.

Выкладываю полную реализацию RelayCommand с решением указанной проблемы.
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
    #region Делегаты для методов WPF команд
    public delegate void ExecuteHandler(object parameter);
    public delegate bool CanExecuteHandler(object parameter);
    #endregion
 
    #region Класс команд - RelayCommand
    /// <summary>Класс реализующий интерфейс ICommand для создания WPF команд</summary>
    public class RelayCommand : ICommand
    {
        private readonly CanExecuteHandler _canExecute;
        private readonly ExecuteHandler _onExecute;
        private readonly EventHandler _requerySuggested;
 
        /// <summary>Событие извещающее об изменении состояния команды</summary>
        public event EventHandler CanExecuteChanged;
 
        /// <summary>Конструктор команды</summary>
        /// <param name="execute">Выполняемый метод команды</param>
        /// <param name="canExecute">Метод разрешающий выполнение команды</param>
        public RelayCommand(ExecuteHandler execute, CanExecuteHandler canExecute = null)
        {
            _onExecute = execute;
            _canExecute = canExecute;
 
            _requerySuggested = (o, e) => Invalidate();
            CommandManager.RequerySuggested += _requerySuggested;
        }
 
        public void Invalidate()
            => Application.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                CanExecuteChanged?.Invoke(this, EventArgs.Empty);
            }), null);
 
        /// <summary>Вызов разрешающего метода команды</summary>
        /// <param name="parameter">Параметр команды</param>
        /// <returns>True - если выполнение команды разрешено</returns>
        public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute.Invoke(parameter);
 
        /// <summary>Вызов выполняющего метода команды</summary>
        /// <param name="parameter">Параметр команды</param>
        public void Execute(object parameter) => _onExecute?.Invoke(parameter);
    }
    #endregion
4
В поиске
103 / 51 / 17
Регистрация: 20.04.2014
Сообщений: 812
07.05.2019, 19:14 65
Элд Хасп, может есть уже актуальная версия а я попал на устаревшую
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        public void OnPropertyChanged([CallerMemberName]string prop = "")
        {
            string[] names = prop.Split("\\/\r \n()\"\'-".ToArray(), StringSplitOptions.RemoveEmptyEntries);
            switch (names.Length)
            {
                case 0: break;
                case 1:
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
                    break;
                default:
                    OnPropertyChanged(names);
                    break;
            }
 
        }
 
        /// <summary>Метод для вызова события извещения об изменении списка свойств</summary>
        /// <param name="propList">Список имён свойств</param>
        public void OnPropertyChanged(IEnumerable<string> propList)
        {
            foreach (string prop in propList.Where(name => !string.IsNullOrWhiteSpace(name)))
                OnPropertyChanged(prop);
        }


Но тут явно какое то извращенство. Либо я читаю не правильно?
Передаем в OnPropertyChanged текст в виде массива разобрали количество не 0 и не 1 идем в OnPropertyChanged(names). там в цыкле пробегаемся по переданному массиву в котором возвращаемся обратно в OnPropertyChanged но уже с одним значением но опять его пытаемся разбить его повторно на массив

Добавлено через 15 минут
Почему к примеру не так? Или есть еще какие либо варианты которые могут пробраться и сломать логику?

Кликните здесь для просмотра всего текста

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
        /// <summary>Метод для вызова события извещения об изменении свойства</summary>
        /// <param name="prop">Изменившееся свойство</param>
        public void OnPropertyChanged([CallerMemberName]string prop = "")
        {
            string[] names = prop.Split("\\/\r \n()\"\'-".ToArray(), StringSplitOptions.RemoveEmptyEntries);
            if (names.Length>0)
            {
                OnPropertyChanged(names);
            }
        }
 
        /// <summary>Метод для вызова события извещения об изменении списка свойств</summary>
        /// <param name="propList">Список имён свойств</param>
        public void OnPropertyChanged(IEnumerable<string> propList)
        {
            if (propList!=null)
            {
                foreach (string prop in propList.Where(name => !string.IsNullOrWhiteSpace(name)))
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
            }
        }
 
        /*/// <summary>Метод для вызова события извещения об изменении списка свойств</summary>
        /// <param name="propList">Список свойств</param>
        public void OnPropertyChanged(IEnumerable<PropertyInfo> propList)
        {
            foreach (PropertyInfo prop in propList)
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop.Name));
        }*/
 
        /// <summary>Метод для вызова события извещения об изменении всех свойств</summary>
        /// <param name="propList">Список свойств</param>
        public void OnAllPropertyChanged()
        {
            OnPropertyChanged(GetType().GetProperties().Select(x=>x.Name));
        }
1
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
07.05.2019, 19:55  [ТС] 66
Цитата Сообщение от КВА696 Посмотреть сообщение
может есть уже актуальная версия а я попал на устаревшую
Я же сам начинающий. Делал в одно время так, в другое по другому. Что казалось важным тогда, потом, по мере опыта, мнение изменилось. Появились новы знания.

Самая простая реализация, которую использую для простых примеров
C#
1
2
3
4
5
6
7
8
9
10
    /// <summary>Базовый класс с реализацией INPC </summary>
    public abstract class OnPropertyChangedClass : INotifyPropertyChanged
    {
        /// <summary>Событие для извещения об изменения свойства</summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>Метод для вызова события извещения об изменении свойства</summary>
        /// <param name="propertyName">Изменившееся свойство</param>
        public void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
Сам использую такую
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
    /// <summary>Базовый класс с реализацией INPC </summary>
    public class OnPropertyChangedClass : INotifyPropertyChanged
    {
        #region Событие PropertyChanged
        /// <summary>Событие для извещения об изменения свойства</summary>
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
 
        #region Методы вызова события PropertyChanged
        /// <summary>Метод для вызова события извещения об изменении свойства</summary>
        /// <param name="propertyName">Изменившееся свойство</param>
        public void OnPropertyChanged([CallerMemberName]string propertyName = "")
        {
            if (string.IsNullOrEmpty(propertyName))
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            string[] names = propertyName.Split("\\/\r \n()\"\'-".ToArray(), StringSplitOptions.RemoveEmptyEntries);
            switch (names.Length)
            {
                case 0: PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); break;
                case 1:
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                    break;
                default:
                    OnPropertyChanged(names);
                    break;
            }
 
        }
 
        /// <summary>Метод для вызова события извещения об изменении списка свойств</summary>
        /// <param name="propList">Список имён свойств</param>
        public void OnPropertyChanged(IEnumerable<string> propList)
        {
            foreach (string propertyName in propList.Where(name => !string.IsNullOrWhiteSpace(name)))
                OnPropertyChanged(propertyName);
        }
 
        /// <summary>Метод для вызова события извещения об изменении всех свойств</summary>
        /// <param name="propList">Список свойств</param>
        public void OnAllPropertyChanged()
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
 
        #endregion
 
        #region Виртуальные защищённые методы для изменения значений свойств
        /// <summary>Виртуальный метод определяющий изменения в значении поля значения свойства</summary>
        /// <param name="fieldProperty">Ссылка на поле значения свойства</param>
        /// <param name="newValue">Новое значение</param>
        /// <param name="propertyName">Название свойства</param>
        protected virtual void SetProperty<T>(ref T fieldProperty, T newValue, [CallerMemberName]string propertyName = "")
        {
            if ((fieldProperty != null && !fieldProperty.Equals(newValue)) || (fieldProperty == null && newValue != null))
                PropertyNewValue(ref fieldProperty, newValue, propertyName);
        }
 
        /// <summary>Виртуальный метод изменяющий значение поля значения свойства</summary>
        /// <param name="fieldProperty">Ссылка на поле значения свойства</param>
        /// <param name="newValue">Новое значение</param>
        /// <param name="propertyName">Название свойства</param>
        protected virtual void PropertyNewValue<T>(ref T fieldProperty, T newValue, string propertyName)
        {
            fieldProperty = newValue;
            OnPropertyChanged(propertyName);
        }
        #endregion
    }
Даже делал реализацию для статических свойств, но сейчас искать её неохота.
0
В поиске
103 / 51 / 17
Регистрация: 20.04.2014
Сообщений: 812
07.05.2019, 20:09 67
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Самая простая реализация, которую использую для простых примеров
Да спасибо у меня такая же только без CallerMemberName было пользовался nameof()

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Сам использую такую
Тут по моему еще хуже стало чем то что я спрашивал
C#
1
2
if (string.IsNullOrEmpty(propertyName))
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
если propertyName приходит нуль или "" то мы вызываем PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(NULL));
зачем? или я не правильно понял?
1
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
07.05.2019, 20:17  [ТС] 68
Цитата Сообщение от КВА696 Посмотреть сообщение
Да спасибо у меня такая же только без CallerMemberName было пользовался nameof()
Использование этого атрибута позволяет в сеттере свойства вызывать OnPropertyChanged() без параметров. Очень удобно. Если меняешь название свойства - не надо следить за тем где его имя прописано.

Цитата Сообщение от КВА696 Посмотреть сообщение
Тут по моему еще хуже стало чем то что я спрашивал
По принятым соглашениям (в WPF) событие PropertyChanged без значения (то есть когда null или Empty) означает, что надо обновить все свойства.
Я сам этого не знал. Когда поправили, то изменил реализацию.
1
В поиске
103 / 51 / 17
Регистрация: 20.04.2014
Сообщений: 812
07.05.2019, 20:35 69
Цитата Сообщение от Элд Хасп Посмотреть сообщение
или Empty) означает, что надо обновить все свойства.
Не знал спасибо.

А вот по поводу того что вы текст склеиваете потом разбиваете на массив может есть смысл сразу передать в виде массива

C#
1
2
3
4
5
public void OnPropertyChanged(params string[] name)
        {
...
 
OnPropertyChanged(nameof(Свойство1),nameof(Свойство2))
и тогда даже если вы будите менять имя свойства вам не надо будит искать где в виде текста его на вставляли в OnPropertyChanged + можно будет поиск по массиву сделать что если есть в массиве "" или null то смысла пробегать по массиву вообще нету

Добавлено через 8 минут
Элд Хасп, а SetProperty и PropertyNewValue для чего нужны можно пример как их вы используете?
1
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
07.05.2019, 21:35  [ТС] 70
Цитата Сообщение от КВА696 Посмотреть сообщение
А вот по поводу того что вы текст склеиваете потом разбиваете на массив может есть смысл сразу передать в виде массива
Посмотрите внимательнее.
Там есть перегрузка принимающая список имён свойств.
Но Ваш вариант со списком параметров (params string[] name) тоже добавлю. Он мне просто в голову не пришёл.
Цитата Сообщение от КВА696 Посмотреть сообщение
а SetProperty и PropertyNewValue для чего нужны можно пример как их вы используете?
Свойство определяется подобным образом
C#
1
        public double Width { get => width; set => SetProperty(ref width, value); }
Для чего используется.
В основном два варианта.
Первый это обработка всех сеттеров в одном месте, а не в сеттере каждого свойства.

Для одного свойства вот так ещё пойдёт
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
        public bool? IsSecondSelected
        {
            get => isSecondSelected;
            set
            {
                SetProperty(ref isSecondSelected, value);
                if (IsSecondSelected == false)
                    SelectedImage = FirstImage;
                else if (IsSecondSelected == true)
                    SelectedImage = SecondImage;
                FirstImage.IsSelected = IsSecondSelected == false;
                SecondImage.IsSelected = IsSecondSelected == true;
            }
        }
А если свойств много и обработка их сложная?
Раскидывать это по всему коду класса очень не удобно.
Создаётся partial класс и в отдельном файле через переопределение SetProperty или PropertyNewValue перехватываются установка значений свойствам. оттуда уже идёт, если надо, ветвление по методам. Все методы в одном месте. Удобно, прозрачно, читаемо.

Второе применение это способ сделать тоже самое в производном классе. В этом случае по другому просто невозможно.
Я очень часто использую базовый класс и потом производные от него. Мне так удобно.

Подобный метод есть у всех Net классов реализующих INPC.
Попробуйте в CB окна вызвать список переопределяемых методов. Среди ни будет и void OnPropertyChanged(DependencyPropertyChangedEventArgs e) предназначенный для того же самого.
1
В поиске
103 / 51 / 17
Регистрация: 20.04.2014
Сообщений: 812
07.05.2019, 22:32 71
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Посмотрите внимательнее.
Там есть перегрузка принимающая список имён свойств.
да видел OnPropertyChanged(IEnumerable<string> propList)
Видимо представить себе такую картину не смог и как то проигнорировал OnPropertyChanged(new string[]{"Поле1","Поле2",..}) + то что эти два метода как то показались зацыкленными OnPropertyChanged первый ссылается на второго, а второй на первый

Но больше всего мне не нравится то что много раз делаете действие одно и тоже

Повторюсь еще может так будет понятнее к примеру (из 66 поста код ниже слов Сам использую такую)

Случай 1 выполняем так OnPropertyChanged("")
первая проверка положительно оповещаем все контролы чтобы обновились. дальше идем пытаемся "" разбить на массив у нас не получается names.Length=0 мы еще раз оповещаем все контролы чтобы обновились. Если я правильно понимаю мы 2 раза заставляем перерисовать те поля которые при биньженые.

случай 2 OnPropertyChanged("Поле1/rПоле2")
первая проверка отрицательна разбиваем на массив из 2 полей передаем массив в OnPropertyChanged(IEnumerable<string> propList)
там фильтруем и перебираем И ВОЗВРАЩАЕМСЯ НАЗАД где опять но уже над одним словом Поле1 первую проверку проходим на отрицательно опять пытаемся из неё сделать массив и поняв что во второй раз уже не получается только потом говорим что надо оповестить контрол об изменении и с Поле2 та же процедура а если полей 100))) их как минимум по 2 раза одну и туже операцию с ними проводите.

Правильно ли я размышляю? Если правильно то моё мнение лучше работать с массивом string и составляя его с помощью nameof() уже сразу, а не играться и разбивать его. Хотя на сколько я знаю если работать с библиотекой mvvm light то они уже реализовали RelayCommand и INotifyPropertyChanged но могу ошибаться.
Я бы наверное просто бы добавил к той вашей самой простой реализации и наверно это и будет на все случаи жизни. name проверять на null думаю бессмысленно так как на это OnPropertyChanged(null) компилятор ругается

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void OnPropertyChanged(params string[] name)
        {
            if (name.FirstOrDefault(x => string.IsNullOrEmpty(x)) == null)
            {
                foreach (var item in name)
                {
                    OnPropertyChanged(item);
                }
            }
            else
            {
                OnPropertyChanged("");
            }
        }

Добавлено через 3 минуты
И если не сложно можете еще раз выложить пример когда в RelayCommand вылетала ошибка на многопоточной ситуации? а то я не понял как её получить при старом коде

Добавлено через 19 минут
1
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
07.05.2019, 22:59  [ТС] 72
Цитата Сообщение от КВА696 Посмотреть сообщение
Случай 1 выполняем так OnPropertyChanged("")
первая проверка положительно оповещаем все контролы чтобы обновились. дальше идем пытаемся ""
Тут, конечно, return должен быть!
Моя оплошность!
Цитата Сообщение от КВА696 Посмотреть сообщение
случай 2 OnPropertyChanged("Поле1/rПоле2")
первая проверка отрицательна разбиваем на массив из 2 полей передаем массив в OnPropertyChanged(IEnumerable<string> propList)
там фильтруем и перебираем И ВОЗВРАЩАЕМСЯ НАЗАД
Это, вроде уже по другому реализовывал.
Уже запутался в реализациях.
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
    /// <summary>Базовый класс с реализацией INPC </summary>
    public class OnPropertyChangedClass : INotifyPropertyChanged
    {
        #region Событие PropertyChanged
        /// <summary>Событие для извещения об изменения свойства</summary>
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
 
        #region Методы вызова события PropertyChanged
        /// <summary>Метод для вызова события извещения об изменении свойства</summary>
        /// <param name="propertyName">Изменившееся свойство</param>
        public void OnPropertyChanged([CallerMemberName]string propertyName = "")
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                return;
            }
 
            string[] names = propertyName.Split("\\/\r \n()\"\'-".ToArray(), StringSplitOptions.RemoveEmptyEntries);
            switch (names.Length)
            {
                case 0: PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); break;
                case 1:
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                    break;
                default:
                    OnPropertyChanged(names);
                    break;
            }
 
        }
 
        /// <summary>Метод для вызова события извещения об изменении списка свойств</summary>
        /// <param name="propList">Список имён свойств</param>
        public void OnPropertyChanged(IEnumerable<string> propList)
        {
            foreach (string propertyName in propList.Where(name => !string.IsNullOrWhiteSpace(name)))
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        /// <summary>Метод для вызова события извещения об изменении перечня свойств</summary>
        /// <param name="propList">Список имён свойств</param>
        public void OnPropertyChanged(params string[] propList)
        {
            foreach (string propertyName in propList.Where(name => !string.IsNullOrWhiteSpace(name)))
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
 
        /// <summary>Метод для вызова события извещения об изменении всех свойств</summary>
        /// <param name="propList">Список свойств</param>
        public void OnAllPropertyChanged()
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
 
        #endregion
 
        #region Виртуальные защищённые методы для изменения значений свойств
        /// <summary>Виртуальный метод определяющий изменения в значении поля значения свойства</summary>
        /// <param name="fieldProperty">Ссылка на поле значения свойства</param>
        /// <param name="newValue">Новое значение</param>
        /// <param name="propertyName">Название свойства</param>
        protected virtual void SetProperty<T>(ref T fieldProperty, T newValue, [CallerMemberName]string propertyName = "")
        {
            if ((fieldProperty != null && !fieldProperty.Equals(newValue)) || (fieldProperty == null && newValue != null))
                PropertyNewValue(ref fieldProperty, newValue, propertyName);
        }
 
        /// <summary>Виртуальный метод изменяющий значение поля значения свойства</summary>
        /// <param name="fieldProperty">Ссылка на поле значения свойства</param>
        /// <param name="newValue">Новое значение</param>
        /// <param name="propertyName">Название свойства</param>
        protected virtual void PropertyNewValue<T>(ref T fieldProperty, T newValue, string propertyName)
        {
            fieldProperty = newValue;
            OnPropertyChanged(propertyName);
        }
        #endregion
    }
Цитата Сообщение от КВА696 Посмотреть сообщение
если работать с библиотекой mvvm light то они уже реализовали RelayCommand и INotifyPropertyChanged но могу ошибаться.
Конечно. И не только там. В любой библиотеке, паттерне реализующих WPF MVVM есть эти реализации.
0
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
07.05.2019, 23:00  [ТС] 73
Новая реализация OnPropertyChangedClass
Цитата Сообщение от КВА696 Посмотреть сообщение
только потом говорим что надо оповестить контрол об изменении и с Поле2 та же процедура
Я сделал эту реализацию, сравнительно давно (по моим мерка изучение C# и WPF).
Сам задумывался, что возможно это излишне.
Но иногда была необходимость известить о нескольких свойствах, поэтому использовал такой способ.
Но сейчас уже изменил стиль программирования (по мере набора опыта) и уже этим пользуюсь очень редко.
А после дополнения перегрузки с Ваши вариантом params string[] propList - это, вообще, потеряло смысл.

Сейчас лучше использовать такую реализацию
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
    /// <summary>Базовый класс с реализацией INPC </summary>
    public abstract class OnPropertyChangedClass : INotifyPropertyChanged
    {
        #region Событие PropertyChanged
        /// <summary>Событие для извещения об изменения свойства</summary>
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
 
        #region Методы вызова события PropertyChanged
        /// <summary>Метод для вызова события извещения об изменении свойства</summary>
        /// <param name="propertyName">Изменившееся свойство</param>
        public void OnPropertyChanged([CallerMemberName]string propertyName = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
 
        /// <summary>Метод для вызова события извещения об изменении списка свойств</summary>
        /// <param name="propList">Список имён свойств</param>
        public void OnPropertyChanged(IEnumerable<string> propList)
        {
            foreach (string propertyName in propList.Where(name => !string.IsNullOrWhiteSpace(name)))
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        /// <summary>Метод для вызова события извещения об изменении перечня свойств</summary>
        /// <param name="propList">Список имён свойств</param>
        public void OnPropertyChanged(params string[] propList)
        {
            foreach (string propertyName in propList.Where(name => !string.IsNullOrWhiteSpace(name)))
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
 
        /// <summary>Метод для вызова события извещения об изменении всех свойств</summary>
        /// <param name="propList">Список свойств</param>
        public void OnAllPropertyChanged()
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
 
        #endregion
 
        #region Виртуальные защищённые методы для изменения значений свойств
        /// <summary>Виртуальный метод определяющий изменения в значении поля значения свойства</summary>
        /// <param name="fieldProperty">Ссылка на поле значения свойства</param>
        /// <param name="newValue">Новое значение</param>
        /// <param name="propertyName">Название свойства</param>
        protected virtual void SetProperty<T>(ref T fieldProperty, T newValue, [CallerMemberName]string propertyName = "")
        {
            if ((fieldProperty != null && !fieldProperty.Equals(newValue)) || (fieldProperty == null && newValue != null))
                PropertyNewValue(ref fieldProperty, newValue, propertyName);
        }
 
        /// <summary>Виртуальный метод изменяющий значение поля значения свойства</summary>
        /// <param name="fieldProperty">Ссылка на поле значения свойства</param>
        /// <param name="newValue">Новое значение</param>
        /// <param name="propertyName">Название свойства</param>
        protected virtual void PropertyNewValue<T>(ref T fieldProperty, T newValue, string propertyName)
        {
            fieldProperty = newValue;
            OnPropertyChanged(propertyName);
        }
        #endregion
    }
5
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
07.05.2019, 23:00  [ТС] 74
Цитата Сообщение от КВА696 Посмотреть сообщение
И если не сложно можете еще раз выложить пример когда в RelayCommand вылетала ошибка на многопоточной ситуации? а то я не понял как её получить при старом коде
У меня она возникла при изменении коллекции из другого потока.
В событии обработки изменения стояла проверка некоторых условий и, если условия менялись, вызывался метод Invalidate. Появлялась указанная ошибка. Если же не вызывать этот метод, то кнопка не становилась активной пока что-то на форме не поменяется. А если окно не в фокусе, то вообще состояние кнопки не менялось.
0
WPF Разработчик
463 / 167 / 42
Регистрация: 20.02.2018
Сообщений: 285
08.11.2019, 15:17 75
Элд Хасп,
Объясните пожалуйста.

Вы пишите команды вот в таком виде, через проверку на null

C#
1
2
3
4
5
6
#region Секция команд
 
    private ICommand _calculateCommand;
    public ICommand CalculateCommand => _calculateCommand ?? (_calculateCommand = new RelayCommand(OnCalculate, IsCanExecuteCalculate));
 
#endregion
Вариант с конструктором

C#
1
2
3
4
5
6
7
8
public SomeVM()
{
    #region  Initialization Commands
        CalculateCommand = new RelayCommand(OnCalculate, IsCanExecuteCalculate));
    #endregion
}
 
public ICommand CalculateCommand { get; }
Почему Вы не инициализируете команду в конструкторе?

Как это вижу в реализации через

Конструктор:
•оптимизированно (инициализация один раз, объявление поля команды как readonly)
•читабельно и эстетично(наглядно видно какие методы переданы во все команды)

Свойство
•оптимизированно (команда инициализируется только тогда, когда она понадобиться)
•громоздко (слегка длинная конструкция, но да, здесь можно по разному обыграть решение, к примеру сделать статический метод которому передаётся поле по ссылке, и в зависимости от ситуации он создаст и вернём, либо просто вернёт значение)
•практично(Когда мы переходим из редактора xaml на свойство команды нам надо знать переданный метод, так более практично, в отличии от конструктора, при котором нам надо перейти в конструктор и отыскать в нём инициализацию команды)

Подводя итоге свойства оказываются менее производительными для постоянного вызова команды, поскольку постоянно проверяется if, так же свойства менее эстетично выглядят, однако они более практичны для разработки и отладки приложения
0
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
08.11.2019, 17:25  [ТС] 76
sttrox, я использую и так, и этак.

Часто конструктор VM, вообще, не создаю.
И создавать его только ради инициализации команды?

Второе, часто инициализация команды содержит логику, а порой и полные коды методов команды.
Зачем это всё в конструкторе?
Это только запутает код.

Третье, скорость проверки на null очень высокая.
Я сомневаюсь даже что каким-либо тестом удастся выявить задержку между при такой проверке.
Тем более что это свойство только для чтения и обращение к нему от WPF элемента будет однократным.
0
WPF Разработчик
463 / 167 / 42
Регистрация: 20.02.2018
Сообщений: 285
08.11.2019, 17:46 77
Элд Хасп, Позвольте, но тогда зачем нам поле, если вызов происходит один раз?
Мы может спокойно написать

C#
1
2
3
4
5
#region Секция команд
 
    public ICommand CalculateCommand =>  new RelayCommand(OnCalculate, IsCanExecuteCalculate);
 
#endregion
Или не можем? Исключая случай когда наша команда динамичная

Проверял в отладчике, вызов этого свойства происходит действительно один раз.
0
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
08.11.2019, 18:02  [ТС] 78
sttrox, я написал обращение к нему от WPF элемента будет однократным.
Но к одной и той же команде может обращаться несколько элементов.
0
WPF Разработчик
463 / 167 / 42
Регистрация: 20.02.2018
Сообщений: 285
08.11.2019, 18:05 79
Элд Хасп, что плохого в том, что у нас будет несколько разных экземпляров команды? Какие могут быть последствия?
0
Модератор
Эксперт .NET
15465 / 10711 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
08.11.2019, 18:18  [ТС] 80
sttrox, засорение памяти.
Иногда бывают реализации где элемент обращающийся к команде много раз пересоздаётся и каждый раз будет создаваться новый экземпляр команды.
Для простых случаев можно не заморачиваться, но это создаёт привычку к плохому коду.
Лучше привыкать всегда делать однотипно и правильно.
0
08.11.2019, 18:18
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
08.11.2019, 18:18
Помогаю со студенческими работами здесь

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

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

Готовые решения, примеры и рекомендации начинающим на WPF [Элд Хасп]
В этой теме перечень полезных тем для начинающих. Обсуждение введите в самих темах. Для включения...

INPC (INotifyPropertyChanged) и получение данных из Модели [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html MVVM состоит из трёх...

Пример создания приложения для тестирования [WPF, Элд Хасп]
Тема из цикла https://www.cyberforum.ru/wpf-silverlight/thread2384523.html Пример практической...

WPF.MVVM и команды
Доброго дня. При изучении команд в MVVM столкнулся с проблемой включения и отключения. есть...


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

Или воспользуйтесь поиском по форуму:
80
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru