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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/845446/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 20:24:50  来源:igfitidea点击:

WPF MVVM : Commands are easy. How to Connect View and ViewModel with RoutedEvent

wpfdesign-patternseventsmvvmcommand

提问by ArielBH

Suppose I have a view implemented as a DataTempate inside a resource Dictionary. And I have a corresponding ViewModel. Binding Commands are easy. But what if my View contains a control such as a ListBox, and I need to Publish an application wide event (Using Prism's Event Aggreagtor) based on the Item being Changed on the List.

假设我有一个在资源字典中实现为 DataTempate 的视图。我有一个相应的 ViewModel。绑定命令很容易。但是,如果我的 View 包含一个控件(例如 ListBox),并且我需要根据列表上正在更改的项目发布应用程序范围的事件(使用 Prism 的事件聚合器),该怎么办。

if ListBox supports a command I could just bind it to a command in the ViewModel and publish the event. But Listbox doesn't allow such an option. How do I bridge this?

如果 ListBox 支持一个命令,我可以将它绑定到 ViewModel 中的一个命令并发布事件。但是列表框不允许这样的选项。我该如何桥接?

EDIT: Many great answers.

编辑:许多很棒的答案。

Take a look at this link http://blogs.microsoft.co.il/blogs/tomershamam/archive/2009/04/14/wpf-commands-everywhere.aspx

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

Thanks

谢谢

Ariel

爱丽儿

采纳答案by Kent Boogaart

One option is to extend the control in question and add support for the particular command you require. For example, I've modified ListView beforeto support the ItemActivatedevent and related command.

一种选择是扩展相关控件并添加对您需要的特定命令的支持。例如,我之前修改过 ListView以支持ItemActivated事件和相关命令。

回答by Cameron MacFarland

Instead of trying to bind a command to when the item changes, I looked at the problem another way.

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

If you bind the selected item of the ListBox to a property in the ViewModel, then when that property is changed you can publish the event. That way the ViewModel remains the source of the event and it is triggered by the item changing, which is what you want.

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

回答by eesh

Extend the control to support ICommandSource and decide which action should trigger the command.

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

I did this with Combo Box and used OnSelectionChanged as the trigger for the command. First I will show in XAML how I bind the command to the extended Control ComboBox which I called CommandComboBox, then I will show the code for CommandComboBox that adds the support for the ICommandSource to ComboBox.

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

1) Using CommandComboBox in your XAML code:

1) 在 XAML 代码中使用 CommandComboBox:

In your XAML namespace declarations include

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

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

Use the CommandComboBox in place of ComboBox and bind the command to it like so: Note that in this example I have a defined a command called SetLanguageCommand im my ViewModel and I am passing the selected value for this ComboBox as the parameter to the command.

使用 CommandComboBox 代替 ComboBox 并将命令绑​​定到它,如下所示:请注意,在此示例中,我在我的 ViewModel 中定义了一个名为 SetLanguageCommand 的命令,我将此 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) The code for CommandComboBox

2) CommandComboBox 的代码

The code for the file CommandComboBox.cs is included below. I added this file to a Class Library called WpfCommandControlsLibrary and made it a separate project so I could easily add any extend commands to whatever solution needed to use them and so I could easily add additional WPF Controls and extend them to support the ICommandSource inteface.

下面包含文件 CommandComboBox.cs 的代码。我将此文件添加到名为 WpfCommandControlsLibrary 的类库中,并将其作为一个单独的项目,以便我可以轻松地将任何扩展命令添加到使用它们所需的任何解决方案中,因此我可以轻松添加其他 WPF 控件并扩展它们以支持 ICommandSource 接口。

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;

  }
}

回答by rmoore

A great solution to this type of problem comes from the usage of Attached Properties. Marlon Grech has taken the usage of Attached Properties to the next level by creating Attached Command Behaviors. Using these it is possible to bind any Command existing in a ViewModel to any Event existing in the view.

此类问题的一个很好的解决方案是使用附加属性。Marlon Grech 通过创建附加命令行为将附加属性的使用提升到一个新的水平。使用这些可以将 ViewModel 中存在的任何命令绑定到视图中存在的任何事件。

This is something I use a lot to deal with similar issues with ListBoxes, where I want them to open, or edit or do some action on a double click.

这是我在处理 ListBox 的类似问题时经常使用的东西,我希望它们在其中打开,或者在双击时进行编辑或执行一些操作。

In this example I'm using an older version of Attached Command Behaviors, but the effect is the same. I have a style that is used for ListBoxItems which I am explicitly keying to. However, it would be easy enough to create a application or window wide style applying to all ListBoxItems that sets the commands at a much higher level. Then, whenever the event for the ListBoxItem attached to the CommandBehavior.Event property would fire, it instead fires off the attached Command.

在这个例子中,我使用的是旧版本的附加命令行为,但效果是一样的。我有一个用于 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>

回答by ArielBH

Well, nobody answered. So I've gave up and moved the implementation of the View outside the Dictionary into a regular UserControl, I've injected him a reference to the ViewModel.

嗯,没有人回答。所以我放弃了,将字典外的 View 实现移到了一个普通的 UserControl 中,我给他注入了一个对 ViewModel 的引用。

Now when the ListBox fire the Event it's calls the ViewModel and from there everything is possible again.

现在,当 ListBox 触发 Event 时,它会调用 ViewModel,从那里一切皆有可能。

Ariel

爱丽儿

回答by Mike Fuchs

I have been writing behaviors (attached properties) to do this, and there are still cases where I need them.

我一直在编写行为(附加属性)来做到这一点,但仍然存在我需要它们的情况。

For the usual case however, simply binding an event to a command, you can do everything in Xaml if you have Blend SDK 4 installed. Note that you will have to add a reference to System.Windows.Interactivity.dll, and to redistribute this assembly.

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

This example is invoking an ICommand DragEnterCommand on the ViewModel when the DragEnter event of the Grid is fired:

此示例在触发 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>

回答by Jarek Kardas

Try using Prism 2.

尝试使用棱镜 2

It comes with great extensions to commanding and opens many new posibilites (like commands to being tied to visual tree).

它对命令进行了很好的扩展,并打开了许多新的可能性(例如与可视化树相关联的命令)。