Search code examples
wpfdatagriddouble-click

IsReadOnlyProperty.OverrideMetadata breaks mouse double-click commands in WPF DataGrid subclass


I have a subclass for DataGrid which supplies some additional functionality and modifies the default values for some of its properties. One thing in particular is I wanted the grid to be read-only by default, so I set IsReadOnly = true in its constructor.

Recently, I ran into an issue whereby using the grid in a template caused it to be read-only regardless how IsReadOnly was set in the template. Looking at the dependency property precedence page, I reached the conclusion that overriding metadata for IsReadOnlyProperty was the correct way to change its default:

IsReadOnlyProperty.OverrideMetadata(typeof(NewDataGrid), new FrameworkPropertyMetadata(true));

While this resolved the issue with control templates, it appears to break mouse double-click command bindings:

<DataGrid.InputBindings>
    <MouseBinding Gesture="LeftDoubleClick" Command="{StaticResource DoubleClickCommand}"/>
</DataGrid.InputBindings>

Is there a better way to change IsReadOnly's default value without either breaking mouse bindings and/or templates?

Clarification about the command:

DoubleClickCommand itself is a RoutedUICommand defined in resources like so:

<RoutedUICommand x:Key="DoubleClickCommand"/>

Out of habit, I typically place this in the parent Window or UserControl's resource dictionary, but I can move it to the grid's resources as well without any change in behavior.

The command is then attached to the grid's command bindings:

<DataGrid.CommandBindings>
    <CommandBinding Command="{StaticResource DoubleClickCommand}" Executed="DoubleClick_Executed" CanExecute="DoubleClick_CanExecute"/>
</DataGrid.CommandBindings>

Solution

  • I doubt that your issue is related to overriding the default value of the DataGrid.IsReadOnly property. There is no logical relation between a read-only DataGrid and mouse input events.

    Instead it looks like the mouse events are handled by the internal elements of the DataGrid like DataGridCell - independent of the control's read-only state. For example double clicking the background of the DataGrid will work as expected, whereas clicking on a cell (or the content of a cell) won't.

    To fix it, I suggest to override the control's Control.OnMouseDoubleClick and/or Control.OnPreviewMouseDoubleClick methods.
    This should be generally your first approach before registering event handlers. First override the virtual members of the customized or extended control. It will give you greater control and your code looks cleaner too.
    This is because the overrides are always invoked even if the override is a virtual event invocator (for example Control.OnPreviewMouseDoubleClick) and the control decides to not raise the public associated event.

    So, instead of registering a mouse gesture, override the inherited event invocators:

    public class NewDataGrid : DataGrid
    {
      static NewDataGrid() 
        => DataGrid.IsReadOnlyProperty.OverrideMetadata(typeof(MyDataGrid), new FrameworkPropertyMetadata(true));
    
      protected override void OnMouseDoubleClick(MouseButtonEventArgs e) 
      {
        base.OnMouseDoubleClick(e);
    
        if (e.LeftButton is MouseButtonState.Pressed)
        {
          // TODO::Handle left double click mouse input
        }
      }
    
      protected override void OnPreviewMouseDoubleClick(MouseButtonEventArgs e)
      {
        base.OnPreviewMouseDoubleClick(e);
    
        if (e.LeftButton is MouseButtonState.Pressed)
        {
          // TODO::Handle left double click preview mouse input
        }
      }
    }
    

    This is not related to the recommended fix, but a general recommendation in regards to your original implementation: the way you set the MouseBinding.Command using the StaticResource markup extension looks odd. The command should be a RoutedCommand that is usually defined as static and referenced in XAML using the x:Static extension:

    public class NewDataGrid : DataGrid
    {
      public static RoutedCommand DoubleClickCommand { get; } = new RoutedUICommand(
        "Raise the tunneling and bubbling MouseDoubleClick event", 
        "DoubleClickCommand", 
        typeof(NewDataGrid));
    
      public NewDataGrid() 
      {
        var doubleClickCommandBinding = new CommandBinding(DoubleClickCommand, ExecuteDoubleClickCommand, CanExecuteDoubleClickCommand);
        this.CommandBindings.Add(doubleClickCommandBinding); 
      }
    
      private void CanExecuteDoubleClickCommand(object sender, CanExecuteRoutedEventArgs e) 
        => e.CanExecute = true;
    
      private void ExecuteDoubleClickCommand(object sender, ExecutedRoutedEventArgs e) 
      {
        var eventArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left);
        OnPreviewMouseDoubleClick(eventArgs);
        OnMouseDoubleClick(eventArgs); 
      }
    }
    
    <NewDataGrid.InputBindings>
        <MouseBinding Gesture="LeftDoubleClick" 
                      Command="{x:Static local:NewDataGrid.DoubleClickCommand}" />
    </NewDataGrid.InputBindings>