21

I have a Datagrid and don't like my workaround to fire a double click command on my viewmodel for the clicked (aka selected) row.

View:

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

ViewModel:

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

I want to get rid of the view related code (the one with the dependency object and the visual tree helper) in my view model, as this breaks testability somehow. But on the other hand this way I avoid that something happens when the user doesn't click on a row but on the header for example.

PS: I tried having a look at attached behaviors, but I cannot download from Skydrive at work, so a 'built in' solution would be best.

metacircle
  • 2,438
  • 4
  • 25
  • 39

4 Answers4

31

Why don't you simply use the 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>

Your command is something like this:

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

        return mouseDoubleClickCommand;
    }
}

EDIT: I now use this instead of Interaction.Triggers:

<DataGrid.InputBindings>
    <MouseBinding MouseAction="LeftDoubleClick"
                  Command="{Binding Path=MouseDoubleClickCommand}"
                  CommandParameter="{Binding ElementName=myGrd, Path=SelectedItem}" />
</DataGrid.InputBindings>
g t
  • 7,287
  • 7
  • 50
  • 85
blindmeis
  • 22,175
  • 7
  • 55
  • 74
  • 1
    The problem is not getting the selected item (it is databound anyway on the VM), but getting the command not to execute when, for example, the headers of the datagrid are doubleclicked. – metacircle Jul 02 '13 at 09:40
  • 2
    if you want to prevent mousedoubleclick you could try PreviewMouseDoubleClick and set e.Handled=true for your conditions. so you can reomve the code from viewmodel and put it in codebehind for your datagrid – blindmeis Jul 02 '13 at 10:57
  • Great idea. Actually I have been doing the complete same thing in my codebehind for OnContextMenuOpening all along. Sometimes you just don't have the right ideas at the right time in mind. Thanks. I am going to mark this as answer. – metacircle Jul 03 '13 at 05:27
  • 8
    How much time and effort did you save leaving the 'i' out of `myGrd`? :-) – ProfK Nov 29 '16 at 09:20
  • InputBindings didn't work for me as it was only working when double clicking the DataGrid header and not the rows. You can also use an `InvokeCommandAction` instead of MVVM Light’s `EventToCommand`. This behavior is part of the System.Windows.Interactivity DLL. It is almost equivalent to `EventToCommand` but without some of the advanced features. – Txaku Jan 05 '17 at 09:31
  • This handles the double click on the DataGrid as a whole and not an individual DataGridRow, meaning if you were to double click a ColumnHeader for example, the the command would still be invoked. – rejy11 May 23 '18 at 09:40
13

Here is how you could implement it using an attached behaviour:

EDIT: Now registers behaviour on DataGridRow rather than DataGrid so that DataGridHeader clicks are ignored.

Behaviour:

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>

You will then need to modify your MouseDoubleClickCommand to remove the MouseButtonEventArgs parameter and replace it with your SelectedItem type.

Richard E
  • 4,819
  • 1
  • 19
  • 25
  • I like this as a near perfect (read that as best) solution. Afterall this is what the attched events are meant for : http://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF – James Feb 07 '14 at 07:21
9

Way simpler than any of the proposed solutions here.

I'm using this one.

<!-- 
requires IsSynchronizedWithCurrentItem
for more info on virtualization/perf https://stackoverflow.com/questions/9949358/datagrid-row-virtualization-display-issue 
 -->
        <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>

from WPF DataGrid: CommandBinding to a double click instead of using Events

Community
  • 1
  • 1
Maslow
  • 18,464
  • 20
  • 106
  • 193
  • 1
    Thank you for the initial link source — it was useful. To others reading this comment: pay special attention to the forward slash at the end of CommandParameter's attribute value (i.e. "SearchItems/"). – Aleksei Mialkin Mar 09 '16 at 23:12
0

You may try this workaround:

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

In this case you have to specify DataTemplate for each column in the DataGrid

Andrey Gordeev
  • 30,606
  • 13
  • 135
  • 162
  • I guess this would work, but if I had to choose between this and my workaround, I would probably stick with mine. If I change columns I always have to repeat your XAML code for each column. – metacircle Jul 02 '13 at 09:42