混合动态和静态XAML菜单项

时间:2009-05-26 15:09:43

标签: c# wpf xaml

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

用作菜单的datacontext的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>

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

1 个答案:

答案 0 :(得分:5)

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

由于您无论采用哪种方式进行硬编码,都不会失去灵活性,如果您选择在将来以不同方式呈现静态菜单项,您将获得保持静态菜单项与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
    }
}