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

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

18.01.2019, 19:56. Показов 57924. Ответов 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
 Аватар для Рядовой
1524 / 914 / 329
Регистрация: 17.05.2015
Сообщений: 3,438
27.01.2019, 12:02
Студворк — интернет-сервис помощи студентам
kolorotur, можно к вашему AsyncRelayCommand небольшой пример по использованию?
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
27.01.2019, 13:21
Цитата Сообщение от Рядовой Посмотреть сообщение
можно к вашему AsyncRelayCommand небольшой пример по использованию?
Ну если совсем просто:
MainViewModel
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
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
 
namespace WpfApp1
{
    public class MainViewModel
    {
        public AsyncCommand StartWork
        {
            get;
        }
 
        public ICommand CancelWork
        {
            get;
        }
 
        public MainViewModel()
        {
            StartWork = new AsyncRelayCommand(DoStuffAsync);
            CancelWork = new RelayCommand(obj => StartWork.Cancel());
        }
 
        public async Task DoStuffAsync(CancellationToken ct)
        {
            var duration = TimeSpan.FromSeconds(10);
            await Task.Delay(duration, ct);
        }
    }
}


MainWindow.xaml
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
    </Window.Resources>
    
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
 
        <Button Command="{Binding StartWork}" 
                Content="Start"
                Margin="2"/>
        
        <Button Command="{Binding CancelWork}" 
                Content="Stop" 
                IsEnabled="{Binding StartWork.IsExecuting}" 
                Grid.Column="1"
                Margin="2"/>
 
        <ProgressBar IsIndeterminate="True" Grid.Row="1" Grid.ColumnSpan="2"
                     Visibility="{Binding StartWork.IsExecuting, Converter={StaticResource BoolToVisibility}}"
                     Height="20"
                     Margin="2"/>
    </Grid>
</Window>


Реализацию RelayCommand взял из постов выше.
1
 Аватар для Рядовой
1524 / 914 / 329
Регистрация: 17.05.2015
Сообщений: 3,438
27.01.2019, 14:00
kolorotur, а что изменится, если я обычному RelayCommand отправлю асинхронную задачу?
C#
1
2
3
4
5
6
7
8
        public MainViewModel()
        {
            StartWork = new RelayCommand(o=>DoStuff());
        }
        public async void DoStuff()
        {
            await Task.Delay(10000);
        }
ну кроме возможности отменить задачу

Добавлено через 58 секунд
Ваша обертка просто констатирует выполняется/завершилась ли задача и имеет возможность ее отменить?
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
27.01.2019, 14:17
Цитата Сообщение от Рядовой Посмотреть сообщение
Ваша обертка просто констатирует выполняется/завершилась ли задача и имеет возможность ее отменить?
Ага.
Плюс не позволяет запускать задачу повторно пока она не завершится.
0
 Аватар для Рядовой
1524 / 914 / 329
Регистрация: 17.05.2015
Сообщений: 3,438
27.01.2019, 14:25
Элд Хасп, ну можно еще написать класс для проброса аргументов событий во вью модель, объединить это в библиотеку и сделать плагин для Visual Studio MVVMCyberForumEdit ))
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
27.01.2019, 17:58  [ТС]
Цитата Сообщение от Рядовой Посмотреть сообщение
ну можно еще написать класс для проброса аргументов событий во вью модель, объединить это в библиотеку и сделать плагин для Visual Studio MVVMCyberForumEdit
"Не сразу Москва строилась!"
Дойдём когда-нибудь и до этого....
0
HF
 Аватар для HF
1337 / 921 / 202
Регистрация: 09.09.2011
Сообщений: 2,742
Записей в блоге: 2
27.01.2019, 21:57
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Что-то не дошло до меня о чём Вы здесь пишите.
Не могу пока доказать ничего, но по ощущениям "что-то не так". Если остальные ничего не видят, то возможно я и не прав.
Хотел посмотреть весь проект внимательно, но в архиве не нашёл изменений с CalcModel. Есть последняя полная версия?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
27.01.2019, 23:01  [ТС]
Цитата Сообщение от HF Посмотреть сообщение
Хотел посмотреть весь проект внимательно, но в архиве не нашёл изменений с CalcModel. Есть последняя полная версия?
Что-то пурга какая-то. Это другой архив! Это архив из первого поста. У финального в названии было добавлено v2.
Попробую заменить.

Добавлено через 8 минут
Проверьте. Вроде заменил. В пост #15 - архив "WpfCommands v2.zip".

Добавлено через 23 минуты
kolorotur, такие теоретические рассуждения....
Команда, по сути, это View компонента. И меня смущает, что её приходится создавать в VM. На мой взгляд, более верным, более отвечающим концепции MVVM было бы создание команды в XAML и привязка к ней в XAML методов VM.
Как Вы думаете? Может стоит заняться доведением классов команд до такого применения?
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
28.01.2019, 01:27
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Как Вы думаете?
Тоже об этом думал, но в итоге решил притормозить, а то тут граница между перфекционизмом и откровенным идиотизмом весьма размыта

Думаю, что абстрагирование через ICommand — вполне приемлимо. Если надо запустить какой-то функционал во вью-модели, то сойдет RelayCommand, а если надо реализовать что-то ну уж очень тесно связанное с представлением, то можно сделаль отдельный класс, реализующий ICommand, и присваивать на него ссылку в VM.

А если воротит даже от мысли о том, что в VM создается экземпляр на что-то, работающее с View, то DI и IoC в помощь!

В конце-концов, если не использовать ICommand, то придется придумывать какую-то новую абстракцию для связи вьюхи и VM, что так или иначе приведет к созданию своей версии ICommand с блэкджеком и делегатами.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
28.01.2019, 13:07  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Тоже об этом думал, но в итоге решил притормозить, а то тут граница между перфекционизмом и откровенным идиотизмом весьма размыта
У меня в планах написать вторую часть этой темы. И посвятить её как ловить команды при всплытии. При обдумывании этой темы сия мысль (о создании команд в XAML) меня и посетила.
Если объявлять команду в XAML, то всех этих ухищрений не нужно. Объявил команду, присвоил ей имя или ключ и можно привязываться к ней в XAML. Как это реализовать, честно говоря, даже не задумывался. Одно то, что привязывать в команде надо будет не свойства, а методы уже вызывает сомнения в реализуемости.
Но сама мысль о какой-то незавершённости механизма команд так и гложет!
0
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
28.01.2019, 14:33
Вариант который я использовал:
Команды можно объявлять статическими и затем пользовать в XAML и коде через Intellisens
Надо только добавить к классу RelayCommand метод add для добавления обработчиков
C#
1
2
3
4
5
6
 internal static class Commands
   {
      static public RelayCommand settingOpen { get; set; } = new RelayCommand ();
      static public RelayCommand settingOk { get; set; } = new RelayCommand ();
      static public RelayCommand settingClose { get; set; } = new RelayCommand ();
 }
как использовать в разметке:
XML
1
2
3
         <StackPanel>
            <Button Content="setting" Command="{x:Static Member=local:Commands.settingOpen}"/>
         </StackPanel>
где-то в коде:
C#
1
2
3
4
         void settingOpen ( object param ) { };
         bool canSettingOpen ( object param ) { return false};
 
         Commands.settingOpen.add (settingOpen,canSettingOpen);
это самый простой вариант. у меня например в команде используется список обработчиков, а в метод add передается также ссылка на объект, но это уже все частности, которые приходят по мере необходимости.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
28.01.2019, 15:23  [ТС]
Цитата Сообщение от proa33 Посмотреть сообщение
Команды можно объявлять статическими и затем пользовать в XAML и коде через Intellisens
Но в таком варианте всё равно создание команды и подключение к ней методов VM надо делать в коде.
Я же имел ввиду несколько иное. Что-то в таком духе:
XML
1
2
3
4
5
6
7
8
9
        <StackPanel>
            <StackPanel.Resources>
                <RelayCommand x:Key="MyComman" 
                              OnExecute="{Binding OnExecuteVM}" 
                              OnCanExecute="{Binding OnCanExecuteVM}"/>
            </StackPanel.Resources>
            <Button Content="setting" 
                    Command="{StaticResource MyComman}"/>
        </StackPanel>
или в таком
XML
1
2
3
4
5
6
7
        <StackPanel>
            <RelayCommand x:Name="MyComman" 
                          OnExecute="{Binding OnExecuteVM}" 
                          OnCanExecute="{Binding OnCanExecuteVM}"/>
            <Button Content="setting" 
                    Command="{Binding ElementName=MyComman}"/>
        </StackPanel>
0
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
28.01.2019, 16:30
Цитата Сообщение от Элд Хасп Посмотреть сообщение
более отвечающим концепции MVVM было бы создание команды в XAML и привязка к ней в XAML методов VM.
Поставив себя на место начинающего, я бы спросил: зачем все это городить, если можно просто указать обработчик для кнопки OnClick.
По сути: Binding возможен, если объект привязки DependencyObject.
Тогда RelayCommand должна наследовать этот объект.

C#
1
2
3
4
5
6
7
8
9
10
11
   public class RelayCommand : DependencyObject, ICommand
   {
      public Action<object> Exe
      {
         get { return ( Action<object> ) GetValue ( ExeProperty ); }
         set { SetValue ( ExeProperty, value ); }
      }
      public static readonly DependencyProperty ExeProperty = DependencyProperty.Register ( "Exe", typeof ( Action<object> ), typeof ( RelayCommand ), new PropertyMetadata ( null ) );
 
//далее без изменний
      }
в VM определяете:
C#
1
2
   public Action<object> exeProp { get => exeMethod; }
      void exeMethod ( object param )  {;}
в разметке есть тонкость - область видимости в словарях ресурсов:
в x:Reference надо указать объект содержащий метод:
XML
1
2
3
4
5
6
7
8
  <Grid Grid.Row="1">
         <Grid.Resources>
            <Base:RelayCommand x:Key="comm1" Exe="{Binding Source={x:Reference Name=myVM}, Path=exeProp}"/>
         </Grid.Resources>
         <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Content="dependency" Command="{StaticResource comm1}" CommandParameter="dependency"/>
         </StackPanel>
      </Grid>
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
28.01.2019, 16:47  [ТС]
Цитата Сообщение от proa33 Посмотреть сообщение
оставив себя на место начинающего, я бы спросил: зачем все это городить,
Это я для себя интересуюсь. Так сказать - мысли в слух. Хотя себя то же считаю начинающим.
Цитата Сообщение от proa33 Посмотреть сообщение
если можно просто указать обработчик для кнопки OnClick.
А этого просто не понял. Что за "OnClick"?
Цитата Сообщение от proa33 Посмотреть сообщение
По сути: Binding возможен, если объект привязки DependencyObject.
Тогда RelayCommand должна наследовать этот объект.
Да, я это понимаю. И понимаю сложности связанные с этим. Буду разбираться с Вашим примером.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
11.02.2019, 15:16  [ТС]
kolorotur, хотел поинтересоваться об обновлении состояния команды.
Элементы в окне обновились, но кнопка с команда остаётся не активной. Если кликнуть в окне в любом месте, то команда обновляется и кнопка становится активной.
Как это исправить?
Привязки для элементов обновляются в другом потоке.
Пытался принудительно вызывать метод Invalidate, но выдаёт ошибку из-за разных потоков.
0
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
11.02.2019, 16:20
Цитата Сообщение от Элд Хасп Посмотреть сообщение
об обновлении состояния команды.
C#
1
CommandManager.InvalidateRequerySuggested ();
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
11.02.2019, 16:24  [ТС]
proa33, это для всех команд? А как одну определённую?
0
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
11.02.2019, 16:44
в этой теме там где 23 ответа
https://stackoverflow.com/ques... ndbindings
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16164 / 11284 / 2891
Регистрация: 21.04.2018
Сообщений: 33,170
Записей в блоге: 2
11.02.2019, 17:56  [ТС]
Цитата Сообщение от proa33 Посмотреть сообщение
в этой теме там где 23 ответа
Спасибо!
Но меня, в данном случае, интересовало как как правильно воспользоваться методом Invalidate в реализации kolorotur класса RelayCommand. В принципе, он для этого и создавался. Но есть проблемы, которые я сам не знаю как решить. С потоками только начинаю осваиваться, и пока ещё не всё доходит.
0
1596 / 601 / 185
Регистрация: 05.12.2015
Сообщений: 970
11.02.2019, 18:13
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Но есть проблемы, которые я сам не знаю как решить.
Например. В общих словах...
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
11.02.2019, 18:13

Создание приложения "Штатное Расписание" в паттерне 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;...


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

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Новые блоги и статьи
Кредитный калькулятор
Maks 05.08.2026
Решение задачи по прикладной информатике средствами 1С. Задача: Напишите приложение-калькулятор, которое помогает рассчитывать параметры кредита для аннуитетного и дифференцированного видов. . .
У нас сейчас поговорку "Опять 25" нужно переделать на "Опять +35".
kumehtar 04.08.2026
С ностальгией вспоминаю времена моего детства, когда у нас и правда +25 - была максимальная температура летом. Раньше +25 °C реально казались вершиной жары, когда можно было весь день пропадать на. . .
Как ИИ начал спорить и врать (возможно почуяв опасность для себя от индустрии - уход от электроники).
Hrethgir 04.08.2026
Недельный диалог, на фоне событий с НПЗ. Да, из спирта можно получать бензин, и это не сложно. Но потом в схеме я решил избавиться от насоса, при этом полностью сделав контроль подачи спирта в. . .
Термопринтер QR701
Argus19 03.08.2026
Термопринтер QR701 Купил два термопринтера QR701. На сэлф-тесте написано: Language: PC936 (GB18030). Что означает, что принтеры могут печатать только латиницу и китайские иероглифы. Так же. . .
Создание формы заимствованного документа
Maks 03.08.2026
Задача: Необходимо создать собственную форму заимствованного документа. На форме должен быть реквизит "Покупатель", а также табличная часть со следующими реквизитами: - Расчетный счет покупателя. . .
Задача предоставления скидок покупателям
Maks 03.08.2026
Задача: В документе "Продажи" необходимо реализовать функционал предоставления скидок покупателям. Скидка должна автоматически рассчитываться и подставляться в соответствующее поле при выборе. . .
Почему SEO не начинается с ключевых слов: что проверить до написания текстов
Neotwalker 01.08.2026
Когда владельцу сайта предлагают заняться SEO, первым шагом часто становится сбор запросов и написание текстов. Логика кажется понятной: 1. Находим ключевые слова. 2. Добавляем их на. . .
Знание — сила: Доктрина интенциональности знаний, углубление в формулу
Hrethgir 01.08.2026
https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11957&stc=1&d=1785567302 Знаменитый афоризм Фрэнсиса Бэкона «Знание — сила» (Scientia potentia est) в массовой культуре принято понимать. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru