【问题标题】:How do i stop drag select on wpf data grid如何在 wpf 数据网格上停止拖动选择
【发布时间】:2019-04-05 06:50:46
【问题描述】:

嗨,这一切听起来可能微不足道,但我想停止在 WPF DataGrid 上拖动选择

我有一个简单的网格,比如

<DataGrid ItemsSource="{Binding Items}" SelectionMode="Extended">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding .}"/>
            </DataGrid.Columns>
</DataGrid>

如何在单击拖动时停止多项选择,但通过 Shift 和 Ctrl 进行多项选择。

谢谢

【问题讨论】:

    标签: c# wpf xaml datagrid drag


    【解决方案1】:

    Mikolaytis's answer 不完整。使用该解决方案,只要按下鼠标按钮,单击未选择的行就会选择它与其上方的第一个选定行之间的所有行。这是 DataGrid._isDraggingSelection 仍然为 true 的副作用,它在其他鼠标事件驱动的操作中进行评估。

    Il Vic's answer,虽然我没有尝试过,但它比必要的复杂得多。

    相反,在 DataGrid.OnMouseMove() 的覆盖中(如 Mikolaytis 的回答中所做的那样),通过反射调用私有方法 DataGrid.EndDragging():

    public class MyDataGrid : DataGrid
    {
        private static readonly FieldInfo s_isDraggingSelectionField = 
            typeof(DataGrid).GetField("_isDraggingSelection", BindingFlags.Instance | BindingFlags.NonPublic);
    
        private static readonly MethodInfo s_endDraggingMethod =
            typeof(DataGrid).GetMethod("EndDragging", BindingFlags.Instance | BindingFlags.NonPublic);
    
        // DataGrid.OnMouseMove() serves no other purpose than to execute click-drag-selection.
        // Bypass that, and stop 'is dragging selection' mode for DataGrid
        protected override void OnMouseMove(MouseEventArgs e)
        {
            if ((bool)(s_isDraggingSelectionField?.GetValue(this) ?? false))
                s_endDraggingMethod.Invoke(this, new object[0]);
        }
    }
    

    简单地说,拖选开始后,下一个鼠标移动事件结束。

    【讨论】:

      【解决方案2】:

      试试这个技巧:创建一个继承 DataGrid 的类并覆盖 OnMouseMove 而不调用 base.OnMouseMove

      【讨论】:

      • 它会杀死拖放行为
      • 所以添加 if 并检查它是否是拖放
      • 对不起,这是我在这种情况下看到的唯一简单方法。
      • 是的,这就是我过去 2 天的感受,MS MS!
      • 在 WPF 中实际上有很多这样的情况。您需要做一些基本的事情,但您需要逐步完成许多技巧或忘记该功能。
      【解决方案3】:

      DataGrid 控件不是为自定义选择手势而设计的,无论如何可以执行一些技巧来达到您的目标。 首先我们需要一个辅助类:

      public static class ReflectionHelper
      {
          public static T GetPropertyValue<T>(object owner, string propertyName) where T : class
          {
              Type ownerType = owner.GetType();
              PropertyInfo propertyInfo = ownerType.GetProperty(propertyName,
                  BindingFlags.Instance | BindingFlags.NonPublic);
      
              while (propertyInfo == null)
              {
                  ownerType = ownerType.BaseType;
                  propertyInfo = ownerType.GetProperty(propertyName,
                      BindingFlags.Instance | BindingFlags.NonPublic);
              }
      
              return propertyInfo.GetValue(owner, null) as T;
          }
      
          public static void Execute(object owner, string methodName, params object[] parameters)
          {
              Type ownerType = owner.GetType();
              MethodInfo methodInfo = ownerType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
      
              while (methodInfo == null)
              {
                  ownerType = ownerType.BaseType;
                  methodInfo = ownerType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
              }
              methodInfo.Invoke(owner, parameters);
          }
      }
      

      我不喜欢,但需要反思。首先,我们需要创建自己的DataGridRowHeader 以更改其OnClick 代码:

      public class DataGridRowHeader : System.Windows.Controls.Primitives.DataGridRowHeader
      {
          protected override void OnClick()
          {
              if (Mouse.Captured == this)
              {
                  base.ReleaseMouseCapture();
              }
              DataGrid dataGridOwner = ReflectionHelper.GetPropertyValue<DataGrid>(this, "DataGridOwner");
              DataGridRow parentRow = ReflectionHelper.GetPropertyValue<DataGridRow>(this, "ParentRow");
      
              if (dataGridOwner != null && parentRow != null)
              {
                  ReflectionHelper.Execute(dataGridOwner, "HandleSelectionForRowHeaderAndDetailsInput", parentRow, false);
              }
          }        
      }
      

      我们要做的就是将 false(而不是 true)作为方法 HandleSelectionForRowHeaderAndDetailsInput 的第二个参数传递。

      我们需要通过创建自己的 DataGridCell 来处理 DataGrid 的单元格:

      public class DataGridCell : System.Windows.Controls.DataGridCell
      {
          static DataGridCell()
          {
              EventManager.RegisterClassHandler(typeof(DataGridCell), 
                  UIElement.MouseLeftButtonDownEvent, 
                  new MouseButtonEventHandler(DataGridCell.OnAnyMouseLeftButtonDownThunk), true);
          }
      
          private static void OnAnyMouseLeftButtonDownThunk(object sender, MouseButtonEventArgs e)
          {
              ((DataGridCell)sender).OnAnyMouseLeftButtonDown(e);
          }
      
          private void OnAnyMouseLeftButtonDown(MouseButtonEventArgs e)
          {
              bool isKeyboardFocusWithin = base.IsKeyboardFocusWithin;
              bool flag = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
              DataGrid dataGridOwner = ReflectionHelper.GetPropertyValue<DataGrid>(this, "DataGridOwner");
              if (isKeyboardFocusWithin && !flag && !e.Handled && this.IsSelected)
              {
                  if (dataGridOwner != null)
                  {
                      ReflectionHelper.Execute(dataGridOwner, "HandleSelectionForCellInput",
                          this, false, true, false);
      
                      if (!this.IsEditing && !this.IsReadOnly)
                      {
                          dataGridOwner.BeginEdit(e);
                          e.Handled = true;
                          return;
                      }
                  }
              }
              else if (!isKeyboardFocusWithin || !this.IsSelected || flag)
              {
                  if (!isKeyboardFocusWithin)
                  {
                      base.Focus();
                  }
                  if (dataGridOwner != null)
                  {
                      ReflectionHelper.Execute(dataGridOwner, "HandleSelectionForCellInput",
                          this, Mouse.Captured == null && flag, true, false);
                  }
                  e.Handled = true;
              }
          }
      }
      

      还需要一个简单的DataGridCellsPresenter:

      public class DataGridCellsPresenter : System.Windows.Controls.Primitives.DataGridCellsPresenter
      {
          protected override DependencyObject GetContainerForItemOverride()
          {
              return new DataGridCell(); /* the one in our namespace */
          }
      }
      

      它会告诉DataGrid 使用我们的DataGridCell。 现在,当然,我们必须创建一个默认样式(它应该放在 Window 资源中)以强制 DataGrid 使用我们的东西:

      <Style x:Key="{x:Type DataGridRow}" TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource {x:Type DataGridRow}}">
          <Setter Property="Control.Template">
              <Setter.Value>
                  <ControlTemplate TargetType="{x:Type DataGridRow}">
                      <Border Name="DGR_Border" Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="{TemplateBinding Control.BorderThickness}" SnapsToDevicePixels="True">
                          <SelectiveScrollingGrid>
                              <SelectiveScrollingGrid.ColumnDefinitions>
                                  <ColumnDefinition Width="Auto" />
                                  <ColumnDefinition Width="*" />
                              </SelectiveScrollingGrid.ColumnDefinitions>
                              <SelectiveScrollingGrid.RowDefinitions>
                                  <RowDefinition Height="*" />
                                  <RowDefinition Height="Auto" />
                              </SelectiveScrollingGrid.RowDefinitions>
                              <DataGridCellsPresenter Grid.Column="1" ItemsPanel="{TemplateBinding DataGridRow.ItemsPanel}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                              <local:DataGridDetailsPresenter Grid.Column="1" Grid.Row="1" Visibility="{TemplateBinding DataGridRow.DetailsVisibility}" SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=AreRowDetailsFrozen, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}}" />
                              <local:DataGridRowHeader SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical" Grid.RowSpan="2" Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.Row}}" />
                          </SelectiveScrollingGrid>
                      </Border>
                  </ControlTemplate>
              </Setter.Value>
          </Setter>
      </Style>
      

      希望对你有帮助。

      【讨论】:

      • 感谢您的回复,但不知怎的,它没有任何效果,我需要查看解决方案,有问题的将再次回复谢谢。
      • OnClick 永远不会被击中:-s
      • 这很奇怪:我用 VS2010 (.NET 4.0) 测试了我的解决方案,它可以工作。您是否将样式放入窗口的资源中?
      • 将它放在本地控制资源中,它的应用就像我将背景颜色放在应用颜色的边框上一样,但我不确定是否应用了这个标题
      • 啊,它似乎只适用于行标题并且它们被隐藏了,我可以以某种方式将它扩展到整行吗?
      猜你喜欢
      • 1970-01-01
      • 2011-01-15
      • 1970-01-01
      • 2017-07-14
      • 2021-05-12
      • 1970-01-01
      • 2011-11-20
      • 1970-01-01
      • 2011-02-12
      相关资源
      最近更新 更多