WPF MVVM:命令很简单。如何使用RoutedEvent连接View和ViewModel

时间:2009-05-10 14:35:52

标签: wpf design-patterns events mvvm command

假设我在资源Dictionary中将视图实现为DataTempate。 我有一个相应的ViewModel。 绑定命令很简单。但是如果我的View包含一个像ListBox这样的控件,我需要根据列表中更改的项目发布一个应用程序范围的事件(使用Prism的事件聚合器)。

如果ListBox支持命令,我可以将它绑定到ViewModel中的命令并发布事件。但是Listbox不允许这样的选择。 我该如何解决这个问题?

编辑: 很多很棒的答案。

请查看此链接http://blogs.microsoft.co.il/blogs/tomershamam/archive/2009/04/14/wpf-commands-everywhere.aspx

由于

爱丽儿

7 个答案:

答案 0 :(得分:44)

我没有尝试将命令绑定到项目更改时,而是以另一种方式查看问题。

如果将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
        }
}

答案 1 :(得分:17)

扩展控件以支持ICommandSource并决定应该触发命令的操作。

我用Combo Box做了这个,并使用OnSelectionChanged作为命令的触发器。首先,我将在XAML中展示如何将命令绑定到我称为CommandComboBox的扩展Control ComboBox,然后我将显示CommandComboBox的代码,它将对ICommandSource的支持添加到ComboBox。

1)在XAML代码中使用CommandComboBox:

在您的XAML名称空间中,声明包括

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

使用CommandComboBox代替ComboBox并将命令绑​​定到它上面:请注意,在此示例中,我有一个名为SetLanguageCommand的命令,我的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接口。 / p>

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;

  }
}

答案 2 :(得分:8)

一种选择是扩展有问题的控件并添加对所需特定命令的支持。例如,我modified ListView before支持ItemActivated事件和相关命令。

答案 3 :(得分:2)

此类问题的一个很好的解决方案来自附加属性的使用。 Marlon Grech通过创建Attached Command Behaviors将附加属性的使用提升到了新的水平。使用这些可以将ViewModel中存在的任何Command绑定到视图中存在的任何Event。

这是我用来处理ListBoxes的类似问题,我希望它们打开,或者在双击时编辑或执行某些操作。

在这个例子中,我使用的是旧版本的附加命令行为,但效果是一样的。我有一个用于ListBoxItems的样式,我明确指出它。 但是,创建一个应用于所有ListBoxItems的应用程序或窗口宽样式是很容易的,它们将命令设置在更高的级别。然后,只要附加到CommandBehavior.Event属性的ListBoxItem的事件将被触发,它就会触发附加的Command。

<!-- 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>

答案 4 :(得分:1)

好吧,没有人回答。 所以我已经放弃并将Dictionary之外的View的实现移动到常规的UserControl中,我已经向他注入了对ViewModel的引用。

现在,当ListBox触发事件时,它会调用ViewModel,并从那里再一切都可以。

爱丽儿

答案 5 :(得分:1)

我一直在编写行为(附加属性)来执行此操作,但仍然存在需要它们的情况。

对于通常的情况,只需将事件绑定到命令,如果安装了Blend SDK 4,就可以在Xaml中执行所有操作。请注意,您必须添加对System.Windows.Interactivity.dll的引用,并重新分发此程序集。

此示例在触发Grid的DragEnter事件时在ViewModel上调用ICommand DragEnterCommand:

<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>

答案 6 :(得分:0)

尝试使用Prism 2

它具有对命令的强大扩展,并打开许多新的posibilites(如命令绑定到可视树)。