【问题标题】:Binding collection to ContextMenu submenu将集合绑定到 ContextMenu 子菜单
【发布时间】:2015-10-12 18:37:39
【问题描述】:

我在做一些看起来很容易但实际上并不容易的事情时遇到了麻烦。

我有一个 ListView,我在其中绑定了一个 ObservableCollection,我希望在右键单击该 ListView 的一个元素时出现一个 ContextMenu。

在那个 ContextMenu 中,我想要一个显示“添加到播放列表”的 MenuItem,其中包含我所有播放列表的列表。

所以我这样做了,这对我来说是正确的:

<ListView Grid.Row="0" Grid.Column="1" x:Name="ListBoxSelectedFolder" ItemsSource="{Binding Path=SelectedFolder.PlayableElements}">
      <ListView.Resources>
        <ContextMenu x:Key="ContextMenu">
          <MenuItem Header="Add to" ItemsSource="{Binding Path=Playlists}">
            <MenuItem Header="{Binding Name}"/>
          </MenuItem>
          <MenuItem Header="Remove from All" />
        </ContextMenu>
      <Style TargetType="{x:Type ListViewItem}">
         <Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
    </Style>
      </ListView.Resources>
      <ListView.View>
        <GridView>
            <GridViewColumn Header="Type" DisplayMemberBinding="{Binding Extension}" />
            <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
        </GridView>
    </ListView.View>
</ListView>

问题是我在子菜单中唯一得到的是我单击的项目的名称:不知何故,它与 ListView 的集合 SelectedFolder.PlayableElements 绑定,因为 SelectedFolder.PlayableElements 和播放列表都有一个名称财产。

所以这两个绑定之间存在某种冲突,我不知道如何解决它。

提前感谢您的回复。

【问题讨论】:

  • &lt;MenuItem Header="Add to" ItemsSource="{Binding Path=Playlists}"&gt;&lt;MenuItem Header="{Binding Name}"/&gt;&lt;/MenuItem&gt; - 这意味着您同时设置了ItemsSource 并将项目添加到父MenuItem 的Items,这应该会在运行时引发一些异常。
  • 它不会抛出异常供您查看,因为 Binding 静默失败。 ContextMenu 与可视化树分离,因此您需要以某种方式显式设置绑定源。
  • @KingKing 我该怎么做?
  • 不清楚您的Playlists 来自哪里?比如来自ListView的DataContext或一些显式的子视图模型?
  • @KingKing 其实这是我的 MainWindow 类的一个属性

标签: c# wpf data-binding


【解决方案1】:

ContextMenu 与可视树分离,因此在其范围内与ElementNameRelativeSource 绑定是不可能的以正常方式。您需要一些代理技术来桥接切割。这里我们有Freezable 元素,它可以继承数据上下文,并且即使在这种情况下也可以轻松地绑定到可视化树。为方便起见,我们应该使用DiscreteObjectKeyFrame,因为它的值可以接受各种对象。如果您关心名称,则可以定义自己的自定义 Freezable 对象。

<ListView Grid.Row="0" Grid.Column="1" x:Name="ListBoxSelectedFolder" ItemsSource="{Binding Path=SelectedFolder.PlayableElements}">
  <ListView.Resources>
    <DiscreteObjectKeyFrame x:Key="proxy" Value="{Binding Playlists, RelativeSource={RelativeSource AncestorType=Window}}"/>
    <ContextMenu x:Key="ContextMenu">
      <MenuItem Header="Add to" ItemsSource="{Binding Value, Source={StaticResource proxy}}">
         <MenuItem.ItemTemplate>
             <DataTemplate>
                 <TextBlock Text="{Binding Name}"/>
             </DataTemplate>
         </MenuItem.ItemTemplate>
      </MenuItem>
      <MenuItem Header="Remove from All" />
    </ContextMenu>
    <Style TargetType="{x:Type ListViewItem}">
     <Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
    </Style>
  </ListView.Resources>
  <!-- ... -->
</ListView>

编辑 - 这是为了帮助您了解如何混合播放列表和 SelectedFolder 中的信息:

