Вопрос

Кто-нибудь еще заметил, что привязки с 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;
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top