上下文菜单中的命令绑定 returns 错误
Command binding in ContextMenu returns error
我有一个 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
命令是在 viewModel 中定义的,而不是在用于定义 LayersFiltered
列表的 class 中定义的
可以有 2 种解决方案:
- 定义命令进入
LayersFiltered
class
- 使用相对上下文,例如
{Binding RelativeSource = {RelativeSource Mode = FindAncestor, AncestorType = {x: Type UserControl}}, Path = DataContext.AddServiceFromContext}
AddServiceFromContext
命令在视图模型中定义,该视图模型还公开了 LayersFiltered
集合。但是,ListBox
中的 ListBoxItem
将其 DataContext
设置为项源中的相应数据项,即 LayersFiltered
。这就是错误告诉您的内容,数据上下文是 ThemeLayer
类型的项目,它不会公开 属性 AddServiceFromContext
.
您需要做的是访问 ListBox
(或共享此数据上下文的任何其他父级)的数据上下文,它是 DockpanePublicDataViewModel
并包含 AddServiceFromContext
命令.问题在于,用于查找父元素的简单 RelativeSource
绑定将不起作用,因为上下文菜单与 ListBox
.
不属于同一可视化树。
在这种无法访问数据上下文的情况下,您可以使用一种解决方法。创建间接提供数据上下文作为资源的绑定代理类型。有关其工作原理的更多信息,您可以参考 this blog post by Thomas Levesque.
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
不是 MenuItem
的 DataContext
的成员。如果它在视图模型中定义,则可以将 ListBoxItem
的 Tag
属性 绑定到视图模型,然后将 Command
绑定到 PlacementTarget
parent 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>
我有一个 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
命令是在 viewModel 中定义的,而不是在用于定义 LayersFiltered
列表的 class 中定义的
可以有 2 种解决方案:
- 定义命令进入
LayersFiltered
class - 使用相对上下文,例如
{Binding RelativeSource = {RelativeSource Mode = FindAncestor, AncestorType = {x: Type UserControl}}, Path = DataContext.AddServiceFromContext}
AddServiceFromContext
命令在视图模型中定义,该视图模型还公开了 LayersFiltered
集合。但是,ListBox
中的 ListBoxItem
将其 DataContext
设置为项源中的相应数据项,即 LayersFiltered
。这就是错误告诉您的内容,数据上下文是 ThemeLayer
类型的项目,它不会公开 属性 AddServiceFromContext
.
您需要做的是访问 ListBox
(或共享此数据上下文的任何其他父级)的数据上下文,它是 DockpanePublicDataViewModel
并包含 AddServiceFromContext
命令.问题在于,用于查找父元素的简单 RelativeSource
绑定将不起作用,因为上下文菜单与 ListBox
.
在这种无法访问数据上下文的情况下,您可以使用一种解决方法。创建间接提供数据上下文作为资源的绑定代理类型。有关其工作原理的更多信息,您可以参考 this blog post by Thomas Levesque.
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
不是 MenuItem
的 DataContext
的成员。如果它在视图模型中定义,则可以将 ListBoxItem
的 Tag
属性 绑定到视图模型,然后将 Command
绑定到 PlacementTarget
parent 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>