【问题标题】:Mixing dynamic and static XAML menu items混合动态和静态 XAML 菜单项
【发布时间】:2009-05-26 15:09:43
【问题描述】:

我有一个场景,我需要同时拥有静态和动态菜单项。静态项将在 XAML 中定义,动态项由视图模型提供。每个动态项本身将由一个 VieModel 表示,我们称之为 CommandViewModel。 CommandViewModel 有一个显示名称,它还可以包含其他 CommandViewModel。

用作菜单数据上下文的 MainViewModel 如下:

public class MainMenuViewModel : INotifyPropertyChanged
{

  private ObservableCollection<CommandViewModel> m_CommandVMList;


  public MainMenuViewModel()
  {
    m_ CommandVMList = new ObservableCollection<CommandViewModel>();

    CommandViewModel cmv = new CommandViewModel();
    cmv.DisplayName = "Dynamic Menu 1";
    m_CommandVMList.Add(cmv);

    cmv = new CommandViewModel();
    cmv.DisplayName = "Dynamic Menu 2";
    m_CommandVMList.Add(cmv);

    cmv = new CommandViewModel();
    cmv.DisplayName = "Dynamic Menu 3";
    m_CommandVMList.Add(cmv);

  }

  public ObservableCollection<CommandViewModel> CommandList
  {
    get { return m_CommandVMList; }
    set
    {
      m_CommandVMList = value;
      OnPropertyChanged("CommandList");
    }
  }

… … …

菜单 XAML:

<Grid>
  <Grid.Resources>
    <HierarchicalDataTemplate DataType="{x:Type Fwf:CommandViewModel}" ItemsSource="{Binding Path=CommandViewModels}">
      <MenuItem Header="{Binding Path=DisplayName}"/>
    </HierarchicalDataTemplate>
  </Grid.Resources>

  <Menu VerticalAlignment="Top" HorizontalAlignment="Stretch">
    <MenuItem Header="Static Top Menu Item 1">
      <MenuItem Header="Static Menu Item 1"/>
        <MenuItem Header="Static Menu Item 2"/>
        <MenuItem Header="Static Menu Item 3"/>
        <ItemsControl ItemsSource="{Binding Path= CommandList}"/>
        <MenuItem Header="Static Menu Item 4"/>
      </MenuItem>
  </Menu>
</Grid>

除了我试图表示动态菜单列表之外,一切都很好,在这种情况下,它是一个 ItemsControl,它在 UI 上显示为一个包含更多菜单项的菜单项,因此动态菜单项的整个集合都被选中当您单击该项目时。集合得到正确表示,因为每个动态菜单项都显示为菜单项本身,但在这个更大的菜单项中。我想我明白为什么菜单只是为每个包含的项目创建一个菜单项,静态或动态它并不关心。有没有办法让每个动态菜单项与示例中的静态菜单项在同一级别上创建并属于父菜单项?

【问题讨论】:

    标签: c# wpf xaml


    【解决方案1】:

    我不会在 XAML 端硬编码您的“静态”菜单项,而是在 VM 端将它们硬编码为 CommandViewModel 对象。

    由于您以任何一种方式对其进行硬编码,因此您不会失去灵活性,并且如果您将来选择以不同方式呈现它们,您将获得使静态菜单项与 HierarchicalDataTemplate 保持同步的额外好处。

    请注意,您可能必须更改您的绑定,以便您的菜单绑定到菜单项的集合。你可以找到这个here的例子。

    编辑:代码示例

    我能够相当快地解决这个问题,并且大多数类定义都不完整(例如 INotifyPropertyChanged),但它应该让您了解您可以做什么。我在第三个命令上添加了一些命令嵌套,以确保 Hierarchical DataTemplate 正常工作。

    这是 XAML

    <Window
        x:Class="WPFDynamicMenuItems.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WPFDynamicMenuItems"
        Title="Window1" Height="300" Width="600">
        <Grid>
            <Grid.Resources>
                <HierarchicalDataTemplate DataType="{x:Type local:CommandViewModel}" ItemsSource="{Binding Path=CommandList}">
                    <ContentPresenter
                        Content="{Binding Path=DisplayName}"
                        RecognizesAccessKey="True" />
                </HierarchicalDataTemplate>
            </Grid.Resources>
            <ToolBarTray>
                <ToolBar>
                <Menu>
                    <Menu.ItemsSource>
                        <CompositeCollection>
                            <MenuItem Header="A"></MenuItem>
                            <MenuItem Header="B"></MenuItem>
                            <MenuItem Header="C"></MenuItem>
    
