As part of a project, I had to have TextBox
es inside ListView
cells to allow text selection inside each cell. However, the TextBox
ate up the MouseDown
event so the ListView
won't select a row when you click on a cell. What made it more complicated was that I also needed multiple selection along with the built-in hotkeys (Ctrl+click, Shift+click, Ctrl+arrow key, etc). To fix this, I subscribed to the PreviewMouseDown
event on each TextBox
and raised the event again on the ListViewItem
(its parent).
Here is what the XAML sort of looked like in my project:
<ListView x:Name ="myListView"
SelectionMode="Multiple"
Grid.IsSharedSizeScope="True">
<ListView.Resources>
<Style x:Key="ReadOnlyTextBoxStyle" TargetType="TextBox">
<EventSetter Event="MouseDown" Handler="TextBox_MouseDown"
HandledEventsToo="True"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="IsReadOnly" Value="True"/>
</Style>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="group1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Style="{StaticResource ReadOnlyTextBoxStyle}"
Text="{Binding FieldLabel}"/>
<Button Text="{Binding FieldValue}"
Command="{Binding SeeDetailsCmd}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
HandleEventsToo vs. PreviewEvent
In the EventSetter set HandleEventsToo to true to handle already "handled" events. This is better than using Preview events to catch handled events in this case since this will preserve the intended order of events handling; selection event handling is activated on the
ListView
after MouseDown
on
TextBox
as opposed to the other way around if
PreviewMouseDown
was used on the
TextBox
instead.
What If Multiple Controls Needs to Re-Raise Events in parent controls?
In the case described above, I only have one
TextBox
that needs to raise an event on the parent
ListView
. However, if I had more than one
TextBox
or other controls that needs to raise the same
Event
(e.g.
MouseDown
), then I should put the
EventSetter
on the parent/container of the those controls (e.g.
ListViewItem
) instead and raise the event there. In this way, the parent/container control will catch all the events propagated from child controls and re-raise them to the desired control.
Here is the handler:
private void TextBox_MouseDown(object sender, MouseButtonEventArgs e)
{
ListViewItem lvi = null;
TextBox tb = sender as TextBox;
DependencyObject temp = tb;
int maxlevel = 0;
while (!(temp is ListViewItem) && maxlevel < 1000)
{
temp = VisualTreeHelper.GetParent(temp);
maxlevel++;
}
lvi = temp as ListViewItem;
if (lvi != null){
{
MouseButtonEventArgs newarg = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp,
e.ChangedButton, e.StylusDevice);
newarg.RoutedEvent = ListViewItem.MouseDownEvent;
newarg.Source = sender;
lvi.RaiseEvent(newarg);
}
}
As GATzilla have pointed out in a comment below(under the first alternate), I could have used a WPF
DataGrid
to achieve the same effect. He/She made some very good points about this tip. However, this was just meant to be an example to illustrate activating parent built-in event handlers for "handled" events. The technique illustrated here is meant to be a "last resort" type of thing to use.
The code above feels more like a hack to me so I'm wondering if anyone can provide a cleaner solution. Thanks in advance. Hope this helps someone.