How can I override DataGrid selection behavior?

前端 未结 3 1791
不思量自难忘°
不思量自难忘° 2021-01-22 06:27

I would like to modify the selection behavior of the DataGrid in the following way. Normally when you have multiple rows selected, and then you click one of the items already se

相关标签:
3条回答
  • 2021-01-22 06:42

    The only thing I could come up with feels like a big hack, so better don't use it as is. But it might be a starting point for finding your own solution.

    Basic ideas:

    • Execute some event handlers even on handled events with EventManager.RegisterClassHandler. This needs some refinement or you end up messing with all cells in the whole application
    • Register for cell selection restore when left mouse click on selected cell without modifiers
    • Consider drag & drop only after left mouse click on selected cell (otherwise the user experience becomes REALLY strange for this combination of requirements)
    • Restore selected cells if previously registered and cells are unselected
    • Remove cell selection restore registration after restoring or when mouse does other things (mouse up or mouse move)

    Customized data grid code:

    public class MyDataGrid : DataGrid
    {
        static MyDataGrid()
        {
            EventManager.RegisterClassHandler(typeof(DataGridCell), UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(PreviewMouseLeftButtonDownHandler));
            EventManager.RegisterClassHandler(typeof(DataGridCell), UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(MouseLeftButtonUpHandler), true);
            EventManager.RegisterClassHandler(typeof(DataGridCell), UIElement.MouseMoveEvent, new MouseEventHandler(MouseMoveHandler), true);
        }
    
        private static bool restoreNextCells = false;
        private static bool isSelectedCell = false;
        private static void PreviewMouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e)
        {
            var cell = sender as DataGridCell;
            isSelectedCell = cell.IsSelected;
            restoreNextCells = cell.IsSelected && Keyboard.Modifiers == ModifierKeys.None;
        }
        private static void MouseMoveHandler(object sender, MouseEventArgs e)
        {
            var cell = sender as DataGridCell;
            if (isSelectedCell && e.LeftButton == MouseButtonState.Pressed && cell.IsSelected && Keyboard.Modifiers == ModifierKeys.None)
            {
                DragDrop.DoDragDrop(cell, new ObjectDataProvider(), DragDropEffects.All);
            }
            restoreNextCells = false;
            isSelectedCell = false;
        }
    
        private static void MouseLeftButtonUpHandler(object sender, MouseButtonEventArgs e)
        {
            restoreNextCells = false;
            isSelectedCell = false;
        }
        protected override void OnSelectedCellsChanged(SelectedCellsChangedEventArgs e)
        {
            if (restoreNextCells && e.RemovedCells.Count > 0)
            {
                foreach (DataGridCellInfo item in e.RemovedCells)
                {
                    SelectedCells.Add(item);
                }
                restoreNextCells = false;
            }
            base.OnSelectedCellsChanged(e);
        }
    }
    

    Use with multi cell selection.

    <local:MyDataGrid SelectionMode="Extended" SelectionUnit="Cell">
    

    Hope I didn't leave out any important part in my explanation... ask if anything is unclear.

    0 讨论(0)
  • 2021-01-22 06:51

    Note: This answer only tries to provide a solution to following issue mentioned in the question; not how to override the grid's selection behavior. I am hoping that once you have a custom DataGridCell in place, it can be a good starting point for what you are trying to do.

    However, I'm having trouble getting the DataGrid to create a MultiDragDataGridCell instead of a normal DataGridCell, since the class that instantiates DataGridCell is internal. Anyone know how I can achieve that..

    Solution: In order to ensure that the DataGrid uses your custom DataGridCell - you need to re-template your DataGridRow to use an extended version of DataGridCellsPresenter which in-turn will provide your custom DataGridCell.

    Please refer following sample code:

    Extending DataGrid controls

    public class ExtendedDataGrid : DataGrid
    {
        protected override DependencyObject GetContainerForItemOverride()
        {
            //This provides the DataGrid with a customized version for DataGridRow
            return new ExtendedDataGridRow();
        }
    }
    
    public class ExtendedDataGridRow : DataGridRow {  }
    
    public class ExtendedDataGridCellsPresenter : System.Windows.Controls.Primitives.DataGridCellsPresenter
    {
        protected override DependencyObject GetContainerForItemOverride()
        {
            //This provides the DataGrid with a customized version for DataGridCell
            return new ExtendedDataGridCell();
        }
    }
    
    public class ExtendedDataGridCell : DataGridCell
    {
        // Your custom/overridden implementation can be added here
    }
    

    Re-template DataGridRow in XAML (a more comprehensive template can be found at this link - I am only using a watered-down version of it for sake of readability).

     <Style TargetType="{x:Type local:ExtendedDataGridRow}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:ExtendedDataGridRow}">
                        <Border x:Name="DGR_Border"
                              BorderBrush="{TemplateBinding BorderBrush}"
                              BorderThickness="{TemplateBinding BorderThickness}"
                              SnapsToDevicePixels="True">
                            <SelectiveScrollingGrid>
                                <SelectiveScrollingGrid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </SelectiveScrollingGrid.ColumnDefinitions>
                                <SelectiveScrollingGrid.RowDefinitions>
                                    <RowDefinition Height="*" />
                                    <RowDefinition Height="Auto" />
                                </SelectiveScrollingGrid.RowDefinitions>
    
                  <!-- Make sure to register your custom DataGridCellsPresenter here as following --> 
    
                                <local:ExtendedDataGridCellsPresenter Grid.Column="1"
                                      ItemsPanel="{TemplateBinding ItemsPanel}"
                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                <DataGridDetailsPresenter Grid.Column="1"
                                        Grid.Row="1"
                                        Visibility="{TemplateBinding DetailsVisibility}"
                                        SelectiveScrollingGrid.SelectiveScrollingOrientation=
                                          "{Binding AreRowDetailsFrozen, 
                                          ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical},
                                          Converter={x:Static DataGrid.RowDetailsScrollingConverter}, 
                                          RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                <DataGridRowHeader Grid.RowSpan="2"
                                     SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
                                     Visibility="{Binding HeadersVisibility, 
                                      ConverterParameter={x:Static DataGridHeadersVisibility.Row}, 
                                      Converter={x:Static DataGrid.HeadersVisibilityConverter}, 
                                      RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />       
                            </SelectiveScrollingGrid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    

    And, your extended DataGrid's visual tree have the custom datagrid-cells:

    Also, please note, it is not mandatory to extend DataGrid, or DataGridRow to provide a custom DataGridCell - you can achieve the same result by just extending DataGridCellsPresenter (and, updating DataGridRow's control-template to use the extended version)

    0 讨论(0)
  • 2021-01-22 06:57

    Actually you had a solution: make a styling for DataGridCell and set an event handler, but I suppose there was a logical error in your event handler: you have set e.Handled to true, if DataGridCell was selected, so the inner controls could not be manipulated, because the default behavior for DataGrid is first select/unselect the row/cell(and only then manipulate the inner controls), so in case you have multiple selection the clicked upon row/cell was selected, so actually you only have needed to prevent selection of row/cell clicked upon in case of multiple selection.

    I suppose this should work as you have expected:

    <DataGrid.Resources>
                <Style TargetType="DataGridCell">
                    <EventSetter Event="PreviewMouseLeftButtonDown" Handler="PreviewMouseDown"/>
                </Style>
            </DataGrid.Resources>
    
    
    private void PreviewMouseDown(object sender, MouseButtonEventArgs e)
            {
                var cell = sender as DataGridCell; if (cell == null) { return; }
                DataGrid parGrid = null;
                var visParent = VisualTreeHelper.GetParent(cell);
                while (parGrid==null && visParent != null)
                {
                    parGrid = visParent as DataGrid;
                    visParent = VisualTreeHelper.GetParent(visParent);
                }
                if (parGrid==null) { return; }
    
                e.Handled = cell.IsSelected && Keyboard.Modifiers == ModifierKeys.None && parGrid.SelectedItems.Count > 1;
            }
    
    0 讨论(0)
提交回复
热议问题