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

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

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

Студворк — интернет-сервис помощи студентам
Тема из цикла Готовые решения, примеры и рекомендации начинающим на 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 Кб, 288 просмотров)
12
Лучшие ответы (1)
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
18.01.2019, 19:56
Ответы с готовыми решениями:

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

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

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

92
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16152 / 11273 / 2890
Регистрация: 21.04.2018
Сообщений: 33,147
Записей в блоге: 2
11.02.2019, 18:16  [ТС]
Студворк — интернет-сервис помощи студентам
Я же сразу написал. При обращение к методу Invalidate выдаёт ошибку из-за разных потоков.
Обновление свойств происходит в другом потоке. Это обновление вызывает метод Invalidate, но ошибка "вызывается из другого потока"
Я так-то знаю, что элементы должны быть все в одном потоке. Но это же код без визуализации. Почему на нём ошибку даёт?
0
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
11.02.2019, 18:52
Попробуй. Может поможет, а может и нет:
заменить строку:
C#
1
CommandManager.RequerySuggested += _requerySuggested;
на эту:
C#
1
2
Application.Current.Dispatcher.Invoke((Action)(() => 
    CommandManager.RequerySuggested += _requerySuggested));
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16152 / 11273 / 2890
Регистрация: 21.04.2018
Сообщений: 33,147
Записей в блоге: 2
11.02.2019, 19:48  [ТС]
Цитата Сообщение от proa33 Посмотреть сообщение
Попробуй. Может поможет, а может и нет:
Это по смыслу не то. Это присоедение обработчика. В этой части всё работает нормально.
Ошибку вызывает другая строка
C#
23
       public void Invalidate() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
0
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
11.02.2019, 21:11
попробуй изменить метод Invalidate:
C#
1
2
3
4
5
6
7
 public void Invalidate ()
      {
         Application.Current.Dispatcher.Invoke ( ( Action ) ( () =>
          {
             CanExecuteChanged?.Invoke ( this, EventArgs.Empty );
          } ));
      }
0
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
12.02.2019, 11:15
Цитата Сообщение от Элд Хасп Посмотреть сообщение
В этой части всё работает нормально.
вы практически тестировали оба варианта?
ошибка при тестировании есть?
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
12.02.2019, 11:37
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Элементы в окне обновились, но кнопка с команда остаётся не активной. Если кликнуть в окне в любом месте, то команда обновляется и кнопка становится активной.
А можно простенький пример?
0
9 / 8 / 2
Регистрация: 20.02.2018
Сообщений: 94
12.02.2019, 11:40
Вот объясните мне - зачем в 2019 году WPF ? Разве эта технология не устарела уже?
Вот я только начал изучать UWP. На нём можно для Windows 10 писать приложения. Скоро (пишут) Windows Core выйдет или Windows lite - там только UWP приложения будут.
А WPF - это что такое вообще? Что на нём можно делать? Почему оно в разделе с UWP?
Это я к чему пишу - можно ли переделать/переписать всю тему под UWP, пожалуйста? И под NET CORE, наверное (скоро планирую начать изучать). Я бы, например - с удовольствием бы попрактиковался в командах на UWP.
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
12.02.2019, 11:52
Цитата Сообщение от Zixi Посмотреть сообщение
Вот объясните мне - зачем в 2019 году WPF ?
Я вам страшное скажу: в 2019 году даже консольные приложения широко используются.
Не говоря о винформах и уж тем более о WPF.

Цитата Сообщение от Zixi Посмотреть сообщение
А WPF - это что такое вообще?
Фреймворк для создания графических интерфейсов.

Цитата Сообщение от Zixi Посмотреть сообщение
Что на нём можно делать?
Приложения с графическим интерфейсом.

Цитата Сообщение от Zixi Посмотреть сообщение
Почему оно в разделе с UWP?
Это UWP в разделе с WPF.
Обратите внимание как UWP в названии раздела занимает скромное второе место.

Цитата Сообщение от Zixi Посмотреть сообщение
можно ли переделать/переписать всю тему под UWP, пожалуйста?
Да действительно — сносите к чертям весь раздел, ведь мимо проходящему гражданину он неинтересен!!!1
0
9 / 8 / 2
Регистрация: 20.02.2018
Сообщений: 94
12.02.2019, 12:01
Ой, извините... Я только предложил подумать о возможности побольше внимания уделять UWP. Только и всего....
0
управление сложностью
 Аватар для Почтальон
1693 / 1306 / 259
Регистрация: 22.03.2015
Сообщений: 7,545
Записей в блоге: 5
12.02.2019, 12:19
Zixi, а чем вы недовольны ?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16152 / 11273 / 2890
Регистрация: 21.04.2018
Сообщений: 33,147
Записей в блоге: 2
12.02.2019, 12:23  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
А можно простенький пример?
Так.... Как я понимаю, должно работать, и это я что-то не так делаю...
Простенький пример.... Сейчас попробую упростить насколько возможно, но совсем простенько не получится.
0
9 / 8 / 2
Регистрация: 20.02.2018
Сообщений: 94
12.02.2019, 13:50
Я всем доволен.
Просто выразил некоторые предложения по темам.
Я был бы рад темам: "UWP команды и MVVM", "Тема из цикла Готовые решения, примеры и рекомендации начинающим на UWP" и т.п.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16152 / 11273 / 2890
Регистрация: 21.04.2018
Сообщений: 33,147
Записей в блоге: 2
12.02.2019, 14:04  [ТС]
Цитата Сообщение от proa33 Посмотреть сообщение
вы практически тестировали оба варианта?
Нет.
В Ваших вариантах ошибка "Application не содержит Current".
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16152 / 11273 / 2890
Регистрация: 21.04.2018
Сообщений: 33,147
Записей в блоге: 2
12.02.2019, 14:04  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
А можно простенький пример?
Насколько мог упростил (Проект примера приложен)
Model
Кликните здесь для просмотра всего текста
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
using CommLibrary;
using System;
using System.Threading.Tasks;
using WebSocketSharp;
 
namespace TaskInvalidate
{
    public class ModelWSocket : OnPropertyChangedClass
    {
        #region Секция свойств
        private bool _isOpen = false;
        private long _countMessage;
        private DateTime _timeLastMessage;
        private string _lastMessage;
 
        /// <summary>WebSocket открыт</summary>
        public bool IsOpen { get => _isOpen; private set { SetProperty(ref _isOpen, value); } }
 
        /// <summary>Количество полученных сообщений от сервера</summary>
        public long CountMessage { get => _countMessage; private set { SetProperty(ref _countMessage, value); } }
 
        /// <summary>Время получения (локальное) последнего сообщения от сервера</summary>
        public DateTime TimeLastMessage { get => _timeLastMessage; private set { SetProperty(ref _timeLastMessage, value); } }
 
        /// <summary>Тело сообщения от сокета</summary>
        public string LastMessage { get => _lastMessage; set { SetProperty(ref _lastMessage, value); } }
        #endregion
 
        const string UrlBitMexTest = "wss://testnet.bitmex.com/realtime";
        /// <summary>Возвращает открытый WebSocket</summary>
        public WebSocket WSocket { get; }
 
        /// <summary>Конструктор</summary>
        public ModelWSocket()
        {
 
            WSocket = new WebSocket
                (
                    UrlBitMexTest + "?subscribe=orderBook10:XBTUSD"
                );
            WSocket.OnClose += Ws_OnClose;
            WSocket.OnError += Ws_OnError;
            WSocket.OnMessage += Ws_OnMessageAsync;
            WSocket.OnOpen += Ws_OnOpen;
 
            WSocket.Connect();
 
        }
 
        private async void Ws_OnMessageAsync(object sender, MessageEventArgs e) 
            => await Task.Run(() => Ws_OnMessage(sender, e));
 
