powered by simpleCommunicator - 2.0.48     © 2025 Programmizd 02
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / Как правильно в MVVM подсоединять методы к событиям?
16 сообщений из 16, страница 1 из 1
Как правильно в MVVM подсоединять методы к событиям?
    #39814986
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
В своё время, в первую очередь с помощью Roman Mejtes , в теме Правильное размещение функционала и привязки команд помогли разобраться с подключением команд и их всплытием в списочных элементах.

Для иллюстрации привожу пример. В нём реализовано изменение выделения цветом текста при нажатии на кнопку в ListBoxItem.

ViewModel с классом для списка. В них используются классы OnPropertyChangedClass - реализация INPC и RelayCommand - реализация ICommand. Здесь их не привожу, если понадобятся - они в приложенном архиве.
Код: 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.
namespace BubblingCommands
{
    public class ListViewModel : OnPropertyChangedClass
    {
        public ObservableCollection<TwoProperty> Collection => new ObservableCollection<TwoProperty>()
        {
            new TwoProperty("Первый"),
            new TwoProperty("Второй", true),
            new TwoProperty("Третий"),
            new TwoProperty("Четвёртый"),
            new TwoProperty("Пятый"),
            new TwoProperty("Шестой", true)
        };
        private RelayCommand _invertMarkedCommand;

        public RelayCommand InvertMarkedCommand => _invertMarkedCommand ?? (_invertMarkedCommand = new RelayCommand(InvertMarkedMetod, InvertMarkedCanMetod));

        private bool InvertMarkedCanMetod(object parameter)
            => parameter is TwoProperty item;

        private void InvertMarkedMetod(object parameter)
        {
            if (parameter is TwoProperty item)
                item.IsMarked = !item.IsMarked;

        }
    }
    public class TwoProperty : OnPropertyChangedClass, ICloneable
    {
        private string _value;
        private bool _isMarked;

        public string Value { get => _value; set { _value = value; OnPropertyChanged(); } }
        public bool IsMarked { get => _isMarked; set { _isMarked = value; OnPropertyChanged(); } }

        public TwoProperty() { }
        public TwoProperty(string value) : this() => Value = value;
        public TwoProperty(string value, bool isMarked) : this(value) => IsMarked = isMarked;

        public TwoProperty Copy() => new TwoProperty(Value, IsMarked);
        public object Clone() => Copy();
    }
}

XAML окна
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
<Window x:Class="BubblingCommands.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BubblingCommands"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="100">
    <Window.DataContext>
        <local:ListViewModel/>
    </Window.DataContext>
    <Grid>
        <local:ContextHolder>
            <local:ContextHolder.Commands>
                <local:CommandBinding RoutedCommand="{x:Static ApplicationCommands.Find}"
                                      RelayCommand="{Binding InvertMarkedCommand}"/>
            </local:ContextHolder.Commands>
            <ListBox ItemsSource="{Binding Collection}" HorizontalContentAlignment="Stretch">
                <ListBox.ItemTemplate>
                    <DataTemplate DataType="{x:Type local:TwoProperty}">
                        <Button Content="{Binding Value}" 
                                Background="{x:Null}" 
                                BorderBrush="{x:Null}"
                                CommandParameter="{Binding}"
                                Command="{x:Static ApplicationCommands.Find}">
                            <Button.Style>
                                <Style TargetType="Button">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsMarked}" Value="true">
                                            <Setter Property="Foreground" Value="Red"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Button.Style>
                        </Button>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </local:ContextHolder>
    </Grid>
</Window>



Но как быть если нужно подсоединить метод к событию? Допустим, надо по двойному клику задублировать элемент. Использовать для этого триггеры взаимодействия (Interaction.Triggers) очень неудобно: их нельзя использовать в стилях, шаблонах, они не всплывают.
На данный момент, в таких случаях, я использую CB окна.

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.
namespace BubblingCommands
{
    public class ListEventViewModel : OnPropertyChangedClass
    {
        public ObservableCollection<TwoProperty> Collection { get; } = new ObservableCollection<TwoProperty>()
        {
            new TwoProperty("Первый"),
            new TwoProperty("Второй", true),
            new TwoProperty("Третий"),
            new TwoProperty("Четвёртый"),
            new TwoProperty("Пятый"),
            new TwoProperty("Шеcтой", true)
        };
        private RelayCommand _invertMarkedCommand;

        public RelayCommand InvertMarkedCommand => _invertMarkedCommand ?? (_invertMarkedCommand = new RelayCommand(InvertMarkedMetod, InvertMarkedCanMetod));

        private bool InvertMarkedCanMetod(object parameter)
            => parameter is TwoProperty item;

        private void InvertMarkedMetod(object parameter)
        {
            if (parameter is TwoProperty item)
                item.IsMarked = !item.IsMarked;

        }

        public bool CopyCanMetod(object parameter)
            => parameter is TwoProperty item && Collection.IndexOf(item) >= 0;
        public void CopyMetod(object parameter)
        {
            int index;
            if (parameter is TwoProperty item && (index = Collection.IndexOf(item)) >= 0)
                Collection.Insert(index, item.Copy());
        }
    }
}



XAML окна
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
<Window x:Class="BubblingCommands.ListEvenWindow"
        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:BubblingCommands"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="100">
    <Window.DataContext>
        <local:ListEventViewModel/>
    </Window.DataContext>
    <Grid>
        <local:ContextHolder>
            <local:ContextHolder.Commands>
                <local:CommandBinding RoutedCommand="{x:Static ApplicationCommands.Find}"
                                      RelayCommand="{Binding InvertMarkedCommand}"/>
            </local:ContextHolder.Commands>
            <ListBox ItemsSource="{Binding Collection}" HorizontalContentAlignment="Stretch">
                <ListBox.ItemTemplate>
                    <DataTemplate DataType="{x:Type local:TwoProperty}">
                        <Button Content="{Binding Value}" 
                                Background="{x:Null}" 
                                BorderBrush="{x:Null}"
                                CommandParameter="{Binding}"
                                Command="{x:Static ApplicationCommands.Find}"
                                MouseDoubleClick="Button_MouseDoubleClick">
                            <Button.Style>
                                <Style TargetType="Button">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsMarked}" Value="true">
                                            <Setter Property="Foreground" Value="Red"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Button.Style>
                        </Button>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </local:ContextHolder>
    </Grid>
</Window>



CB окна
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
    public partial class ListEvenWindow : Window
    {
        public ListEvenWindow()
        {
            InitializeComponent();
        }

        private void Button_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (DataContext is ListEventViewModel viewModel && sender is ButtonBase button)
                viewModel.CopyMetod(button.CommandParameter);
        }
    }



Такая реализация мне категорически не нравится. Но как сделать по другому я не знаю.
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39814987
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Eld Hasp
Код: xml
1.
2.
                <local:CommandBinding RoutedCommand="{x:Static ApplicationCommands.Find}"
                                      RelayCommand="{Binding InvertMarkedCommand}"/>

Извини, глубоко в твой код не вчитывался, но что мешает перехватывать всплывающее событие и направлять его на RelayCommand точно так же, как перехватывается всплывающая команда.
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39814990
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Shocker.ProEld Hasp..., но что мешает перехватывать всплывающее событие и направлять его на RelayCommand точно так же, как перехватывается всплывающая команда.

Но для этого надо подсоединить к событию не обработчик, а команду (RoutedCommand или RoutedUICommand) добавить передачу параметра для этой команды. Как сделать это с помощью триггеров взаимодействия я знаю. Но команды подсоединённые таким образом не всплывают и их нельзя использовать в стилях и шаблонах. Ну, или я их не правильно подключаю.
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39814991
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Не надо к нему команду присоединять. Я же привел кусок твоего кода.
Будет примерно так:
Код: xml
1.
2.
<local:RoutedEventBinding RoutedEvent="UIElement.MouseEnter"
                          RelayCommand="{Binding InvertMarkedCommand}"/>
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39814999
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Shocker.ProНе надо к нему команду присоединять. Я же привел кусок твоего кода.
Будет примерно так:
Код: xml
1.
2.
<local:RoutedEventBinding RoutedEvent="UIElement.MouseEnter"
                          RelayCommand="{Binding InvertMarkedCommand}"/>


То есть изменить реализацию ContextHolder?
Даже если изменить реализацию ContextHolder для отлова событий, то всё равно не выйдет. Так как событие не имеет параметра как команда. И для обработки события View в ViewModel придётся работать с UI элементами View и нужны знания о их компоновке в View.
А это, на мой взгляд, неправильно.
Это тоже самое, как вместо команды передавать в ViewModel событие Click.
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39815009
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Eld HaspТо есть изменить реализацию ContextHolder?не изменить, а дополнить
Eld HaspТак как событие не имеет параметра как команда.почему не имеет? RoutedEventArgs - вот параметры.
Eld HaspИ для обработки события View в ViewModel придётся работать с UI элементами View и нужны знания о их компоновке в View.нет, ты же при привязке команды к кнопке не рассказываешь VM он компоновке. Фактически, ты можешь с тем же успехом перехватить Button.Click вместо Command

И что самое интересное, если ты в качестве RelayCommand вместо Binding укажешь Static на какую-то команду, то она пойдет себе дальше по дереву. То есть с помощью уже имеющегося у тебя local:CommandBinding ты можешь конвертировать одну команду в другую в какой-то точке всплытия и она поплывет дальше. А с помощью потенциального local:RoutedEventBinding ты точно так же можешь конвертировать событие в команду, если это понадобится
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39815010
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Proпочему не имеет? RoutedEventArgs - вот параметры.если же необходимо совместить - я паковал в такой класс
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
	/// <summary>
	/// Враппер для EventArgs и CommandParameter
	/// </summary>
	public class EventArgsPack
	{
		public object Sender { get; set; }
		public EventArgs EventArgs { get; set; }
		public object CommandParameter { get; set; }
	}
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39815016
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.ProEld HaspТак как событие не имеет параметра как команда.почему не имеет? RoutedEventArgs - вот параметры.я неправильно прочитал вопрос, ну да ладно
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39815018
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Shocker.Proне изменить, а дополнитьДа, это я понял. Просто, может, неправильно выразился.
Shocker.Proпочему не имеет? RoutedEventArgs - вот параметры.
.........
нет, ты же при привязке команды к кнопке не рассказываешь VM он компоновке. Фактически, ты можешь с тем же успехом перехватить Button.Click вместо Command
За параметры события я знаю. Но параметры команды это несколько иное. В лучшем случае мне придётся приводить источник события к какому-то UI элементы и из его свойства вытаскивать значение для привязки параметра. Как у меня сейчас сделано в этом коде CB окна
Код: c#
1.
2.
3.
4.
5.
        private void Button_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (DataContext is ListEventViewModel viewModel && sender is ButtonBase button)
                viewModel.CopyMetod(button.CommandParameter);
        }

Я привожу источник команды к ButtonBase и извлекаю параметр из button.CommandParameter. Делая это в VM, получается надо знать какого типа источник события и в каком свойстве источника события содержится параметр.
Обработка " RoutedEventArgs e " тоже немного помогает в этом.
Shocker.ProА с помощью потенциального local:RoutedEventBinding ты точно так же можешь конвертировать событие в команду, если это понадобитсяНадо подумать над этим. Не приходит в голову как можно решить с параметром...
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39815020
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Я никак не пойму твою проблему с параметром. Что тебе мешает
Код: xml
1.
2.
3.
<local:RoutedEventBinding RoutedEvent="UIElement.MouseEnter"
                          RelayCommand="{Binding InvertMarkedCommand}"
                          CommandParameter="{Binding.....}" />
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39815022
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Shocker.ProЯ никак не пойму твою проблему с параметром. Что тебе мешает
Код: xml
1.
2.
3.
<local:RoutedEventBinding RoutedEvent="UIElement.MouseEnter"
                          RelayCommand="{Binding InvertMarkedCommand}"
                          CommandParameter="{Binding.....}" />

