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

时间:2016-07-05 13:07:19

标签: c# wpf xaml datagrid drag

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

我有一个像

这样的简单网格
<DataGrid ItemsSource="{Binding Items}" SelectionMode="Extended">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding .}"/>
            </DataGrid.Columns>
</DataGrid>

如何在单击拖动时停止多个选择,但按Shift和Ctrl进行多次选择。

感谢

3 个答案:

答案 0 :(得分:1)

尝试破解:创建一个继承DataGrid的类并覆盖OnMouseMove而不调用base.OnMouseMove

答案 1 :(得分:0)

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>

我希望它可以提供帮助。

答案 2 :(得分:0)

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

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