Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / WPF, Silverlight Новый топик    Ответить
Топик располагается на нескольких страницах: 1 2      [все]
 Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Наверное, замучил всех своими "детскими" вопросами... Извините если, что .... Но я опять за своё :)
У меня вопрос по правильному размещению функционала и привязки команд.
Для объяснения простой пример.
Код ViewModel - коллекция строк и методы для добавления, удаления и изменения элементов коллекции, и свойства для биндинга команд
namespace WpfQuestions
{
    public class ExampleCommandVM : INotifyPropertyChanged
    {
        private ObservableCollection<string> _listSource = new ObservableCollection<string>();
        private int maxId = 0;
        #region Событие PropertyChanged и метод для его вызова
        /// <summary>Событие для извещения об изменения свойства</summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>Метод для вызова события извещения об изменении свойства</summary>
        /// <param name="prop">Изменившееся свойство</param>
        public void OnPropertyChanged([CallerMemberName]string prop = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
        #endregion

        public ObservableCollection<string> ListSource { get => _listSource; set { _listSource = value; OnPropertyChanged(); } }

        public ExampleCommandVM()
        {
        }
        static public ExampleCommandVM Create()
        {
            ExampleCommandVM ret = new ExampleCommandVM();
            for (int ind = 0; ind < 5; ind++)
                ret.AddNew();

            return ret;
        }

        public void AddNew()
        {
            ListSource.Add("Строка №" + maxId.ToString().PadLeft(2, '0'));
            maxId++;
        }

        public void Remove(string Value)
        {
            ListSource.Remove(Value);
        }

        public void Save(string oldValue, string newValue)
        {
            ListSource[ListSource.IndexOf(oldValue)] = newValue;
        }

        private ICommand _commAdd;
        public ICommand CommAdd => _commAdd ?? (_commAdd = new RelayCommand(o => { AddNew(); }));

        private ICommand _commRemove;

        public ICommand CommRemove => _commRemove ?? (_commRemove = new RelayCommand(o => { Remove((string)o); }));
    }

    public class RelayCommand : ICommand
    {
        private Action<object> execute;
        private Func<object, bool> canExecute;

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            this.execute = execute;
            this.canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return this.canExecute == null || this.canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            this.execute(parameter);
        }
    }
}
Xaml окна
<Window x:Name="window" x:Class="WpfQuestions.ExampleCommand"
        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:WpfQuestions"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d" SizeToContent="WidthAndHeight"
        Title="ExampleCommand" >
    <d:Window.DataContext>
         <local:ExampleCommandVM>
            <local:ExampleCommandVM.ListSource>
                <sys:String>Строка 1</sys:String>
                <sys:String>Строка 2</sys:String>
                <sys:String>Строка 3</sys:String>
                <sys:String>Строка 4</sys:String>
            </local:ExampleCommandVM.ListSource>
        </local:ExampleCommandVM>
    </d:Window.DataContext>
    <Window.Resources>
        <Style TargetType="ListBoxItem" x:Key="ListBoxItemExamp">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBoxItem">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition/>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <TextBox Text="{Binding Mode=OneWay}" VerticalAlignment="Center"/>
                            <Button Grid.Column="1" Margin="5" Padding="2">Save</Button>
                            <Button Grid.Column="2" Margin="5" Padding="2" Command="{Binding DataContext.CommRemove, ElementName=window}" CommandParameter="{Binding}" >Remove</Button>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <StackPanel>
        <ListBox ItemsSource="{Binding ListSource}" ItemContainerStyle="{StaticResource ListBoxItemExamp}"/>
        <Button Margin="10" Command="{Binding CommAdd}">Add</Button>
    </StackPanel>
</Window>
Привязка к VM в коде окна
        private ExampleCommandVM vm;
        public ExampleCommand()
        {
            InitializeComponent();
            vm = ExampleCommandVM.Create();
            DataContext = vm;
        }

С кнопкой Add - вопросов ни каких. Она биндится в VM из View. Вроде все принципы WPF и MVVM соблюдаются.
С кнопкой Remove - уже у меня сомнения. Приходится из шаблона биндить её к свойству DataContext окна. Но, по идее, шаблон не должен ничего знать про окно. Понимаю, что не правильно, но не знаю как сделать правильно.
С кнопкой Save - вообще, ступор. В метод сохранения надо передать текущее значение строки (оно служит идентификатором, который я для уменьшения кода примера не вводил) и новое значение из TextBox (он связан с VM в режиме OneWay). Как их оба одновременно передать не соображу ни как.
31 окт 18, 13:53    [21720286]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Roman Mejtes
Member

