2017-05-05 76 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