21
votes

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.

4

4 Answers

31
votes

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

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.

9
votes

Way simpler than any of the proposed solutions here.

I'm using this one.

<!-- 
requires IsSynchronizedWithCurrentItem
for more info on virtualization/perf https://stackguides.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

0
votes

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