MVVM Dynamic Menu UI from binding with ViewModel

前端 未结 5 1223
南方客
南方客 2020-11-29 00:53

I am working with a team on LoB application. We would like to have a dynamic Menu control, which creates the menu based on the logged in user profile. In previo

相关标签:
5条回答
  • 2020-11-29 01:23

    If you're wondering how to do separators it's really quite easy.

    The code below is part of my ViewModel. Since XAML uses reflection all I need to do is to return 'object' which can be a MenuItemViewModel, Separator, or (if for some wierd reason I needed to) an actual MenuItem.

    I'm using yield to dynamically generate the items because it just seems to read better for me. Even though I'm using yield - if the items change I still need to raise a PropertyChanged event for "ContextMenu" as usual but I don't unnecessarily generate the list until it's needed.

        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;
        }
    
    0 讨论(0)
  • 2020-11-29 01:26

    This solution doesn't need any code in code behind and that makes it simpler solution.

            <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>
    

    And MenuItem is represented as:

    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()
            {
    
            }
        }
    

    The concrete MenuItems can be either instantiated directly or you could make your own SubTypes through inheritance.

    0 讨论(0)
  • 2020-11-29 01:26

    I know this is an old post but I need this plus how to bind Commands.

    As to Guge's question on how to bind Commands: VMMenuItems is a property in my view model class of type

    ObservableCollection<Menu>
    

    and Menu is the class defined above. The MenuItem's Command Property is being bound to the Command Property of the Menu class. In my view model class

    Menu.Command = _fou
    

    where

    private ICommand _fou;
    

    The xaml

    <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>
    
    0 讨论(0)
  • 2020-11-29 01:35

    This should get you where you are going

    <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}"/>
    

    Note that in my example, my menu Item has a property of type ICommand called Command.

    0 讨论(0)
  • 2020-11-29 01:36

    Try something like this:

    public class MenuItemViewModel
    {
        public MenuItemViewModel()
        {
            this.MenuItems = new List<MenuItemViewModel>();
        }
    
        public string Text { get; set; }
    
        public IList<MenuItemViewModel> MenuItems { get; private set; }
    }
    

    Assume that your DataContext has a property called MenuItems which is a list of MenuItemViewModel. Something like this should work, then:

    <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>
    
    0 讨论(0)
提交回复
热议问题