Привязка ElementName из MenuItem в ContextMenu
-
06-07-2019 - |
Вопрос
Кто-нибудь еще заметил, что привязки с 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));
Другие советы
Как говорили другие, ContextMenu не содержится в визуальном дереве, и привязка ElementName не будет работать. Установка 'NameScope' в контекстном меню, как предлагается в принятом ответе, работает, только если контекстное меню не определено в шаблоне данных. Я решил эту проблему с помощью {x: Reference} разметки-расширения которая похожа на привязку 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 и форма XAML BAML в настоящее время не поддержка ключевых слов и функций языка XAML 2009.
что бы это ни значило ... Хотя у меня работает.
Вот еще один обходной путь только для xaml. (Это также предполагает, что вы хотите, что находится внутри DataContext , например, вы MVVMing это)
Первый вариант, когда родительский элемент ContextMenu отсутствует в DataTemplate :
Command="{Binding PlacementTarget.DataContext.MyCommand,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Это сработает для вопроса ОП. Это не сработает, если вы находитесь внутри DataTemplate . В этих случаях DataContext часто является одним из многих в коллекции, а ICommand , к которому вы хотите привязать, является родственным свойством коллекции в том же самом 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>
Контекстные меню сложно связать. Они существуют вне визуального дерева вашего элемента управления, поэтому они не могут найти имя вашего элемента.
Попробуйте установить текстовый контекст вашего контекстного меню для цели размещения. Вы должны использовать RelativeSource.
<ContextMenu
DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...
Немного поэкспериментировав, я обнаружил одну работу вокруг:
Создайте Window
/ UserControl
, реализующий INameScope
и установите NameScope
для 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
}
Это позволяет контекстному меню находить именованные элементы внутри окна
. Есть другие варианты?
Я не уверен, зачем прибегать к волшебным уловкам, просто чтобы избежать одной строки кода внутри обработчика событий для щелчка мышью, который вы уже обрабатываете:
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;
}