16

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

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

编辑:很多很好的答案。

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

谢谢

爱丽儿

4

7 回答 7

43

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

如果将 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
        }
}
于 2009-05-17T12:49:43.037 回答
17

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

我使用组合框执行此操作,并使用 OnSelectionChanged 作为命令的触发器。首先,我将在 XAML 中展示如何将命令绑定到我称为 CommandComboBox 的扩展 Control ComboBox,然后我将展示 CommandComboBox 的代码,它将对 ICommandSource 的支持添加到 ComboBox。

1) 在 XAML 代码中使用 CommandComboBox:

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

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

使用 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) CommandComboBox 的代码

下面包含文件 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;

  }
}
于 2009-05-12T15:01:28.687 回答
7

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

于 2009-05-11T13:11:04.063 回答
2

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

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

爱丽儿

于 2009-05-10T15:47:32.397 回答
2

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

这是我在处理 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>
于 2009-05-12T00:00:26.740 回答
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>
于 2013-03-25T15:16:38.180 回答
0

尝试使用Prism 2

它对命令进行了很好的扩展,并打开了许多新的可能性(比如绑定到可视树的命令)。

于 2009-05-10T16:21:44.917 回答