上下文菜单中的命令绑定 returns 错误

Command binding in ContextMenu returns error

我有一个 ListBox,其中的项目类型为 ThemeLayer,我想为这些项目添加上下文菜单功能。

ListBoxXAML定义如下:

<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 种解决方案:

  1. 定义命令进入LayersFilteredclass
  2. 使用相对上下文,例如

{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 不是 MenuItemDataContext 的成员。如果它在视图模型中定义,则可以将 ListBoxItemTag 属性 绑定到视图模型,然后将 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>