Reputation: 2616
I have a DataGrid
, in each DataGridRow
I have row details, which hold a couple of controls.
What I'd like is, if anything is clicked inside the row details to:
- Not select the row, or more accurately,
- Change the existing DataGrid
selection.
I was thinking along the lines of handling PreviewMouseDown and MouseDown events in a behavior, somehow making the DataGrid skip over the selection process, however not sure how to proceed.
Eventually I'm going to have a TabControl in the details with more info as well, so I also don't want a TabItem click to change the DataGrid's existing selection.
Would there be a way to start the tunnelling of PreviewMouseDown at the Grid "DetailsContainer" level and stop the bubbling of MouseDown at the Grid "DetailsContainer" level
<DataGrid Name="dgAudit"
CanUserReorderColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
IsReadOnly="True"
ItemsSource="{Binding GEOM_ASSET_OC_LIST}"
VirtualizingPanel.ScrollUnit="Pixel"
RowDetailsVisibilityMode="Visible"
>
<i:Interaction.Behaviors>
<behaviors:DataGridBehaviors />
</i:Interaction.Behaviors>
<DataGrid.Columns>
<DataGridTextColumn Header="Asset ID" Binding="{Binding ASSET_ID}" Width="200" />
<DataGridTextColumn Header="Asset Type" Binding="{Binding ASSET_TYPE}" Width="200" />
<DataGridTextColumn Header="Last Update By" Binding="{Binding LAST_UPDATE_BY}" Width="150" />
<DataGridTextColumn Header="Last Update Date" Binding="{Binding LAST_UPDATE_DATETIME, StringFormat=\{0:dd.MM.yy HH:mm:ss tt\}}" Width="150" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Grid Name="DetailsContainer">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Name Text="{Binding Notes}" Width="400" HorizontalAlignment="Left" TextWrapping="Wrap"/>
<Button Content="Button" Grid.Column="1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Just a quick mock up of an empty behavior
public class DataGridBehaviors : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.MouseDown += DataGrid_MouseDown;
this.AssociatedObject.PreviewMouseDown += DataGrid_PreviewMouseDown;
}
protected override void OnDetaching()
{
this.AssociatedObject.PreviewMouseDown -= DataGrid_PreviewMouseDown;
this.AssociatedObject.MouseDown -= DataGrid_MouseDown;
base.OnDetaching();
}
private void DataGrid_MouseDown(object sender, MouseButtonEventArgs e)
{
}
private void DataGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
DependencyObject obj = (DependencyObject)e.OriginalSource;
DataGridDetailsPresenter RowsDetails = FindParent<DataGridDetailsPresenter>(obj);
if (RowsDetails != null)
{
//Skip over selection, maybe temporarily removed native selection handler???
}
}
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
}
Upvotes: 3
Views: 1991
Reputation: 2616
I found another possible work-around. By creating a small Canvas
inside the DataGridRowHeader
then in the next child container setting ClipsToBounds
to false
. It is then free of the header but also can be clicked on without affecting the DataGrid
s current selection. It is bound to the IsExpanded
of the row details and looks the part.
One word of warning is that if the row details contain a significant amount of data, with the code as it is below, may be quite inefficient, loading time and scroll delay. Depending on if virtualization
is being used. This data will load at the same time as the row and may be quite inefficient, where as when the controls are in the RowDetailsTemplate
they load on demand. Probably best to strictly control the loading of this data through a User Control
or dynamically.
Also you'll probably need to listen to the DataGrid
LayoutUpdated
event to adjust the width of the detail control.
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<Grid>
<Expander Template="{StaticResource StretchyExpanderTemp}"
OverridesDefaultStyle="True"
Header=""
HorizontalAlignment="Right"
VerticalAlignment="Top"
Expanded="Expander_Expanded" Collapsed="Expander_Collapsed"
IsExpanded="{Binding DataContext.IsExpanded, RelativeSource={RelativeSource AncestorType=DataGridRowHeader}}" />
<Canvas Background="BlueViolet"
Width="5"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Height="5">
<TabControl Name="tcAA"
TabStripPlacement="Left"
Margin="20,18,0,0"
Height="185"
Width="500"
VerticalAlignment="Top"
HorizontalAlignment="Left"
ItemsSource="{Binding DataContext.AAUDIT, RelativeSource={RelativeSource AncestorType=DataGridRowHeader}}"
SelectedIndex="0"
ClipToBounds="False"
Visibility="{Binding DataContext.IsExpanded, RelativeSource={RelativeSource AncestorType=DataGridRowHeader}, Converter={StaticResource bool2VisibilityConverter}}"
>
...
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding DISPLAY_NAME}" />
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
...
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Canvas>
</Grid>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Grid Height="185" >
</Grid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
As it stands here's a quick example of what I've produced:
Upvotes: 1
Reputation: 15197
Unfortunately, with the current WPF DataGrid
's implementation, it is not possible to achieve what you want.
The DataGridDetailsPresenter
registers a class event handler for the MouseLeftButtonDownEvent
routed event using the EventManager
API:
EventManager.RegisterClassHandler(
typeof(DataGridDetailsPresenter),
MouseLeftButtonDownEvent,
new MouseButtonEventHandler(OnAnyMouseLeftButtonDownThunk),
true);
Note that last parameter which is set to true
. It is a flag indicating whether or not the listener wants to hear about events that have already been handled. In this case, even if you set the RoutedEventArgs.Handled
property to true
at any level, the internal DataGridDetailsPresenter
's event handler will be called anyway.
This event handler is responsible for focusing the DataRow
you click the details view within. And you surely know, that in WPF, the class event handlers are called prior to the instance event handlers. So the first thing will happen on a left click on a details row's presenter is the focusing of the containing row (actually, of that row's first cell).
Please also note that this behavior manages the realizing of the virtual items inside the RowPresenter
too, so disabling it might lead to unwanted GUI side-effects.
You could set the IsHitTestVisible
property of your container Grid
to false
, this will disable the automatic row selection behavior. But obviously, you won't be able to handle any clicks inside the row details at all.
Upvotes: 3