                            <CollectionContainer x:Name="dynamicMenuItems">
                            </CollectionContainer>
    
                            <MenuItem Header="D"></MenuItem>
    
                        </CompositeCollection>
                    </Menu.ItemsSource>
    
                </Menu>
                    </ToolBar>
            </ToolBarTray>
        </Grid>
    </Window>
    

    下面是代码隐藏:

    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Windows;
    
    namespace WPFDynamicMenuItems
    {
        /// <summary>
        /// Interaction logic for Window1.xaml
        /// </summary>
        public partial class Window1 : Window
        {
            private MainMenuViewModel _mainMenuVM = new MainMenuViewModel();
    
            public Window1()
            {
                InitializeComponent();
    
                this.dynamicMenuItems.Collection = this._mainMenuVM.CommandList;
            }
        }
    
    
        public class MainMenuViewModel : INotifyPropertyChanged
        {
            private ObservableCollection<CommandViewModel> m_CommandVMList;
    
            public MainMenuViewModel()
            {
                m_CommandVMList = new ObservableCollection<CommandViewModel>();
                CommandViewModel cmv = new CommandViewModel();
                cmv.DisplayName = "Dynamic Menu 1";
                m_CommandVMList.Add(cmv);
                cmv = new CommandViewModel();
                cmv.DisplayName = "Dynamic Menu 2";
                m_CommandVMList.Add(cmv);
                cmv = new CommandViewModel();
                cmv.DisplayName = "Dynamic Menu 3";
                m_CommandVMList.Add(cmv);
    
                CommandViewModel nestedCMV = new CommandViewModel();
                nestedCMV.DisplayName = "Nested Menu 1";
                cmv.CommandList.Add(nestedCMV);
    
                nestedCMV = new CommandViewModel();
                nestedCMV.DisplayName = "Nested Menu 2";
                cmv.CommandList.Add(nestedCMV);
            }
            public ObservableCollection<CommandViewModel> CommandList
            {
                get { return m_CommandVMList; }
                set { m_CommandVMList = value; OnPropertyChanged("CommandList"); }
            }
    
            protected void OnPropertyChanged(string propertyName)
            {
                //  Hook up event...
            }
    
            #region INotifyPropertyChanged Members
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            #endregion
        }
    
        public class CommandViewModel : INotifyPropertyChanged
        {
            private ObservableCollection<CommandViewModel> m_CommandVMList;
    
            public CommandViewModel()
            {
                this.m_CommandVMList = new ObservableCollection<CommandViewModel>();
            }
    
            public string DisplayName { get; set; }
    
            public ObservableCollection<CommandViewModel> CommandList
            {
                get { return m_CommandVMList; }
                set { m_CommandVMList = value; OnPropertyChanged("CommandList"); }
            }
    
            protected void OnPropertyChanged(string propertyName)
            {
                //  Hook up event...
            }
    
            #region INotifyPropertyChanged Members
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            #endregion
        }
    }
    

    【讨论】:

    • 是的,我希望我能做到,但不幸的是我做不到,原因主要是 XAML 几乎不在我的掌控之中。它由应用程序开发人员创建/管理,他们只是使用我的模型来增加他们的菜单,其中包含需要从代码驱动的内容。
    • 那很不幸。有什么方法可以更改 XAML 以利用 CompositeCollection (msdn.microsoft.com/en-us/library/…)?这并不理想,但它可能允许您注入动态项目。我能看到的唯一缺点是生成的 MenuItems 是如何排序的,但是您可以通过将之前的 MenuItems 视为一个集合,将之后的 MenuItems 视为另一个来做到这一点。
    • Micahtan - 也许您应该将 CompositeCollection 类的解决方案写到您的帖子中?因为这似乎是一个正确的方法。谢谢,我也遇到了同样的问题。
    • arconaut - 享受。但是请在投入生产之前清理它:)
    • 谢谢 :) 但我并没有真正为自己询问代码,只是为了让这个成为一个很好的问答主题并刺激将您的帖子标记为 Adrian 的答案。不过,他似乎并不在意,或者答案不适合他:)
    猜你喜欢
    • 2018-04-14
    • 2018-04-04
    • 2019-09-05
    • 1970-01-01
    • 2011-12-21
    • 1970-01-01
    • 1970-01-01
    • 2019-03-28
    • 1970-01-01
    相关资源
    最近更新 更多