【问题标题】:How can I incorporate a data bound list of MenuItems to another MenuItem in WPF?如何将 MenuItem 的数据绑定列表合并到 WPF 中的另一个 MenuItem?
【发布时间】:2009-09-09 11:36:27
【问题描述】:

我有一个“文件”MenuItem 我想显示最近打开的文件列表。

这是我现在拥有的 xaml:

<MenuItem Header="File}">
  <MenuItem Header="Preferences..." Command="{Binding ShowOptionsViewCommand}" />
  <Separator />
  <ItemsControl ItemsSource="{Binding RecentFiles}">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <MenuItem Header="{Binding DisplayPath}" CommandParameter="{Binding}"
            Command="{Binding Path=DataContext.OpenRecentFileCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
        </MenuItem>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
  <Separator />
  <MenuItem Header="Exit" Command="{Binding CloseCommand}" />
</MenuItem>

但是,当我使用此代码时,MenuItems 周围有一个奇怪的偏移量,看起来它们周围有一个容器。我怎样才能摆脱它?

这是它的截图:

alt text http://www.cote-soleil.be/FileMenu.png

【问题讨论】:

标签: wpf mvvm binding menuitem


【解决方案1】:

“奇怪的偏移量”是MenuItem。父 MenuItem 已经为您生成了一个子 MenuItem,但是您的 DataTemplate 添加了第二个。试试这个:

<MenuItem Header="File}">
  <MenuItem Header="Preferences..." Command="{Binding ShowOptionsViewCommand}" />
  <Separator />
  <ItemsControl ItemsSource="{Binding RecentFiles}">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding DisplayPath}"/>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemContainerStyle>
      <Style TargetType="MenuItem">
        <Setter Property="Command" Value="{Binding DataContext.OpenRecentFileCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
        <Setter Property="CommandParameter" Value="{Binding}"/>
      </Style>
    </ItemsControl.ItemContainerStyle>
  </ItemsControl>
  <Separator />
  <MenuItem Header="Exit" Command="{Binding CloseCommand}" />
</MenuItem>

注意简化的DataTemplate,它只包含一个TextBlock,ItemContainerStyle 用于在生成的MenuItem 上设置属性。

【讨论】:

  • 这看起来很有希望,但我收到一个异常,指出 用于类型“MenuItem”的样式不能应用于类型“ContentPresenter”。你知道如何解决这个问题吗?
  • 没有正确阅读您的代码。您不能真正将生成的项目与非生成的项目混合在一起。摆脱 ItemsControl 并改为绑定顶级 MenuItem。只需将非生成项(如 Preferences)粘贴在菜单项集合中,或使用 CompositeCollection 将它们分开。
  • 我尝试使用 CompositeCollection,但由于 wpf 中的错误,我无法将 CollectionContainer 绑定到我的 VM。我不想将其他 MenuItem 放在列表中,因为它们不属于那里。所以最后,我只是创建了一个“最近的文件”子菜单(我已经解决了,但我真的很想在“文件”菜单中有这个列表)。
【解决方案2】:

我尝试按照 Kent Boogaart 的建议使用CompositeCollection,但由于bug in wpf 不允许在CollectionContainer 中使用RelativeSource 绑定,我无法使其工作。

我使用的解决方案是在自己的子菜单中将RecentFiles 绑定到集合通过 ItemsSource 属性。

我真的很想在“文件”菜单中有这个列表,但我想这是下一个最好的东西......

编辑

this article 的启发,我构建了一个自定义且更通用的MenuItemList

public class MenuItemList : Separator {

  #region Private Members

  private MenuItem m_Parent;
  private List<MenuItem> m_InsertedMenuItems;

  #endregion

  public MenuItemList() {
    Loaded += (s, e) => HookFileMenu();
  }

  private void HookFileMenu() {
    m_Parent = Parent as MenuItem;
    if (m_Parent == null) {
      throw new InvalidOperationException("Parent must be a MenuItem");
    }
    if (ParentMenuItem == m_Parent) {
      return;
    }
    if (ParentMenuItem != null) {
      ParentMenuItem.SubmenuOpened -= _FileMenu_SubmenuOpened;
    }
    ParentMenuItem = m_Parent;
    ParentMenuItem.SubmenuOpened += _FileMenu_SubmenuOpened;
  }

  private void _FileMenu_SubmenuOpened(object sender, RoutedEventArgs e) {
    DataBind();
  }

