2017-05-05 71 views
2

我想通过以下方式修改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添加处理程序。这有效,但它阻止我点击单元格内的任何按钮等。

回答

2

注:这个答案试图提供解决问题中提及以下问题;而不是如何覆盖网格的选择行为。我希望,一旦你在的地方有一个自定义DataGridCell,它可以为你正在尝试做一个很好的起点。

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

解决方案:为了确保DataGrid使用您的自定义DataGridCell - 你需要重新模板您DataGridRow使用的DataGridCellsPresenter的扩展版本,后者又将提供您的自定义DataGridCell

请参考下面的示例代码:

扩展的DataGrid控件在XAML

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 
} 

重新模板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

而且,请注意,这不是强制性的延长DataGrid,或DataGridRow提供自定义DataGridCell - 只需延长DataGridCellsPresenter(并更新DataGridRow的控制模板以使用扩展版本即可达到相同结果)

1

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

基本思路:

  • 执行一些事件处理程序,即使与EventManager.RegisterClassHandler处理的事件。 这需要一些细化或者你最终当上选定单元格点击鼠标左键不修饰
  • 只考虑后鼠标左键点击拖动&下降在整个应用程序
  • 注册小区选择还原所有细胞搞乱选择的小区(否则用户体验变得很奇怪的要求此组合)
  • 恢复所选细胞如果先前注册和细胞未被选择
  • 删除细胞本身经文恢复恢复后登记或当小鼠做其他事情(鼠标向上或鼠标移动)

定制数据网格代码:

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

希望我没有在我的解释中忽略任何重要的部分...问有没有什么不清楚的地方。

+0

我尝试了这一点,它的工作原理,但由于某些原因,它打乱了小区选择的显示。显示器似乎仍然遵循旧的行为,但实际SelectedItems是你所期望的。 – hypehuman

1

其实你有一个解决方案:做出DataGridCell一个造型,并设置一个事件处理程序,但是我想如果在你的事件处理程序的逻辑错误:您已设置e.Handledtrue,如果选择DataGridCell,所以内部控制不能被操纵,因为对于数据网格的默认行为是第一选择/取消选择的行/细胞(并且仅然后操纵所述内对照),所以如果你有多个选择被点击时行/小区选择,所以实际上只有需要到防止行的选择/小区中多个选择的情况下点击时。

我想这应该工作,你有预期:

<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

我觉得这个假设,如果visParent不是细胞,它必然会处理该事件。但我不确定情况总是如此。 – hypehuman

+0

@hypehuman其实visParent从来就不是细胞。你在循环中通过细胞的视觉父母。您可以稍后在行级别启动它,然后因为必须设置行的样式。 – Rekshino

+0

哦,我误解了。我想我现在明白了。但是现在如果选择了一行,我认为它会阻止你操纵行/单元格中的控件。 – hypehuman