将DataGrid行的DoubleClick命令绑定到VM

21

我有一个数据网格,并不喜欢我的解决方法来在我的视图模型上触发双击命令,以处理单击(也就是选定)的行。

视图:


   <DataGrid  EnableRowVirtualization="True"
              ItemsSource="{Binding SearchItems}"
              SelectedItem="{Binding SelectedItem}"
              SelectionMode="Single"
              SelectionUnit="FullRow">

        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseDoubleClick">
                <cmd:EventToCommand Command="{Binding MouseDoubleClickCommand}" PassEventArgsToCommand="True" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
        ...
  </DataGrid>

视图模型:

    public ICommand MouseDoubleClickCommand
    {
        get
        {
            if (mouseDoubleClickCommand == null)
            {
                mouseDoubleClickCommand = new RelayCommand<MouseButtonEventArgs>(
                    args =>
                    {
                        var sender = args.OriginalSource as DependencyObject;
                        if (sender == null)
                        {
                            return;
                        }
                        var ancestor = VisualTreeHelpers.FindAncestor<DataGridRow>(sender);
                        if (ancestor != null)
                        {
                            MessengerInstance.Send(new FindDetailsMessage(this, SelectedItem.Name, false));
                        }
                    }
                    );
            }
            return mouseDoubleClickCommand;
        }
    }

我希望在我的视图模型中摆脱与视图相关的代码(具有依赖对象和可视化树助手的代码),因为这会以某种方式破坏可测试性。但另一方面,这样做可以避免用户不仅单击行而且也点击标题时发生什么事情。

PS:我尝试查看了附加行为,但我无法在工作中从Skydrive下载,因此最好使用一个“内置”的解决方案。

4个回答

31

为什么不直接使用CommandParameter呢?

<DataGrid x:Name="myGrd"
          ItemsSource="{Binding SearchItems}"
          SelectedItem="{Binding SelectedItem}"
          SelectionMode="Single"
          SelectionUnit="FullRow">

    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
            <cmd:EventToCommand Command="{Binding MouseDoubleClickCommand}"  
                                CommandParameter="{Binding ElementName=myGrd, Path=SelectedItem}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    ...
</DataGrid>

你的命令大致如下:

public ICommand MouseDoubleClickCommand
{
    get
    {
        if (mouseDoubleClickCommand == null)
        {
            mouseDoubleClickCommand = new RelayCommand<SearchItem>(
                item =>
                {
                    var selectedItem = item;
                });
        }

        return mouseDoubleClickCommand;
    }
}

编辑:我现在使用这个,而不是Interaction.Triggers

<DataGrid.InputBindings>
    <MouseBinding MouseAction="LeftDoubleClick"
                  Command="{Binding Path=MouseDoubleClickCommand}"
                  CommandParameter="{Binding ElementName=myGrd, Path=SelectedItem}" />
</DataGrid.InputBindings>

1
问题不在于获取所选项目(它已经在VM上绑定了数据),而在于当例如DataGrid的标题被双击时,如何防止命令执行。 - metacircle
2
如果你想防止鼠标双击,可以尝试使用PreviewMouseDoubleClick,并为你的条件设置e.Handled=true。这样,你就可以将代码从ViewModel中移除,并将其放入DataGrid的CodeBehind中。 - blindmeis
好主意。实际上,我一直在我的代码后台为OnContextMenuOpening做完全相同的事情。有时候你就是没有恰当的想法。谢谢。我会将这个标记为答案。 - metacircle
8
你省略 myGrd 中的字母 "i" 后,你节省了多少时间和精力? :-) - ProfK
InputBindings 对我没有起作用,因为它只在双击 DataGrid 标头而不是行时才能工作。 您还可以使用 InvokeCommandAction 代替 MVVM Light 的 EventToCommand。这种行为是 System.Windows.Interactivity DLL 的一部分。它几乎等同于 EventToCommand,但缺少一些高级功能。 - Txaku
这个处理程序处理整个DataGrid的双击事件,而不是单独的DataGridRow。这意味着,如果您双击ColumnHeader,命令仍将被调用。 - rejy11

13

以下是使用附加行为实现的方法:

编辑:现在在DataGridRow上注册行为,而不是DataGrid,以忽略DataGridHeader的点击。