  #region Properties

  public MenuItem ParentMenuItem { get; private set; }

  #region ItemsSource

  /// <summary>
  /// ItemsSource Dependency Property
  /// </summary>
  public static readonly DependencyProperty ItemsSourceProperty =
      DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MenuItemList),
          new FrameworkPropertyMetadata(null,
              new PropertyChangedCallback(OnItemsSourceChanged)));

  /// <summary>
  /// Gets or sets a collection used to generate the content of the <see cref="MenuItemList"/>. This is a dependency property.
  /// </summary>
  public IEnumerable ItemsSource {
    get { return (IEnumerable) GetValue(ItemsSourceProperty); }
    set { SetValue(ItemsSourceProperty, value); }
  }

  /// <summary>
  /// Handles changes to the ItemsSource property.
  /// </summary>
  private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    ((MenuItemList) d).OnItemsSourceChanged(e);
  }

  /// <summary>
  /// Provides derived classes an opportunity to handle changes to the ItemsSource property.
  /// </summary>
  protected virtual void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e) {
    DataBind();
  }

  #endregion

  #region ItemContainerStyle

  /// <summary>
  /// ItemsContainerStyle Dependency Property
  /// </summary>
  public static readonly DependencyProperty ItemContainerStyleProperty =
      DependencyProperty.Register("ItemContainerStyle", typeof(Style), typeof(MenuItemList),
          new FrameworkPropertyMetadata((Style) null));

  /// <summary>
  /// Gets or sets the <see cref="System.Windows.Style"/> that is applied to the container element generated for each item. This is a dependency property.
  /// </summary>
  public Style ItemContainerStyle {
    get { return (Style) GetValue(ItemContainerStyleProperty); }
    set { SetValue(ItemContainerStyleProperty, value); }
  }

  #endregion

  #endregion

  private void DataBind() {
    RemoveMenuItems();
    InsertMenuItems();
  }

  private void RemoveMenuItems() {
    if (m_InsertedMenuItems != null) {
      foreach (var menuItem in m_InsertedMenuItems) {
        ParentMenuItem.Items.Remove(menuItem);
      }
    }
  }

  private void InsertMenuItems() {
    if (ItemsSource == null) {
      return;
    }
    if (ParentMenuItem != null) {
      m_InsertedMenuItems = new List<MenuItem>();
      int iMenuItem = ParentMenuItem.Items.IndexOf(this);
      foreach (var item in ItemsSource) {
        var menuItem = new MenuItem();
        menuItem.DataContext = item;
        menuItem.Style = ItemContainerStyle;
        ParentMenuItem.Items.Insert(++iMenuItem, menuItem);
        m_InsertedMenuItems.Add(menuItem);
      }
    }
  }

}

它远非完美,但它对我有用。随意评论...

【讨论】:

    【解决方案3】:

    尝试使用带有内部 ContentPresenter 的 HierarchicalDataTemplate。 Take a look at this SO answer for more details.

    【讨论】:

    • 您链接的 SO 答案实际上是针对子菜单的。我希望动态列表成为非动态菜单的一部分...您还有其他想法吗?
    • 所以基本上你想将一些动态的MenuItems合并到一个静态定义的Menu中? AFAIK,当您进行数据绑定时,要么绑定完整的 ItemsSource,要么手动构建内容(在 XAML 中或从代码中)。我要做的是将整个菜单数据绑定到 MenuItemViewModel,然后通过 API 确保只有动态列表是可编辑的。
    【解决方案4】:

    上面提到的错误已经修复。请注意,我已将最近的文件列表与 close 命令混合在一起。 “最近文件”列表位于其自己的菜单中。这有效:

    <MenuItem Header="Recent Files" ItemsSource="{Binding RecentFiles}">
    <MenuItem.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding DisplayPath}" ToolTip="{Binding FullPath}" />
        </DataTemplate>
    </MenuItem.ItemTemplate>
    <MenuItem.ItemContainerStyle>
        <Style TargetType="MenuItem">
            <Setter Property="Command" Value="{Binding DataContext.SelectRecentFileCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
            <Setter Property="CommandParameter" Value="{Binding}"/>
            <Setter Property="IsChecked" Value="{Binding IsChecked}" />
        </Style>
    </MenuItem.ItemContainerStyle>
    </MenuItem>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-15
      相关资源
      最近更新 更多