如何在 wpf 数据网格上停止拖动选择

本文关键字:拖动 选择 网格 数据网 wpf 数据 | 更新日期: 2023-09-27 17:55:08

嗨,所有这些听起来可能微不足道,但我想停止拖动选择WPF DataGrid

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

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

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

谢谢

如何在 wpf 数据网格上停止拖动选择

Mikolaytis的答案是不完整的。 使用该解决方案,单击未选择的行将选择该行与其上方的第一个选定行之间的所有行,只要按下鼠标按钮即可。 这是DataGrid._isDraggingSelection仍然正确的副作用,在其他鼠标事件驱动的操作中对此进行了评估。

Il Vic的回答,虽然我没有尝试过,但比必要的要复杂得多。

相反,在 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]);
    }
}

简而言之,在开始拖动选择后,下一个鼠标移动事件将结束它。

试试这个技巧:创建一个继承DataGrid的类,并在不调用base.OnMouseMove的情况下覆盖OnMouseMove

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>

我希望它能有所帮助。

这对

我有用:

public partial class DataGridView : DataGrid
{
    // ...
    
    protected override void OnPreviewMouseMove(MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed && SelectionMode == DataGridSelectionMode.Extended)
        {
            // Do some stuff which you need for dragging
        }
        base.OnPreviewMouseMove(e);
    }
    protected override void OnMouseMove(MouseEventArgs e)
    {
        //base.OnMouseMove(e);
    }
    
    // ...
}