行为:

public class Behaviours
{
    public static DependencyProperty DoubleClickCommandProperty =
       DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(Behaviours),
                                           new PropertyMetadata(DoubleClick_PropertyChanged));

    public static void SetDoubleClickCommand(UIElement element, ICommand value)
    {
        element.SetValue(DoubleClickCommandProperty, value);
    }

    public static ICommand GetDoubleClickCommand(UIElement element)
    {
        return (ICommand)element.GetValue(DoubleClickCommandProperty);
    }

    private static void DoubleClick_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var row = d as DataGridRow;
        if (row == null) return;

        if (e.NewValue != null)
        {
            row.AddHandler(DataGridRow.MouseDoubleClickEvent, new RoutedEventHandler(DataGrid_MouseDoubleClick));
        }
        else
        {
            row.RemoveHandler(DataGridRow.MouseDoubleClickEvent, new RoutedEventHandler(DataGrid_MouseDoubleClick));
        }
    }

    private static void DataGrid_MouseDoubleClick(object sender, RoutedEventArgs e)
    {
        var row= sender as DataGridRow;

        if (row!= null)
        {
            var cmd = GetDoubleClickCommand(row);
            if (cmd.CanExecute(row.Item))
                cmd.Execute(row.Item);
        }
    }
}

Xaml:

    <DataGrid x:Name="grid" EnableRowVirtualization="True"
          SelectedItem="{Binding SelectedItem}"
          SelectionMode="Single"
          SelectionUnit="FullRow" ItemsSource="{Binding SearchItems}">
       <DataGrid.RowStyle>
           <Style TargetType="DataGridRow">
                <Setter Property="Behaviours.DoubleClickCommand" Value="{Binding ElementName=grid, Path=DataContext.SortStateCommand}"/>
           </Style>
       </DataGrid.RowStyle>

然后您需要修改 MouseDoubleClickCommand 以删除 MouseButtonEventArgs 参数,并将其替换为 SelectedItem 类型。


我认为这是一种几乎完美(也就是最好的)的解决方案。毕竟这就是附加事件的初衷: http://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF - James

9
比这里提出的任何解决方案都要简单得多。我正在使用这个。
<!-- 
requires IsSynchronizedWithCurrentItem
for more info on virtualization/perf https://dev59.com/Q2kw5IYBdhLWcg3wXZX8 
 -->
        <DataGrid ItemsSource="{Binding SearchItems}" 
                  IsSynchronizedWithCurrentItem="True"
                  AutoGenerateColumns="false" CanUserAddRows="False" CanUserDeleteRows="False" IsReadOnly="True" EnableRowVirtualization="True"
                  >

            <!-- for details on ICollection view (the magic behind {Binding Accounts/} https://marlongrech.wordpress.com/2008/11/22/icollectionview-explained/ -->

            <DataGrid.InputBindings>
                <MouseBinding
                    MouseAction="LeftDoubleClick"
                    Command="{Binding MouseDoubleClickCommand}"
                    CommandParameter="{Binding SearchItems/}" />
            </DataGrid.InputBindings>
        </DataGrid>

来自WPF DataGrid:使用CommandBinding实现双击而不是事件


1
感谢您提供的初始链接源 - 它很有用。对于阅读此评论的其他人:特别注意CommandParameter属性值末尾的正斜杠(即“SearchItems /”)。 - Aleksei Mialkin

0
您可以尝试这个解决方法:
<DataGrid  EnableRowVirtualization="True"
          ItemsSource="{Binding SearchItems}"
          SelectedItem="{Binding SelectedItem}"
          SelectionMode="Single"
          SelectionUnit="FullRow">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header=".....">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                        <TextBlock .....>
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="MouseDoubleClick">
                                    <cmd:EventToCommand Command="{Binding MouseDoubleClickCommand}" PassEventArgsToCommand="True" />
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </TextBlock>
                </DataTemplate>
    ...................

在这种情况下,您必须为DataGrid中的每一列指定DataTemplate

我猜这个方法可以行得通,但如果我必须在这个方法和我的解决方案之间做出选择,我可能会坚持使用我的。如果我更改列,我总是不得不为每一列重复你的XAML代码。 - metacircle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接