Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / WPF, Silverlight Новый топик    Ответить
 Команды в WPF (ListBoxItem.Selected)  [new]
Eld Hasp
Member

Откуда:
Сообщений: 178
Изучаю WPF недавно. Дошёл до WPF команд. Читаю-читаю и что-то, по мере чтения, в голове только каша увеличивается.
Помогите, на примере понять как создать команду, как ею пользоваться.
Описание примера:
Простой ListBox, отображающий свойство ListItem класса AuxiliaryClass, выполняющего роль ViewModel.
При выделении элемента ListBox (событие ListBoxItem.Selected) его Content передаётся в свойство AuxiliaryClass.SelectedString и TextBlock, который привязан к этому свойству, отображает его.

На примере показываю как это реализовано с помощью кода и событий.

Класс AuxiliaryClass (ViewModel)
    public class AuxiliaryClass : INotifyPropertyChanged
    {
        #region Событие PropertyChanged и метод для его вызова
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName]string prop = "")
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
        #endregion
 
 
        public List<string> ListItem { get; } = new List<string>(@"
Строка 1
Строка 2
Строка 3
Строка 4
Строка 5"
        .Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries));
 
        private string _selectedStrin;
        public string SelectedString
        { get => _selectedStrin;
            set { _selectedStrin = value; OnPropertyChanged("SelectedString"); } }
    }
XAML окна
    <Window.DataContext>
        <local:AuxiliaryClass/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
 
        <ListBox x:Name="myLB" Margin="10" ItemsSource="{Binding ListItem}">
            <ListBox.Resources>
                <Style TargetType="ListBoxItem">
                    <EventSetter Event="ListBoxItem.Selected" Handler="ListBoxItem_Selected"></EventSetter>
                </Style>
            </ListBox.Resources>
        </ListBox>
        <TextBlock Text="{Binding SelectedString}" DockPanel.Dock="Bottom" VerticalAlignment="Bottom" Margin="10" Grid.Row="1"/>
    </Grid>
Обработчик в коде окна
        private void ListBoxItem_Selected(object sender, RoutedEventArgs e)
        {
           ((AuxiliaryClass) DataContext).SelectedString = ((ListBoxItem)sender).Content.ToString();
        }

Окажите помощь - как создать и подключить WPF команду для тех же самых действий?
8 окт 18, 00:25    [21697612]     Ответить | Цитировать Сообщить модератору
 Re: Команды в WPF (ListBoxItem.Selected)  [new]
Сон Веры Павловны
Member

Откуда:
Сообщений: 4604
Что-то здесь совсем странное нарисовано. Во-первых, зачем выделение у ListBox устанавливать через событие, когда его можно просто забиндить на свойство модели?
Во-вторых, использование EventSetter - не самый лучший выход, т.к. они работают только с хэндлерами, определенными в code behind, который есть далеко не всегда (например, в словарном стиле его нет). Здесь лучше использовать AttachedCommandBehavior. Или связку EventTrigger из System.Windows.Interactivity+EventToCommand из MVVM Light Toolkit:
<ListBox
  Margin="5"
  ItemsSource="{Binding ListItems}"
  SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectionChanged">
      <command:EventToCommand
        Command="{Binding ItemsSelectionChangedCommand}"
        CommandParameter="{Binding SelectedItem}"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
</ListBox>

public partial class MainWindow
{
  public MainWindow()
  {
    InitializeComponent();
    DataContext = new AuxiliaryClass();
  }
}

public class AuxiliaryClass : INotifyPropertyChanged
{
  public AuxiliaryClass()
  {
    ListItems = typeof(Colors).GetProperties(BindingFlags.Public | BindingFlags.Static)
      .Select(f => f.Name)
      .ToList();
    ItemsSelectionChangedCommand = new RelayCommand(
      ItemsSelectionChanged,
      o => o is string s && s.StartsWith("D", StringComparison.OrdinalIgnoreCase)
    );
    SelectedItem = ListItems.First();
  }

  public IList<string> ListItems { get; }
  public event PropertyChangedEventHandler PropertyChanged;

  string _selectedItem;
  public string SelectedItem
  {
    get => _selectedItem;
    set
    {
      _selectedItem = value;
      OnPropertyChanged();
      Console.WriteLine("SelectedItem changed to {0}", SelectedItem);
    }
  }

  [NotifyPropertyChangedInvocator]
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
  public RelayCommand ItemsSelectionChangedCommand { get; }

  void ItemsSelectionChanged(object parameter)
  {
    Console.WriteLine("Selection changed: {0}", parameter ?? "null");
  }
}

(класс RelayCommand - тоже из MVVM Light).
8 окт 18, 09:05    [21697700]     Ответить | Цитировать Сообщить модератору
 Re: Команды в WPF (ListBoxItem.Selected)  [new]
Eld Hasp
Member

Откуда:
Сообщений: 178
Сон Веры Павловны
автор
Что-то здесь совсем странное нарисовано

