MVVM Dynamic Menu UI from binding with ViewModel
我正在与LoB应用程序团队合作。我们希望有一个动态的
解决方案:
借助注释器的输入,我能够将
XAML:
1 2 3 4 5 6 7 8 9 10 11 12 | <HierarchicalDataTemplate DataType="{x:Type self:Menu}" ItemsSource="{Binding Path=Children, UpdateSourceTrigger=PropertyChanged}"> <ContentPresenter Content="{Binding Path=MenuText}" RecognizesAccessKey="True"/> </HierarchicalDataTemplate> [...] <Menu Height="21" Margin="0" Name="mainMenu" VerticalAlignment="Top" HorizontalAlignment="Stretch" ItemsSource="{Binding Path=MenuItems, UpdateSourceTrigger=PropertyChanged}" ItemContainerStyle="{StaticResource TopMenuItems}"> <Menu.Background> <ImageBrush ImageSource="/Wpf.Modules;component/Images/MenuBg.jpg" /> </Menu.Background> </Menu> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | public class Menu : ViewModelBase { public Menu() { IsEnabled = true; Children = new List<Menu>(); } #region [ Menu Properties ] private bool _isEnabled; private string _menuText; private ICommand _command; private IList<Menu> _children; public string MenuText { get { return _menuText; } set { _menuText = value; base.OnPropertyChanged("MenuText"); } } public bool IsEnabled { get { return _isEnabled; } set { _isEnabled = value; base.OnPropertyChanged("IsEnabled"); } } public ICommand Command { get { return _command; } set { _command = value; base.OnPropertyChanged("Command"); } } public IList<Menu> Children { get { return _children; } set { _children = value; } } #endregion } |
尝试类似这样的内容:
1 2 3 4 5 6 7 8 9 10 11 | public class MenuItemViewModel { public MenuItemViewModel() { this.MenuItems = new List<MenuItemViewModel>(); } public string Text { get; set; } public IList<MenuItemViewModel> MenuItems { get; private set; } } |
假定您的DataContext具有一个名为MenuItems的属性,该属性是MenuItemViewModel的列表。这样的事情应该起作用,然后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:self="clr-namespace:WpfApplication1" Title="Window1" Height="300" Width="300"> <Window.Resources> <HierarchicalDataTemplate DataType="{x:Type self:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}"> <ContentPresenter Content="{Binding Path=Text}" /> </HierarchicalDataTemplate> </Window.Resources> <DockPanel> <Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" /> <Grid /> </DockPanel> </Window> |
这应该带您去哪里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <UserControl x:Class="WindowsUI.Views.Default.MenuView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ViewModels="clr-namespace:WindowsUI.ViewModels" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <UserControl.Resources> <Style TargetType="{x:Type MenuItem}"> <Setter Property="Header" Value="{Binding Path=DisplayName}"/> <Setter Property="Command" Value="{Binding Path=Command}"/> </Style> <HierarchicalDataTemplate DataType="{x:Type ViewModels:MenuItemViewModel}" ItemsSource="{Binding Path=Items}"> </HierarchicalDataTemplate> </UserControl.Resources> <Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=Items}"/> |
请注意,在我的示例中,菜单项具有ICommand类型的属性,称为Command。
此解决方案不需要在代码中包含任何代码,因此使它变得更简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <Menu> <MenuItem ItemsSource="{Binding Path=ChildMenuItems}" Header="{Binding Path=Header}"> <MenuItem.Resources> <HierarchicalDataTemplate DataType="{x:Type vm:MenuItemViewModel}" ItemsSource="{Binding ChildMenuItems}"> <MenuItem Header="{Binding Path=Header}" Command="{Binding Path=Command}"/> </HierarchicalDataTemplate> <DataTemplate DataType="{x:Type vm:SeparatorViewModel}"> <Separator> <Separator.Template> <ControlTemplate> <Line X1="0" X2="1" Stroke="Black" StrokeThickness="1" Stretch="Fill"/> </ControlTemplate> </Separator.Template> </Separator> </DataTemplate> </MenuItem.Resources> </MenuItem> </Menu> |
并且MenuItem表示为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | public class MenuItemViewModel : BaseViewModel { /// <summary> /// Initializes a new instance of the <see cref="MenuItemViewModel"/> class. /// </summary> /// <param name="parentViewModel">The parent view model.</param> public MenuItemViewModel(MenuItemViewModel parentViewModel) { ParentViewModel = parentViewModel; _childMenuItems = new ObservableCollection<MenuItemViewModel>(); } private ObservableCollection<MenuItemViewModel> _childMenuItems; /// <summary> /// Gets the child menu items. /// </summary> /// <value>The child menu items.</value> public ObservableCollection<MenuItemViewModel> ChildMenuItems { get { return _childMenuItems; } } private string _header; /// <summary> /// Gets or sets the header. /// </summary> /// <value>The header.</value> public string Header { get { return _header; } set { _header = value; NotifyOnPropertyChanged("Header"); } } /// <summary> /// Gets or sets the parent view model. /// </summary> /// <value>The parent view model.</value> public MenuItemViewModel ParentViewModel { get; set; } public virtual void LoadChildMenuItems() { } } |
具体的MenuItems可以直接实例化,也可以通过继承来创建自己的SubType。
我知道这是一篇老文章,但是我需要它以及如何绑定命令。
关于Guge关于如何绑定命令的问题:
VMMenuItems是我的视图模型类中的属性,类型为
1 | ObservableCollection<Menu> |
和Menu是上面定义的类。 MenuItem \\的Command属性绑定到Menu类的Command属性。
在我看来,模型类
1 | Menu.Command = _fou |
其中
1 | private ICommand _fou; |
xaml
1 2 3 4 5 6 7 8 9 | <ListView.ContextMenu> <ContextMenu ItemsSource="{Binding Path=VMMenuItems}"> <ContextMenu.ItemContainerStyle> <Style TargetType="{x:Type MenuItem}"> <Setter Property="Command" Value="{Binding Command}"/> </Style> </ContextMenu.ItemContainerStyle> </ContextMenu> </ListView.ContextMenu> |
如果您想知道如何进行分隔符,那真的很容易。
下面的代码是我的ViewModel的一部分。由于XAML使用反射,所以我要做的就是返回\\'object \\',它可以是
我正在使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public IEnumerable<object> ContextMenu { get { // ToArray() needed or else they get garbage collected return GetContextMenu().ToArray(); } } public IEnumerable<object> GetContextMenu() { yield return new MenuItemViewModel() { Text ="Clear all flags", }; // adds a normal 'Separator' menuitem yield return new Separator(); yield return new MenuItemViewModel() { Text ="High Priority" }; yield return new MenuItemViewModel() { Text ="Medium Priority" }; yield return new MenuItemViewModel() { Text ="Low Priority" }; yield break; } |