简体   繁体   中英

Dynamic menu in WPF

How to add menu items to menu control (not contextmenu) in WPF from a database table with Bindings and Observable collections?. I have this menu:

<Menu HorizontalAlignment="Left" Height="27" VerticalAlignment="Top" Width="649">
    <MenuItem Header="_File">
       <MenuItem Header="_Exit" Command="{Binding ExitCommand}"/>
       </MenuItem>
    <MenuItem Header="_MyMenu">
       <MenuItem Header="_SubMenu1" Command="{Binding  SubMenu1Command}" />
       <MenuItem Header="_SubMenu2" Command="{Binding  SubMenu2Command}" />
    </MenuItem>
</Menu>

The "SubMenu1" and "_SuMenu2" are values from the database table:

codSubMenu | SubMenuColum | CommandColumn

1_ _ __ _ __ _ __ | SubMenu1 _ __ _ _ | SubMenu1Command 2 _ __ _ __ _ ___ | SubMenu2 _ ____ |_SubMenu2Command

I need something this:

<Menu HorizontalAlignment="Left" Height="27" VerticalAlignment="Top" Width="649"
    ItemsSource="{Binding ObservableCollectionMenu}">
    <MenuItem Header="_File">
       <MenuItem Header="_Exit" Command="{Binding ExitCommand}"/>
    </MenuItem>
    <MenuItem Header="_MyMenu">
        <MenuItem Header="{Binding  ObservableCollectionMenu.SubMenuColumn}" Command="{Binding  ObservableCollectionMenu.CommandColumn}" />
    </MenuItem>
</Menu>

When I run the app the menu must show this when I press the options File and MyMenu:

File | MyMenu

Exit | SubMenu1

___ | SubMenu2

Use the ItemsSource property of the Menu and the MenuItems (in a style) to bind your collections:

<Menu ItemsSource="{Binding YourCollection}" />

and

<Style TargetType="MenuItem">
    <Setter Property="Header" Value="{Binding Path=Name}" />
    <Setter Property="ItemsSource" Value="{Binding Path=Children}" />
</Style>

Edit: For command binding do the following:

  1. Add a setter like this to the template of the MenuItem:

     <Setter Property="Command" Value="{Binding Path=Command}" /> 
  2. Use this structure for a MenuItem view model:

     public class BindableMenuItem { public string Name { get; set; } public BindableMenuItem[] Children { get; set; } public ICommand Command { get; set; } } 
  3. Add the root items to a collection of BindableMenuItems and bind this collection to the menu.

This is how I solved it,

I created a MenuItem class (notice that it has a list of Items so you can build sub-menus):

public class MenuItem : ModelBase<MenuItem>
{
    private List<MenuItem> _Items;

    public MenuItem(string header, ICommand command)
    {
        Header = header;
        Command = command;
    }

    public MenuItem()
    {

    }

    public string Header { get; set; }

    public List<IMenuItem> Items
    {
        get { return _Items ?? (_Items = new List<IMenuItem>()); }
        set { _Items = value; }
    }

    public ICommand Command { get; set; }
    public string CommandName { get; set; }
    public object Icon { get; set; }
    public bool IsCheckable { get; set; }
    private bool _IsChecked;
    public bool IsChecked
    {
        get { return _IsChecked; }
        set
        {
            _IsChecked = value;
            NotifyPropertyChanged(m=>m.IsChecked);
        }
    }

    public bool Visible { get; set; }
    public bool IsSeparator { get; set; }
    public string InputGestureText { get; set; }
    public string ToolTip { get; set; }
    public int MenuHierarchyID { get; set; }
    public int ParentMenuHierarchyID { get; set; }
    public string IconPath { get; set; }
    public bool IsAdminOnly { get; set; }
    public object Context { get; set; }
    public IMenuItem Parent { get; set; }
    public int int_Sequence { get; set; }
    public int int_KeyIndex { get; set; }
}

And a View:

<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MainMenu}">
    <Menu.ItemContainerStyle>
        <Style>
            <Setter Property="MenuItem.Header" Value="{Binding Path=Header}" />
            <Setter Property="MenuItem.ItemsSource" Value="{Binding Path=Items}" />
            <Setter Property="MenuItem.Icon" Value="{Binding Path=Icon}" />
            <Setter Property="MenuItem.IsCheckable" Value="{Binding Path=IsCheckable}" />
            <Setter Property="MenuItem.IsChecked" Value="{Binding Path=IsChecked,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
            <Setter Property="MenuItem.Command" Value="{Binding Path=Command}" />
            <!--<Setter Property="MenuItem.CommandParameter" Value="{Binding Path=IsChecked}"/>-->
            <Setter Property="MenuItem.CommandParameter" Value="{Binding Path=.}"/>
            <Setter Property="MenuItem.InputGestureText" Value="{Binding Path=InputGestureText}"/>
            <Setter Property="MenuItem.ToolTip" Value="{Binding Path=ToolTip}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=IsSeparator}" Value="true">
                    <Setter Property="MenuItem.Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type MenuItem}">
                                <Separator Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}" />
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Menu.ItemContainerStyle>
</Menu>

Where MainMenu is an ObservableCollection property in my main ViewModel, which you can populate from your database.

    public ObservableCollection<MenuItem> MainMenu
    {
        get { return _MainMenu; }
        set
        {
            _MainMenu = value;
            NotifyPropertyChanged(x => x.MainMenu);
        }
    }

I don't have a quick solution in XAML. I needed get submenus items from database, according specific profiles, some users had all items others only 2 or 3 items. The unique way is create the menu in XAML with disabled items, pass the menu reference to ViewModel(if is MVVM App) and compare with the ObservableCollection, only the items equals are enabled:

<menu horizontalalignment="Left" height="27" verticalalignment="Top" width="649" name="menu1">
      <menuitem header="_File">
          <menuitem header="_Exit" command="{Binding ExitCommand}" />
      </menuitem>
      <menuitem header="_MyMenu">
          <menuitem header="_SubMenu1" command="{Binding  Command1}" isenabled="False" />
          <menuitem header="_SubMenu2" command="{Binding  Command2}" isenabled="False" />
      </menuitem>
</menu>

ViewModel:

for (int i = 0; i < ObservableCollectionMenu.Count; i++)
{

    for (int j = 0; j < ((MenuItem)menu1.Items[1]).Items.Count; j++)
    {
         if (((MenuItem)((MenuItem)menu1.Items[1]).Items[j]).Header.ToString().Equals(ObservableCollectionMenu[i].SubMenuColumn))
         {
            ((MenuItem)((MenuItem)menu1.Items[1]).Items[j]).IsEnabled = true;
             break;
         }
    }
}

Thanks to all who answered my question, stackoverflow has better help that codeproject.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM