ElementName Binding from MenuItem in ContextMenu - wpf

Has anybody else noticed that Bindings with ElementName do not resolve correctly for MenuItem objects that are contained within ContextMenu objects? Check out this sample:
<Window x:Class="EmptyWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
x:Name="window">
<Grid x:Name="grid" Background="Wheat">
<Grid.ContextMenu>
<ContextMenu x:Name="menu">
<MenuItem x:Name="menuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/>
<MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/>
<MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/>
<MenuItem Header="Menu Item" Tag="{Binding ElementName=menuItem}" Click="MenuItem_Click"/>
</ContextMenu>
</Grid.ContextMenu>
<Button Content="Menu"
HorizontalAlignment="Center" VerticalAlignment="Center"
Click="MenuItem_Click" Tag="{Binding ElementName=menu}"/>
<Menu HorizontalAlignment="Center" VerticalAlignment="Bottom">
<MenuItem x:Name="anotherMenuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/>
<MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/>
<MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/>
<MenuItem Header="Menu Item" Tag="{Binding ElementName=anotherMenuItem}" Click="MenuItem_Click"/>
</Menu>
</Grid>
</Window>
All of the bindings work great except for the bindings contained within the ContextMenu. They print an error to the Output window during runtime.
Any one know of any work arounds? What's going on here?

I found a much simpler solution.
In the code behind for the UserControl:
NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));

As said by others, the 'ContextMenu' is not contained in the visual tree and an 'ElementName' binding won't work. Setting the context menu's 'NameScope' as suggested by the accepted answer only works if the context menu is not defined in a 'DataTemplate'. I have solved this by using the {x:Reference} Markup-Extension which is similar to the 'ElementName' binding but resolves the binding differently, bypassing the visual tree. I consider this to be far more readable than using 'PlacementTarget'. Here is an example:
<Image Source="{Binding Image}">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding Source={x:Reference Name=Root}, Path=DataContext.RemoveImage}"
CommandParameter="{Binding}" />
</ContextMenu>
</Image.ContextMenu>
</Image>
According to the MSDN-documentation
x:Reference is a construct defined in XAML 2009. In WPF, you can use
XAML 2009 features, but only for XAML that is not WPF markup-compiled.
Markup-compiled XAML and the BAML form of XAML do not currently
support the XAML 2009 language keywords and features.
whatever that means... Works for me, though.

Here's another xaml-only workaround. (This also assumes you want what's inside the DataContext, e.g., you're MVVMing it)
Option one, where the parent element of the ContextMenu is not in a DataTemplate:
Command="{Binding PlacementTarget.DataContext.MyCommand,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"
This would work for OP's question. This won't work if you are inside of a DataTemplate. In these cases, the DataContext is often one of many in a collection, and the ICommand you wish to bind to is a sibling property of the collection within the same ViewModel (the DataContext of the Window, say).
In these cases, you can take advantage of the Tag to temporarily hold the parent DataContext which contains both the collection AND your ICommand:
class ViewModel
{
public ObservableCollection<Derp> Derps { get;set;}
public ICommand DeleteDerp {get; set;}
}
and in the xaml
<!-- ItemsSource binds to Derps in the DataContext -->
<StackPanel
Tag="{Binding DataContext, ElementName=root}">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem
Header="Derp"
Command="{Binding PlacementTarget.Tag.DeleteDerp,
RelativeSource={RelativeSource
AncestorType=ContextMenu}}"
CommandParameter="{Binding PlacementTarget.DataContext,
RelativeSource={RelativeSource AncestorType=ContextMenu}}">
</MenuItem>

Context menus are tricky to bind against. They exist outside the visual tree of your control, hence they can't find your element name.
Try setting the datacontext of your context menu to its placement target. You have to use RelativeSource.
<ContextMenu
DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...

After experimenting a bit, I discovered one work around:
Make top level Window/UserControl implement INameScope and set NameScope of ContextMenu to the top level control.
public class Window1 : Window, INameScope
{
public Window1()
{
InitializeComponent();
NameScope.SetNameScope(contextMenu, this);
}
// Event handlers and etc...
// Implement INameScope similar to this:
#region INameScope Members
Dictionary<string, object> items = new Dictionary<string, object>();
object INameScope.FindName(string name)
{
return items[name];
}
void INameScope.RegisterName(string name, object scopedElement)
{
items.Add(name, scopedElement);
}
void INameScope.UnregisterName(string name)
{
items.Remove(name);
}
#endregion
}
This allows the context menu to find named items inside of the Window. Any other options?

I'm not sure why resort to magic tricks just to avoid a one line of code inside the eventhandler for the mouse click you already handle:
private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e)
{
// this would be your tag - whatever control can be put as string intot he tag
UIElement elm = Window.GetWindow(sender as MenuItem).FindName("whatever control") as UIElement;
}

Related

WPF Set Visibility on DataTemplate UI Element from MVVM View Model

I have a control that is set up as a DataTemplate:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataTemplate x:Key="KEYBOARD_EN">
<StackPanel>
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
</StackPanel>
</DataTemplate>
In this DataTemplate there is a control on which I wish to set the Visibility from various view models:
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource ...} > Register </Button>
I do routed events with my control, so I tried to set up something similar, but no matter what I try, the RegisterButtonVisible property does not get picked up:
public partial class MainKeyboard : UserControl
{
public static DependencyProperty RegisterButtonVisibleProperty;
public Visibility RegisterButtonVisible
{
get { return (Visibility)GetValue(RegisterButtonVisibleProperty); }
set { SetValue(RegisterButtonVisibleProperty, value); }
}
static MainKeyboard()
{
RegisterButtonVisibleProperty = DependencyProperty.Register("RegisterButtonVisible", typeof (Visibility),
typeof (MainKeyboard));
}
}
In my ViewModel I do this:
public Visibility RegisterButtonVisible // get, set, raisepropchange, etc
My DataTemplate with the button in it is wrapped in a userControl:
<UserControl x:Class="Bleh.Assets.MainKeyboard"
x:Name="TheControl"
Unloaded="UserControl_Unloaded">
<Viewbox>
<Grid>
<ContentControl Name="ctrlContent" Button.Click="Grid_Click" />
</Grid>
</Viewbox>
and is used in my views like this:
<assets:MainKeyboard
RegisterButtonVisible="Collapsed"
Loaded="MainKeyboard_Loaded">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Register">
<b:InvokeCommandAction Command="{Binding ConfirmEmailAddressCommand}"/>
</b:EventTrigger>
<b:EventTrigger EventName="Enter">
<b:InvokeCommandAction Command="{Binding EnterKeyCommand}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
</assets:MainKeyboard>
Please note this attribute:
RegisterButtonVisible="Collapsed"
This is my dependency property. It shows up in intelliesense, so the CLR has registered it correctly, but it does NOT pick up the property assignment (Collapsed is ignored).
This makes me feel like it is very close, but I do remember someone telling me I can not do this, thus the EventTriggers (this is a common issue with datatemplates and MVVM apparently).
So one option is to use something in the Interaction namespace, like I do my event triggers ( I just need to fire a "Visibility" trigger on this button somehow, at least I figure).
What is the right ANY way to do this in MVVM?
Fixing your code
In order to make your existing code work, you need to tell need to tell WPF what object RegisterButtonVisible should be read from. If it's a user control, give the UserControl a name and then reference that element via ElementName, like so:
<UserControl ... lots of stuff here
x:Name="TheControl"
>
In your button binding:
<Button Visibility="{Binding ElementName=TheControl, Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, if you can't do that because the button and the usercontrol are in different files, you can still use an ancestor binding:
<Button Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type assets:MainKeyboard}},
Path=RegisterButtonVisible}"
Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
which, for each button, will walk up to find the closest instance of assets:MainKeyboard and then bind to the RegisterButtonVisible property.
Using MVVM
If you want to achieve the same using MVVM (instead of on a control), you need to use a converter to convert a boolean to a visibility property, like so:
<Button Visibility="{Binding IsRegistrationAllowed, Converter={StaticResource BoolToVis}}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, that assumes that your DataContext is set up correctly and pointing at your ViewModel.

WPF DataTemplates - why the difference in rendering?