<ListView Grid.Row="0" Grid.Column="1" x:Name="ListBoxSelectedFolder" ItemsSource="{Binding Path=SelectedFolder.PlayableElements}">
  <ListView.Resources>
    <DiscreteObjectKeyFrame x:Key="proxy" Value="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
    <ContextMenu x:Key="ContextMenu" DataContext="{Binding Value, Source={StaticResource proxy}}">
      <MenuItem Header="Add to" ItemsSource="{Binding Playlists}">
         <MenuItem.ItemTemplate>
             <DataTemplate>                     
                 <TextBlock Text="{Binding Name}"/>        
             </DataTemplate>
         </MenuItem.ItemTemplate>
      </MenuItem>
      <MenuItem Header="Playable elements" ItemsSource="{Binding SelectedFolder.PlayableElements}"/>
      <MenuItem Header="Remove from All" />
    </ContextMenu>
    <Style TargetType="{x:Type ListViewItem}">
     <Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
    </Style>
  </ListView.Resources>
  <!-- ... -->
</ListView>

如您所见,添加的菜单项(在Remove from all 的正上方)将其ItemsSource 设置为SelectedFolder.PlayableElementsContextMenu 现在通过proxy 将其DataContext 设置为Window 实例。因此,在其范围内使用的所有绑定没有明确的 SourceRelativeSourceElementName 设置将获得 DataContext 的解析源(您的窗口)。

【讨论】:

  • 有效!我不明白为什么它一开始就没有,为什么它听起来如此合乎逻辑时必须如此复杂?总之感谢。你有一些资源来了解更多关于 wpf、c# 和 .net 的信息吗?我脑子里真的很困惑。
  • 另外,如果我想在我的MenuItem 中混合来自SelectedFolder.PlayableElementsPlaylists 的属性怎么办?
  • @Devz 是的,我认为它不应该那么复杂,WPF 本身存在一些许多人根本不喜欢的问题(实际上它完全可以改进,但不知何故他们没有)。我在这里写的内容来自对 ContextMenu(和其他一些)的经验,因此您当然应该体验很多以了解更多关于 WPF 的信息。我建议您阅读 WPF Unleashed - 这是一本对初学者很有价值的书,只需浏览其中的示例和指南即可。当然这还不够,还需要时间去体验,WPF的学习曲线很陡。
  • 对于你的附加问题,我真的不明白你想如何混合它们,举个例子就可以了。
  • 比如说我想要在我的子菜单中,除了来自Playlists的元素,我还想要来自SelectedFolder.SomeObservableCollection的元素。我该怎么做?
【解决方案2】:

使用您展示的代码,您刚刚将另一个菜单项绑定到 name 属性。我猜您正在寻找的是 MenuItemItemTemplate 属性。通过这种方式,您可以定义应为该菜单项的每个子项显示哪些数据。 检查下面的标记:

<ListView Grid.Row="0" Grid.Column="1" x:Name="ListBoxSelectedFolder" ItemsSource="{Binding Path=SelectedFolder.PlayableElements}">
          <ListView.Resources>
            <ContextMenu x:Key="ContextMenu">
              <MenuItem Header="Add to" ItemsSource="{Binding Path=Playlists}">
                 <MenuItem.ItemTemplate>
                    <DataTemplate>
                        <MenuItem Header="{Binding Path=Name}" />
                    </DataTemplate>
                 </MenuItem.ItemTemplate>
                <MenuItem Header="{Binding Name}"/>
              </MenuItem>
              <MenuItem Header="Remove from All" />
            </ContextMenu>
          <Style TargetType="{x:Type ListViewItem}">
             <Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
        </Style>
          </ListView.Resources>
          <ListView.View>
            <GridView>
                <GridViewColumn Header="Type" DisplayMemberBinding="{Binding Extension}" />
                <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
            </GridView>
        </ListView.View>
    </ListView>

【讨论】:

  • 绑定路径和简单绑定有什么区别?我是 .NET 特性的新手 :(
  • 嗯,稍后会检查它。 “绑定”和“绑定路径”没有区别,我只是觉得“路径”更具描述性
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多