У меня есть 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, но как это будет выглядеть и правильное ли это решение?
Внутри базового класса ViewModel ```внутренний класс DockpanePublicDataViewModel : DockPane```
@TomGeo DockpanePublicDataViewModel - это контекст данных DataLayerListListBox? Значит, он также выставляет коллекцию LayersFiltered?
Да, DockpanePublicDataViewModel является контекстом данных ListBox и также предоставляет LayersFiltered как List<ThemeLayer>.





Проблема, по-видимому, связана с тем, что команда AddServiceFromContext определена в модели представления, а не в классе, используемом для определения списка LayersFiltered.
Решения может быть 2:
LayersFiltered
{Binding RelativeSource = {RelativeSource Mode = FindAncestor, AncestorType = {x: Type UserControl}}, Path = DataContext.AddServiceFromContext}
LayersFiltered также хорошо определен в ViewModel.
Хорошо, но где определяется команда AddServiceFromContext ? Потому что, когда вы привязываете команду к своему полю ListBox, код попытается найти объект с именем AddServiceFromContext в каждом элементе, содержащемся в ItemSource. Если AddServiceFromContext определен в модели представления, а не в классе элементов, содержащихся в LayersFiltered, код вызывает ошибку! Если вы не определите RelativeSource в своей привязке (решение 2), это предоставит коду информацию о том, где найти команду.
ПРИМЕЧАНИЕ. Я предлагаю вам попробовать решение номер 2, которое, похоже, хорошо подходит для вашего варианта использования. Здесь docs.microsoft.com/en-us/dotnet/api/… немного документации Microsoft.
Команда 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>
В какой класс помещается AddServiceFromContext?