Reputation: 4116
Hi all this might sound trivial but I want to stop drag select on WPF
DataGrid
I have a simple grid like
<DataGrid ItemsSource="{Binding Items}" SelectionMode="Extended">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding .}"/>
</DataGrid.Columns>
</DataGrid>
how can I stop multiple selection on click drag but and have multiple selection by Shift and Ctrl.
thanks
Upvotes: 2
Views: 2611
Reputation: 61
That works for me:
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);
}
// ...
}
Upvotes: 0
Reputation: 96
Mikolaytis's answer is incomplete. With that solution, clicking an unselected row selects all rows between it and the first selected row above it, for as long as the mouse button is down. This is a side effect of DataGrid._isDraggingSelection still being true, which is evaluated in other mouse-event-driven operations.
Il Vic's answer, while I did not try it, is far more complex than necessary.
Instead, in the override for DataGrid.OnMouseMove() (as was done in Mikolaytis's answer), call private method DataGrid.EndDragging(), via reflection:
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]);
}
}
Put simply, after drag-selection is started, the next mouse move event ends it.
Upvotes: 4
Reputation: 5666
The DataGrid
control is not designed for customizing the selection gestures, anyway some hacks can be performed to reach your target.
First of all we need an helper class:
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);
}
}
I do not like it, but reflection is needed. First of all we need to create our own DataGridRowHeader
in order to change its OnClick
code:
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);
}
}
}
All we want to do is to pass false (instead of true) as the second parameter of the method HandleSelectionForRowHeaderAndDetailsInput
.
We need to handle the DataGrid's cells by creating our own DataGridCell:
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;
}
}
}
A simple DataGridCellsPresenter
is also needed:
public class DataGridCellsPresenter : System.Windows.Controls.Primitives.DataGridCellsPresenter
{
protected override DependencyObject GetContainerForItemOverride()
{
return new DataGridCell(); /* the one in our namespace */
}
}
It will say to the DataGrid
to use our DataGridCell
.
Now, of course, we have to create a default style (it should be placed in the Window
resources) for forcing the DataGrid
to use our stuff:
<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>
I hope it can help.
Upvotes: 0
Reputation: 962
Try that hack: create a class that inherits DataGrid
and override OnMouseMove
without calling base.OnMouseMove
Upvotes: 1