Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.98/55: Рейтинг темы: голосов - 55, средняя оценка - 4.98
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2

Библиотека элементов для реализации WPF MVVM Решений [WPF, Элд Хасп]

27.11.2020, 19:21. Показов 12978. Ответов 15
Метки mvvm, wpf (Все метки)

Студворк — интернет-сервис помощи студентам
Решил собрать элементы используемые в темах в этом разделе.
В библиотеку включаю элементы которые, на мой взгляд, имеют универсальное применение не жёстко связанное с какой-то определённой задачей.
Исходные коды на GitHub https://github.com/WpfMvvm.
Пакеты будут также заливаться на NuGet.
В состав пакетов включена полная XML документация.

В этой теме будут примеры по использованию элементов, типов.
Исходные коды будут приводиться только там где это необходимо для прояснения работы.

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

Выкладывать пакеты буду по мере их готовности.

Для примеров будут только исходные коды на https://github.com/WpfMvvm/WpfMvvm.Examples
4
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
27.11.2020, 19:21
Ответы с готовыми решениями:

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

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

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

15
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
27.11.2020, 19:25  [ТС]
Простые реализации для тем на форуме
Исходные коды: https://github.com/WpfMvvm/Wpf... mentations
  1. Базовый класс для ViewModel - BaseInpc - реализация INotifyPropertyChanged и вспомогательных методов.
  2. RelayCommand и RelayCommand<T> - реализация команд для методов с параметром и без.


КонвертерыИсходные коды: https://github.com/WpfMvvm/WpfMvvm.Converters
NuGet пакет WpfMvvm.Converters: https://www.nuget.org/packages/WpfMvvm.Converters/
  1. BooleanNotConverter - логическое отрицание.
  2. DefaultValueConverter - дефолтное преобразование в другой тип.
  3. ChainOfConverters и ReadOnlyChainOfConverters - цепочки конвертеров.
  4. BooleanToVisibilityConverter и BooleanToVisibilityHiddenConverter - bool в видимость.
  5. TraceConverter - трассировка привязок.
  6. EnumValuesConverter - получение списка значений перечисления.
  7. EqualsConverter - сравнение значения с параметром.
  8. GetTypeConverter - получение типа значения.
  9. DictionaryConverter и DictionaryTypeConverter - конвертация по словарю.
  10. ExpressionConverter - вычисление простых математических выражений.


Команды
Исходные коды: https://github.com/WpfMvvm/WpfMvvm.Commands
NuGet пакет:https://www.nuget.org/packages/WpfMvvm.Commands/
  1. RelayCommand и RelayCommand<T> общего применения - реализация команд для применения вне слоя WPF.


Команды для WPF
Исходные коды: https://github.com/WpfMvvm/WpfMvvm.WpfCommands
NuGet пакет:https://www.nuget.org/packages... fCommands/
  1. RelayCommand и RelayCommand<T> для применения в WPF - реализация команд для применения в WPF.
3
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
27.11.2020, 20:29  [ТС]
BooleanNotConverter
Простейший конвертер реализующий логическое отрицание.
Код методов конвертера очень прост и одинаковый для обоих методов:
C#
27
28
29
30
31
32
33
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value.TryParse(out bool valBool))
                return (!valBool).ConvertToType(targetType, culture);
 
            return DependencyProperty.UnsetValue;
        }
Используется внешний статический метод:
C#
18
19
20
21
22
23
24
        public static object ConvertToType(this bool value, Type targetType, CultureInfo culture)
        {
            if (typeof(string).IsAssignableFrom(targetType))
                return value.ToString(culture);
 
            return value;
        }
Так как в конвертере нет никаких членов экземпляра, то имеет смысл предоставить один его экземпляр в статическом неизменяемом свойстве:
C#
55
56
        /// <summary>Экземпляр конвертера.</summary>
        public static BooleanNotConverter Instance { get; } = new BooleanNotConverter();
Используется он очень просто:
XML
24
25
26
27
28
29
30
31
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <FrameworkElement.Resources>
                <cnvs:BooleanNotConverter x:Key="BooleanNotConverter"/>
            </FrameworkElement.Resources>
            <CheckBox x:Name="checkBox" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox}"/>
            <TextBlock Text="{Binding IsChecked, Converter={StaticResource BooleanNotConverter}, ElementName=checkBox}"/>
        </UniformGrid>
Но, как и писал выше, создание каждый раз нового экземпляра конвертера большого смысла не имеет.
Можно обращаться к его статическому экземпляру:
XML
32
33
34
35
36
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <CheckBox x:Name="checkBox1" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox1}"/>
            <TextBlock Text="{Binding IsChecked, Converter={x:Static cnvs:BooleanNotConverter.Instance}, ElementName=checkBox1}"/>
        </UniformGrid>
Чтобы упростить в XAML обращение к конвертеру создано расширение разметки BooleanNotConverterExtension:
C#
16
17
        public override object ProvideValue(IServiceProvider serviceProvider)
            => BooleanNotConverter.Instance;
Применение конвертера становится ещё проще:
XML
37
38
39
40
41
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <CheckBox x:Name="checkBox2" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox2}"/>
            <TextBlock Text="{Binding IsChecked, Converter={cnvs:BooleanNotConverter}, ElementName=checkBox2}"/>
        </UniformGrid>
Полные коды конвертера, расширения разметки и примера

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
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
 
namespace WpfMvvm.Converters
{
    /// <summary>Инвертирует полученное логическое значение.</summary>
    /// <remarks>Если приходит <see cref="string"/>, то значение конвертируется 
    /// в <see cref="bool"/> методом <see cref="bool.TryParse(string, out bool)"/>.</remarks>
    /// <returns>Если значение не <see cref="bool"/> или <see cref="string"/>, конвертируемое в <see cref="bool"/> - возвращается <see cref="DependencyProperty.UnsetValue"/>.</returns>
    [ValueConversion(typeof(bool), typeof(bool))]
    [ValueConversion(typeof(string), typeof(bool))]
    [ValueConversion(typeof(bool), typeof(string))]
    [ValueConversion(typeof(string), typeof(string))]
    public class BooleanNotConverter : IValueConverter
    {
        /// <summary>Инвертирует полученное логическое значение.</summary>
        /// <param name="value">Значение в типе <see cref="bool"/> или <see cref="string"/>.</param>
        /// <param name="targetType">Тип целевого свойства.</param>
        /// <param name="parameter">Параметр конвертера. Не используется.</param>
        /// <param name="culture">Культура конвертера. Используется при парсинге <see cref="string"/> в <see cref="bool"/>.</param>
        /// <returns>Возвращает инверсию входного значения.<br/>
        /// Если значение не <see cref="bool"/> или <see cref="string"/>, конвертируемое в <see cref="bool"/>
        /// - возвращается <see cref="DependencyProperty.UnsetValue"/>.<para/>
        /// Если <paramref name="targetType"/>=<see cref="string"/>, то выходное значение конвертируется в строку.</returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value.TryParse(out bool valBool))
                return (!valBool).ConvertToType(targetType, culture);
 
            return DependencyProperty.UnsetValue;
        }
 
        /// <summary>Инвертирует полученное логическое значение.</summary>
        /// <param name="value">Значение в типе <see cref="bool"/> или <see cref="string"/>.</param>
        /// <param name="targetType">Тип свойства источника.</param>
        /// <param name="parameter">Параметр конвертера. Не используется.</param>
        /// <param name="culture">Культура конвертера. Используется при парсинге <see cref="string"/> в <see cref="bool"/>.</param>
        /// <returns>Возвращает инверсию входного значения.<br/>
        /// Если значение не <see cref="bool"/> или <see cref="string"/>, конвертируемое в <see cref="bool"/>
        /// - возвращается <see cref="DependencyProperty.UnsetValue"/>.<para/>
        /// Если <paramref name="targetType"/>=<see cref="string"/>, то выходное значение конвертируется в строку.</returns>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value.TryParse(out bool valBool))
                return (!valBool).ConvertToType(targetType, culture);
 
            return DependencyProperty.UnsetValue;
        }
 
        /// <summary>Создаёт экземпляр <see cref="BooleanNotConverter"/>.</summary>
        public BooleanNotConverter() { }
 
        /// <summary>Экземпляр конвертера.</summary>
        public static BooleanNotConverter Instance { get; } = new BooleanNotConverter();
    }
}
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.Windows.Data;
using System.Windows.Markup;
 
namespace WpfMvvm.Converters
{
    /// <summary>Предоставляет экземпляр <see cref="BooleanNotConverter"/> из <see cref="BooleanNotConverter.Instance"/>.</summary>
    [MarkupExtensionReturnType(typeof(BooleanNotConverter))]
    public class BooleanNotConverterExtension : MarkupExtension
    {
        /// <summary>Возвращает экземпляр конвертера из <see cref="BooleanNotConverter.Instance"/>.</summary>
        /// <param name="serviceProvider">Вспомогательный объект поставщика служб,
        /// способный предоставлять службы для расширения разметки.<para/>
        /// Не используется.</param>
        /// <returns>Экземпляр из <see cref="BooleanNotConverter.Instance"/>.</returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
            => BooleanNotConverter.Instance;
 
        /// <summary>Создаёт экземпляр <see cref="BooleanNotConverterExtension"/>.</summary>
        public BooleanNotConverterExtension() { }
    }
}
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
43
<Window x:Class="WpfMvvm.Converters.Examples.BooleanNot.BooleanNotWindow"
        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:WpfMvvm.Converters.Examples.BooleanNot"
        xmlns:cnvs="clr-namespace:WpfMvvm.Converters;assembly=WpfMvvm.Converters"
        mc:Ignorable="d"
        Title="BooleanNotWindow" Height="450" Width="800">
    <FrameworkElement.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Background" Value="LightPink"/>
            <Setter Property="Margin" Value="20,0"/>
            <Setter Property="TextAlignment" Value="Center"/>
            <Setter Property="Padding" Value="15,5"/>
            <Style.Triggers>
                <Trigger Property="Text" Value="True">
                    <Setter Property="Background" Value="LightGreen"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </FrameworkElement.Resources>
    <UniformGrid Columns="1">
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <FrameworkElement.Resources>
                <cnvs:BooleanNotConverter x:Key="BooleanNotConverter"/>
            </FrameworkElement.Resources>
            <CheckBox x:Name="checkBox" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox}"/>
            <TextBlock Text="{Binding IsChecked, Converter={StaticResource BooleanNotConverter}, ElementName=checkBox}"/>
        </UniformGrid>
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <CheckBox x:Name="checkBox1" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox1}"/>
            <TextBlock Text="{Binding IsChecked, Converter={x:Static cnvs:BooleanNotConverter.Instance}, ElementName=checkBox1}"/>
        </UniformGrid>
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <CheckBox x:Name="checkBox2" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox2}"/>
            <TextBlock Text="{Binding IsChecked, Converter={cnvs:BooleanNotConverter}, ElementName=checkBox2}"/>
        </UniformGrid>
    </UniformGrid>
</Window>
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
27.11.2020, 20:44  [ТС]
DefaultValueConverter
Часто возникает (как мне кажется) задача преобразования значения в другой тип эквивалентно тому как это делают привязки.
К сожалению конвертер осуществляющий это преобразование доступен только внутри сборки (модификатор internal): https://referencesource.micros... 3beb6d4d79.

В моём конвертере происходит (с использованием к рефлексии) получение этого конвертера и применение его к значению.
Предусмотрена передача в конвертер типов целевого свойства и свойства источника (для обратного преобразования).
Также создано расширение разметки для облегчения использования конвертера в XAML.

Пример его использования:
XML
31
32
33
34
35
36
37
38
    <UniformGrid x:Name="uniformGrid" Columns="2" 
                 Tag="{Binding Text, ElementName=textBox, Converter={cnvs:DefaultValueConverter TargetType=Point}}">
        <TextBlock Text="Значения X и Y через разделитель:"/>
        <TextBox x:Name="textBox" Text="123 456"/>
        <TextBlock Text="Значение X:"/>
        <TextBlock Text="{Binding Tag.X, ElementName=uniformGrid}"/>
        <TextBlock Text="Значение Y:"/>
        <TextBlock Text="{Binding Tag.Y, ElementName=uniformGrid}"/>
В примере происходит получение значения TextBox, его преобразование в Point и присвоение преобразованного значения свойству Tag.
Из этого свойства мы уже можем получить отдельно каждое из свойство Point.

Очень часто задача преобразования типа встречается при передаче значения в параметр команды с обобщённым типом.
Так как свойство CommandParameter имеет тип Object, то привязка к нему не делает никаких преобразований.
В тоже время, реализаций команды с обобщённым типом предполагает получение в параметре типа приводимого к использованному и получения в другом типе (чаще всего в String) приводит к ошибке.
Для решения этого приходится в ViewModel добавлять свойство этого типа, только с целью получения автоматического преобразования типа.
И в параметр передавать не исходное значение из View, а значение этого свойства.

Пример реализации команды с обобщённым типом:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
 
namespace WpfMvvm.Converters.Examples
{
    public class RelayCommand<T> : RelayCommand
    {
        public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
            : base
            (
                  p =>
                  {
                      if (p is T t)
                          execute(t);
                  },
                  p => (p is T t) && (canExecute?.Invoke(t) ?? true)
            )
        { }
    }
}
Пример ViewModel:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Windows;
using System.Windows.Input;
 
namespace WpfMvvm.Converters.Examples.DefaultValue
{
    public class DefaultValueViewModel : BaseInpc
    {
        private Point _currentPoint;
        private ICommand _changePointCommand;
 
        public Point CurrentPoint { get => _currentPoint; set => Set(ref _currentPoint, value); }
 
        public ICommand ChangePointCommand => _changePointCommand
            ?? (_changePointCommand = new RelayCommand<Point>(point => CurrentPoint = point));
 
    }
 
}
Пример XAML (продолжающий вышеуказанный):
XML
39
40
41
42
43
44
45
46
47
        <Button Content="Присвоить значение (привязка без конвертера)"
                Command="{Binding ChangePointCommand, Mode=OneWay}"
                CommandParameter="{Binding Text, ElementName=textBox}"/>
        <Button Content="Присвоить значение (привязка с конвертером)"
                Command="{Binding ChangePointCommand, Mode=OneWay}"
                CommandParameter="{Binding Text, ElementName=textBox, Converter={cnvs:DefaultValueConverter TargetType=Point}}"/>
        <TextBlock Text="Значение свойства CurrentPoint:"/>
        <TextBlock Text="{Binding CurrentPoint}"/>
    </UniformGrid>
Кнопка без конвертера не реагирует на изменение значения TextBox, так как в параметр команды приходит String и СanExecute всегда возвращает false.
Кнопка с конвертером получает уже Point и, если в TextBox корректное значение, кнопка активируется.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
27.11.2020, 21:03  [ТС]
ChainOfConverters и ReadOnlyChainOfConverters
Конвертеры позволяющие соединить несколько конвертеров в цепочку.
У ChainOfConverters эта цепочка мутабельна и его легко использовать в XAML.
У ReadOnlyChainOfConverters задаётся один раз в конструкторе и после этого её изменить нельзя. Он предназначен для использования в коде C#.

Для примера простой конвертер для получения по bool значению двух цветов:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
 
namespace WpfMvvm.Converters.Examples.Chains
{
    public partial class BooleanToColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool val)
                return val ? Brushes.LightGreen : Brushes.LightPink;
            return DependencyProperty.UnsetValue;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
А допустим в XAML нам требуется также инверсная версия этого конвертера.
И получить мы её можем так:
XML
18
19
20
21
22
23
24
25
26
27
28
29
30
31
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <FrameworkElement.Resources>
                <local:BooleanToColorConverter x:Key="BooleanToColorConverter"/>
                <cnvs:ChainOfConverters x:Key="NotBooleanToColorConverter">
                    <x:Static Member="cnvs:BooleanNotConverter.Instance"/>
                    <StaticResource ResourceKey="BooleanToColorConverter"/>
                </cnvs:ChainOfConverters>
            </FrameworkElement.Resources>
            <CheckBox x:Name="checkBox" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox}"
                       Background="{Binding IsChecked, Converter={StaticResource BooleanToColorConverter}, ElementName=checkBox}"/>
            <TextBlock Text="{Binding IsChecked, Converter={cnvs:BooleanNotConverter}, ElementName=checkBox}"
                       Background="{Binding IsChecked, Converter={StaticResource NotBooleanToColorConverter}, ElementName=checkBox}"/>
        </UniformGrid>

Но можно задать статические экземпляры для прямой и инверсной привязки:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System.Windows.Data;
 
namespace WpfMvvm.Converters.Examples.Chains
{
    public partial class BooleanToColorConverter
    {
        /// <summary>Экземпляр конвертера.</summary>
        public static BooleanToColorConverter Instance { get; } = new BooleanToColorConverter();
 
        /// <summary>Экземпляр конвертера с отрицанием.</summary>
        public static IValueConverter InstanceNot { get; }
            = new ReadOnlyChainOfConverters(BooleanNotConverter.Instance, Instance);
 
    }
}
И с ними привязка будет такой:
XML
32
33
34
35
36
37
38
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <CheckBox x:Name="checkBox1" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox1}"
                       Background="{Binding IsChecked, Converter={x:Static local:BooleanToColorConverter.Instance}, ElementName=checkBox1}"/>
            <TextBlock Text="{Binding IsChecked, Converter={cnvs:BooleanNotConverter}, ElementName=checkBox1}"
                       Background="{Binding IsChecked, Converter={x:Static local:BooleanToColorConverter.InstanceNot}, ElementName=checkBox1}"/>
        </UniformGrid>
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
27.11.2020, 21:03  [ТС]
BooleanToVisibilityConverter и BooleanToVisibilityHiddenConverter
Имеющийся в Net конвертер обеспечивает привязку bool к видимости и для false возвращается коллапс.
Но иногда возникают задачи в которых надо получить инверсную привязку и/или вместо коллапса получить скрытие элемента.
В конвертере BooleanToVisibilityHiddenConverter заменяется значение Visibility.Collapsed, возвращаемое BooleanToVisibilityConverter, на значение Visibility.Hidden.
Инверсные версии конвертеров получаются с использованием цепочки конвертеров:
C#
55
56
57
58
        /// <summary>Экземпляр конвертера.</summary>
        public static BooleanToVisibilityConverter Instance { get; } = new BooleanToVisibilityConverter();
        /// <summary>Экземпляр конвертера инверсный к <see cref="Instance"/>.</summary>
        public static ReadOnlyChainOfConverters NotInstance { get; } = new ReadOnlyChainOfConverters(BooleanNotConverter.Instance, Instance);
Также, для облечения использования, создано расширение разметки.

Пример использования:
XML
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
    <FrameworkElement.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Margin" Value="20,10"/>
            <Setter Property="TextAlignment" Value="Center"/>
            <Setter Property="Padding" Value="15,5"/>
        </Style>
    </FrameworkElement.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <CheckBox x:Name="checkBox" Content="Включить/выключить" Margin="10" HorizontalAlignment="Center"/>
        <UniformGrid Columns="1" Grid.Row="1">
        <Border Background="LightBlue" Padding="10" VerticalAlignment="Center">
            <UniformGrid Columns="1" VerticalAlignment="Center">
                <TextBlock Text="Демонстрация BooleanToVisibilityConverter (с Коллапсом)"/>
                <Border BorderBrush="Blue" BorderThickness="2" HorizontalAlignment="Center" Margin="5">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="True" Background="LightGreen" Width="200"
                           Visibility="{Binding IsChecked, Converter={cnvs:BooleanToVisibility}, ElementName=checkBox}"/>
                        <TextBlock Text="Конец StackPanel" Background="AliceBlue"/>
                    </StackPanel>
                </Border>
                <Border BorderBrush="Blue" BorderThickness="2" HorizontalAlignment="Center" Margin="5">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="True" Background="LightGreen" Width="200"
                           Visibility="{Binding IsChecked, Converter={cnvs:BooleanToVisibility}, ElementName=checkBox}"/>
                        <TextBlock Text="False" Background="LightPink" Width="200"
                           Visibility="{Binding IsChecked, Converter={cnvs:BooleanToVisibility Mode=Not}, ElementName=checkBox}"/>
                        <TextBlock Text="Конец StackPanel" Background="AliceBlue"/>
                    </StackPanel>
                </Border>
            </UniformGrid>
        </Border>
        <Border Background="LightBlue" Padding="10" VerticalAlignment="Center">
            <UniformGrid Columns="1" VerticalAlignment="Center">
                <TextBlock Text="Демонстрация BooleanToVisibilityHiddenConverter (со Скрытием)"/>
                <Border BorderBrush="Blue" BorderThickness="2" HorizontalAlignment="Center" Margin="5">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="True" Background="LightGreen" Width="200"
                           Visibility="{Binding IsChecked, Converter={cnvs:BooleanToVisibility Mode=Hidden}, ElementName=checkBox}"/>
                        <TextBlock Text="Конец StackPanel" Background="AliceBlue"/>
                    </StackPanel>
                </Border>
                <Border BorderBrush="Blue" BorderThickness="2" HorizontalAlignment="Center">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="True" Background="LightGreen" Width="200"
                           Visibility="{Binding IsChecked, Converter={cnvs:BooleanToVisibility Mode=Hidden}, ElementName=checkBox}"/>
                        <TextBlock Text="False" Background="LightPink" Width="200"
                           Visibility="{Binding IsChecked, Converter={cnvs:BooleanToVisibility Mode=NotHiden}, ElementName=checkBox}"/>
                        <TextBlock Text="Конец StackPanel" Background="AliceBlue"/>
                    </StackPanel>
                </Border>
            </UniformGrid>
        </Border>
    </UniformGrid></Grid>
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
28.11.2020, 08:57  [ТС]
TraceConverter
Конвертер для трассировки привязок.
Выводит во время Отладки в окно Вывода получаемое/возвращаемое значение Привязки.
Конвертер никак не изменяет входное значение.
Если входное значение имеет тип String, то его вывод обрамляется кавычками.
Для отличия выводов из разных точек предусмотрено свойство Title, со значения которого будет начинаться строка вывода.
В расширении разметки оно может передаваться первым неименованным параметром.

Пример использования:
XML
24
25
26
27
28
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <CheckBox x:Name="checkBox1" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox1, Converter={cnvs:TraceConverter Nornal}}"/>
            <TextBlock Text="{Binding IsChecked, Converter={cnvs:BooleanNotConverter}, ElementName=checkBox1}"/>
        </UniformGrid>
В расширении разметки предусмотрено соединение в цепочку с другим конвертером, для проверки работы конвертера.
В цепочке TraceConverter может быть перед проверяемым конвертером (то есть между ним и целевым свойством) и/или после.
Положение конвертера указывается в свойстве AfterBefore расширения разметки значением перечисления AfterBeforeEnum.
Может передаваться неименованным параметром.
В примере соединение в цепочку TraceConverter -> BooleanNotConverter -> TraceConverter.
Трассируется значения передаваемое в конвертер и возвращаемое им.
XML
29
30
31
32
33
34
35
36
        <UniformGrid Columns="3" VerticalAlignment="Center">
            <FrameworkElement.Resources>
                <cnvs:BooleanNotConverter x:Key="BooleanNotConverter"/>
            </FrameworkElement.Resources>
            <CheckBox x:Name="checkBox" Content="Включить/выключить" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
            <TextBlock Text="{Binding IsChecked, ElementName=checkBox}"/>
            <TextBlock Text="{Binding IsChecked, Converter={cnvs:TraceConverter Negation, AfterAndBefore, Converter={cnvs:BooleanNotConverter}}, ElementName=checkBox}"/>
        </UniformGrid>
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
28.11.2020, 09:06  [ТС]
EnumValuesConverter
Часто возникающая задача: Получение списка значений перечисления.
Конвертер возвращает Array со всеми значениями перечисления.
Перечисление может передаваться как тип или как одно из его значений.
Может быть передано в источнике, или в параметре конвертера.
У источника приоритет выше.

Примеры использования:
XML
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    <FrameworkElement.Resources>
        <x:Type TypeName="Visibility" x:Key="visibility"/>
        <x:Static Member="Visibility.Hidden" x:Key="hidden"/>
    </FrameworkElement.Resources>
    <UniformGrid Columns="2"
                 DataContext="{DynamicResource hidden}">
        <ItemsControl ItemsSource="{Binding Converter={cnvs:EnumValuesConverter}}"
                      HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <ItemsControl ItemsSource="{Binding Converter={cnvs:EnumValuesConverter}, ConverterParameter={StaticResource hidden}}"
                      HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <ItemsControl ItemsSource="{Binding Source={StaticResource visibility}, Converter={cnvs:EnumValuesConverter}}"
                      HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <ItemsControl ItemsSource="{Binding Converter={cnvs:EnumValuesConverter}, ConverterParameter={x:Type Visibility}}"
                      HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </UniformGrid>
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
28.11.2020, 09:21  [ТС]
EqualsConverter
Конвертер сравнивающий значение с параметром.
В свойстве IsNot можно задать инверсию сравнения.
В обратной конвертации при значении true (если задана инверсия, то при false) происходит присвоение источнику параметра конвертера.
Иначе присвоение не происходит - возвращается Binding.DoNothing.

Применение конвертера может быть весьма различно.
В примере показано использование для придания группе CheckBox поведения сходного с RadioButton.
XML
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
    <FrameworkElement.Resources>
        <x:Array x:Key="array" Type="sys:String">
            <sys:String>Один</sys:String>
            <sys:String>Два</sys:String>
            <sys:String>Три</sys:String>
            <sys:String>Четыре</sys:String>
            <sys:String>Пять</sys:String>
        </x:Array>
        <DataTemplate x:Key="String.Template.Equals">
            <CheckBox Content="{Binding }"
                      IsChecked="{Binding SelectedItem, ElementName=cBox, Converter={cnvs:EqualsConverter}}"/>
        </DataTemplate>
    </FrameworkElement.Resources>
    <UniformGrid Columns="1">
        <ComboBox x:Name="cBox" HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="100"
                      ItemsSource="{DynamicResource array}"
                      SelectedIndex="1" />
        <UniformGrid Columns="2">
            <StackPanel>
                <CheckBox Content="Один"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Один}"/>
                <CheckBox Content="Два"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Два}"/>
                <CheckBox Content="Три"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Три}"/>
                <CheckBox Content="Четыре"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Четыре}"/>
                <CheckBox Content="Пять"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Пять}"/>
            </StackPanel>
            <StackPanel>
                <CheckBox Content="Один"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Один}"/>
                <CheckBox Content="Два"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Два}"/>
                <CheckBox Content="Три"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Три}"/>
                <CheckBox Content="Четыре"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Четыре}"/>
                <CheckBox Content="Пять"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Пять}"/>
            </StackPanel>
Также он может использоваться для привязки RadioButtоn находящихся в разных панелях и даже окнах.
Они связываются между собой через свойство-источник и поэтому наличие общей группы не играет значения:
XML
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
            <StackPanel>
                <Grid>
                    <RadioButton Content="Один"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Один}"/>
                </Grid>
                <Grid>
                    <RadioButton Content="Два"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Два}"/>
                </Grid>
                <Grid>
                    <RadioButton Content="Три"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Три}"/>
                </Grid>
                <Grid>
                    <RadioButton Content="Четыре"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Четыре}"/>
                </Grid>
                <Grid>
                    <RadioButton Content="Пять"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter},
                          ConverterParameter=Пять}"/>
                </Grid>
            </StackPanel>
            <StackPanel>
                <Grid>
                    <RadioButton Content="Один"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Один}"
                          Click="RadioButtonClick"/>
                </Grid>
                <Grid>
                    <RadioButton Content="Два"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Два}"
                          Click="RadioButtonClick"/>
                </Grid>
                <Grid>
                    <RadioButton Content="Три"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Три}"
                          Click="RadioButtonClick"/>
                </Grid>
                <Grid>
                    <RadioButton Content="Четыре"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Четыре}"
                          Click="RadioButtonClick"/>
                </Grid>
                <Grid>
                    <RadioButton Content="Пять"
                          IsChecked="{Binding SelectedItem, ElementName=cBox,
                          Converter={cnvs:EqualsConverter IsTrue=False},
                          ConverterParameter=Пять}"
                          Click="RadioButtonClick"/>
                </Grid>
                <x:Code>
                    <![CDATA[
        private void RadioButtonClick(object sender, RoutedEventArgs e)
        {
            var button = (RadioButton)sender;
            button.IsChecked = false;
        }
                    ]]>
                </x:Code>
            </StackPanel>
        </UniformGrid>
    </UniformGrid>
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
28.11.2020, 09:29  [ТС]
GetTypeConverter
Возвращает тип значения.
В примере показано использование для задания разным типам разных шаблонов представления.

Классы фигур:
C#
1
2
3
4
5
6
7
8
9
10
11
12
namespace WpfMvvm.Converters.Examples.GetType
{
    public abstract class Figure
    {
        public double Width { get; set; }
        public double Height { get; set; }
    }
 
    public class Line : Figure { }
    public class Circle : Figure { }
    public class Rectangle : Figure { }
}
Коллекция в ресурсах окна:
XML
10
11
12
13
14
15
16
17
        <x:Array x:Key="figures" Type="{x:Type local:Figure}">
            <local:Circle Width="100" Height="50"/>
            <local:Line Width="100" Height="50"/>
            <local:Rectangle Width="100" Height="50"/>
            <local:Circle Width="50" Height="100"/>
            <local:Line Width="50" Height="100"/>
            <local:Rectangle Width="50" Height="100"/>
        </x:Array>
Стиль использующий GetTypeConverter:
XML
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
        <Style x:Key="Item.Style" TargetType="ContentPresenter">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Converter={cnvs:GetTypeConverter}}" Value="{x:Type local:Circle}">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate DataType="{x:Type local:Circle}">
                                <Ellipse Width="{Binding Width}" Height="{Binding Height}"
                                         StrokeThickness="2" Stroke="Aqua"/>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
                <DataTrigger Binding="{Binding Converter={cnvs:GetTypeConverter}}" Value="{x:Type local:Rectangle}">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate DataType="{x:Type local:Rectangle}">
                                <Rectangle Width="{Binding Width}" Height="{Binding Height}"
                                           StrokeThickness="2" Stroke="LightGreen"/>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
                <DataTrigger Binding="{Binding Converter={cnvs:GetTypeConverter}}" Value="{x:Type local:Line}">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate DataType="{x:Type Line}">
                                <Line Width="{Binding Width}" Height="{Binding Height}"
                                      StrokeThickness="2" Stroke="Coral"
                                      X2="{Binding Width}" Y2="{Binding Height}"/>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
ItemsControl выводящий коллекцию разными фигурами:
XML
54
55
56
    <Grid>
        <ItemsControl ItemsSource="{DynamicResource figures}" ItemContainerStyle="{StaticResource Item.Style}"/>
    </Grid>
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
28.11.2020, 10:06  [ТС]
DictionaryConverter и DictionaryTypeConverter
DictionaryConverter для преобразования значения использует словарь.
Словарь может быть передан в свойстве Dictionary или в параметре конвертера.
DictionaryConverter производный от Freezable и свойство Dictionary может быть привязано.
Приоритет у параметра конвертера выше чем у свойства Dictionary.

В статическом свойстве Instance предоставлен замороженный экземпляр конвертера.

Пример использования.
ViewModel:
C#
1
2
3
4
5
6
7
8
namespace WpfMvvm.Converters.Examples.Dictionaries
{
    public class DictionariesViewModel
    {
        public int[] Numbers { get; } = { 1, 4, 5, 6, 2, 3 };
        public string[] Colors { get; } = { "Голубой", "Зелёный", "Жёлтый" };
    }
}
Статический класс со словарём:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Collections.Generic;
using System.Windows.Media;
 
namespace WpfMvvm.Converters.Examples.Dictionaries
{
    public static class ValuesForView
    {
        public static Dictionary<object, object> Dictionary { get; } = new Dictionary<object, object>()
        {
            {1, "Один"},
            {2, "Два"},
            {3, "Три"},
            {4, "Четыре"},
            {5, "Пять"},
            { "Голубой", Brushes.LightBlue},
            { "Зелёный", Brushes.Green},
            { "Жёлтый", Brushes.Yellow},
        };
    }
}
XAML Окна:
XML
9
10
11
12
13
14
15
16
17
18
19
20
21
    <FrameworkElement.DataContext>
        <local:DictionariesViewModel/>
    </FrameworkElement.DataContext>
    <UniformGrid Columns="2">
        <ListBox x:Name="listBox" ItemsSource="{Binding Numbers}" SelectedIndex="1"/>
        <TextBlock Text="{Binding SelectedItem, ElementName=listBox,
                   Converter={cnvs:DictionaryConverter},
                   ConverterParameter={x:Static local:ValuesForView.Dictionary}}"/>
        <ListBox x:Name="listBox1" ItemsSource="{Binding Colors}" SelectedIndex="1"/>
        <Border Background="{Binding SelectedItem, ElementName=listBox1,
                Converter={cnvs:DictionaryConverter},
                ConverterParameter={x:Static local:ValuesForView.Dictionary}}"/>
    </UniformGrid>

DictionaryTypeConverter отличается тем, что если значение имеет не тип Type, то для ключа используется не само значение, а его тип.
Если такого типа в ключах словаря нет, то можно быть произведён поиск базовых типов.
Включение такого поиска задаётся в свойстве UseBasicTypes.
В статических свойствах InstanceBaseTypes и InstanceEqualsTypes предоставлено два замороженных экземпляра конвертера, соответственно, с использованием базовых типов и только эквивалентных.

В расширении разметки свойством UseTypes задаётся какой из конвертеров будет возвращён.

Пример использования.
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
using System;
using System.Collections.Generic;
 
namespace WpfMvvm.Converters.Examples.Dictionaries
{
    public class DictionariesTypeViewModel
    {
        public Figure[] Figures { get; }
        =   {
                new Circle() {Width = 100, Height = 100},
                new Line() {Width = 100, Height = 100},
                new Rectangle() {Width = 100, Height = 100},
                new Circle() {Width = 50, Height = 100},
                new Square() {Width = 50, Height = 100},
                new Line() {Width = 50, Height = 100}
            };
 
        public Dictionary<Type, string> TypesFigures { get; }
            = new Dictionary<Type, string>()
            {
                { typeof(Figure), "Фигура" },
                { typeof(Circle), "Элипс" },
                { typeof(Rectangle), "Прямоугольник" },
                { typeof(Square), "Квадрат" }
            };
    }
}
Ресурсный словарь:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfMvvm.Converters.Examples.Dictionaries"
                    xmlns:cnvs="clr-namespace:WpfMvvm.Converters;assembly=WpfMvvm.Converters">
    <cnvs:DictionaryTypeConverter x:Key="FiguresTemplates">
        <DataTemplate x:Key="{x:Type local:Circle}">
            <Ellipse  Width="{Binding Width}" Height="{Binding Height}"
                      StrokeThickness="2" Stroke="Aqua"/>
        </DataTemplate>
        <DataTemplate x:Key="{x:Type local:Rectangle}">
            <Rectangle Width="{Binding Width}" Height="{Binding Height}"
                       StrokeThickness="2" Stroke="LightGreen"/>
        </DataTemplate>
        <DataTemplate x:Key="{x:Type local:Line}">
            <Line Width="{Binding Width}" Height="{Binding Height}"
                  StrokeThickness="2" Stroke="Coral"
                  X2="{Binding Width}" Y2="{Binding Height}"/>
        </DataTemplate>
    </cnvs:DictionaryTypeConverter>
</ResourceDictionary>
XAML Окна:
XML
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    <FrameworkElement.Resources>
        <ResourceDictionary Source="FiguresDictionary.xaml"/>
    </FrameworkElement.Resources>
    <FrameworkElement.DataContext>
        <local:DictionariesTypeViewModel/>
    </FrameworkElement.DataContext>
    <Grid>
        <FrameworkElement.Resources>
            <cnvs:DictionaryTypeConverter x:Key="FiguresNames" Dictionary="{Binding TypesFigures}"/>
        </FrameworkElement.Resources>
        <ItemsControl ItemsSource="{Binding Figures}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <ContentControl Content="{Binding}"
                                        ContentTemplate="{Binding Converter={StaticResource FiguresTemplates}}"/>
                        <TextBlock Text="{Binding Converter={StaticResource FiguresNames}}"
                                   VerticalAlignment="Center" HorizontalAlignment="Center"/>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>

Подразумевается, что в конвертер передаётся статический словарь, то есть задаваемый один раз до показа (Loaded) элемента с привязкой использующей этот конвертер.
Так как используется обычный конвертер (а не мульти), то обращение к нему происходит только при изменении значения свойства источника.
Изменение словаря не обновит привязку.
Это видно в последнем примере, в режиме разработки, названия фигур не выводятся. Так как события Loaded не происходит. А словарь из ресурсов привязывается уже после создания вложенных элементов Окна.
А при запуске приложения происходит событие Loaded, по которому происходит перепроверка всех привязанных значений.
На этот момент словарь уже привязан и названия фигур нормально выводятся.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
28.11.2020, 20:44  [ТС]
ExpressionConverter
Конвертер вычисляющий простые арифметические выражения.
Для получения строкового выражения из привязок используется метод Format(String, Object[]).
Для строки составного формата используется либо параметр конвертера, либо значение первой привязки.
Для вычисления полученного выражения в строке используется метод DataTable.Compute(String, String).
Первым значением передаются полученная строка с выражением, вторым - пустая строка.

Конвертер можно использовать как обычный конвертер для одного значения:
XML
37
38
39
40
        <TextBlock Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center">
            <Run Text="Половина высоты окна:"/>
            <Run Text="{Binding ActualHeight, ElementName=window, Mode=OneWay, Converter={cnvs:ExpressionConverter}, ConverterParameter='{}{0} / 2.0'}"/>
        </TextBlock>
Можно использовать и как мультиконвертер для нескольких значений:
XML
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
    <FrameworkElement.Resources>
        <x:Array Type="sys:String" x:Key="operators">
            <sys:String>+</sys:String>
            <sys:String>-</sys:String>
            <sys:String>/</sys:String>
            <sys:String>*</sys:String>
        </x:Array>
    </FrameworkElement.Resources>
    <UniformGrid Columns="1">
        <UniformGrid Columns="5" VerticalAlignment="Center">
            <TextBox x:Name="tb1" Text="1.2" Margin="5" HorizontalContentAlignment="Center"/>
            <ComboBox x:Name="cBox" Margin="5" HorizontalContentAlignment="Center"
                      ItemsSource="{DynamicResource operators}"
                      SelectedIndex="0"/>
            <TextBox x:Name="tb2" Text="3.4" Margin="5" HorizontalContentAlignment="Center"/>
            <TextBlock Text="=" TextAlignment="Center" Margin="5"/>
            <TextBlock TextAlignment="Center" Margin="5">
                <TextBlock.Text>
                    <MultiBinding Converter="{cnvs:ExpressionConverter}" ConverterParameter="{}({0}) {1} ({2})">
                        <Binding Path="Text" ElementName="tb1"/>
                        <Binding Path="SelectedItem" ElementName="cBox"/>
                        <Binding Path="Text" ElementName="tb2"/>
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </UniformGrid>
2
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
12.12.2020, 21:06  [ТС]
RelayCommand и RelayCommand<T> общего применения
Реализация ICommand для общего применения.
Дополнительно к интерфейсу добавлен метод для подъёма события CanExecuteChanged и свойство с типом методов команды.
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
namespace WpfMvvm.Commands
{
    /// <summary>Интерфейс добавляющий в интерфейс <see cref="ICommand"/>
    /// метод <see cref="RaiseCanExecuteChanged"/>, поднимающий событие <see cref="ICommand.CanExecuteChanged"/>.</summary>
    public interface ICommandRaise : ICommand
    {
        /// <summary>Подымает (создаёт) событие <see cref="ICommand.CanExecuteChanged"/>.</summary>
        void RaiseCanExecuteChanged();
    }
 
