Подтвердить что ты не робот

WPF MVVM: Команды просты. Как подключить View и ViewModel с помощью RoutedEvent

Предположим, что у меня есть представление, реализованное как DataTempate внутри ресурсного словаря. И у меня есть соответствующая ViewModel. Команды привязки просты. Но что, если мой вид содержит элемент управления, такой как ListBox, и мне нужно опубликовать широковещательное событие приложения (с использованием Prism Event Aggreagtor) на основе элемента, измененного в списке.

если ListBox поддерживает команду, я могу просто привязать ее к команде в ViewModel и опубликовать событие. Но Listbox не допускает такой опции. Как это сделать?

EDIT: Много отличных ответов.

Взгляните на эту ссылку http://blogs.microsoft.co.il/blogs/tomershamam/archive/2009/04/14/wpf-commands-everywhere.aspx

Спасибо

Ariel

4b9b3361

Ответ 1

Один из вариантов заключается в том, чтобы расширить рассматриваемый элемент управления и добавить поддержку для конкретной команды, которая вам нужна. Например, я изменен ListView до для поддержки события ItemActivated и связанной с ним команды.

Ответ 2

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

Если вы привязываете выбранный элемент ListBox к свойству в ViewModel, то при изменении этого свойства вы можете опубликовать событие. Таким образом, ViewModel остается источником события, и он срабатывает при изменении элемента, что вам нужно.

<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />

...

public class ViewModel
{
    public IEnumerable<Item> Items { get; set; } 

    private Item selectedItem;
    public Item SelectedItem
    {
        get { return selectedItem; }
        set
        {
            if (selectedItem == value)
                return;
            selectedItem = value;
            // Publish event when the selected item changes
        }
}

Ответ 3

Расширьте элемент управления для поддержки ICommandSource и определите, какое действие должно инициировать команду.

Я сделал это с помощью Combo Box и использовал OnSelectionChanged как триггер для этой команды. Сначала я покажу в XAML, как я привязываю команду к расширенному Control ComboBox, который я назвал CommandComboBox, тогда я покажу код для CommandComboBox, который добавит поддержку ICommandSource в ComboBox.

1) Использование CommandComboBox в вашем коде XAML:

В ваших объявлениях пространства имен XAML есть

   xmlns:custom="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary">

Используйте CommandComboBox вместо ComboBox и привяжите команду к нему следующим образом: Обратите внимание, что в этом примере у меня есть определенная команда SetLanguageCommand im my ViewModel, и я передаю выбранное значение для этого ComboBox в качестве параметра для команда.

 <custom:CommandComboBox 
    x:Name="ux_cbSelectLanguage"
    ItemsSource="{Binding Path = ImagesAndCultures}"
    ItemTemplate="{DynamicResource LanguageComboBoxTemplate}"           
    Command="{Binding Path=SetLanguageCommand, Mode=Default}"
    CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=SelectedValue, Mode=Default}"
    IsSynchronizedWithCurrentItem="True" 
    HorizontalAlignment="Right" 
    VerticalAlignment="Center" 
    Grid.Column="1" Margin="0,0,20,0" Style="{DynamicResource GlassyComboBox}" ScrollViewer.IsDeferredScrollingEnabled="True"
 />

2) Код CommandComboBox

