28

I am trying to create a dynamic menu using binding. I my viewmodel I have a list of objects which contains an header and a command. However, it is not working. I think the problem is in the data template. See my code below:

<Menu Background="{x:Null}" Grid.Row="0" Grid.Column="1" Panel.ZIndex="2" Width="865" Height="85" HorizontalAlignment="Left" ItemsSource="{Binding Path=MenuItems}">

        <Menu.ItemTemplate>
            <HierarchicalDataTemplate DataType="MenuItemViewModel" ItemsSource="{Binding Path=MenuItems}">
                <MenuItem Header="{Binding Header}" Style="{DynamicResource MenuItemStyle1}" ItemsSource="{Binding Path=MenuItems}" Padding="10,12,10,0" Height="44.1" Margin="30,0,0,0" FontWeight="Bold">
                    <MenuItem.ItemsPanel>
                        <ItemsPanelTemplate>
                            <VirtualizingStackPanel Orientation="Horizontal"/>
                        </ItemsPanelTemplate>
                    </MenuItem.ItemsPanel>
                </MenuItem>
                <HierarchicalDataTemplate.ItemTemplate>
                    <DataTemplate>
                        <MenuItem Header="{Binding Header}" Style="{DynamicResource MenuItemStyle1}" Padding="0,8,0,0" Height="38">
                        </MenuItem>
                    </DataTemplate>
                </HierarchicalDataTemplate.ItemTemplate>
            </HierarchicalDataTemplate>
        </Menu.ItemTemplate>            
    </Menu>

The result only shows the first menu. The submenus are not shown but they are there since the menus which have children, the arrow is print after menu header.

Could anyone find something wrong on the binding? Or any suggestion?

Just for information, MenuItems is a list of MenuItemViewModel objects which has an header and a list of MenuItemViewModel objects (submenus) called MenuItems too.

Jannibelli
  • 331
  • 1
  • 5
  • 10

3 Answers3

57

For me, it worked with this simple template:

<Menu.ItemContainerStyle>
    <Style TargetType="{x:Type MenuItem}">
        <Setter Property="Command" Value="{Binding Command}" />
    </Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
    <HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
        <TextBlock Text="{Binding Header}"/>
    </HierarchicalDataTemplate>
</Menu.ItemTemplate>

Here is the complete example:

MainWindow.xaml:

<Window x:Class="WpfApplication14.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication14"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}">
            <Menu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="Command" Value="{Binding Command}" />
                </Style>
            </Menu.ItemContainerStyle>
            <Menu.ItemTemplate>
                <HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
                    <TextBlock Text="{Binding Header}"/>
                </HierarchicalDataTemplate>
            </Menu.ItemTemplate>
        </Menu>
        <Grid>
        </Grid>
    </DockPanel>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication14
{
    public partial class MainWindow : Window
    {
        public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            MenuItems = new ObservableCollection<MenuItemViewModel>
            {
                new MenuItemViewModel { Header = "Alpha" },
                new MenuItemViewModel { Header = "Beta",
                    MenuItems = new ObservableCollection<MenuItemViewModel>
                        {
                            new MenuItemViewModel { Header = "Beta1" },
                            new MenuItemViewModel { Header = "Beta2",
                                MenuItems = new ObservableCollection<MenuItemViewModel>
                                {
                                    new MenuItemViewModel { Header = "Beta1a" },
                                    new MenuItemViewModel { Header = "Beta1b" },
                                    new MenuItemViewModel { Header = "Beta1c" }
                                }
                            },
                            new MenuItemViewModel { Header = "Beta3" }
                        }
                },
                new MenuItemViewModel { Header = "Gamma" }
            };

            DataContext = this;
        }
    }

    public class MenuItemViewModel
    {
        private readonly ICommand _command;

        public MenuItemViewModel()
        {
            _command = new CommandViewModel(Execute);
        }

        public string Header { get; set; }

        public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }

        public ICommand Command
        {
            get
            {
                return _command;
            }
        }

        private void Execute()
        {
            // (NOTE: In a view model, you normally should not use MessageBox.Show()).
            MessageBox.Show("Clicked at " + Header);
        }
    }

    public class CommandViewModel : ICommand
    {
        private readonly Action _action;

        public CommandViewModel(Action action)
        {
            _action = action;
        }

        public void Execute(object o)
        {
            _action();
        }

        public bool CanExecute(object o)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged
        {
            add { }
            remove { }
        }
    }
}

The resulting window looks like this:

screen shot

  • Thanks! The menu is building properly. However, how can I binding the command inside the MenuItemViewModel? – Jannibelli May 30 '14 at 11:40
  • Yes, that is possible. Please see my updated example above. When clicking at a menu item, a message box with the menu item's header text appears. –  May 30 '14 at 13:28
  • I can't add `Separator` (( – Andrei Krasutski Mar 06 '18 at 13:56
  • – AJAY KUMAR Aug 18 '21 at 08:56
3

that is very easy,you can use this code for your nested menu

ViewModel: TopMenuViewModel.cs

public partial class TopMenuViewModel 
{
    public TopMenuViewModel()
    {
        TopMenuItems = new ObservableCollection<MenuItem>
        {
            new MenuItem
            {
                Title = "File",
                PageName =typeof(OfficeListView).FullName,
                ChildMenuItems= {
                    new MenuItem
                    {
                        Title = "New"
                    },
                     new MenuItem
                    {
                        Title = "Open"
                    },
                     new MenuItem
                    {
                        Title = "Save"
                    }
                }
            },
            new MenuItem
            {
                Title = "Edit"
            },
            new MenuItem
            {
                Title = "Search"
            }
        };
    }

View: TopMenuView.xaml

<Menu IsMainMenu="True" ItemsSource="{Binding TopMenuItems}">
            <Menu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="Header" Value="{Binding Title}"/>
                    <Setter Property="ItemsSource" Value="{Binding Path=ChildMenuItems}"/>
                </Style>
            </Menu.ItemContainerStyle>
</Menu>   
pejman
  • 740
  • 7
  • 13
2

If like me you are keen to keep UI constructs in the XAML code, after some difficulty, I have worked a nice way of binding custom-typed collections to create menu items.

In XAML:

<Menu DockPanel.Dock="Top">
    <MenuItem Header="SomeHeaderName" ItemsSource="{Binding Path=MyCollection}">
        <MenuItem.ItemsContainerStyle>
            <Setter Property="Header" Value="{Binding Path=SomeRelevantTextProperty}"/>
            <EventSetter Event="Click" Handler="SomeMenuItemClickEventHandler"/>
        </MenuItem.ItemsContainerStyle>
    </MenuItem>
</Menu>

In code-behind:

ObservableCollection<MyClass> MyCollection;

private void SomeMenuItemClickEventHandler(object sender, RoutedEventArgs e)
{
    MenuItem menuItem = sender as MenuItem;
    MyClass myClass = menuItem.DataContext as MyClass;
    // do something useful!
}
public class MyClass
{
    public string SomeRelevantTextProperty { get; }
}
PJRobot
  • 1,306
  • 13
  • 14