Откуда: г. Пермь
Сообщений: 3311
команды всплывают по визуальному дереву, так как работают на маршрутизированных команда, их можно обрабатывать на уровне ListBox, а не ListBoxItem
31 окт 18, 14:29    [21720342]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Roman Mejtes
команды всплывают по визуальному дереву, так как работают на маршрутизированных команда, их можно обрабатывать на уровне ListBox, а не ListBoxItem
Но саму же команду надо как-то присоединить к кнопке. Как это можно сделать не ссылаясь на общую VM, а используя информацию доступную только ListBoxItem (ControlTemplate )?
31 окт 18, 15:32    [21720459]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
Но саму же команду надо как-то присоединить к кнопке.
Команду можно присоединить к кнопке на уровне ListBoxItem. И при нажатии кнопки команда начнет всплывать. А ловить и переадресовывать ее на RelayCommand нужно уже выше .
31 окт 18, 15:40    [21720468]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Shocker.Pro
Eld Hasp
Но саму же команду надо как-то присоединить к кнопке.
Команду можно присоединить к кнопке на уровне ListBoxItem. И при нажатии кнопки команда начнет всплывать. А ловить и переадресовывать ее на RelayCommand нужно уже выше .
Не дошло... Если команда у меня на уровне VM, как её присоединить на уровне ListBoxItem? На уровне ListBoxItem этой же команды нет. Или надо какую-то другую команду присоединить?
Можно здесь подробнее и на примере.
31 окт 18, 15:52    [21720489]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Мне тут как-то в голову пришла гениальная идея для таких случаев, хотя может она и банальная, интересно мнение Романа на этот счет. Берем простейший класс без визуального оформления:
	public class ContextHolder : ContentControl
	{
	}


Используем его как контейнер для ListBox, при этом явно привязавшись к модели
    <stw:ContextHolder DataContext="{Binding}">
      <ListBox ..........>
      </ListBox>
    </stw:ContextHolder>


А затем внутри шаблона для ListBoxItem-а просто ссылаемся на него
<AnyTag AnyProperty="{Binding DataContext.MyProperty, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=stw:ContextHolder}" />
Я не пробовал это с командами - но что мешает свойство Command привязать таким образом к VM (не забыв про CommandParameter="{Binding}", чтобы знать, какой именно элемент вызывал команду)
31 окт 18, 15:58    [21720498]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
Если команда у меня на уровне VM, как её присоединить на уровне ListBoxItem? На уровне ListBoxItem этой же команды нет
Ты путаешь саму команду, которая определяется как public static readonly и видна отовсюду и привязку ее к ICommand. Нужно просто еще раз повнимательней перечитать теорию.

Однако, выше я привел пример, который, вероятно, позволит обойтись без всплывающей команды, привязавшись напрямую из кнопки к модели.
31 окт 18, 16:00    [21720509]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Сон Веры Павловны
Member

Откуда:
Сообщений: 4710
Для биндинга элемента ItemsControl на свойство датаконтекста самого ItemsControl я обычно использую решение Джоша Смита. А если в параметр команды нужно передать составное значение, состоящее частично из значений свойст датаконтекста вложенного элемента, частично - из значений свойств датаконтекста вышестоящего элемента, то вышепредложенный ElementSpy+мультибиндинг+IMultuValueConverter эту проблему вполне решают.
31 окт 18, 17:08    [21720609]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Roman Mejtes
Member

Откуда: г. Пермь
Сообщений: 3311
В данном примере, при вызове команды ApplicationCommands.Delete для элемента списка, будет вызвана команда DeleteCommand из модели MainModel и элемент удалится. Кнопка удаления как и в вашем примере находится в шаблоне элемента списка.
<Window x:Class="CommandExample.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:CommandExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainModel/>
    </Window.DataContext>
    <Grid>
        <local:ContextHolder>
            <local:ContextHolder.Commands>
                <local:CommandBinding RoutedCommand="{x:Static ApplicationCommands.Delete}" 
                                      RelayCommand="{Binding DeleteCommand}"/>
            </local:ContextHolder.Commands>
            <ListBox ItemsSource="{Binding Persones}">
                <ListBox.ItemTemplate>
                    <DataTemplate DataType="{x:Type local:Person}" >
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Text="{Binding Name}" Margin="5"/>
                            <Button Grid.Column="1" Content="Remove" Padding="5,2" Margin="5"
                                    Command="{x:Static ApplicationCommands.Delete}" 
                                    CommandParameter="{Binding}"/>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </local:ContextHolder>
    </Grid>