Код для файла CommandComboBox.cs приведен ниже. Я добавил этот файл в библиотеку классов под названием WpfCommandControlsLibrary и сделал его отдельным проектом, поэтому я мог легко добавить любые команды расширения в любое решение, необходимое для их использования, и поэтому я мог бы легко добавить дополнительные элементы управления WPF и расширить их для поддержки ICommandSource inteface.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WpfCommandControlsLibrary
{
   /// <summary>
   /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
   ///
   /// Step 1a) Using this custom control in a XAML file that exists in the current project.
   /// Add this XmlNamespace attribute to the root element of the markup file where it is 
   /// to be used:
   ///
   ///     xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary"
   ///
   ///
   /// Step 1b) Using this custom control in a XAML file that exists in a different project.
   /// Add this XmlNamespace attribute to the root element of the markup file where it is 
   /// to be used:
   ///
   ///     xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary"
   ///
   /// You will also need to add a project reference from the project where the XAML file lives
   /// to this project and Rebuild to avoid compilation errors:
   ///
   ///     Right click on the target project in the Solution Explorer and
   ///     "Add Reference"->"Projects"->[Select this project]
   ///
   ///
   /// Step 2)
   /// Go ahead and use your control in the XAML file.
   ///
   ///     <MyNamespace:CustomControl1/>
   ///
   /// </summary>

   public class CommandComboBox : ComboBox, ICommandSource
   {
      public CommandComboBox() : base()
      {
      }

  #region Dependency Properties
  // Make Command a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandProperty =
      DependencyProperty.Register(
          "Command",
          typeof(ICommand),
          typeof(CommandComboBox),
          new PropertyMetadata((ICommand)null,
          new PropertyChangedCallback(CommandChanged)));

  public ICommand Command
  {
     get
     {
        return (ICommand)GetValue(CommandProperty);
     }
     set
     {
        SetValue(CommandProperty, value);
     }
  }

  // Make CommandTarget a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandTargetProperty =
      DependencyProperty.Register(
          "CommandTarget",
          typeof(IInputElement),
          typeof(CommandComboBox),
          new PropertyMetadata((IInputElement)null));

  public IInputElement CommandTarget
  {
     get
     {
        return (IInputElement)GetValue(CommandTargetProperty);
     }
     set
     {
        SetValue(CommandTargetProperty, value);
     }
  }

  // Make CommandParameter a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandParameterProperty =
      DependencyProperty.Register(
          "CommandParameter",
          typeof(object),
          typeof(CommandComboBox),
          new PropertyMetadata((object)null));

  public object CommandParameter
  {
     get
     {
        return (object)GetValue(CommandParameterProperty);
     }
     set
     {
        SetValue(CommandParameterProperty, value);
     }
  }

  #endregion

  // Command dependency property change callback.
  private static void CommandChanged(DependencyObject d,
      DependencyPropertyChangedEventArgs e)
  {
     CommandComboBox cb = (CommandComboBox)d;
     cb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
  }

  // Add a new command to the Command Property.
  private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
  {
     // If oldCommand is not null, then we need to remove the handlers.
     if (oldCommand != null)
     {
        RemoveCommand(oldCommand, newCommand);
     }
     AddCommand(oldCommand, newCommand);
  }

  // Remove an old command from the Command Property.
  private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
  {
     EventHandler handler = CanExecuteChanged;
     oldCommand.CanExecuteChanged -= handler;
  }

  // Add the command.
  private void AddCommand(ICommand oldCommand, ICommand newCommand)
  {
     EventHandler handler = new EventHandler(CanExecuteChanged);
     canExecuteChangedHandler = handler;
     if (newCommand != null)
     {
        newCommand.CanExecuteChanged += canExecuteChangedHandler;
     }
  }
  private void CanExecuteChanged(object sender, EventArgs e)
  {

     if (this.Command != null)
     {
        RoutedCommand command = this.Command as RoutedCommand;

        // If a RoutedCommand.
        if (command != null)
        {
           if (command.CanExecute(CommandParameter, CommandTarget))
           {
              this.IsEnabled = true;
           }
           else
           {
              this.IsEnabled = false;
           }
        }
        // If a not RoutedCommand.
        else
        {
           if (Command.CanExecute(CommandParameter))
           {
              this.IsEnabled = true;
           }
           else
           {
              this.IsEnabled = false;
           }
        }
     }
  }

  // If Command is defined, selecting a combo box item will invoke the command;
  // Otherwise, combo box will behave normally.
  protected override void OnSelectionChanged(SelectionChangedEventArgs e)
  {
     base.OnSelectionChanged(e);

     if (this.Command != null)
     {
        RoutedCommand command = Command as RoutedCommand;

        if (command != null)
        {
           command.Execute(CommandParameter, CommandTarget);
        }
        else
        {
           ((ICommand)Command).Execute(CommandParameter);
        }
     }
  }

  // Keep a copy of the handler so it doesn't get garbage collected.
  private static EventHandler canExecuteChangedHandler;

  }
}

Ответ 4

Отличное решение этой проблемы связано с использованием Attached Properties. Марлон Греч применил Attached Properties на следующем уровне, создав "Привязанные действия команд" . Используя их, можно связать любую Команду, существующую в ViewModel, с любым Событием, существующим в представлении.

Это то, что я много использую для решения подобных проблем с ListBoxes, где я хочу, чтобы они открывали или редактировали или выполняли некоторые действия при двойном щелчке.

В этом примере я использую более старую версию командных команд, но эффект тот же. У меня есть стиль, который используется для ListBoxItems, к которым я явно привязываюсь. Однако было бы достаточно просто создать стиль приложения или окна, применимый ко всем элементам ListBoxItems, которые устанавливают команды на гораздо более высоком уровне. Затем, когда событие для объекта ListBoxItem, связанного с свойством CommandBehavior.Event, будет срабатывать, вместо этого он отключается от прилагаемой команды.

<!-- acb is the namespace reference to the Attached Command Behaviors -->
<Style x:Key="Local_OpenListItemCommandStyle">
    <Setter Property="acb:CommandBehavior.Event"
            Value="MouseDoubleClick" />
    <Setter Property="acb:CommandBehavior.Command"
            Value="{Binding ElementName=uiMyListBorder, Path=DataContext.OpenListItemCommand}" />
    <Setter Property="acb:CommandBehavior.CommandParameter"
            Value="{Binding}" />
</Style>

<DataTemplate x:Key="MyView">
<Border x:Name="uiMyListBorder">
<ListBox  ItemsSource="{Binding MyItems}"
          ItemContainerStyle="{StaticResource local_OpenListItemCommandStyle}" />
</Border>
</DataTemplate>

Ответ 5

Ну, никто не ответил. Поэтому я отказался и перевел реализацию View вне словаря в обычный UserControl, я ввел ему ссылку на ViewModel.

Теперь, когда ListBox запускает Событие, он вызывает ViewModel, и оттуда все возможно снова.

Ariel

Ответ 6

Я писал поведение (прикрепленные свойства), чтобы сделать это, и все еще есть случаи, когда они мне нужны.

Однако для обычного случая, просто привязывая событие к команде, вы можете делать все в Xaml, если у вас установлен Blend SDK 4. Обратите внимание, что вам нужно будет добавить ссылку на System.Windows.Interactivity.dll и перераспределить эту сборку.

В этом примере вызывается ICommand DragEnterCommand на ViewModel при запуске события DragEnter в Grid:

<UserControl xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" >
    <Grid>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="DragEnter">
                <i:InvokeCommandAction Command="{Binding DragEnterCommand}" CommandParameter="{Binding ...}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Grid>
</UserControl>

Ответ 7

Попробуйте использовать Prism 2.

Он поставляется с большими расширениями для командования и открывает много новых posibilites (например, команды привязаны к визуальному дереву).