In a WPF app with .NetFramework 4.8 I have an ItemsControl
. Each item has its context menu which I can open via right mouse button. Now I want to support the apps key (right of 'Alt Gr' key). In XAML I added a handler for this key. In handler I get the selected item, get its context menu and open it. But then 3 issues appear:
- the context menu opens and closes immediately.
- the context menu of the item should have a data binding to the items data context. But
ContextMenu.Tag
is null and MenuItem.Tag
is null, although it is set in XAML.
- the context menu opens always near by the mouse, not at its parent item, the item of the
ItemsControl
.
How can I manage that?
<Window x:Class="ContextMenuAppsKey.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ContextMenuAppsKey"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<RoutedCommand x:Key="AppsCommand" />
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource AppsCommand}" Executed="Apps_Executed" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="Apps" Command="{StaticResource AppsCommand}" />
</Window.InputBindings>
<Grid>
<ItemsControl Name="listBox" ItemsSource="{Binding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Background="{Binding Background}" FontSize="20">
<TextBlock.ContextMenu>
<ContextMenu Opened="ContextMenu_Opened" Tag="{Binding}">
<MenuItem Header="Say hello!" Tag="{Binding}" Click="menuItem_Click" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
namespace ContextMenuAppsKey
{
public class Person
{
internal bool IsSelected { get; set; }
public string Name { get; internal set; }
public Brush Background => new SolidColorBrush(IsSelected ? Colors.PowderBlue : Colors.GhostWhite);
}
public partial class MainWindow : Window
{
private List<Person> People;
public MainWindow()
{
InitializeComponent();
People = new List<Person>() {
new Person() { Name = "Susan" },
new Person() { Name = "Tim", IsSelected = true },
new Person() { Name = "Tom" },
};
listBox.ItemsSource = People;
}
private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
}
private void Apps_Executed(object sender, ExecutedRoutedEventArgs e)
{
e.Handled = true;
Person selected = People.FirstOrDefault(p => p.IsSelected);
if (selected == null)
return;
ContextMenu menu = FindContextMenu(selected);
menu.IsOpen = true;
}
private ContextMenu FindContextMenu(Person entry)
{
DependencyObject dep = listBox.ItemContainerGenerator.ContainerFromItem(entry);
if (dep == null)
return null;
TextBlock textBlock = FindChild_Recursive(dep, d => d is TextBlock) as TextBlock;
return textBlock?.ContextMenu;
}
private static DependencyObject FindChild_Recursive(DependencyObject parent, Func<DependencyObject, bool> match)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
DependencyObject dep;
for (int i = 0; i < count; ++i)
{
dep = VisualTreeHelper.GetChild(parent, i);
if (match(dep))
return dep;
}
for (int i = 0; i < count; ++i)
{
dep = VisualTreeHelper.GetChild(parent, i);
dep = FindChild_Recursive(dep, match);
if (dep != null)
return dep;
}
return null;
}
private void menuItem_Click(object sender, RoutedEventArgs e)
{
Person entry = (sender as FrameworkElement).Tag as Person;
MessageBox.Show("Hello, my name is " + (entry?.Name ?? "<null>"));
}
}
}