Странное .... Поэтому и обращаюсь за помощью.
Часть странностей из-за попытки упростить пример.
автор
Во-первых, зачем выделение у ListBox устанавливать через событие, когда его можно просто забиндить на свойство модели?
В данном случае работать будет несколько по иному. Можно было бы и просто забиндить TextBlock на SelectItem. Но при снятии выделения в ListBox (допустим, при обновлении коллекции) обнулится и связанное свойство. Если же делать это по событию, то в свойстве останется последнее выделенное значение.
Идея примера - передать выделенное значение в связанный класс (играющий роль VM).
Пример чисто для обучения. Понять как тоже самое можно сделать (или нельзя?) используя вместо события WPF команду.
автор
Во-вторых, использование EventSetter - не самый лучший выход, т.к. они работают только с хэндлерами, определенными в code behind, который есть далеко не всегда (например, в словарном стиле его нет). Здесь лучше использовать AttachedCommandBehavior. Или связку EventTrigger из System.Windows.Interactivity+EventToCommand из MVVM Light Toolkit:
В Вашем решении, Вы подключаете команду через Триггер. То есть команду к событию состыковать можно только таким образом?
Меня просто ставит немного в тупик как в примерах из интернета просто подключается команда. Например
 <Button  Content="Button" Command="{Binding Add}"
Ни каких триггеров. Просто указана команда и всё. Поэтому никак понять и не могу, как добиться такой простоты использования собственной команды? Пытаюсь понять всё это - но от большей информации, только больший бардак в голове.
8 окт 18, 16:43    [21698380]     Ответить | Цитировать Сообщить модератору
 Re: Команды в WPF (ListBoxItem.Selected)  [new]
Сон Веры Павловны
Member

Откуда:
Сообщений: 4604
Eld Hasp
В данном случае работать будет несколько по иному. Можно было бы и просто забиндить TextBlock на SelectItem. Но при снятии выделения в ListBox (допустим, при обновлении коллекции) обнулится и связанное свойство. Если же делать это по событию, то в свойстве останется последнее выделенное значение.

Ничего не мешает реализовать логику сеттера забинденного свойства так, чтобы свойство либо не менялось при null-значениях, либо хранило последнее установленное значение где-то еще. Почему я настаиваю именно на биндинге свойства: у вас свойство, и логика его установки разнесены по разным классам, это есть нарушение принципа единственности ответственности, и размывание логики, инкапсулированной во вьюмодели.
Eld Hasp
Пример чисто для обучения. Понять как тоже самое можно сделать (или нельзя?) используя вместо события WPF команду.

Странный пример. Из области "как научиться пользоваться отверткой, используя её вместо стамески".
Eld Hasp
автор
Во-вторых, использование EventSetter - не самый лучший выход, т.к. они работают только с хэндлерами, определенными в code behind, который есть далеко не всегда (например, в словарном стиле его нет). Здесь лучше использовать AttachedCommandBehavior. Или связку EventTrigger из System.Windows.Interactivity+EventToCommand из MVVM Light Toolkit:
В Вашем решении, Вы подключаете команду через Триггер. То есть команду к событию состыковать можно только таким образом?
Меня просто ставит немного в тупик как в примерах из интернета просто подключается команда. Например
 <Button  Content="Button" Command="{Binding Add}"
Ни каких триггеров. Просто указана команда и всё. Поэтому никак понять и не могу, как добиться такой простоты использования собственной команды? Пытаюсь понять всё это - но от большей информации, только больший бардак в голове.

Просто указана команда потому, что Button реализует ICommandSource, и знает, как вызвать команду (ICommand), как передать ей параметры, как проверить, можно ли выполнять команду, итд. А ListBox не реализует ICommandSource, и не знает, как работать с командами, поэтому команды для него назначаются окольными путями - ACB, EventTrigger'ы, итд. Отдельный случай - когда элемент реализует ICommandSource, но нам нужно связать модель с событием, которое не инициирует команд - например, MouseEnter.
Но, поскольку у вас вызывает затруднения даже самый простой случай, рекомендую пока разбираться только с ним:
public partial class MainWindow
{
  public MainWindow()
  {
    InitializeComponent();
    DataContext = new TestModel(o=>MessageBox.Show((string)o));
  }
}

public class TestModel : INotifyPropertyChanged
{
  public TestModel(Action<object> showText)
  {
    ShowTextCommand = new MyCommand(showText, o=>!string.IsNullOrEmpty(Text));
  }

  public event PropertyChangedEventHandler PropertyChanged;

  string _text;
  public string Text
  {
    get => _text;
    set
    {
      _text = value;
      OnPropertyChanged();
      ShowTextCommand.RaiseCanExecuteChanged();
    }
  }

  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }

  public MyCommand ShowTextCommand { get; }
}

public class MyCommand : ICommand
{
  readonly Action<object> _onExecute;
  readonly Func<object, bool> _canExecute;

  public MyCommand(Action<object> onExecute, Func<object, bool> canExecute)
  {
    _onExecute = onExecute ?? throw new ArgumentNullException(nameof(onExecute));
    _canExecute = canExecute;
  }

  public bool CanExecute(object parameter)
  {
    return _canExecute==null || _canExecute(parameter);
  }

  public void Execute(object parameter)
  {
    _onExecute(parameter);
  }

  public event EventHandler CanExecuteChanged;
  
  public void RaiseCanExecuteChanged()
  {
    CanExecuteChanged?.Invoke(this, EventArgs.Empty);
  }
}

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
  </Grid.RowDefinitions>
  <TextBox
    VerticalAlignment="Center"
    Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
  <Button
    Grid.Row="1"
    Content="Show Text"
    VerticalAlignment="Center"
    HorizontalAlignment="Center"
    Padding="3"
    Command="{Binding ShowTextCommand}"
    CommandParameter="{Binding Text}"/>
</Grid>

Разумеется, в жизни редко когда создают свои специализированные имплементации ICommand на каждый случай использования - существует куча готовых реализаций, тот же RelayCommand из MVVM Toolkit (вышеприведенный MyCommand примерно так и реализован).
9 окт 18, 08:12    [21698846]     Ответить | Цитировать Сообщить модератору
Все форумы / WPF, Silverlight Ответить