Reputation: 676
I have a treeview where I have added an eventsetter to the item container style to catch whenever a F1 is pressed while hovering a mouse over. So in the code behind I tried to find the child object that the mouse is over. The child object is only found in the tree after a node have been expanded and tried once before, the key down is catched correctly every time. So it is only the second time the IsMouseOver
child object is found.
I have disabled virtualization for the target tree, but it doesn't make any difference.
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="PreviewKeyDown" Handler="EventSetter_OnHandler"></EventSetter>
<Setter Property="IsSelected">
<Setter.Value>
<MultiBinding Mode="OneWay" Converter="{StaticResource ActiveReportTypeMatchToBoolConverter}">
<Binding Path="DataContext.ActiveReportType" ElementName="TreeViewExpander" />
<Binding />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="UIElement.Uid" Value="{Binding Name}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
The Code behind event handler
private void EventSetter_OnHandler(object sender, KeyEventArgs e) {
if (e.Key == Key.F1) {
foreach (var item in TreeViewReportType.Items) {
TreeViewItem anItem = TreeViewReportType.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (anItem?.IsMouseOver == true) {
foreach (ReportType childItem in anItem.Items) {
TreeViewItem childTreeViewItem = anItem.ItemContainerGenerator.ContainerFromItem(childItem) as TreeViewItem;
if (childTreeViewItem?.IsMouseOver == true) {
ApplicationCommands.Help.Execute(childItem.HelpId, childTreeViewItem);
}
}
return;
}
}
}
}
Do any of you know of a magic trick here ?
I have tried to do a TreeViewReportType.UpdateLayout()
and also anItem.UpdateLayout()
to see if it made any changes. But it didn't help.
Tried to look on previous answers but it is datagrid related and is to disable virtualization, which doesn't work here ?
Upvotes: 0
Views: 381
Reputation: 37066
Controls, such as TreeViewItems, have to have focus in order to receive keyboard events.
You've got a few options here. You can put the event on the TreeView itself, but that only works if the treeview has keyboard focus. As long as it's the only control in the window, you're fine. If it isn't, you're in trouble and you need to handle the key event at the window level. Another option would be to write a trigger in your ItemContainerStyle which gives the tree view items keyboard focus when IsMouseOver. But that's silly.
Let's do it at the window level, because we can generalize it: Wherever you press F1 in this window, we'll search the control under the mouse and its parents for one that has a DataContext with a HelpId
property. If we find one, we'll use it. If ReportType
is the only vm class that has a HelpId, it'll work fine. If you add HelpId to another class, that'll just work. If you decide to list ReportTypes in a ListView too, that'll just work.
This replaces all of the code in your question.
MainWindow.xaml
...
PreviewKeyDown="Window_PreviewKeyDown"
...
MainWindow.xaml.cs
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F1)
{
var window = sender as Window;
var point = Mouse.GetPosition(window);
Helpers.ExecuteHelpUnderPoint(window, point);
}
}
Helpers.cs
public static class Helpers
{
public static void ExecuteHelpUnderPoint(FrameworkElement parent, Point point)
{
var hittestctl = parent.InputHitTest(point) as FrameworkElement;
var helpID = GetNearestDataContextHelpID(hittestctl);
if (helpID != null)
{
ApplicationCommands.Help.Execute(helpID, hittestctl);
}
}
public static IEnumerable<T> GetAncestorsOfType<T>(DependencyObject dobj) where T : DependencyObject
{
dobj = VisualTreeHelper.GetParent(dobj);
while (dobj != null)
{
if (dobj is T t)
yield return t;
dobj = VisualTreeHelper.GetParent(dobj);
}
}
public static Object GetNearestDataContextHelpID(DependencyObject dobj)
{
var dataContexts = GetAncestorsOfType<FrameworkElement>(dobj)
.Select(fe => fe.DataContext).Where(dc => dc != null);
// LINQ distinct probably doesn't affect order, but that's not guaranteed.
// https://stackoverflow.com/a/4734876/424129
object prev = null;
foreach (var dc in dataContexts)
{
if (dc != prev)
{
var prop = dc.GetType().GetProperty("HelpId");
if (prop != null)
{
var value = prop.GetValue(dc);
if (value != null)
{
return value;
}
}
prev = dc;
}
}
return null;
}
}
Upvotes: 1