</Window>

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace CommandExample
{
    public class CommandsCollection : FreezableCollection<CommandBinding> { }

    public class CommandBinding : Freezable
    {
        public ICommand RoutedCommand
        {
            get { return (ICommand)GetValue(RoutedCommandProperty); }
            set { SetValue(RoutedCommandProperty, value); }
        }
        public static readonly DependencyProperty RoutedCommandProperty =
            DependencyProperty.Register("RoutedCommand", typeof(ICommand), typeof(CommandBinding), new PropertyMetadata(null));


        public ICommand RelayCommand
        {
            get { return (ICommand)GetValue(RelayCommandProperty); }
            set { SetValue(RelayCommandProperty, value); }
        }
        public static readonly DependencyProperty RelayCommandProperty =
            DependencyProperty.Register("RelayCommand", typeof(ICommand), typeof(CommandBinding), new PropertyMetadata(null));

        protected override Freezable CreateInstanceCore()
        {
            return new CommandBinding();
        }
    }

    public class ContextHolder : ContentControl
    {
        private readonly Dictionary<ICommand, CommandBinding> _dict;

        public CommandsCollection Commands
        {
            get { return (CommandsCollection)GetValue(CommandsProperty); }
            set { SetValue(CommandsProperty, value); }
        }

        public static readonly DependencyProperty CommandsProperty =
            DependencyProperty.Register("Commands", typeof(CommandsCollection), typeof(ContextHolder), new PropertyMetadata(null));

        public ContextHolder()
        {
            Commands = new CommandsCollection();
            Commands.Changed += FreezableCollectionChanged;
            _dict = new Dictionary<ICommand, CommandBinding>();

            CommandManager.AddCanExecuteHandler(this, CanExecute);
            CommandManager.AddExecutedHandler(this, OnExecute);
        }

        private void FreezableCollectionChanged(object sender, EventArgs e)
        {
            _dict.Clear();
            foreach (var i in Commands) { _dict.Add(i.RoutedCommand, i); }
        }


        private void OnExecute(object sender, ExecutedRoutedEventArgs e)
        {
            if (_dict.TryGetValue(e.Command, out CommandBinding binding))
            {
                if (binding?.RelayCommand != null) binding.RelayCommand.Execute(e.Parameter);
            }
        }

        private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (_dict.TryGetValue(e.Command, out CommandBinding binding))
            {
                if (binding?.RelayCommand != null) e.CanExecute = binding.RelayCommand.CanExecute(e.Parameter);
            }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace CommandExample
{
    public class Person
    {
        public Person(string name, int age)
        {
            Name = name;
            Age = age;
        }

        public string Name { get; }
        public int Age { get; }
    }

    public class MainModel
    {
        private ICommand _deleteCommand;
        public ICommand DeleteCommand => _deleteCommand ?? (_deleteCommand = new RelayCommand(OnDelete));

        private void OnDelete(object obj)
        {
            if (obj is Person person) Persones.Remove(person);
        }

        public ObservableCollection<Person> Persones { get; } = new ObservableCollection<Person>()
        {
            new Person("Roman", 36),
            new Person("Sofia", 46)
        };
    }

    public class RelayCommand : ICommand
    {

        public event EventHandler CanExecuteChanged;
        private readonly Func<object, bool> _canExecute;
        private readonly Action<object> _onExecute;

        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            _onExecute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null) return true;
            return _canExecute.Invoke(parameter);
        }

        public void Execute(object parameter)
        {
            _onExecute?.Invoke(parameter);
        }
    }
}

можно, примерно, вот так. Только тут всё на коленке сделано, лишь бы работало. Но пример рабочий и используется вместо своей команды, ApplicationCommand'а, а лучше своей пользоваться
31 окт 18, 19:06    [21720777]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Roman Mejtes
В данном примере, при вызове команды ApplicationCommands.Delete для элемента списка, будет вызвана команда DeleteCommand из модели MainModel и элемент удалится. Кнопка удаления как и в вашем примере находится в шаблоне элемента списка
......
можно, примерно, вот так. Только тут всё на коленке сделано, лишь бы работало. Но пример рабочий и используется вместо своей команды, ApplicationCommand'а, а лучше своей пользоваться
Да, мне бы лишь принцип понять.
А в целом, правильно ли биндить, элементы шаблона (или DataTemplate) не к свойствам Item? Может более верным будет создание в объекте Item'а нужных свойств во ViewModel и биндить к ним? В принципе (почитал ещё вчера, сегодня), в этом отношении команда - это тоже свойство, метод и его можно создать в каждом объекте Item'а.
Как концептуально, белее верно это будет сделать?
1 ноя 18, 12:49    [21721387]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Petro123
Member

Откуда: Загрузочный сектор Москвы (AutoPOI.ru)
Сообщений: 38491
Eld Hasp
Как концептуально, белее верно это будет сделать?
ты сделал оба варианта? Тогда уже сам можешь сделать вывод что тебе проще.
Паттерн MV**** никто 100 процентно по буквам не разделяет. Т..к.с некоторого момента издержки больше профита.
Главное что вьюКонтроллер класс без гуи полностью поддерживает состояние объекта.
Imho
1 ноя 18, 12:58    [21721407]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
А в целом, правильно ли биндить, элементы шаблона (или DataTemplate) не к свойствам Item? Может более верным будет создание в объекте Item'а нужных свойств во ViewModel и биндить к ним? В принципе (почитал ещё вчера, сегодня), в этом отношении команда - это тоже свойство, метод и его можно создать в каждом объекте Item'а.
Как концептуально, белее верно это будет сделать?

Можно, но ведь под ListBoxItem у тебя отдельная вьюмодель и она по-правильному ничего не знает о своем родителе. Но ведь информация о нажатой кнопке нужна не самому элементу, а родителю. Тогда в родительской модели придется подписываться на события модели Item-а. А это значит, что придется обрабатывать добавление и удаление Item-ов в коллекцию (отписываться тоже обязательно, чтобы не было утечки памяти), то есть либо организовывать прокси для записи в коллекцию, либо отслеживать все события изменения коллекции.

В общем, гораздо проще воспользоваться всплытием команды.
1 ноя 18, 13:06    [21721418]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Eld Hasp
Как концептуально, белее верно это будет сделать?
Вот допустим у меня есть ListBox отображающий два свойства Name, Vaulue.
Значение Value индивидуально для каждого Item'a, а Name оно одно и тоже. И меняется в зависимости от какого-то внешнего (по отношению к Item) свойства ViewModel. Я сейчас делаю таким образом. Когда создаю список для ListBox, то в ViewModel прописываю в каждом свойстве Name списка одно и тоже значение из общего свойства.
Верный ли такой подход? Если верный, то может верным будет и поступить так с командами?
1 ноя 18, 13:12    [21721438]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
Если верный, то может верным будет и поступить так с командами?
нет. Name - это свойство уровня модели. Команда - это уровень представления. Команда - свойство кнопки на форме, на уровне модели кнопки нет.

Можно на уровне модели обработать команду кнопки. И если нажатие кнопки нужно обрабатывать только в пределах модели Item-а, то это годится. Но если нажатие кнопки в Item-е требуется отслеживать выше по иерархии - этот подход превратится в головняк (см, что я писал выше)
1 ноя 18, 13:21    [21721453]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Shocker.Pro
Можно, но ведь под ListBoxItem у тебя отдельная вьюмодель и она по-правильному ничего не знает о своем родителе. Но ведь информация о нажатой кнопке нужна не самому элементу, а родителю. Тогда в родительской модели придется подписываться на события модели Item-а. А это значит, что придется обрабатывать добавление и удаление Item-ов в коллекцию (отписываться тоже обязательно, чтобы не было утечки памяти), то есть либо организовывать прокси для записи в коллекцию, либо отслеживать все события изменения коллекции.

В общем, гораздо проще воспользоваться всплытием команды.
Нет, может я Вас не правильно понимаю, может я не правильно свою мысль изложил.
Сделать на уровне VM в каждом объекте Item'а свойство ссылающееся на общую команду. То есть, то что делалось в шаблоне с привязкой команды к DataContext окна (это же VM), сделать явно в VM. И тогда в шаблоне будет привязка команды к свойству Item'а.

Получается VM знает о связях между отображаемыми данными - но это ей и положено знать.
А View этой информацией не владеет, и может работать замкнуто внутри себя.
1 ноя 18, 13:25    [21721456]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Shocker.Pro
нет. Name - это свойство уровня модели. Команда - это уровень представления. Команда - свойство кнопки на форме, на уровне модели кнопки нет.

Можно на уровне модели обработать команду кнопки. И если нажатие кнопки нужно обрабатывать только в пределах модели Item-а, то это годится. Но если нажатие кнопки в Item-е требуется отслеживать выше по иерархии - этот подход превратится в головняк (см, что я писал выше)
Команда - уровень представления. Но привязки для неё создаются в VM. Методы которые она вызывает - это, по моему, вообще, уровень модели. Просто в примере модели для простоты нет, но теоретически изменять данные должна именно модель. Разве не так?

То что Вы пишите - я внимательно читаю. И пытаюсь "переварить". Отсюда и "детские" вопросы.
1 ноя 18, 13:31    [21721468]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
Сделать на уровне VM в каждом объекте Item'а свойство ссылающееся на общую команду. То есть, то что делалось в шаблоне с привязкой команды к DataContext окна (это же VM), сделать явно в VM. И тогда в шаблоне будет привязка команды к свойству Item'а.
Ссылка из ребенка на родителя - фиговая идея. Не делайте так в принципе (не только тут).
1 ноя 18, 13:36    [21721480]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Roman Mejtes
Member

Откуда: г. Пермь
Сообщений: 3311
Shocker.Pro,

если у всех элементов дерева одинаковое время жизни, то это не так критично. Циклические ссылки сборщиком учитываются, все будет очищено, как только будет удалена ссылка на корневой элемент.

Eld Hasp,
Команды тут, слишком хорошо заходят, чтоб их игнорировать, это универсальный инструмент, который используется в WPF приложениях. Это их предназначение и не надо придумывать велосипед.

RoutedCommand сама по себе определена на уровне View и является часть представления. Никакой связи с моделью представления такая команда не имеет и действует только в пределах визального дерева.
Какой то элемент управления "возбуждает" маршрутизируемую команду, а какой то элемент выше по дереву её обрабатывает.
Если вы посмотрите реализацию элементов управления в WPF, то там так и сделано (команды ApplicationCommands и д.р.). И для такого функционала никакая модель представления не нужна, по сути команда это обычное RoutedEvent.
Но так как часто необходимо команду выполнить на уровне VM команду можно вызвать напрямую из модели представления через связывание (Binding), либо как я привел пример выше, скомбинировать оба подхода, на одному уровне представление возбуждается команда, а любом уровне выше команда обрабатывается.

Не надо забывать, что каждая ссылка на другой объект, это L байт и если список элементов очень большой, умножайте на N.
GC приходится работать дольше. Всё это ударит по перфомансу и если сейчас это не критично, то потом может сыграть злую шутку.
1 ноя 18, 14:24    [21721551]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Roman Mejtes
если у всех элементов дерева одинаковое время жизни, то это не так критично. Циклические ссылки сборщиком учитываются, все будет очищено, как только будет удалена ссылка на корневой элемент.
Да, но десктопное приложение может не закрываться днями и неделями. При этом, если список элементов подвержен фильтрации, то он может обновляться часто, полностью очищаясь. И если забыть про отписку от событий, все элементы очищаемого списка из памяти не исчезнут - здравствуй утечка. Можно, конечно, использовать слабосвязанные события, но тут нет никакого смысла.
1 ноя 18, 14:36    [21721575]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Shocker.Pro
если список элементов подвержен фильтрации
имеется ввиду, конечно, не встроенная фильтрация представления, а именно перезагрузка списка
1 ноя 18, 14:37    [21721579]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Roman Mejtes
В данном примере, при вызове команды ApplicationCommands.Delete для элемента списка.........
Смотрю я на Ваши примеры и охватывает меня уныние от понимания ничтожности собственных знаний.......... :(
1 ноя 18, 15:48    [21721685]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
[quot Shocker.Pro]
Roman Mejtes
......... И если забыть про отписку от событий, все элементы очищаемого списка из памяти не исчезнут - здравствуй утечка ..............
То есть привязка команды содержит в себе подписку на событие? И при изменении списка, если в нём содержатся привязанные команды, элементы списка не отправятся в мусор? Из-за подписки на событие они будут оставаться в памяти?
1 ноя 18, 15:54    [21721695]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
То есть привязка команды содержит в себе подписку на событие? И при изменении списка, если в нём содержатся привязанные команды, элементы списка не отправятся в мусор? Из-за подписки на событие они будут оставаться в памяти?
Нет. Я же говорил про обычные события внутри вьюмодели.
Команды как раз-таки построены на слабосвязанных событиях, так что за них можно не раздумывать.
1 ноя 18, 16:03    [21721708]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Что-то долго до меня всё доходит.
С вашей помощью и подсказками, сделал так.
Код ViewModel
    public class ExampleCommandVM 
    {
        private int maxId = 0;

        public ObservableCollection<string> ListSource { get; set; } = new ObservableCollection<string>();

        public ExampleCommandVM()
        {
        }
        static public ExampleCommandVM Create()
        {
            ExampleCommandVM ret = new ExampleCommandVM();
            for (int ind = 0; ind < 5; ind++)
                ret.OnAdd();
            return ret;
        }

        public void OnAdd(object Value = null)
        {
            if (Value is string value)
                ListSource.Add(value);
            else
                ListSource.Add("Строка №" + maxId.ToString().PadLeft(2, '0'));
            maxId++;
        }

        public void OnRemove(object Value)
        {
            if (Value is string str) ListSource.Remove(str);
        }

        public void OnSave(object changeValue)
        {
            if (!(changeValue is ChangeValue chgVal)) return;
            ListSource[ListSource.IndexOf(chgVal.OldValue)] = chgVal.NewValue;
        }

        private ICommand _addComm;
        public ICommand AddComm => _addComm ?? (_addComm = new RelayCommand(OnAdd));

        private ICommand _removeComm;
        public ICommand RemoveComm => _removeComm ?? (_removeComm = new RelayCommand(OnRemove));

        private ICommand _saveComm;
        public ICommand SaveComm => _saveComm ?? (_saveComm = new RelayCommand(OnSave));

    }

CommandsCollection и RelayCommand - не менял. Полностью из поста 21720777 от Roman Mejtes.

Словарь ExampleCommandDict.xaml с шаблонам и конверторами
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfQuestions">
    <local:ChangeValueConverter x:Key="ChangeValueConverter"/>
    <local:IsChangeValueConverter x:Key="IsChangeValueConverter"/>
    <Style TargetType="ListBoxItem" x:Key="ListBoxItemExamp">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <Grid x:Name="PART_Grid" Background="{TemplateBinding Background}" >
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="{Binding Mode=OneWay}" VerticalAlignment="Center" Margin="5"/>
                        <TextBox x:Name="PART_TextBox" Grid.Column="1" Text="{Binding Mode=OneWay}" VerticalAlignment="Center" Margin="5"/>
                        <Button x:Name="PART_Button_Save" Content="Save" Grid.Column="2" Margin="5" Padding="2"
                                    Command="{x:Static ApplicationCommands.Save}" 
                                    >
                            <Button.CommandParameter>
                                <MultiBinding Converter="{StaticResource ChangeValueConverter}">
                                    <Binding/>
                                    <Binding ElementName="PART_TextBox" Path="Text"/>
                                </MultiBinding>
                            </Button.CommandParameter>
                        </Button>
                        <Button Content="Remove" Grid.Column="3" Margin="5" Padding="2" 
                                    Command="{x:Static ApplicationCommands.Delete}" 
                                    CommandParameter="{Binding}" />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <DataTrigger Value="True">
                            <DataTrigger.Binding>
                                <MultiBinding Converter="{StaticResource IsChangeValueConverter}">
                                    <Binding/>
                                    <Binding  ElementName="PART_TextBox" Path="Text"/>
                                </MultiBinding>
                            </DataTrigger.Binding>
                            <Setter TargetName="PART_TextBox" Property="Foreground" Value="Red"/>
                            <Setter TargetName="PART_Button_Save" Property="IsEnabled" Value="True"/>
                        </DataTrigger>
                        <DataTrigger Value="False">
                            <DataTrigger.Binding>
                                <MultiBinding Converter="{StaticResource IsChangeValueConverter}">
                                    <Binding/>
                                    <Binding  ElementName="PART_TextBox" Path="Text"/>
                                </MultiBinding>
                            </DataTrigger.Binding>
                            <Setter TargetName="PART_TextBox" Property="Foreground" Value="Black"/>
                            <Setter TargetName="PART_Button_Save" Property="IsEnabled" Value="False"/>
                        </DataTrigger>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Background" Value="LightBlue"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        
    </Style>
</ResourceDictionary>
И само окно ExampleCommand.xaml
<Window x:Name="window" x:Class="WpfQuestions.ExampleCommand"
        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:WpfQuestions"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d" SizeToContent="WidthAndHeight"
        Title="ExampleCommand" >
    <d:Window.DataContext>
        <local:ExampleCommandVM>
            <local:ExampleCommandVM.ListSource>
                <sys:String>Строка 1</sys:String>
                <sys:String>Строка 2</sys:String>
            </local:ExampleCommandVM.ListSource>
        </local:ExampleCommandVM>
    </d:Window.DataContext>
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="ExampleCommandDict.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <local:ContextHolder>
        <local:ContextHolder.Commands>
            <local:CommandBinding RoutedCommand="{x:Static ApplicationCommands.Delete}"
                                      RelayCommand="{Binding RemoveComm}"
                                      />
            <local:CommandBinding RoutedCommand="{x:Static ApplicationCommands.Save}"
                                      RelayCommand="{Binding SaveComm}"
                                      />
            <local:CommandBinding RoutedCommand="{x:Static ApplicationCommands.New}"
                                      RelayCommand="{Binding AddComm}"
                                      />
        </local:ContextHolder.Commands>
        <StackPanel>
            <ListBox ItemsSource="{Binding ListSource}" ItemContainerStyle="{StaticResource ListBoxItemExamp}"/>
            <Button Margin="10" Content="Add" Command="{x:Static ApplicationCommands.New}"/>
        </StackPanel>
    </local:ContextHolder>


</Window>


Из того, что понял.
- Каких-то "стандартных" методов для подключения кастомных команд в списочных элементах в WPF не предусмотрено. Поэтому и пришлось создавать класс ContextHolder, который отлавливает команды из вложенных элементов и в котором можно указать привязки команд на методы VM.
- Для "отлова команды" в классе CommandBinding используется привязка к одной из команд класса RoutedUICommand. Это обязательное условие?
- Если использовать напрямую команды из класса RoutedUICommand, то они требуют присоединения обработчика в самом окне на уровне View. Какая-то, как мне кажется, не завершённость в реализации... Или я опять чего-то не до понял?
3 ноя 18, 15:58    [21723286]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
- Для "отлова команды" в классе CommandBinding используется привязка к одной из команд класса RoutedUICommand. Это обязательное условие?
Это не привязка. Я советовал перечитать, что собой представляет команда. Команда - это просто экземпляр класса "RoutedUICommand", присвоенный статической переменной. Команда САМА ПО СЕБЕ ничего не делает - это просто маркер.

Button заставляет всплывать экземпляр, присвоенный статической переменной ApplicationCommands.New. Он отлавливается уровнем выше с помощью local:CommandBinding. Но этот "уловитель" должен же знать, на какую команду реагировать, иначе он будет реагировать на все команды, которые придут "снизу". Поэтому в его свойстве RoutedCommand указывается тот самый маркер, который нужно отлавливать именно ему.
3 ноя 18, 16:19    [21723294]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
- Каких-то "стандартных" методов для подключения кастомных команд в списочных элементах в WPF не предусмотрено. Поэтому и пришлось создавать класс ContextHolder
Это высказывание справедливо для работы в парадигме MVVM. Кстати, RelayCommand тоже не существует "из коробки".
3 ноя 18, 16:21    [21723295]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
- Если использовать напрямую команды из класса RoutedUICommand, то они требуют присоединения обработчика в самом окне на уровне View. Какая-то, как мне кажется, не завершённость в реализации... Или я опять чего-то не до понял?
Ну это опять же к предыдущему вопросу. Присоединение обработчика возможно, когда работаешь с CodeBehind, а когда используешь MVVM - нужна примочка в виде перехватчика всплывающих команд, которая будет направлять их выполнение в VM на RelayCommand
3 ноя 18, 16:24    [21723296]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Shocker.Pro
Это не привязка. Я советовал перечитать, что собой представляет команда. Команда - это просто экземпляр класса "RoutedUICommand", присвоенный статической переменной. Команда САМА ПО СЕБЕ ничего не делает - это просто маркер.

Button заставляет всплывать экземпляр, присвоенный статической переменной ApplicationCommands.New. Он отлавливается уровнем выше с помощью local:CommandBinding. Но этот "уловитель" должен же знать, на какую команду реагировать, иначе он будет реагировать на все команды, которые придут "снизу". Поэтому в его свойстве RoutedCommand указывается тот самый маркер, который нужно отлавливать именно ему.
Спасибо за разъяснение.
Читать-то я читаю - но до конца в голове не всё укладывается.

- "Команда - это просто экземпляр класса "RoutedUICommand", присвоенный статической переменной." - это я понял. Просто неправильно написал, выразил свою мысль.

- "Но этот "уловитель" должен же знать, на какую команду реагировать, иначе он будет реагировать на все команды, которые придут "снизу"" - это тоже понятно.
Не понятно другое. Всюду где читал, смотрел примеры - идёт объяснение привязки обработчика RoutedUICommand к обработчику в View. Почему? По идее WPF очень сильно завязан на MVVM. И не предусмотреть возможности привязки обработчика RoutedUICommand к VM ? Странно как-то.... Закрадывается впечатление, что я что-то не понимаю, или читаю не то что нужно.....
Почему не сделать подход, используемый Roman Mejtes (свойства класса CommandBinding), стандартным для RoutedUICommand ? Это же намного удобнее и логичнее для паттерна MVVM.
3 ноя 18, 16:46    [21723304]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
Не понятно другое. Всюду где читал, смотрел примеры - идёт объяснение привязки обработчика RoutedUICommand к обработчику в View. Почему? По идее WPF очень сильно завязан на MVVM
Да, я тоже согласен, что это странно - но как есть.
3 ноя 18, 16:48    [21723305]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Shocker.Pro
Да, я тоже согласен, что это странно - но как есть.
Ну... чуть отлегло от сердца. А то уже думал, что я сам такой тупой недалёкий....
Оказывается - нет! Это опять MS над всеми издевается!
3 ноя 18, 17:00    [21723310]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Я сам учился у Романа, спасибо ему.
Может будет полезно изучить этот топик https://www.sql.ru/forum/1179489
3 ноя 18, 17:07    [21723313]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Shocker.Pro
Я сам учился у Романа, спасибо ему.
Может будет полезно изучить этот топик https://www.sql.ru/forum/1179489
Спасибо!
Прочитал - очень интересно, но с первого раза многое что не дошло. Буду вчитываться.
3 ноя 18, 17:45    [21723334]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Там есть ссылка на рабочий пример.

Там, в общем-то о том же, но перехватчик команд присоединяется не через специальный класс ContextHolder, а через механизм, использующий AttachedProperty. Этот способ иногда еще называют behaviors, но это не те "поведения", которые идут почти из коробки и больше заточены для использования совместно с Blend.
3 ноя 18, 18:07    [21723341]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Shocker.Pro
Там есть ссылка на рабочий пример.
Если Вы пишите о ссылке RoutedListenerSample.zip, то ссылка рабочая, но файл там уже удалён.
3 ноя 18, 19:58    [21723387]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 20292
Eld Hasp
Shocker.Pro
Там есть ссылка на рабочий пример.
Если Вы пишите о ссылке RoutedListenerSample.zip, то ссылка рабочая, но файл там уже удалён.


К сообщению приложен файл (RLS.zip - 112Kb) cкачать
3 ноя 18, 20:31    [21723402]     Ответить | Цитировать Сообщить модератору
 Re: Правильное размещение функционала и привязки команд  [new]
Eld Hasp
Member

Откуда:
Сообщений: 233
Shocker.Pro, СПАСИБО!
4 ноя 18, 00:34    [21723471]     Ответить | Цитировать Сообщить модератору
Топик располагается на нескольких страницах: 1 2      [все]
Все форумы / WPF, Silverlight Ответить