        /// <summary>Обработчик события получения сообщения от сокета</summary>
        private void Ws_OnMessage(object sender, MessageEventArgs e)
        {
            CountMessage++;
            TimeLastMessage = DateTime.UtcNow;
            LastMessage = e.Data;
        }
 
        private void Ws_OnOpen(object sender, EventArgs e) => IsOpen = true;
 
        private void Ws_OnError(object sender, ErrorEventArgs e) => Console.WriteLine($"WebSocket ERROR: \"{e.Message}\"");
 
        private void Ws_OnClose(object sender, CloseEventArgs e) => IsOpen = false;
    }
}

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
using CommLibrary;
using System;
 
namespace TaskInvalidate
{
    public class ViewModelWSocket : OnPropertyChangedClass
    {
        private RelayCommand _syncComm;
        private RelayCommand _asyncComm;
 
        public RelayCommand SyncComm => _syncComm ?? (_syncComm = new RelayCommand(OnSync, CanSync));
        public RelayCommand AsyncComm => _asyncComm ?? (_asyncComm = new RelayCommand(OnAsync, CanAsync));
 
        private void OnSync(object parameter) { }
        private void OnAsync(object parameter) { }
 
        private bool CanSync(object parameter) => WSocket.IsOpen;
        private bool CanAsync(object parameter) => WSocket.TimeLastMessage.Second % 10 < 5;
 
 
        private ModelWSocket _wSocket;
        public ModelWSocket WSocket
        {
            get
            {
                if (_wSocket == null)
                {
                    _wSocket = new ModelWSocket();
                    _wSocket.PropertyChanged += _wSocket_PropertyChanged;
                }
 
                return _wSocket;
            }
        }
 
        private void _wSocket_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            string propName = e.PropertyName;
            if (string.IsNullOrWhiteSpace(propName) || propName == "IsOpen")
                SyncComm.Invalidate();
            if (string.IsNullOrWhiteSpace(propName) || propName == "TimeLastMessage")
                AsyncComm.Invalidate();
            OnAllPropertyChanged();
        }
 
 
        #region Секция свойств
        /// <summary>WebSocket открыт</summary>
        public bool IsOpen => WSocket.IsOpen;
 
        /// <summary>Количество полученных сообщений от сервера</summary>
        public long CountMessage => WSocket.CountMessage;
 
        /// <summary>Время получения (локальное) последнего сообщения от сервера</summary>
        public DateTime TimeLastMessage => WSocket.TimeLastMessage;
 