I've encountered an oddity with a very basic WPF exercise I've devised for myself, namely dynamically populating menus from a ViewModel. Given the following main window markup:
<Window x:Class="Demosne.Client.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:project="clr-namespace:Demosne.Client.WPF">
<Grid>
<Menu Height="26" Name="menu1" VerticalAlignment="Top" HorizontalAlignment="Stretch" ItemsSource="{Binding MainMenuItems}">
<Menu.ItemTemplate>
<HierarchicalDataTemplate >
<MenuItem Header="{Binding Text, Mode=OneTime}" ItemsSource="{Binding MenuItems}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
<!--<MenuItem Header="File" />
<MenuItem Header="Edit" />-->
</Menu>
</Grid>
and the ViewModel(s):
public class MainWindowViewModel
{
private IList<MenuItemViewModel> _menuItems = new List<MenuItemViewModel>()
{
new MenuItemViewModel() { Text = "File" },
new MenuItemViewModel() { Text = "Edit" }
};
public IList<MenuItemViewModel> MainMenuItems
{
get
{
return _menuItems;
}
}
}
public class MenuItemViewModel
{
public string Text { get; set; }
public IList<MenuItemViewModel> MenuItems
{
get
{
return _menuItems;
}
}
private IList<MenuItemViewModel> _menuItems = new List<MenuItemViewModel>();
}
I would expect the GUI to exactly reproduce the the result of the two commented-out lines in the markup - two MenuItems called File and Edit.
However, the bound version behaves strangely on mouseover:
Markup version:
Bound version:
Why are they different?
You are getting funny results, because you are not really using the HierarchicalDataTemplate the correct way.
When you set a itemssource on a Menu, it will create a MenuItem for each object in the collection, and if you also supply a HierarchicalDataTemplate with a itemssource set, it will create MenuItems for each of the child objects in that collection as well, down the hierarchy.
In your case, you've added a MenuItem yourself in the template, which is not needed. The framework creates those items implicitly for you. And this is causing the menu to behave oddly.
So to get a correct result you should do something like this:
<HierarchicalDataTemplate ItemsSource="{Binding MenuItems}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
</StackPanel>
</HierarchicalDataTemplate>
Update
By setting a DataTemplate on something, you are telling WPF that you want to control, how each of its items should be displayed.
In this case a HierarchicalDataTemplate is used, which is a template for generating headered controls. This kind of control contains a header and an items collection.
When you apply this kind of template to an object, whatever you have put in the template will be used as the header, and the items collection will be created by applying the template to each of the child objects in the collection set as the ItemsSource on the template. So it will recursively apply the template to all objects in the hierarchy.
In your example, you have a Menu. You could just create it by doing this:
<Menu ItemsSource="{Binding MainMenuItems}" />
It would work fine, but since you have not applied a template, to tell it how the items in the collection should be displayed, it will just create a MenuItem for each object in the itemssource and call ToString() on it. This value will then be used as the Header property on the MenuItem.
Since that not what you want, you have to apply a template, to tell WPF what you would like to be displayed as the content in the header of the implicitly generated MenuItem.
In my example I simply made a template containing a TextBlock, which binds to the Text property on the viewmodel.
Update 2
If you now want to set properties on the implicitly created menuitems, you have to that by setting the ItemContainerStyle property on the HierarchicalDataTemplate. The styled defined here will be applied to all the generated menuitems.
So to bind the Command property of the MenuItem to a Command property on the viewmodel you can do this:
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Command}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
Try this HierarchicalDataTemplate:
<HierarchicalDataTemplate>
<MenuItem ItemsSource="{Binding MenuItems}">
<MenuItem.Template>
<ControlTemplate>
<TextBlock Text="{Binding Text, Mode=OneTime}" />
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</HierarchicalDataTemplate>
MenuItem ControlTemplate Example (msdn link)
Controls in Windows Presentation Foundation (WPF) have a
ControlTemplate that contains the visual tree of that control. You can
change the structure and appearance of a control by modifying the
ControlTemplate of that control. There is no way to replace only part
of the visual tree of a control; to change the visual tree of a
control you must set the Template property of the control to its new
and complete ControlTemplate.
OK let's see now on the visual tree.
If we have something like this:
<Menu Height="26" Grid.Row="1">
<MenuItem Header="File" />
<MenuItem Header="Edit" />
</Menu>
Visual tree of this is represented below:
Ok, so MenuItem has ContentPresenter with TextBlock.
What happens if we have HierarchicalDataTemplate ?
<Menu.ItemTemplate>
<HierarchicalDataTemplate >
<MenuItem Header="{Binding Text, Mode=OneTime}" ItemsSource="{Binding MenuItems}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
Let's see on the visual tree:
Wow, what it is???
So if you don't specified ControlTemplate of MenuItem, it is itself ContentPresenter of the MenuItem (you can see this on the second screen). So, you must override ControlTemplate of the MenuItem if you want use it in HierarchicalDataTemplate (first screen).
Below is the visual tree with my solution:

