Привязка команды в ContextMenu возвращает ошибку

У меня есть ListBox с элементами типа ThemeLayer, и я хочу добавить к этим элементам функциональность контекстного меню.

Определение ListBox XAML выглядит следующим образом:

<ListBox x:Name = "DataLayerList" Grid.Row = "2" Grid.ColumnSpan = "2" MaxHeight = "800"
         Width = "{Binding ActualWidth, ElementName=PanelGrid}" Margin = "0,10,0,0" 
         ScrollViewer.CanContentScroll = "True" ScrollViewer.VerticalScrollBarVisibility = "Auto" 
         ScrollViewer.HorizontalScrollBarVisibility = "Disabled"
         HorizontalAlignment = "Left" SelectionMode = "Extended" IsTextSearchEnabled = "True"
         ItemsSource = "{Binding LayersFiltered, Mode=TwoWay}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation = "Horizontal">
                <Image Source = "{Binding Image}" Height = "16" Width = "16" Margin = "5,0"/>
                <TextBlock Text = "{Binding Path=Name}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemContainerStyle>
        <Style TargetType = "ListBoxItem">
            <Setter Property = "IsSelected" Value = "{Binding IsSelected}"/>
            <Setter Property = "ContextMenu">
                <Setter.Value>
                    <ContextMenu>
                        <MenuItem Header = "Add to current / new Map" 
                                  Command = "{Binding AddServiceFromContext}">
                            <MenuItem.Icon>
                                <Image Source = "{DynamicResource AddContent16}" Width = "16"
                                       RenderOptions.BitmapScalingMode = "HighQuality"/>
                            </MenuItem.Icon>
                        </MenuItem>
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

Определение AddServiceFromContext находится в моей ViewModel:

public ICommand AddServiceFromContext
{
    get
    {
        return new RelayCommand(() =>
        {
            IEnumerable<ThemeLayer> selectedThemeLayers = LayersFiltered.Where(i => i.IsSelected);
        }, true);
    }
}

Как только я вызываю и визуализирую контекстное меню, в ошибках привязки XAML появляется следующая ошибка:

ThemeLayer AddServiceFromContext MenuItem.Command ICommand AddServiceFromContext property not found on object of type ThemeLayer.

Возможно, решение этой проблемы тривиально и означает, что я должен добавить свойство в определение ThemeLayer, но как это будет выглядеть и правильное ли это решение?

В какой класс помещается AddServiceFromContext?

user2250152 18.03.2022 12:07

Внутри базового класса ViewModel ```внутренний класс DockpanePublicDataViewModel : DockPane```

TomGeo 18.03.2022 12:09

@TomGeo DockpanePublicDataViewModel - это контекст данных DataLayerListListBox? Значит, он также выставляет коллекцию LayersFiltered?

thatguy 18.03.2022 12:18

Да, DockpanePublicDataViewModel является контекстом данных ListBox и также предоставляет LayersFiltered как List<ThemeLayer>.

TomGeo 18.03.2022 12:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
42
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Проблема, по-видимому, связана с тем, что команда AddServiceFromContext определена в модели представления, а не в классе, используемом для определения списка LayersFiltered.

Решения может быть 2:

  1. Определите команду в классе LayersFiltered
  2. использовать относительный контекст, что-то вроде

{Binding RelativeSource = {RelativeSource Mode = FindAncestor, AncestorType = {x: Type UserControl}}, Path = DataContext.AddServiceFromContext}

LayersFiltered также хорошо определен в ViewModel.

TomGeo 18.03.2022 12:16

Хорошо, но где определяется команда AddServiceFromContext ? Потому что, когда вы привязываете команду к своему полю ListBox, код попытается найти объект с именем AddServiceFromContext в каждом элементе, содержащемся в ItemSource. Если AddServiceFromContext определен в модели представления, а не в классе элементов, содержащихся в LayersFiltered, код вызывает ошибку! Если вы не определите RelativeSource в своей привязке (решение 2), это предоставит коду информацию о том, где найти команду.

Piccio95 18.03.2022 12:23

ПРИМЕЧАНИЕ. Я предлагаю вам попробовать решение номер 2, которое, похоже, хорошо подходит для вашего варианта использования. Здесь docs.microsoft.com/en-us/dotnet/api/… немного документации Microsoft.

Piccio95 18.03.2022 12:28
Ответ принят как подходящий

Команда AddServiceFromContext определена в модели представления, которая также предоставляет коллекцию LayersFiltered. Однако ListBoxItem внутри ListBox получает DataContext значение соответствующего элемента данных в источнике элементов, то есть LayersFiltered. Об этом говорит вам ошибка, контекст данных является элементом типа ThemeLayer и не предоставляет свойство AddServiceFromContext.

Что вам нужно сделать, так это получить доступ к контексту данных ListBox (или любого другого родителя, который разделяет этот контекст данных), который является DockpanePublicDataViewModel и содержит команду AddServiceFromContext. Проблема в том, что простая привязка RelativeSource для поиска родительских элементов не будет работать, так как контекстное меню не является частью того же визуального дерева, что и ListBox.

Существует обходной путь, который вы можете использовать в подобных случаях, когда контекст данных недоступен. Создайте тип прокси-сервера привязки, который косвенно предоставляет контекст данных в качестве ресурса. Для получения дополнительной информации о том, как это работает, вы можете обратиться к этот пост в блоге Томаса Левеска.

public class BindingProxy : Freezable
{
   protected override Freezable CreateInstanceCore()
   {
      return new BindingProxy();
   }

   public object Data
   {
      get => GetValue(DataProperty);
      set => SetValue(DataProperty, value);
   }

   public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
      nameof(Data), typeof(object), typeof(BindingProxy));
}

Создайте экземпляр прокси-сервера привязки в ресурсах ListBox или где-либо еще, где вы можете привязать целевой контекст данных через свойство Data.

<ListBox.Resources>
   <local:BindingProxy x:Key = "DataLayerListBindingProxy" Data = "{Binding}"/>
</ListBox.Resources>

Затем вы можете связать команду, используя прокси-сервер привязки как Source.

<Setter Property = "ContextMenu">
    <Setter.Value>
        <ContextMenu>
            <MenuItem Header = "Add to current / new Map" 
                      Command = "{Binding Data.AddServiceFromContext, Source = {StaticResource DataLayerListBindingProxy}}">
                <MenuItem.Icon>
                    <Image Source = "{DynamicResource AddContent16}" Width = "16"
                           RenderOptions.BitmapScalingMode = "HighQuality"/>
                </MenuItem.Icon>
            </MenuItem>
        </ContextMenu>
    </Setter.Value>
</Setter>

AddServiceFromContext не является членом DataContext группы MenuItem. Если он определен в модели представления, вы можете привязать свойство TagListBoxItem к модели представления, а затем привязать Command к PlacementTarget родителя ContextMenu:

<Style TargetType = "ListBoxItem">
    <Setter Property = "IsSelected" Value = "{Binding IsSelected}"/>
    <Setter Property = "Tag" Value = "{Binding DataContext,
        RelativeSource = {RelativeSource AncestorType=ListBox}}" />
    <Setter Property = "ContextMenu">
        <Setter.Value>
            <ContextMenu>
                <MenuItem Header = "Add to current / new Map" 
                          Command = "{Binding PlacementTarget.Tag.AddServiceFromContext, 
                            RelativeSource = {RelativeSource AncestorType=ContextMenu}}">
                    <MenuItem.Icon>
                        ...
                    </MenuItem.Icon>
                </MenuItem>
            </ContextMenu>
        </Setter.Value>
    </Setter>
</Style>

Другие вопросы по теме