        /// <summary>Тело сообщения от сокета</summary>
        public string Message => WSocket.LastMessage;
        #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
30
31
32
33
34
35
<Window x:Class="TaskInvalidate.MainWindow"
        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:TaskInvalidate"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ViewModelWSocket/>
    </Window.DataContext>
    <Grid Margin="5">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="3*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Margin="5">
            <TextBlock Margin="5">
                <Run Text="IsOpen :"/>
                <Run Text="{Binding IsOpen, Mode=OneWay}"/>
            </TextBlock>
            <TextBlock Margin="5">
                <Run Text="Count Message :"/>
                <Run Text="{Binding CountMessage, Mode=OneWay}"/>
            </TextBlock>
            <TextBlock Margin="5">
                <Run Text="Time Message :"/>
                <Run Text="{Binding TimeLastMessage, ConverterParameter=mm, Mode=OneWay, StringFormat='hh:mm:ss'}"/>
            </TextBlock>
            <Button Content="Sync" Command="{Binding SyncComm}" Margin="5"/>
            <Button Content="Async" Command="{Binding AsyncComm}" Margin="5"/>
        </StackPanel>
        <TextBlock Grid.Column="1" Text="{Binding Message, Mode=OneWay}" TextWrapping="Wrap" Margin="5"/>
    </Grid>
</Window>


kolorotur, забыл приложить библиотеку с классами RelayCommand и OnPropertyChangedClass
Добавлено через 3 минуты
kolorotur, если закомментить в ViewModel
C#
41
42
            // if (string.IsNullOrWhiteSpace(propName) || propName == "TimeLastMessage")
            //    AsyncComm.Invalidate();
То ошибки не будет.
Вложения
Тип файла: 7z TaskInvalidate.7z (106.3 Кб, 14 просмотров)
Тип файла: 7z CommLibrary.7z (11.8 Кб, 14 просмотров)
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16152 / 11273 / 2890
Регистрация: 21.04.2018
Сообщений: 33,147
Записей в блоге: 2
12.02.2019, 14:12  [ТС]
Цитата Сообщение от Zixi Посмотреть сообщение
Я был бы рад темам: "UWP команды и MVVM", "Тема из цикла Готовые решения, примеры и рекомендации начинающим на UWP" и т.п.
Это была моя идея.
Я сам начинающий и только полгода осваиваю WPF. О UWP, мало что знаю. Что-то есть общее с WPF, но есть и принципиальные отличия.
Возможно когда-нибудь начну осваивать и UWP и напишу о своём опыте.

P.S. Если у Вас есть хорошие примеры, опыт - напишите. Выложите тему. Я, думаю, все только рады будут.
1
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
12.02.2019, 15:53
Цитата Сообщение от Элд Хасп Посмотреть сообщение
В Ваших вариантах ошибка "Application не содержит Current".
ваша строка:
C#
1
2
3
4
5
if ( string.IsNullOrWhiteSpace ( propName ) || propName == "TimeLastMessage" )
            Application.Current.Dispatcher.BeginInvoke ( new Action ( () =>
                    {
                       AsyncComm.Invalidate ();
                    } ), null );
лучше сразу выкладывать примерчики, иначе одни неприятности

Добавлено через 15 минут
вариант №2
включите в вашу DLL сборки:
PresentationCore.dll
PresentationFramework.dll
WindowsBase.dll

и затем используйте вызов, как я писал ранее:
C#
1
2
3
4
 Application.Current.Dispatcher.BeginInvoke ( new Action ( () =>
         {
            CanExecuteChanged?.Invoke ( this, EventArgs.Empty );
         } ), null );
тут проблема в том, что DLL получается жестко завязана требованием использования только с WPF.
первый вариант свободен от этого.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16152 / 11273 / 2890
Регистрация: 21.04.2018
Сообщений: 33,147
Записей в блоге: 2
12.02.2019, 17:56  [ТС]
proa33, не было ссылки на PresentationFramework.dll
Спасибо! Ваш пример был решением. Использую второй вариант - изменил код класса RelayCommand.
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
13.02.2019, 12:44
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Проект примера приложен
Странно, конечно, что биндинг на команду валит приложение из другого потока, но ничего не поделать — надо маршаллить вызов события в UI-поток.
Только я бы это делал не в самой команде, а в месте вызова Invalidate. Вызывающему типу лучше знать в каком потоке он это делает.

И да, уберите синхронизацию при привязывании/отвязывании к PropertyChanged: мало того, что она реализована коряво, так она там в принципе не нужна.
Реализуйте событие как обычно:
C#
1
public event PropertyChangedEventHandler PropertyChanged;
Спек языка гарантирует, что добавление и удаление подписчиков в автоматически реализуемых событиях потокобезопасно, так что всю синхронизацию реализует за вас компилятор, только сделает это правильно и более эффективно.

Вызов события при привязывании — это вообще зло.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16152 / 11273 / 2890
Регистрация: 21.04.2018
Сообщений: 33,147
Записей в блоге: 2
13.02.2019, 13:31  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Спек языка гарантирует, что добавление и удаление подписчиков в автоматически реализуемых событиях потокобезопасно, так что всю синхронизацию реализует за вас компилятор, только сделает это правильно и более эффективно.
Вызов события при привязывании — это вообще зло.
Понял.
А как тогда лучше сделать инициирование свойств?
Преследовал такую идею, что после подсоединения прослушки, посылается событие - обновить все свойства.
Если делать не так, то как? В коде класса откуда подсоединяется прослушка, явно после это прописывать инициализацию всех свойств?
Что посоветуете, какие более типичное решение?

Цитата Сообщение от kolorotur Посмотреть сообщение
Странно, конечно, что биндинг на команду валит приложение из другого потока, но ничего не поделать — надо маршаллить вызов события в UI-поток.
Только я бы это делал не в самой команде, а в месте вызова Invalidate. Вызывающему типу лучше знать в каком потоке он это делает.
Не совсем так. В данном случае, параллельный поток возникает в Модели. И откуда ViewModel может знать обновление свойств происходит в том же потоке или в параллельном?
Тем более, мы с Вами это уже обсуждали, команды это даже не ViewModel, а View. Просто инициализация их в Xaml - заморочна.

P.S. Пока времени нет, а имеющихся знаний не хватает. Но собираюсь проработать тему создания команд непосредственно в XAML/ Если получится, то концептуально будет более чистая реализация MVVM.
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
13.02.2019, 13:49
Цитата Сообщение от Элд Хасп Посмотреть сообщение
А как тогда лучше сделать инициирование свойств?
А какая задумка скрывается под словом "инициирование"?
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
13.02.2019, 13:49
Помогаю со студенческими работами здесь

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

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

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

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

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


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

Или воспользуйтесь поиском по форуму:
60
Ответ Создать тему
Новые блоги и статьи
Транскрипция 55-минутного видео через Whisper: WhisperDesktop облажался, спас Google Colab[
anaschu 01.06.2026
Понадобилось получить текст из свежезагруженного видео на YouTube. Казалось бы, задача на пять минут. Заняла полтора часа. Делюсь опытом — может кому пригодится последовательность решений. . . .
21 мат мед. Планы на развитие модели здравоСохранения
anaschu 01.06.2026
AnyLogic: план развития симуляционной модели рабочего коллектива — динамический абсентеизм, реальные данные, три сценария сравнения Продолжаю серию постов о дискретно-событийной модели рабочего. . .
20. Мат мед. Абсентеизм как отдельный тип простоя
anaschu 29.05.2026
Апдейт модели: исправленные баги, абсентеизм и новые механизмы Продолжаю развивать ранее описанную модель рабочего коллектива на AnyLogic. За последние несколько дней был проведён серьёзный. . .
19. здоровье, усталость и психотип работника влияют на производительность предприятия, и наоборот, производительность на здоровье, усталось и психотип
anaschu 28.05.2026
Дискретно-событийная модель рабочего коллектива на AnyLogic: здоровье, выгорание, психотипы и микростимуляция Привет, коллеги. Хочу поделиться итогами нескольких недель работы над симуляционной. . .
"Прокси" для последовательного порта
Eddy_Em 28.05.2026
Эту штуку написал я достаточно давно. Но сейчас вот понадобилось настроить датчик грозы, но при этом не отключать его от "метеодемона". Соответственно, надо запустить этот "прокси": метеодемон будет. . .
Рефакторинг программы уравнивания.
Massaraksh7 26.05.2026
Пример по предыдущей записи в блоге. Но, надо заметить, что, во-первых, там оптимизация не только математики, но и работы с базой данных, и с графами, а во-вторых, это ещё не всё.
Использование TThread в Lazarus для математических вычислений.
Massaraksh7 25.05.2026
Производя рефакторинг своих программ на предмет ускорения их работы, обратил внимание на такой аспект, как сокращение времени матвычислений. Дело в том, что приходится работать с большими матрицами. . .
Модель здравосохранения 18. Чем здоровее работник, тем быстрее выгорает
anaschu 24.05.2026
Имитационная модель корпоративного здравоохранения: что показывает математика Сегодня в модели рабочего коллектива на AnyLogic появились три новые механики — выгорание через накопленную усталость,. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru