Привязка ElementName из меню в 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. Они печатают ошибку в окне вывода во время выполнения.

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

6 62

6 ответов:

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

в коде позади для UserControl:

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

вот еще один обходной путь только для xaml. (Это предполагает, что вы хотите, что внутри DataContext, например, ты MVVMing его)

вариант один, где родительский элемент ContextMenu не DataTemplate:

Command="{Binding PlacementTarget.DataContext.MyCommand, 
         RelativeSource={RelativeSource AncestorType=ContextMenu}}"

Это будет работать для ОП вопрос. Это не будет работать, если вы находитесь внутри DataTemplate. В этих случаях!--4-- > DataContext часто является одним из многих в коллекции, и то ICommand вы хотите привязать к является родственным свойством коллекции в том же ViewModel ( DataContext окна, скажем).

В этих случаях, вы можете воспользоваться Tag временно удерживать родителя 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: ссылка} разметка-расширение который похож на привязку '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: Reference-это конструкция, определенная в XAML 2009. В WPF можно использовать Функции XAML 2009, но только для XAML, который не скомпилирован разметкой WPF. Разметка-скомпилированный XAML и форма BAML XAML в настоящее время не работают поддержка ключевых слов и функций языка XAML 2009.

что бы это ни значило... Но это работает на меня.

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

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

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

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

сделать верхний уровень Window/UserControl реализовать INameScope и set 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
}

это позволяет контекстному меню найти именованные элементы внутри Window. Есть еще варианты?

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

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