从上下文菜单访问数据上下文属性

Access Data Context properties from Context Menu

我试着了解一下 listboxview 与上下文菜单的结合方式,所以我编写了以下 XAML 代码:

<UserControl>
   ...
     <ListView
                x:Name="level1Lister"
                Grid.Row="1"
                behaviours:AutoScrollListViewBehaviour.ScrollOnNewItem="True"
                ItemsSource="{Binding LogBuffer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="Padding" Value="1" />
                        <Setter Property="Margin" Value="2,0" />
                        <Setter Property="BorderThickness" Value="0" />
                    </Style>
                </ListView.ItemContainerStyle>
                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Vertical" />
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <StackPanel.ContextMenu>
                                <ContextMenu>
                                    <MenuItem Command="{Binding Path=DataContext.ValidateAllCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, FallbackValue=9999999999}" Header="Copy" />
                                </ContextMenu>
                            </StackPanel.ContextMenu>
                            <TextBlock Foreground="{Binding Color, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
</UserControl>

我的主要问题是我无法访问我的“ValidateAllCommand”函数,出于某种原因...我认为这与“RelativeSource={RelativeSource FindAncestor ...”部分有关,但我不知道怎么办。

ContextMenu 的“问题”是它不是可视化树的一部分。原因是 MenuItem 使用 Popup 来承载内容。虽然 Popup 本身是可视化树的一部分,但它的子内容在新的 Window 实例中呈现时会断开连接。我们知道树中只能有一个 Window,而这个 Window 必须是根。
由于 Binding.RelativeSource 遍历可视化树,从 Popup 的分离树开始寻找绑定源,因此 Bindig 没有解析。

Popup.Child内容继承了PopupDataContext。在 ContextMenu 的情况下,它意味着 MenuItemContextMenuContextMenu 的父级继承 DataContext,更准确地说。在您的场景中,DataContextListBoxItem 的数据模型,即 DataTemplateDataContext

这意味着,一种解决方案是在项目模型中实施命令。

第二种解决方案是使用路由命令。此解决方案在您的场景中可能更合理。路由command/event可以越过两棵树之间的边界

在下面的示例中,我使用了 ApplicationCommands.Copy 命令,这是预定义的路由命令之一。 MenuItem.Parameter 绑定到 DataContext,这是项目数据模型(如前所述,继承自 ContextMenu)。这样命令处理程序就可以知道数据源。
可以使用 UIElement.CommandBindings 属性 将事件处理程序附加到任何父元素。在示例中,处理程序附加到 ListBox 元素:

MainWindow.xaml

<ListBox>
  <ListBox.CommandBindings>
    <CommandBinding Command="{x:Static ApplicationCommands.Copy}"
                    Executed="CopyCommand_Executed" />
  </ListBox.CommandBindings>
  <ListBox.ItemTemplate>
    <DataTemplate DataType="{x:Type local:DataItem}">
      <StackPanel>
        <StackPanel.ContextMenu>
          <ContextMenu>
      
            <!-- 
              Here we are in a detached visual tree. 
              The DataContext is inherited from the ContextMenu element. 
              The framework allows routed events to cross the boundaries between the trees.
            -->
            <MenuItem Command="{x:Static ApplicationCommands.Copy}"
                      CommandParameter="{Binding}"
                      Header="Copy" />
          </ContextMenu>
        </StackPanel.ContextMenu>

        <TextBlock />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

MainWindow.xaml.cs

private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
  var listBoxItemModel = e.Parameter as LogBufferItem;

  // TODO::Handle Copy command. For example:
  var commandTarget = this.DataContext as ICommandModel;
  commandTarget.ValidateAllCommand.Execute(listBoxItemModel);
}

第三种但不推荐的解决方案是将 ContextMenu 父级的 DataContext 绑定到感兴趣的上下文:

  <ListBox.ItemTemplate>
    <DataTemplate DataType="{x:Type local:DataItem}">
      <StackPanel>
        <!-- DataTemplate DataContext -->

        <Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext}">

          <!-- ListBox DataContext -->
          <Grid.ContextMenu>
            <ContextMenu>
      
              <!-- 
                Here we are in a detached visual tree. 
                The DataContext is inherited from the ContextMenu/Grid element. 
              -->
              <MenuItem Command="{Binding ValidateAllCommand}"
                        Header="Copy" />
            </ContextMenu>
          </Grid.ContextMenu>
        </Grid>

        <!-- DataTemplate DataContext -->
        <TextBlock />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox