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

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

18.01.2019, 19:56. Показов 55935. Ответов 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
управление сложностью
 Аватар для Почтальон
1693 / 1306 / 259
Регистрация: 22.03.2015
Сообщений: 7,545
Записей в блоге: 5
19.01.2019, 11:48
Может стоит закрепить тему ?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
19.01.2019, 12:45  [ТС]
Цитата Сообщение от Почтальон Посмотреть сообщение
Может стоит закрепить тему ?
Я её внёс в Готовые решения, примеры и рекомендации начинающим на WPF [Элд Хасп]
Подобные темы планирую периодически создавать (раз-два в месяц). И если все закреплять, топик слишком разрастётся.
2
HF
 Аватар для HF
1303 / 882 / 199
Регистрация: 09.09.2011
Сообщений: 2,590
Записей в блоге: 2
19.01.2019, 20:55
Всё-таки считаю примеры очень сложными. Я, даже зная тему, не понял смысла и цели, пока не прочёл 4 раза сверху вниз и обратно. Если эти темы для начинающих, то я снова посоветую облегчать пример максимально возможно. Одна только разметка может быть уменьшена в 2 раза. Оставлять только то о чём идёт речь, не усложнять стилями, ресурсами, гридами и т.п.
Когда я учился, смотрел простейший пример - ввод текста в одно только поле! Ничего лишнего - текстбокс, кнопка. Всё. Во вьюмодели соответственно тоже - конструктор и создание кнопки (тоже в более лёгком виде).
5
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
20.01.2019, 00:05  [ТС]
Цитата Сообщение от HF Посмотреть сообщение
простейший пример - ввод текста в одно только поле!
Я до такого не додумался, хотя пытался упростить насколько мог. Предложите пример. Я буду только рад. Это же не мой личный форум.
0
HF
 Аватар для HF
1303 / 882 / 199
Регистрация: 09.09.2011
Сообщений: 2,590
Записей в блоге: 2
20.01.2019, 00:22
Лучший ответ Сообщение было отмечено NightmareZ как решение

Решение

Да любое. Текст + кнопка.
XML
1
2
3
4
5
6
7
8
9
10
<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Пользовательские команды" Height="350" Width="525" Loaded="Window_Loaded">
    <StackPanel>
        <TextBox Name="TextBox1" Margin="10"></TextBox>
        <Button Name="Button1" Margin="10" Command="{Binding Path=DoCommand}">
            Кнопка</Button>
    </StackPanel>
</Window>
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace CF_Test
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
 
        public ICommand DoCommand;
 
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            DoCommand = new RelayCommand(CommandExecute, CanCommandExecute);
        }
 
        private void CommandExecute(object parameter)
        {
            MessageBox.Show("Привет "  + Convert.ToString(parameter));
        }
 
        private bool CanCommandExecute(object parameter)
        {
            return TextBox1.Text != string.Empty;
        }
    }
}
В команде показать CanExecute например
- банальная проверка что поле текста не пустое
- "посложнее" - проверка что введены только цифры
Ведь цель то показать - возможности (что кнопка будет недоступная, что выполняются экшены) и как работают только нужные компоненты (чтобы когда смотришь на код, не нужно было ещё и разбиратся где именно что находится).

Конечно это не MVVM, нашёл старый код, быстро скинул. Но быстро накидать ещё класс совсем не сложно.
Хотя опять же - Command не связаны сильно с MVVM. Это самостоятельный механизм. Его можно использовать и так и так. Поэтому опять же, когда вы пишите везде слово MVVM, то это выглядит словно это "обязательно и только так". А то смотрите, придут знатоки с Prism и откроют глаза на другую правду.
2
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
20.01.2019, 00:28  [ТС]
Цитата Сообщение от HF Посмотреть сообщение
Конечно это не MVVM, нашёл старый код, быстро скинул. Но быстро накидать ещё класс совсем не сложно.
Но "соль" темы это именно применение команд в MVVM.
Я это сразу уточнил в начале темы.
Я Вас не тороплю, подумайте. Нужен простой пример применения WPF команд в MVVM. Обязательно нужно выделить ViewModel и сделать привязку команд к методам ViewModel.
0
HF
 Аватар для HF
1303 / 882 / 199
Регистрация: 09.09.2011
Сообщений: 2,590
Записей в блоге: 2
20.01.2019, 00:38
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Я Вас не тороплю, подумайте.
Нечего тут думать. Если вы сами не сделаете, то завтра я накидаю ещё за 5 минут один лишний файл ...VM и добавлю строчку с DataContext в разметку или в конструктор основного класса.
Пока напишите мне пожалуйста в чём разница команды и команды в mvvm? Если кроме место расположения кода есть что-то ещё, то было бы интересно узнать.
0
 Аватар для Рядовой
1524 / 914 / 329
Регистрация: 17.05.2015
Сообщений: 3,438
20.01.2019, 01:29
HF, на программном уровне разницы нет. Разница в восприятии.
Лично мне было бы неясно, зачем это городить, если можно в клике все прописать.
Сам начал использовать этот паттерн, только когда начал путаться в большом проекте.
Но из примера автора и правда можно было бы убрать стили и всякие IReadOnlyCollection...
0
 Аватар для Lexeq
1151 / 743 / 483
Регистрация: 21.01.2014
Сообщений: 1,903
20.01.2019, 02:19
Элд Хасп, мне тоже кажется, что все слишком переусложнено, особенно для новичков. OnPropertyChangedClass монстр на все случаи жизни. Модель и вьюмодель слились воедино, хотя вы пишете что главное тут показать команды в рамках MVVM. Логика, построенная на сравнении строк тоже попахивает(что-то из разряда if(button.Text == "Ok") в формах). И код в посте отличается от кода в проекте:
C#
1
public event EventHandler CanExecuteChanged; // в WPF не используется
а в проекте
C#
1
2
3
4
5
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
что и обеспечивает магию обновления источника комманд. Ну и следуя принципу критикуешь-предлагай накидал вот такой пример
Кликните здесь для просмотра всего текста

Command
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
public class Command : ICommand
    {
        Action action;
        Func<bool> canExecute;
 
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
 
        public Command(Action action, Func<bool> canExecute)
        {
            this.action = action;
            this.canExecute = canExecute;
        }
 
        public bool CanExecute(object parameter)
        {
            return canExecute();
        }
 
        public void Execute(object parameter)
        {
            action();
        }
    }


Model

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    public interface IAdviser
    {
        string GetAdvice();
    }
 
    public class Adviser : IAdviser
    {
        private static Random Random = new Random();
 
        private string[] advices = {
            "Делай зарядку по утрам",
            "Бутерброд лучше есть колбасой вниз.",
            "Никогда не сдавайся.",
            "Используй MVVM в WPF.",
            "Не паникуй."};
 
        /// <summary>Выдает случайный совет из массива advices</summary>
        /// <returns>Случайный совет</returns>
        public string GetAdvice() => advices[Random.Next(advices.Length)];
    }


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
    /// <summary>Реализация INPC</summary>
    public class ViewModeBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        protected void RaisePropertyChanged([CallerMemberName]string propName = "") 
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
 
    public class MainViewModel : ViewModeBase
    {
        /// <summary>Поле для хранения ссылки на Модель</summary>
        IAdviser model;
 
        /// <summary>Конструктор принимающий Модель</summary>
        /// <param name="model">Созданная Модель</param>
        public MainViewModel(IAdviser model) => this.model = model;
 
        /// <summary>Конструктор по умолчанию, сам создающий Модель</summary>
        public MainViewModel() => this.model = new Adviser();
 
        
        private bool enabled;
        /// <summary>Свойство для привязки CheckBox</summary>
        public bool Enabled
        {
            get => enabled;
            set
            {
                enabled = value;
                RaisePropertyChanged();
            }
        }
 
        private string advice;
        /// <summary>Свойство для привязки TextBlock</summary>
        public string Advice
        {
            get { return advice; }
            set
            {
                advice = value;
                RaisePropertyChanged();
            }
        }
 
 
        private ICommand doCommand;
        /// <summary>Свойство для привязки команды</summary>
        public ICommand DoCommand
            => doCommand ?? (doCommand = new Command
            (
                () => Advice = model.GetAdvice(), // Исполняемое по команде действие
                () => Enabled // Ссылка на поле разрешающее выполнение команды
            ));
    }


View
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
<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"
    Title="MainWindow"
    Width="400"
    Height="200"
    mc:Ignorable="d">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <StackPanel VerticalAlignment="Center">
        <CheckBox
            Margin="0,5"
            HorizontalAlignment="Center"
            Content="Enable button"
            IsChecked="{Binding Enabled}" />
 
        <Button
            Margin="0,5"
            HorizontalAlignment="Center"
            Command="{Binding DoCmd}"
            Content="Show advice" />
 
        <TextBlock
            IsEnabled="False"
            Text="{Binding Advice}"
            TextAlignment="Center" />
    </StackPanel>
</Window>



Добавлено через 23 минуты
Fix
4
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
20.01.2019, 02:42  [ТС]
Цитата Сообщение от Lexeq Посмотреть сообщение
И код в посте отличается от кода в проекте:
Дико извиняюсь! Моя оплошность! Исправление в начале темы внёс.
Цитата Сообщение от Lexeq Посмотреть сообщение
OnPropertyChangedClass монстр на все случаи жизни.
Да, я его делал под себя. На скорости он не сказывается. А до разбора интерфейса и его различных реализаций, я ещё не дошёл.
Цитата Сообщение от Lexeq Посмотреть сообщение
Модель и вьюмодель слились воедино,
Так и есть. Посчитал, что в данном случае для демонстрации привязки команд к методам ViewModel это не важно. А пример упрощает. Возможно, ошибаюсь.
0
HF
 Аватар для HF
1303 / 882 / 199
Регистрация: 09.09.2011
Сообщений: 2,590
Записей в блоге: 2
20.01.2019, 20:43
Я не стал ничего придумывать. Lexeq опубликовал хороший пример.
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
26.01.2019, 09:52
Цитата Сообщение от Элд Хасп Посмотреть сообщение
C#
1
2
3
4
5
public event EventHandler CanExecuteChanged
    {
       add { CommandManager.RequerySuggested += value; }
       remove { CommandManager.RequerySuggested -= value; }
    }
Вот эта часть — самая досадная и дурацкая, однако почему-то ее копируют по всему интернету.

Идея понятна: если надо заставить кишки WPF вызвать CanExecute, делаем вызов CommandManager.InvalidateRequerySuggeste d, который через сквозное привязывание обновляет забинденный элемент.
Проблема в том, что это вызовет обновления на всех забинденных командах, что может быть довольно накладно, если их в интерфейсе много (список с кнопочками?).

Более правильный вариант:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    private readonly EventHandler _requerySuggested;
 
    /// <summary>Событие извещающее об измении состояния команды</summary>
    public event EventHandler CanExecuteChanged;
 
    /// <summary>Конструктор команды</summary>
    /// <param name="execute">Выполняемый метод команды</param>
    /// <param name="canExecute">Метод разрешающий выполнение команды</param>
    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _onExecute = execute;
        _canExecute = canExecute;
 
        _requerySuggested = (o, e) => Invalidate();
        CommandManager.RequerySuggested += _requerySuggested;
    }
 
    public void Invalidate() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
Позволяет обновить только текущую команду, а не все в интерфейсе, плюс реагирует на вызов CommandManager.InvalidateRequerySuggeste d.
3
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
26.01.2019, 15:47  [ТС]
Цитата Сообщение от kolorotur Посмотреть сообщение
Вот эта часть — самая досадная и дурацкая, однако почему-то ее копируют по всему интернету.
Наверное, где-то на заре появления WPF был такой пример в документации от MS и с того времени его как стандартный копируют.
О проблемах вызывающих его использование читал, но как их решить не задумывался - не было надобности. Ваш пример обязательно учту. Спасибо!
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
26.01.2019, 16:07  [ТС]
Привожу финальный вариант с учётом замечаний HF, Lexeq, Рядовой, 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
    /// <summary>Класс реализующий интерфейс ICommand для создания WPF команд</summary>
    public class RelayCommand : ICommand
    {
        private readonly Func<object, bool> _canExecute;
        private readonly Action<object> _onExecute;
        private readonly EventHandler _requerySuggested;
 
        /// <summary>Событие извещающее об измении состояния команды</summary>
        public event EventHandler CanExecuteChanged;
 
        /// <summary>Конструктор команды</summary>
        /// <param name="execute">Выполняемый метод команды</param>
        /// <param name="canExecute">Метод разрешающий выполнение команды</param>
        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            _onExecute = execute;
            _canExecute = canExecute;
 
            _requerySuggested = (o, e) => Invalidate();
            CommandManager.RequerySuggested += _requerySuggested;
        }
 
        public void Invalidate() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
 
        /// <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);
    }
Модель и перечислитель (enum) допустимых операторов
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
    /// <summary>Перечисление допустимых операторов</summary>
    public enum OperatorsEnum { Сложение, Умножение, Вычитание, Деление };
    /// <summary>Класс Модели</summary>
    public class CalcModel
    {
        /// <summary>Возвращает последовательность значений перечисления OperatorsEnum</summary>
        /// <returns></returns>
        public IEnumerable<OperatorsEnum> GetOperators() 
            => Enum.GetValues(typeof(OperatorsEnum)).Cast<OperatorsEnum>();
 
        /// <summary>Вычисляет значение </summary>
        /// <returns>Значение, если оно вычислено</returns>
        public int? Calculate(int First, OperatorsEnum Operator, int Second)
        {
            switch (Operator)
            {
                case OperatorsEnum.Сложение: return First + Second;
                case OperatorsEnum.Умножение: return First * Second;
                case OperatorsEnum.Вычитание: return First - Second;
                case OperatorsEnum.Деление: return First / Second;
            }
            return null;
        }
 
        /// <summary>Определяет допустимо ли вычисление значения</summary>
        /// <returns>true - если вычисление разрешено</returns>
        public bool IsCanExecuteCalculate(int First, OperatorsEnum Operator, int Second)
            => Operator != OperatorsEnum.Деление || Second != 0;
 
    }
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
    /// <summary>Класс Модели Представления</summary>
    public class CalcViewModel : 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 IEnumerable<OperatorsEnum> Operators => model.GetOperators();
        #endregion
 
        #region Секция команд
        private ICommand _calculateCommand;
        public ICommand CalculateCommand => _calculateCommand ?? (_calculateCommand = new RelayCommand(OnCalculate, IsCanExecuteCalculate));
        #endregion
 
        CalcModel model = new CalcModel(); // Ссылка на модель
 
        #region Секция методов
        private void OnCalculate(object parameter)
        {
            if (parameter is OperatorsEnum Operator)
                Result = model.Calculate(First, Operator, Second);
            else
                Result = null;
        }
        private bool IsCanExecuteCalculate(object parameter)
        {
            if (parameter is OperatorsEnum Operator)
                return model.IsCanExecuteCalculate(First, Operator, Second);
            return false;
        }
        #endregion
    }
XAML окна
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Window x:Class="WpfCommands.CalcWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfCommands"
        mc:Ignorable="d"
        Title="Калькулятор" SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <local:CalcViewModel/>
    </Window.DataContext>
    <StackPanel Orientation="Horizontal">
        <TextBox VerticalAlignment="Center" 
                 Text="{Binding First}"
                 Margin="5" MinWidth="80"/>
        <TextBox Grid.Column="2" VerticalAlignment="Center" 
                 Text="{Binding Second}"
                 Margin="5"  MinWidth="80"/>
        <ComboBox x:Name="comboBox" Grid.Column="1" VerticalAlignment="Center" ItemsSource="{Binding Operators}"
                  Margin="5" MinWidth="80"/>
        <Button Content="Вычислить" Grid.Column="3" Command="{Binding CalculateCommand, Mode=OneWay}" 
                CommandParameter="{Binding SelectedValue, ElementName=comboBox}"
                Margin="5" MinWidth="80"/>
        <TextBox Grid.Column="4" VerticalAlignment="Center" 
                 Text="{Binding Result, Mode=OneWay}" 
                 Margin="5" IsReadOnly="True" MinWidth="80"/>
    </StackPanel>
</Window>
Общий архив приложен. В архиве также есть код Lexeq из поста #10
WpfCommands v2.zip - архив проекта без Решения. Проект надо подключить к имеющемуся Решению.
WpfCommandsSln.7z - архив Решения с проектом.
Вложения
Тип файла: zip WpfCommands v2.zip (108.6 Кб, 97 просмотров)
Тип файла: 7z WpfCommandsSln.7z (9.7 Кб, 103 просмотров)
2
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
26.01.2019, 17:51
Раз уж пошла такая пьянка, приведу свой вариант реализации команды для обертки асинхронных операций:
AsyncCommand.cs
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
 
public abstract class AsyncCommand : ICommand, INotifyPropertyChanged
{
    private bool _isExecuting;
    private CancellationTokenSource _cts;
    private readonly EventHandler _requerySuggestedHandler;
 
    public AsyncCommand()
    {
        _requerySuggestedHandler = (o, e) => RaiseCanExecuteChanged();
        CommandManager.RequerySuggested += _requerySuggestedHandler;
    }
 
    public bool IsExecuting
    {
        get => _isExecuting;
        set
        {
            if (_isExecuting != value)
            {
                _isExecuting = value;
                RaisePropertyChanged();
                RaiseCanExecuteChanged();
            }
        }
    }
    public TimeSpan? Timeout
    {
        get;
        set;
    }
 
    bool ICommand.CanExecute(object parameter) => !IsExecuting && CanExecuteCore();
 
    public async Task ExecuteAsync()
    {
        var command = this as ICommand;
        if (!command.CanExecute(null))
            return;
 
        IsExecuting = true;
 
        _cts = new CancellationTokenSource();
        if (Timeout != null)
            _cts.CancelAfter(Timeout.Value);
 
        try
        {
            await ExecuteCoreAsync(_cts.Token);
        }
        catch (OperationCanceledException)
            when (_cts.IsCancellationRequested)
        {
            ExecutionCancelled();
        }
        finally
        {
            _cts.Dispose();
            _cts = null;
            IsExecuting = false;
        }
    }
    async void ICommand.Execute(object parameter)
    {
        await ExecuteAsync();
    }
    public void Cancel() => _cts?.Cancel();
    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
 
    public event EventHandler CanExecuteChanged;
    public event PropertyChangedEventHandler PropertyChanged;
 
    protected abstract Task ExecuteCoreAsync(CancellationToken cancellationToken);
    protected abstract bool CanExecuteCore();
 
    protected virtual void ExecutionCancelled() { }
    protected void RaisePropertyChanged([CallerMemberName] string propertyName = null, bool allProperties = false)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(allProperties ? null : propertyName));
    }
}


Блокирует повторный запуск на время выполнения и предоставляет свойство IsExecuting с уведомлением об изменении для биндинга и всяких свистелок вроде отображения прогресса.
Поддерживает отмену, так же опционально можно задавать таймаут, но реализующий класс должен следить за передаваемым токеном.
В случае отмены вручную или по таймауту вызывается виртуальный метод ExecutionCancelled для дополнительной обработки наследующимся классом. Дефолтная реализация пустая.

Наследующийся от нее AsyncRelayCommand для обертки асинхронного метода:
AsyncRelayCommand.cs
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Threading;
using System.Threading.Tasks;
 
public class AsyncRelayCommand : AsyncCommand
{
    private readonly Func<bool> _canExecute;
    private readonly Func<CancellationToken, Task> _execute;
 
    public AsyncRelayCommand(Func<CancellationToken, Task> execute, Func<bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }
    public AsyncRelayCommand(Func<Task> execute, Func<bool> canExecute = null) : this((c) => execute(), canExecute)
    {
 
    }
 
    protected override bool CanExecuteCore() => _canExecute == null || _canExecute();
    protected override Task ExecuteCoreAsync(CancellationToken cancellationToken) => _execute(cancellationToken);
}
3
HF
 Аватар для HF
1303 / 882 / 199
Регистрация: 09.09.2011
Сообщений: 2,590
Записей в блоге: 2
26.01.2019, 18:13
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Привожу финальный вариант...
Вроде бы на первый взгляд смотрится нормально.

Но блин.. сразу в глаза бросается В двух местах. Значит можно вынести в метод.
C#
1
2
if (parameter is OperatorsEnum Operator)
  return model.IsCanExecuteCalculate(First, Operator, Second);
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
26.01.2019, 18:37  [ТС]
Цитата Сообщение от HF Посмотреть сообщение
Но блин.. сразу в глаза бросается В двух местах. Значит можно вынести в метод.
C#
58
59
if (parameter is OperatorsEnum Operator)
  return model.IsCanExecuteCalculate(First, Operator, Second);
Вроде же в одном месте.... Или я чего-то не замечаю?
0
HF
 Аватар для HF
1303 / 882 / 199
Регистрация: 09.09.2011
Сообщений: 2,590
Записей в блоге: 2
26.01.2019, 19:44
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Вроде же в одном месте.... Или я чего-то не замечаю?
Извиняюсь, проглядел. Там вызовы после условия разные. Условия одинаковые. Я что-то даже не понял сразу что методы ещё куда-то ссылаются, на какую-то модель. В ней тоже подобные методы? CalcModel будет заниматься ещё и решением типа IsCanExecuteCalculate? Об этом вроде бы и речи не было, да и я готов поспорить, что эта модель с нарушениями создана. Одно то, что она должна знать о каком то OperatorsEnum уже говорит о том что CalcModel не чистая модель или зависит сильно он этой CalcViewModel, а не должна.

Добавлено через 23 минуты
Плюс OnCalculate повторяет логику IsCanExecuteCalculate. Получается что метод CanExecute и не нужен. Он снова проверится.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
26.01.2019, 20:15  [ТС]
Цитата Сообщение от HF Посмотреть сообщение
Об этом вроде бы и речи не было, да и я готов поспорить, что эта модель с нарушениями создана
Давайте обсудим.
Напишу почему я так сделал. Сразу оговорюсь - не претендую на правильность, напротив интересно иное мнение и критика.
Цитата Сообщение от HF Посмотреть сообщение
Я что-то даже не понял сразу что методы ещё куда-то ссылаются, на какую-то модель.
Ввёл Модель так как это было одно из замечаний.
Методы VM и Model различаются функционально. В Model они оперируют с данными, а в VM приводят данные к нужному типу и если приведение удалось то обращаются к методам модели. Теоретически parameter может быть привязан к чему угодно (в том числе и по ошибке). В зависимости от его значения дальнейшие действия VM могут быть различны. В данном случае parameter проверяется только на тип. Но, допустим, в модели не единый метод для всех значений parameter , тогда VM будет ещё анализировать и его значение и в зависимости от результата обращаться к разным методам Модели.
Передавать же напрямую parameter в Модель без предварительной обработки, на мой взгляд, не корректно. Для обработки parameter нужны знания о View, а Модель ими не обладает
Цитата Сообщение от HF Посмотреть сообщение
Одно то, что она должна знать о каком то OperatorsEnum уже говорит о том что CalcModel не чистая модель или зависит сильно он этой CalcViewModel, а не должна.
На мой взгляд, OperatorsEnum - это часть Модели. Ну, для примера, как север сообщает о допустимых методах. OperatorsEnum - это и есть список допустимых для Модели методов. VM получает его не сама, а от Модели из метода GetOperators. Тип OperatorsEnum используется для типизации свойств VM и для проверки в методах VM. Так как это знание VM получает от модели, то это корректно.
View же ничего о OperatorsEnum не знает. Ей это знание не нужно.

Добавлено через 7 минут
Цитата Сообщение от HF Посмотреть сообщение
Плюс OnCalculate повторяет логику IsCanExecuteCalculate. Получается что метод CanExecute и не нужен. Он снова проверится.
Перечитал это несколько раз просмотрел код, но так и не понял о чём Вы здесь пишите. OnCalculate и IsCanExecuteCalculate функционально разные. Одинаковые они только по параметру. Но так и должно быть.
CanExecute это метод класса RelayCommand - он здесь причём?

Что-то не дошло до меня о чём Вы здесь пишите.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
26.01.2019, 20:15
Помогаю со студенческими работами здесь

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


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

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