    /// <summary>Интерфейс для команд с методом <see cref="ICommandRaise.RaiseCanExecuteChanged"/>
    /// и заданным типом параметра.</summary>
    /// <remarks>Если команда получает параметр в ином типе,
    /// то он сначала должен быть конвертирован в тип <see cref="ParameterType"/>
    /// и только потом передаваться в методы его обрабатывающие.<br/>
    /// Если <see cref="ParameterType"/>=<see langword="null"/>, то значит методы не принимают параметра.<br/>
    /// Если параметр нельзя конвертировать в заданный тип, то реализация интерфейса может выкинуть исключение,
    /// но не обязательно. Это поведение определяется в самой реализации.</remarks>
    public interface IRelayCommand : ICommandRaise, ICommand
    {
        /// <summary>Тип параметра.</summary>
        Type ParameterType { get; }
    }
}
Интерфейс IRelayCommand реализован в базовом классе RelayCommand, принимающем в конструкторе делегаты методов с object параметром и методов без параметра.
От этого класса создан производный RelayCommand<T>, принимающем в конструкторе делегаты методов с обобщённым параметром.

Для методов с object параметром и с обобщённым параметром объявлены делегаты:
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
namespace WpfMvvm.Commands
{
    /// <summary>Делегат метода <see cref="ICommand.Execute(object)"/>.</summary>
    /// <param name="parameter">Параметр команды.</param>
    public delegate void ExecuteCommandHandler(object parameter);
 
    /// <summary>Делегат метода <see cref="ICommand.CanExecute(object)"/>.</summary>
    /// <param name="parameter">Параметр команды.</param>
    /// <returns>Значение <see langword="true"/>, если эту команду можно выполнить;<br/>
    /// в противном случае — значение <see langword="false"/>.</returns>
    public delegate bool CanExecuteCommandHandler(object parameter);
 
    /// <summary>Делегат исполнительного метода команды с обобщённым параметром.</summary>
    /// <typeparam name="T">Тип параметра команды</typeparam>
    /// <param name="parameter">Параметр команды.</param>
    public delegate void ExecuteCommandHandler<T>(T parameter);
 
    /// <summary>Делегат метода состояния команды с обобщённым параметром.</summary>
    /// <typeparam name="T">Тип параметра команды</typeparam>
    /// <param name="parameter">Параметр команды.</param>
    /// <returns>Значение <see langword="true"/>, если эту команду можно выполнить;<br/>
    /// в противном случае — значение <see langword="false"/>.</returns>
    public delegate bool CanExecuteCommandHandler<T>(T parameter);
}
В этих реализациях поднятие события происходит в том же потоке в каком был вызван метод RaiseCanExecuteChanged().
Так же надо "в ручную" отслеживать изменение объектов от которых зависит состояние команды.
Это позволяет уменьшить издержки вносимые реализаций команды.
Эти реализации, в том числе, могут применяться в UWP, так ка в нём нет CommandManager и надо обеспечивать запуск приложений на "слабых" устройствах.
Так же эти реализации могут применяться слоях не связанных с GUI.

Для WPF использование этих реализаций потребует дополнительного кода для вызова события RaiseCanExecuteChanged().
При этом порой с неочевидной реализацией.
Ниже показан пример использования этих классов.
Обратите внимание на кнопку "Команда с bool параметром". В ней задана привязка параметра напрямую к UI элементу.
И она работает как бы в противофазе к привязанному значению.
Связанно это с тем, что привязка в изменяемом свойстве срабатывает ДО изменения в свойстве.
Поэтому сеттер свойства IsHello, в котором вызывается метод RaiseCanExecuteChanged(), вызывает обновление команды ещё до того как в параметр команды по привязке поступит новое значение.

Так же в примере демонстрируется, что передача в метод параметра в типе не приводимом по шаблону к типу методов, полученных в конструкторе, вызовет исключение.
Для WPF более понятно и ожидаемо будет использование DefaultValueConverter и отсутствие исключения.

Демонстрационный пример:
C#
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
    public class CommandsViewModel : Simplified.BaseInpc
    {
        /// <summary>Для привязки CheckBox.</summary>
        public bool IsHello
        {
            get => _isHello;
            set
            {
                Set(ref _isHello, value);
                HelloCommand.RaiseCanExecuteChanged();
                HelloArgCommand.RaiseCanExecuteChanged();
                HelloBoolCommand.RaiseCanExecuteChanged();
            }
        }
 
        private RelayCommand _helloCommand;
        private RelayCommand _helloArgCommand;
        private RelayCommand _helloBoolCommand;
        private RelayCommand _exceptionCommand;
        private bool _isHello;
 
        /// <summary>Команда без параметров.</summary>
        public RelayCommand HelloCommand => _helloCommand
             ?? (_helloCommand = new RelayCommand(HelloMethod, HelloCanMethod));
 
        private void HelloMethod()
            => MessageBox.Show($"Привет!\r\nIsHello={IsHello}");
 
        private bool HelloCanMethod() => IsHello;
 
        /// <summary>Команда с object параметром.</summary>
        public RelayCommand HelloArgCommand => _helloArgCommand
         ?? (_helloArgCommand = new RelayCommand(HelloArgMethod, HelloArgCanMethod));
 
        private void HelloArgMethod(object parameter) => HelloMethod();
 
        private bool HelloArgCanMethod(object parameter)
            => parameter is bool bl && bl;
 
        /// <summary>Команда с bool параметром.</summary>
        public RelayCommand HelloBoolCommand => _helloBoolCommand
         ?? (_helloBoolCommand = new RelayCommand<bool>(HelloBoolMethod, HelloBoolCanMethod));
 
        private void HelloBoolMethod(bool parameter) => HelloMethod();
 
        private bool HelloBoolCanMethod(bool parameter)
            => parameter;
 
        /// <summary>Команда с для проверки исключения.</summary>
        public RelayCommand ExceptionCommand => _exceptionCommand
         ?? (_exceptionCommand = new RelayCommand(() =>
         {
             try
             {
                 HelloBoolCommand.Execute("false");
             }
             catch (Exception)
             {
                 MessageBox.Show("Исключение");
             }
         }));
    }
XML
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
        DataContext="{DynamicResource model}">
    <FrameworkElement.Resources>
        <local:CommandsViewModel x:Key="model"/>
    </FrameworkElement.Resources>
    <UniformGrid Columns="1">
        <CheckBox x:Name="checkBox" Content="Включить/Выключить"
                  HorizontalAlignment="Center" VerticalAlignment="Center"
                  IsChecked="{Binding IsHello}"/>
        <Button Content="Команда без параметров" Padding="15,5"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Command="{Binding HelloCommand, Mode=OneWay}"/>
        <Button Content="Команда с object параметром" Padding="15,5"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Command="{Binding HelloArgCommand, Mode=OneWay}"
                CommandParameter="{Binding IsHello}"/>
        <Button Content="Команда с bool параметром" Padding="15,5"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Command="{Binding HelloBoolCommand, Mode=OneWay}"
                CommandParameter="{Binding IsChecked, ElementName=checkBox}"/>
        <Button Content="Команда для проверки исключения" Padding="15,5"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Command="{Binding ExceptionCommand, Mode=OneWay}"/>
    </UniformGrid>