Так можно подсоединить параметр в точке отлова всплытия.
А нужно в месте возникновения события.
Как здесь для команды
Код: xml
1.
2.
3.
4.
5.
6.
                        <Button Content="{Binding Value}" 
                                Background="{x:Null}" 
                                BorderBrush="{x:Null}"
                                CommandParameter="{Binding}"
                                Command="{x:Static ApplicationCommands.Find}"
                                MouseDoubleClick="Button_MouseDoubleClick">
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39815106
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Eld HaspТак можно подсоединить параметр в точке отлова всплытия.Для этого существует достаточно стандартный паттерн - присоединяемое свойство, которое ты можешь присоединить непосредственно к Button.

Но я не вижу проблемы. Как с командой, так и с событием, используемый ContextHolder существует в том же DataContext-е, что и кнопка, так что CommandParameter="{Binding}" точно так же сработает как для события, так и для команды
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39815126
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Shocker.ProEld HaspТак можно подсоединить параметр в точке отлова всплытия.Для этого существует достаточно стандартный паттерн - присоединяемое свойство, которое ты можешь присоединить непосредственно к Button.

Но я не вижу проблемы. Как с командой, так и с событием, используемый ContextHolder существует в том же DataContext-е, что и кнопка, так что CommandParameter="{Binding}" точно так же сработает как для события, так и для командыButton - приведён для иллюстрации вопроса - как это сделано для команды.
А так в этом месте может стоять любой элемент. Допустим, TextBlock и у него нет свойства CommandParameter.
А если надо обрабатывать два разных события и у каждого своя привязка параметра, то... вооще лес дремучий.

Просто сделать - я могу. Но хочется сделать красиво: и чтобы с MVVM не противоречить, и CB окна не использовать, и чтобы в стилях, шаблонах можно было использовать, и отделить само событие от команды выполняющейся по событию (как Click и Command).

С присоединяемыми свойствами - не пробовал. Надо повозиться. Пока не совсем хорошо представляю как их делать кастомно. Почитаю.
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39815154
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Eld Hasp,

В событие на double click добавьте код возбуждающий маршрутизируемую команду, она в свою очередь, через маршрутизиуремые события всплывет наверх и будет обработано в ContentHolder'е, добавить команду кнопке можно через AP или Behavior, или в CodeBehind файле, как вам угодно. У делегата маршрутизируемого события команд есть параметр, который передается, если параметр не нужен, просто не используйте его.
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39818583
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Eld HaspС присоединяемыми свойствами - не пробовал. Надо повозиться. Пока не совсем хорошо представляю как их делать кастомно. Почитаю.
Аккуратнее - здесь есть неприятный подводный камень с утечкой памяти. Сама по себе реализация там проста, как первый русский трактор: при изменении присоединенного свойства (тип свойства - ICommand) - через биндинг, или напрямую, роли не играет - в PropertyChangedCallback к DependencyObject присобачивается банальный обработчик нужного события, вызывающий эту нашу ICommand. Если объекты, которые подписываются на события, являются, например, частями DataTemplate, и/или соответствующие им элементы VM могут массово создаваться/уничтожаться, то если забыть корректно отписать объект от обработчика, будут утечки памяти. А если подписываемый DependencyObject находится выше по иерархии наследования, чем FrameworkElement, то там даже не будет события OnUnload, чтобы автоматически отписываться по нему.

А вообще всё вышеописанное, и вроде как с аккуратно обработанными граблями с утечками памяти есть здесь: https://www.nuget.org/packages/AttachedCommandBehavior/
...
Рейтинг: 0 / 0
Как правильно в MVVM подсоединять методы к событиям?
    #39818639
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Сон Веры ПавловныАккуратнее - здесь есть неприятный подводный камень с утечкой памяти....

А вообще всё вышеописанное, и вроде как с аккуратно обработанными граблями с утечками памяти есть здесь: https://www.nuget.org/packages/AttachedCommandBehavior/
Спасибо!
...
Рейтинг: 0 / 0
16 сообщений из 16, страница 1 из 1
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / Как правильно в MVVM подсоединять методы к событиям?
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]