ElementName привязка из MenuItem в ContextMenu

Кто-нибудь еще заметил, что привязки с 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. Они печатают ошибку в окне "Выход" во время выполнения.

Кто-нибудь знает о какой-либо работе? Что здесь происходит?

Ответ 1

Я нашел гораздо более простое решение.

В коде для UserControl:

NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));

Ответ 2

Вот еще одно обходное решение 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>

Ответ 3

Как сказано другими, "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.

что бы это ни значило... Работает для меня, однако.

Ответ 4

Контекстные меню сложно связать. Они существуют вне визуального дерева вашего контроля, поэтому они не могут найти ваше имя элемента.

Попробуйте настроить datacontext вашего контекстного меню на цель размещения. Вы должны использовать RelativeSource.

<ContextMenu 
   DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...

Ответ 5

После нескольких экспериментов я обнаружил одну работу:

Сделайте верхний уровень 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. Любые другие варианты?

Ответ 6

Я не уверен, зачем прибегать к волшебным трюкам, чтобы избежать одной строки кода внутри обработчика событий для мыши, которую вы уже обрабатываете:

    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;
    }