</Window>
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
12.12.2020, 22:44  [ТС]
RelayCommand и RelayCommand<T> для применения в WPF
Для облегчения использования команд в WPF необходимо добавить автоматическую подписку метода RaiseCanExecuteChanged() на событие CommandManager.RequerySuggested.
Так же нужно предусмотреть создание CanExecuteChanged только в потоке диспетчера. Для этого в команду добавляется свойство Dispatcher из интерфейса
C#
1
2
3
4
5
6
7
8
9
10
11
namespace WpfMvvm.ViewModel
{
    /// <summary>Интерфейс предоставляющий свойство с диспетчером.</summary>
    public interface IDispatcher
    {
        /// <summary>Диспетчер экземпляра.<br/>
        /// Если <see langword="null"/> значит для экземпляра 
        /// не важно в каком потоке он работает.</summary>
      Dispatcher Dispatcher { get; }
    }
}
Объявлен интерфейс объединяющий IDispatcher с другими интерфейсами команды:
C#
1
2
3
4
5
6
7
namespace WpfMvvm.WpfCommands
{
    /// <summary>Интерфейс команд для свойств ViewModel предназначенных для привязок в View.<br/>
    /// Объединяет в один интерфейс <see cref="Commands.IRelayCommand"/> и <see cref="IDispatcher"/>.</summary>
    public interface IRelayCommand : Commands.IRelayCommand, ICommandRaise, ICommand, IDispatcher
    { }
}
Для команды WPF диспетчер всегда должен быть задан.
Если используется конструктор без явного задания диспетчера, то команда его получает из свойства Application.Current.Dispatcher.

В основном в WPF команды используются в свойствах "только для чтения" и задаётся экземпляр живущий всё время жизни приложения.
На случай необходимости явного уничтожения экземпляра команды необходимо в команде реализовать интерфейс IDisposable. Но я посчитал это избыточным и отказался от его реализации.

Пример использования аналогичен примеру из поста RelayCommand и RelayCommand<T> общего применения .
За исключением только команды в кнопке "Команда для проверки конвертации string в bool".
Изменяя значение в текстовом поле видно, что текст корректно преобразуется в bool значение.

В реализации VM уже нет необходимости в явном вызове метода RaiseCanExecuteChanged().
Поэтому из VM даже убрана реализация INPC.
C#
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
namespace Examples.WpfCommands
{
    public class WpfCommandsViewModel
    {
        /// <summary>Для привязки CheckBox.</summary>
        public bool IsHello { get; set; }
 
        private RelayCommand _helloCommand;
        private RelayCommand _helloArgCommand;
        private RelayCommand _helloBoolCommand;
 
        /// <summary>Команда без параметров.</summary>
        public RelayCommand HelloCommand => _helloCommand
             ?? (_helloCommand = new RelayCommand(HelloMethod, HelloCanMethod));
 
        private void HelloMethod()
            => MessageBox.Show($"Привет!\r\nIsHello={IsHello}");
 
        private bool HelloCanMethod() => IsHello;
 
        /// <summary>Команда с object параметром.</summary>
        public RelayCommand HelloArgCommand => _helloArgCommand
         ?? (_helloArgCommand = new RelayCommand(HelloArgMethod, HelloArgCanMethod));
 
        private void HelloArgMethod(object parameter) => HelloMethod();
 
        private bool HelloArgCanMethod(object parameter)
            => parameter is bool bl && bl;
 
        /// <summary>Команда с bool параметром.</summary>
        public RelayCommand HelloBoolCommand => _helloBoolCommand
         ?? (_helloBoolCommand = new RelayCommand<bool>(HelloBoolMethod, HelloBoolCanMethod));
 
        private void HelloBoolMethod(bool parameter) => HelloMethod();
 
        private bool HelloBoolCanMethod(bool parameter)
            => parameter;
 
    }
}
XML
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
        DataContext="{DynamicResource model}">
    <FrameworkElement.Resources>
        <local:WpfCommandsViewModel x:Key="model"/>
    </FrameworkElement.Resources>
    <UniformGrid Columns="1">
        <CheckBox x:Name="checkBox" Content="Включить/Выключить"
                  HorizontalAlignment="Center" VerticalAlignment="Center"
                  IsChecked="{Binding IsHello}"/>
        <Button Content="Команда без параметров" Padding="15,5"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Command="{Binding HelloCommand, Mode=OneWay}"/>
        <Button Content="Команда с object параметром" Padding="15,5"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Command="{Binding HelloArgCommand, Mode=OneWay}"
                CommandParameter="{Binding IsChecked, ElementName=checkBox}"/>
        <Button Content="Команда с bool параметром" Padding="15,5"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Command="{Binding HelloBoolCommand, Mode=OneWay}"
                CommandParameter="{Binding IsChecked, ElementName=checkBox}"/>
        <TextBox x:Name="textBox" Width="100" Text="true"
                 HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Button Content="Команда для проверки конвертации string в bool" Padding="15,5"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Command="{Binding HelloBoolCommand, Mode=OneWay}"
                CommandParameter="{Binding Text, ElementName=textBox}"/>
    </UniformGrid>
</Window>
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
12.12.2020, 22:59  [ТС]
Простые реализации: Базовый класс для ViewModel - BaseInpc
Класс очень простой.
Большая его часть это XML документация.

Кроме события PropertyChanged и метода для его создания RaisePropertyChanged(), добавлены метод Set для изменения значения свойства и метод OnPropertyChanged, который переопределяется в производном классе для реализации зависимости от свойств.
C#
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
namespace Simplified
{
    /// <summary>Базовый класс с реализацией <see cref="INotifyPropertyChanged"/>.</summary>
    public abstract class BaseInpc : INotifyPropertyChanged
    {
        /// <inheritdoc cref="INotifyPropertyChanged"/>
        public event PropertyChangedEventHandler PropertyChanged;
 
        /// <summary>Защищённый метод для создания события <see cref="PropertyChanged"/>.</summary>
        /// <param name="propertyName">Имя изменившегося свойства. 
        /// Если значение не задано, то используется имя метода в котором был вызов.</param>
        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
 
        /// <summary>Защищённый метод для присвоения значения полю и
        /// создания события <see cref="PropertyChanged"/>.</summary>
        /// <typeparam name="T">Тип поля и присваиваемого значения.</typeparam>
        /// <param name="propertyFiled">Ссылка на поле.</param>
        /// <param name="newValue">Присваиваемое значение.</param>
        /// <param name="propertyName">Имя изменившегося свойства. 
        /// Если значение не задано, то используется имя метода в котором был вызов.</param>
        /// <remarks>Метод предназначен для использования в сеттере свойства.<br/>
        /// Для проверки на изменение используется метод <see cref="object.Equals(object, object)"/>.
        /// Если присваиваемое значение не эквивалентно значению поля, то оно присваивается полю.<br/>
        /// После присвоения создаётся событие <see cref="PropertyChanged"/> вызовом
        /// метода <see cref="RaisePropertyChanged(string)"/>
        /// с передачей ему параметра <paramref name="propertyName"/>.<br/>
        /// После создания события вызывается метод <see cref="OnPropertyChanged(string, object, object)"/>.</remarks>
        protected void Set<T>(ref T propertyFiled, T newValue, [CallerMemberName] string propertyName = null)
        {
            if (!object.Equals(propertyFiled, newValue))
            {
                T oldValue = propertyFiled;
                propertyFiled = newValue;
                RaisePropertyChanged(propertyName);
 
                OnPropertyChanged(propertyName, oldValue, newValue);
            }
        }
 
        /// <summary>Защищённый виртуальный метод вызывается после присвоения значения
        /// свойству и после создания события <see cref="PropertyChanged"/>.</summary>
        /// <param name="propertyName">Имя изменившегося свойства.</param>
        /// <param name="oldValue">Старое значение свойства.</param>
        /// <param name="newValue">Новое значение свойства.</param>
        /// <remarks>Переопределяется в производных классах для реализации
        /// реакции на изменение значения свойства.<br/>
        /// Рекомендуется в переопределённом методе первым оператором вызывать базовый метод.<br/>
        /// Если в переопределённом методе не будет вызова базового, то возможно нежелательное изменение логики базового класса.</remarks>
        protected virtual void OnPropertyChanged(string propertyName, object oldValue, object newValue){}
    }
}

Пример использования:
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
namespace Simplified
{
    public class ExampleViewModel : BaseInpc
    {
        private int _number;
        private int _modNumber;
        private int _mod;
 
        /// <summary>Любое целое число.</summary>
        public int Number { get => _number; set => Set(ref _number, value); }
 
        /// <summary>Любое целое число.</summary>
        public int Mod { get => _mod; set => Set(ref _mod, value); }
 
        /// <summary>Модуль числа Number по основанию Mod.</summary>
        public int ModNumber { get => _modNumber; private set => Set(ref _modNumber, value); }
 
        protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue)
        {
            base.OnPropertyChanged(propertyName, oldValue, newValue);
 
            // Проверяется имя изменившегося свойства и производятся необходимые действия
            if (propertyName == nameof(Number) || propertyName == nameof(Mod))
                ModNumber = Number % Mod;
        }
    }
}
3
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16065 / 11191 / 2881
Регистрация: 21.04.2018
Сообщений: 32,903
Записей в блоге: 2
12.12.2020, 23:00  [ТС]
RelayCommand и RelayCommand<T>
Базовый класс основан на Новая реализация RelayCommand с исправлениями от proa33 и kolorotur [WPF, Элд Хасп].

Отличается названием метода для генерации события и конструктором для методов без параметра.
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
namespace Simplified
{
    #region Делегаты для методов WPF команд
    public delegate void ExecuteHandler(object parameter);
    public delegate bool CanExecuteHandler(object parameter);
    #endregion
 
    #region Класс команд - RelayCommand
    /// <summary>Класс реализующий <see cref="ICommand"/>.<br/>
    /// Реализация взята из <see href="https://www.cyberforum.ru/wpf-silverlight/thread2390714-page4.html#post13535649"/>
    /// и дополнена конструктором для методов без параметра.</summary>
    public class RelayCommand : ICommand
    {
        private readonly CanExecuteHandler canExecute;
        private readonly ExecuteHandler execute;
        private readonly EventHandler requerySuggested;
 
        /// <summary>Событие извещающее об изменении состояния команды.</summary>
        public event EventHandler CanExecuteChanged;
 
        /// <summary>Конструктор команды.</summary>
        /// <param name="execute">Выполняемый метод команды.</param>
        /// <param name="canExecute">Метод, возвращающий состояние команды.</param>
        public RelayCommand(ExecuteHandler execute, CanExecuteHandler canExecute = null)
            :this()
        {
            this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
            this.canExecute = canExecute;
 
            requerySuggested = (o, e) => Invalidate();
            CommandManager.RequerySuggested += requerySuggested;
        }
 
        /// <inheritdoc cref="RelayCommand(ExecuteHandler, CanExecuteHandler)"/>
        public RelayCommand(Action execute, Func<bool> canExecute = null)
                : this
                (
                      p => execute(),
                      p => canExecute?.Invoke() ?? true
                )
        { }
 
        private RelayCommand()
            => dispatcher = Application.Current.Dispatcher;
 
        private readonly Dispatcher dispatcher;
 
        /// <summary>Метод, подымающий событие <see cref="CanExecuteChanged"/>.</summary>
        public void RaiseCanExecuteChanged()
        {
            if (dispatcher.CheckAccess())
                Invalidate();
            else
                dispatcher.BeginInvoke((Action)Invalidate);
        }
        private void Invalidate()
            => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
 
        /// <summary>Вызов метода, возвращающего состояние команды.</summary>
        /// <param name="parameter">Параметр команды.</param>
        /// <returns><see langword="true"/> - если выполнение команды разрешено.</returns>
        public bool CanExecute(object parameter) => canExecute?.Invoke(parameter) ?? true;
 
        /// <summary>Вызов выполняющего метода команды.</summary>
        /// <param name="parameter">Параметр команды.</param>
        public void Execute(object parameter) => execute?.Invoke(parameter);
    }
    #endregion
}
В производном классе для методов с обобщённым параметром только конструктор.
Object параметр приводится к обобщённому сопоставлением с шаблоном, поэтому тип отличный от требуемого должен быть конвертирован до привязки к праметру команды.
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
namespace Simplified
{
    #region Делегаты для методов WPF команд
    public delegate void ExecuteHandler<T>(T parameter);
    public delegate bool CanExecuteHandler<T>(T parameter);
    #endregion
 
    /// <summary>Реализация RelayCommand для методов с обобщённым параметром.</summary>
    /// <typeparam name="T">Тип параметра методов.</typeparam>
    public class RelayCommand<T> : RelayCommand
    {
        /// <inheritdoc cref="RelayCommand(ExecuteHandler, CanExecuteHandler)"/>
        public RelayCommand(ExecuteHandler<T> execute, CanExecuteHandler<T> canExecute = null)
            : base
            (
                  p =>
                  {
                      if (p is T t)
                          execute(t);
                  },
                  p => (p is T t) && (canExecute?.Invoke(t) ?? true)
            )
        { }
    }
}
:

В применении эти классы аналогичны RelayCommand и RelayCommand<T> для применения в WPF .
4
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
12.12.2020, 23:00
Помогаю со студенческими работами здесь

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

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

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

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

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


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

Или воспользуйтесь поиском по форуму:
16
Закрытая тема Создать тему
Новые блоги и статьи
Защита API Spring Boot с помощью шлюза JWT
Javaican 01.09.2025
В 2023 году, согласно отчёту OWASP, нарушения аутентификации и управления сессиями остаются в топ-3 самых критичных уязвимостей веб-приложений. На мой взгляд, это происходит не из-за отсутствия. . .
Архитектура ПО для разработчиков или Зачем нам системное мышление
ArchitectMsa 31.08.2025
Давай я расскажу, что происходит в большинстве проектов, с которыми мне приходилось работать. Вначале всё выглядит прекрасно: чистые интерфейсы, продуманные абстракции, явные зависимости. А через. . .
Пик Победы
XLAT 30.08.2025
Pmb__mCLTFM
Kafka и SQS: сравнение инструментов потоковой передачи
AI_Generated 30.08.2025
Сегодня я хочу поговорить о двух титанах в мире потоковой передачи данных: Apache Kafka и Amazon SQS. Или, как я их называю - "тяжелая артилерия" и "снайперская винтовка" в арсенале современного. . .
Python и FastAPI: руководство для начинающих
py-thonny 30.08.2025
FastAPI появился относительно недавно (в 2018 году), но уже успел стать популярным по всему миру. И причин тому несколько. Во-первых, скорость. FastAPI основан на Starlette и Pydantic, что делает. . .
Системное мышление: как подходить к решению сложных программных проблем
ArchitectMsa 29.08.2025
Когда я только начинал свой путь в разработке крупных систем, у меня была наивная вера в то, что любую проблему можно решить, просто написав хороший код. Потом я столкнулся с реальностью - даже. . .
Статический ИИ-анализ: устранение утечек памяти в C с помощью DeepCode
bytestream 28.08.2025
Мой первый серьезный проект на C - система обработки финансовых транзакций для одного банка. Мы обрабатывали миллионы транзакций в день, и всё работало как швейцарские часы. . . первые две недели. А. . .
К сожалению, я снова начал движение в мир динозавров
Etyuhibosecyu 27.08.2025
Мой самый главный проект больше не может быть обновлен под актуальные версии движка. И если в прошлый раз это было потому, что компьютер не "тянул" - серьезная и весомая причина - то сейчас движок. . .
Как работать с модулем ESP-12E NodeMcu V3 в ArduinoIDE
Wired 26.08.2025
Когда я впервые держал в руках NodeMcu, то не мог поверить, что такой малыш с ценником в пару долларов может похвастаться встроенным Wi-Fi и приличной вычислительной мощностью. Это же настоящий. . .
Создание агентов LangChain для LLM на Python
AI_Generated 25.08.2025
Эх, помню времена, когда все мы восхищались простыми чат-ботами на основе больших языковых моделей! Напишешь запрос, получишь ответ — и вроде бы магия. Но потом наступает разочарование: модель не. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru