如何覆盖DataGrid选择行为?

时间:2017-05-05 19:20:41

标签: c# wpf datagrid

我想以下列方式修改DataGrid的选择行为。通常,如果选择了多个行,然后单击其中一个已选择的项,则选择将重置为仅单击的项。我想更改它,以便如果单击其中一个多选行而没有任何键盘修饰符,则不会修改选择。这样做的目的是允许多项拖放。

我注意到,当激活上述默认行为时,调用堆栈包括:

at System.Windows.Controls.DataGrid.OnSelectionChanged(SelectionChangedEventArgs e)
at System.Windows.Controls.Primitives.Selector.SelectionChanger.End()
at System.Windows.Controls.DataGrid.MakeFullRowSelection(ItemInfo info, Boolean allowsExtendSelect, Boolean allowsMinimalSelect)
at System.Windows.Controls.DataGrid.HandleSelectionForCellInput(DataGridCell cell, Boolean startDragging, Boolean allowsExtendSelect, Boolean allowsMinimalSelect)
at System.Windows.Controls.DataGridCell.OnAnyMouseLeftButtonDown(MouseButtonEventArgs e)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
at System.Windows.UIElement.OnMouseDownThunk(Object sender, MouseButtonEventArgs e)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)

因此看起来我应该可以通过重写DataGridCell.OnMouseLeftButtonDown来修改行为,如下所示:

class MultiDragDataGridCell : DataGridCell
{
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        // This allows users to click-and-drag a multi-selection by handling the event before
        // the default behavior (deselecting everything but the clicked cell) kicks in.
        if (IsSelected && Keyboard.Modifiers == ModifierKeys.None)
        {
            e.Handled = true;
        }

        base.OnMouseLeftButtonDown(e);
    }
}

但是,我无法让DataGrid创建MultiDragDataGridCell而不是普通的DataGridCell,因为实例化DataGridCell的类是内部的。任何人都知道如何实现这一目标,或者是否有其他方法可以实现我想要的行为?

我试过的其他事情:

  • 为DataGridCell设置样式以向MouseLeftButtonDown添加处理程序。这不起作用,因为它在选择已经更改后执行。
  • 为DataGridCell设置样式以向PreviewMouseLeftButtonDown添加处理程序。这有效,但它阻止我点击单元格内的任何按钮等。

3 个答案:

答案 0 :(得分:3)

注意:此答案仅尝试为问题中提到的以下问题提供解决方案;而不是如何覆盖网格的选择行为。我希望一旦你有一个自定义DataGridCell,它就可以成为你想要做的事情的一个很好的起点。

  

但是,我无法让DataGrid创建MultiDragDataGridCell而不是普通的DataGridCell,因为实例化DataGridCell的类是内部的。任何人都知道如何实现这一点..

解决方案:为了确保DataGrid使用您的自定义DataGridCell,您需要重新模板DataGridRow以使用扩展版本DataGridCellsPresenter反过来会提供您的自定义DataGridCell

请参阅以下示例代码:

扩展DataGrid控件

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
}

在XAML中重新模板化DataGridRow (更全面的template can be found at this link - 为了便于阅读,我只使用了它的淡化版本。)

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

而且,您的扩展DataGrid可视树具有自定义数据网格单元格:

enter image description here

此外,请注意,扩展DataGridDataGridRow并非强制要求提供自定义DataGridCell - 只需扩展DataGridCellsPresenter即可获得相同的结果(并且,更新DataGridRow的控制模板以使用扩展版本)

答案 1 :(得分:1)

我唯一能想到的就像是一个大黑客,所以最好不要按原样使用它。但它可能是找到自己的解决方案的起点。

基本想法:

  • 即使对于EventManager.RegisterClassHandler的已处理事件,也会执行一些事件处理程序。 这需要进行一些改进,否则您最终会搞乱整个应用程序中的所有单元格
  • 当鼠标左键单击没有修饰符的选定单元格
  • 时,注册单元格选择恢复
  • 考虑拖拽&amp;只有在鼠标左键单击所选单元格后才会丢弃(否则,对于这种要求组合,用户体验会变得非常奇怪)
  • 如果以前已注册并且单元格未被选中,则恢复所选单元格
  • 恢复后或鼠标执行其他操作(鼠标移动或鼠标移动)时删除单元格选择恢复注册

自定义数据网格代码:

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

用于多小区选择。

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

希望我在解释中没有遗漏任何重要的部分......如果有什么不清楚的话。

答案 2 :(得分:1)

实际上你有一个解决方案:为DataGridCell创建一个样式并设置一个事件处理程序,但我想你的事件处理程序中存在一个逻辑错误:你已经将e.Handled设置为true,如果是DataGridCell已被选中,因此无法操纵内部控件,因为DataGrid的默认行为是首先选择/取消选择行/单元格(然后才操纵内部控件),因此如果您有多个选择,则单击行/单元格已被选中,所以实际上你只需要阻止选择在多次选择的情况下点击的行/单元格

我认为这应该按照您的预期运作:

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