Ответ 1
Я нашел гораздо более простое решение.
В коде для UserControl:
NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
Кто-нибудь еще заметил, что привязки с ElementName не корректно разрешены для объектов MenuItem
, которые содержатся в объектах ContextMenu
? Проверьте этот образец:
<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>
Все привязки отлично работают, за исключением привязок, содержащихся в ContextMenu. Они печатают ошибку в окне "Выход" во время выполнения.
Кто-нибудь знает о какой-либо работе? Что здесь происходит?
Я нашел гораздо более простое решение.
В коде для UserControl:
NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
Вот еще одно обходное решение xaml. (Это также предполагает, что вы хотите, что внутри DataContext, например, вы MVVMing)
Вариант один, где родительский элемент ContextMenu не находится в DataTemplate:
Command="{Binding PlacementTarget.DataContext.MyCommand,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Это будет работать для вопроса OP. Это не будет работать, если вы находитесь в DataTemplate. В этих случаях DataContext часто является одним из многих в коллекции, а ICommand, к которому вы хотите привязать, - это свойство sibling коллекции в пределах той же ViewModel ( DataContext > окна, скажем).
В этих случаях вы можете воспользоваться тегом, чтобы временно сохранить родительский DataContext, который содержит и коллекция И ваша ICommand:
class ViewModel
{
public ObservableCollection<Derp> Derps { get;set;}
public ICommand DeleteDerp {get; set;}
}
и в 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>
Как сказано другими, "ContextMenu" не содержится в визуальном дереве, а привязка "ElementName" не будет работать. Настройка контекстного меню "NameScope", как предложено принятым ответом, работает только в том случае, если контекстное меню не определено в "DataTemplate". Я решил это, используя {x: Reference} Markup-Extension, который похож на привязку 'ElementName', но решает привязку по-другому, обходя визуальное дерево. Я считаю это более читаемым, чем использование "PlacementTarget". Вот пример:
<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>
В соответствии с документацией MSDN
x: Ссылка - это конструкция, определенная в XAML 2009. В WPF вы можете использовать XAML 2009, но только для XAML, который не скомпилирован WPF. Скомпилированный XAML и BAML-форма XAML в настоящее время поддерживайте ключевые слова и функции языка XAML 2009.
что бы это ни значило... Работает для меня, однако.
Контекстные меню сложно связать. Они существуют вне визуального дерева вашего контроля, поэтому они не могут найти ваше имя элемента.
Попробуйте настроить datacontext вашего контекстного меню на цель размещения. Вы должны использовать RelativeSource.
<ContextMenu
DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...
После нескольких экспериментов я обнаружил одну работу:
Сделайте верхний уровень Window
/UserControl
реализуйте INameScope
и установите NameScope
of ContextMenu
в элемент управления верхнего уровня.
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
}
Это позволяет контекстному меню находить именованные элементы внутри Window
. Любые другие варианты?
Я не уверен, зачем прибегать к волшебным трюкам, чтобы избежать одной строки кода внутри обработчика событий для мыши, которую вы уже обрабатываете:
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;
}