Cannot find sourcefor binding with reference 'ElementName=Field'

I have a context menu on a textbox that I'm trying to bind the isChecked property to one of the properties in that textbox's datacontext with a value converter.
The problem I'm having is very similar, I beleive, to this post... WPF MenuItem.Command binding to ElementName results to System.Windows.Data Error: 4 : Cannot find source for binding with reference
In there Aran Mulholland suggests 3 different solutions. The one I've been trying to get working, and have yet to see an actual working example of, is #2. I think this is the most MVVM friendly approach, and to that end the most elegant... then again, I'm pretty new to this.
Here is my xaml
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock x:Name="Field" Text="{Binding Path=FieldName}" >
<TextBlock.ContextMenu>
<!--<ContextMenu PlacementTarget="{Binding ElementName=Field}" > -->
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext}">
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<!--<MenuItem Header="String" IsCheckable="True" IsChecked="{Binding Path=PlacementTarget.DataContext.FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>-->
<MenuItem Header="String" IsCheckable="True" IsChecked="{Binding Path=FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
I'm using the DataTemplate to populate the following ListBox...
<ListBox DnD:DragDropHelper.IsDragSource="True" Name="sourceFieldsLB" Height="238" HorizontalAlignment="Left" Margin="20,286,0,0" VerticalAlignment="Top" Width="150" ItemTemplate="{StaticResource SFTemplateWithContextMenu}" ItemsSource="{Binding Selection.SourceFields, Mode=TwoWay}" AllowDrop="True" >
I downloaded Snoop to look inside and see just what is going on. I've tried a couple of different methods with varying degrees of failure.
The commented out piece is the previous way I was trying to accomplish my goal. The problem there is that i was getting the error...
"cannot find source for binding with reference 'elementname=Field'" But the TextBlock shows using Snoop that its name IS Field.
In the current way I'm doing it I can see that the textblock has a local namescope and its name is Field - which is what I would expect and want. The ContextMenu value shows that it has a ContextMenu with 2 items... which is correct. So I click on ContextMenu to see how things look and low and behold the ContextMenu has no DataContext.
Any help and direction on this would be greatful. I'm not sure exactly what I'm missing here. I've looked around and whenever someone seems to get close to having this work they mention they found some "workaround" or other way to do it and never get it working. This HAS to have the ability to work... I'm just too new to this to see the missing piece.
I know it can be done to do a true MVVM way... right?
Bryce, the main problem is that ContextMenus are not part of the standard visual tree and the only real connection they have is via the PlacementTarget property. So, it's normally best to hook that up as soon as possible. So...
Given a ViewModel
public class ViewModel
{
public string Field { get; set; }
public string FieldType { get; set; }
}
And a MainWindow
<Window x:Class="ContextMenuSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Text="{Binding Field}">
<TextBlock.ContextMenu>
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext}">
<MenuItem Header="{Binding FieldType}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
</Window>
And an app.xaml.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
namespace ContextMenuSample
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var shell = new MainWindow();
shell.DataContext = new ViewModel { Field = "FirstName", FieldType = "String" };
shell.Show();
}
}
}
You can see that the DataContext for the ContextMenu gets hooked up correctly by the line
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext}">
and you should get a nicely behaved ContextMenu that talks to the underlying VieWModel.

Binding to two different DataContexts in a ContextMenu

I'm trying to bind to a property of a container from inside a DataTemplate. A simplified version of my markup looks like:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type myCustomItem}">
<!--Visual stuff-->
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Item"
Command="{Binding myCustomItemsICommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type CustomContainerType}}, Path=ContainerProperty}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<CustomContainerType/>
</Grid>
My approach is based on this post but it doesn't seem to be working. The issue seems to arise from the placement of the ContextMenu within the visual tree. Basically I am trying to bind the Command to the DataContext of the DataTemplate but bind the CommandParameter to a DataContext outside the DataTemplate.
ContextMenus are not in the same visual tree as the rest of the controls, there are a few questions regarding how to do bindings accross that boundary but this might be somewhat difficult without specifying names.
ElementName fails as well because of the lacking tree connection, but you could use x:Reference in the Binding.Source instead.

C+Ctrl KeyBinding is not causing copying to happen

I have set up a ListBox like so:
<ListBox ItemsSource="{Binding Logs, Mode=OneWay}" x:Name="logListView">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}">
<TextBlock.InputBindings>
<KeyBinding Key="C"
Modifiers="Ctrl"
Command="Copy"/>
</TextBlock.InputBindings>
<TextBlock.CommandBindings>
<CommandBinding Command="Copy"
Executed="KeyCopyLog_Executed"
CanExecute="CopyLog_CanExecute"/>
</TextBlock.CommandBindings>
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Command="Copy">
<MenuItem.CommandBindings>
<CommandBinding Command="Copy"
Executed="MenuCopyLog_Executed"
CanExecute="CopyLog_CanExecute"/>
</MenuItem.CommandBindings>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see, in the template, each TextBlock has a context menu that allows the user to copy the text. This works.
Also in the TextBlock there is an KeyBinding to ctrl+c and a CommandBinding to copy. When I press ctrl+c the method KeyCopyLog_Executed is not executed. I've checked with the debugger.
How should I be binding the keys to the TextBlock?
There are a couple of problems here.
First of all, KeyBindings will work only if currently focused element is located inside the element where KeyBindings are defined. In your case you have a ListBoxItem focused, but the KeyBindings are defined on the child element - TextBlock. So, defining a KeyBindings on a TextBlock will not work in any case, since a TextBlock cannot receive focus.
Second of all, you probably need to know what to copy, so you need to pass the currently selected log item as parameter to the Copy command.
Furthermore, if you define a ContextMenu on a TextBlock element it will be opened only if your right-click exactly on the TextBlock. If you click on any other part of the list item, it will not open. So, you need to define the ContextMenu on the list box item itself.
Considering all of that, what I believe you are trying to do can be done in the following way:
<ListBox ItemsSource="{Binding Logs, Mode=OneWay}"
x:Name="logListView"
IsSynchronizedWithCurrentItem="True">
<ListBox.InputBindings>
<KeyBinding Key="C"
Modifiers="Ctrl"
Command="Copy"
CommandParameter="{Binding Logs/}" />
</ListBox.InputBindings>
<ListBox.CommandBindings>
<CommandBinding Command="Copy"
Executed="CopyLogExecuted"
CanExecute="CanExecuteCopyLog" />
</ListBox.CommandBindings>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="Copy"
CommandParameter="{Binding}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here we define a KeyBinding on the ListBox itself and specify as a CommandParameter currently selected list box item (log entry).
CommandBinding is also defined at the ListBox level and it is a single binding for both right click menu and the keyboard shortcut.
The ContextMenu we define in the style for ListBoxItem and bind CommandParameter to the data item represented by this ListBoxItem (log entry).
The DataTemplate just declares a TextBlock with binding to current data item.
And finally, there is only one handler for the Copy command in the code-behind:
private void CopyLogExecuted(object sender, ExecutedRoutedEventArgs e) {
var logItem = e.Parameter;
// Copy log item to the clipboard
}
private void CanExecuteCopyLog(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
Thanks to Pavlov Glazkov for explaining that the key and command bindings need to go at the ListBox level, rather than the item template level.
This is the solution I now have:
<ListBox ItemsSource="{Binding Logs, Mode=OneWay}">
<ListBox.InputBindings>
<KeyBinding Key="C"
Modifiers="Ctrl"
Command="Copy"/>
</ListBox.InputBindings>
<ListBox.CommandBindings>
<CommandBinding Command="Copy"
Executed="KeyCopyLog_Executed"
CanExecute="CopyLog_CanExecute"/>
</ListBox.CommandBindings>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Command="Copy">
<MenuItem.CommandBindings>
<CommandBinding Command="Copy"
Executed="MenuCopyLog_Executed"
CanExecute="CopyLog_CanExecute"/>
</MenuItem.CommandBindings>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Where KeyCopyLog_Executed is:
private void KeyCopyLog_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
if ((sender as ListBox).SelectedItem != null)
{
LogItem item = (LogItem) (sender as ListBox).SelectedItem;
Clipboard.SetData("Text", item.ToString());
}
}
and MenuCopyLog_Executed is:
private void MenuCopyLog_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
LogItem item = (LogItem) ((sender as MenuItem).TemplatedParent as ContentPresenter).DataContext;
Clipboard.SetData("Text", item.ToString());
}

Resources