I would like to modify the selection behavior of the DataGrid in the following way. Normally when you have multiple rows selected, and then you click one of the items already se
The only thing I could come up with feels like a big hack, so better don't use it as is. But it might be a starting point for finding your own solution.
Basic ideas:
EventManager.RegisterClassHandler
. This needs some refinement or you end up messing with all cells in the whole applicationCustomized data grid code:
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);
}
}
Use with multi cell selection.
<local:MyDataGrid SelectionMode="Extended" SelectionUnit="Cell">
Hope I didn't leave out any important part in my explanation... ask if anything is unclear.
Note: This answer only tries to provide a solution to following issue mentioned in the question; not how to override the grid's selection behavior. I am hoping that once you have a custom DataGridCell
in place, it can be a good starting point for what you are trying to do.
However, I'm having trouble getting the DataGrid to create a MultiDragDataGridCell instead of a normal DataGridCell, since the class that instantiates DataGridCell is internal. Anyone know how I can achieve that..
Solution: In order to ensure that the DataGrid
uses your custom DataGridCell
- you need to re-template your DataGridRow
to use an extended version of DataGridCellsPresenter
which in-turn will provide your custom DataGridCell
.
Please refer following sample code:
Extending DataGrid controls
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
}
Re-template DataGridRow in XAML (a more comprehensive template can be found at this link - I am only using a watered-down version of it for sake of readability).
<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>
And, your extended DataGrid
's visual tree have the custom datagrid-cells:
Also, please note, it is not mandatory to extend DataGrid
, or DataGridRow
to provide a custom DataGridCell
- you can achieve the same result by just extending DataGridCellsPresenter
(and, updating DataGridRow
's control-template to use the extended version)
Actually you had a solution: make a styling for DataGridCell and set an event handler, but I suppose there was a logical error in your event handler: you have set e.Handled
to true
, if DataGridCell was selected, so the inner controls could not be manipulated, because the default behavior for DataGrid is first select/unselect the row/cell(and only then manipulate the inner controls), so in case you have multiple selection the clicked upon row/cell was selected, so actually you only have needed to prevent selection of row/cell clicked upon in case of multiple selection.
I suppose this should work as you have expected:
<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;
}