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

Откуда:
Сообщений: 225
Наверное, замучил всех своими "детскими" вопросами... Извините если, что .... Но я опять за своё :)
У меня вопрос по правильному размещению функционала и привязки команд.
Для объяснения простой пример.
Код 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

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

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

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

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

Откуда: ->|<- :адуктО
Сообщений: 20286
Мне тут как-то в голову пришла гениальная идея для таких случаев, хотя может она и банальная, интересно мнение Романа на этот счет. Берем простейший класс без визуального оформления:
	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

Откуда: ->|<- :адуктО
Сообщений: 20286
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

Откуда: г. Пермь
Сообщений: 3305
В данном примере, при вызове команды 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Откуда: г. Пермь
Сообщений: 3305
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

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

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

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

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

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

Откуда:
Сообщений: 225
Что-то долго до меня всё доходит.
С вашей помощью и подсказками, сделал так.
Код 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

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

Button заставляет всплывать экземпляр, присвоенный статической переменной ApplicationCommands.New. Он отлавливается уровнем выше с помощью local:CommandBinding. Но этот "уловитель" должен же знать, на какую команду реагировать, иначе он будет реагировать на все команды, которые придут "снизу". Поэтому в его свойстве RoutedCommand указывается тот самый маркер, который нужно отлавливать именно ему.
3 ноя 18, 16:19    [21723294]     Ответить | Цитировать Сообщить модератору
Топик располагается на нескольких страницах: [1] 2   вперед  Ctrl      все
Все форумы / WPF, Silverlight Ответить