Привязка ElementName из MenuItem в ContextMenu - PullRequest
65 голосов
/ 18 июня 2009

Кто-нибудь еще заметил, что привязки с 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 ]

52 голосов
/ 01 июля 2009

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

В коде для UserControl:

NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
22 голосов
/ 16 августа 2016

Как говорили другие, ContextMenu не содержится в визуальном дереве, и привязка ElementName не будет работать. Установка 'NameScope' в контекстном меню, как предлагается в принятом ответе, работает, только если контекстное меню не определено в шаблоне данных. Я решил это, используя {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 и форма XAML BAML в настоящее время не поддержка ключевых слов и функций языка XAML 2009.

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

19 голосов
/ 18 марта 2011

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

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

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

Это сработает для вопроса ОП. Это не будет работать, если вы находитесь внутри DataTemplate . В этих случаях DataContext часто является одним из многих в коллекции, а ICommand , к которому вы хотите привязать, является родственным свойством коллекции в рамках той же модели представления ( 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>
5 голосов
/ 18 июня 2009

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

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

<ContextMenu 
   DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...
4 голосов
/ 18 июня 2009

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

Сделать верхний уровень 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
}

Это позволяет контекстному меню находить именованные элементы внутри Window. Любые другие варианты?

1 голос
/ 18 июня 2011

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

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