Search code examples
c#wpfmenumenuitem

get selected menuitem wpf


I have a menu and some submenus

MenuItems = new ObservableCollection<MenuItemViewModel>
        {

            new MenuItemViewModel { Header = "Select a Building",
                MenuItems = new ObservableCollection<MenuItemViewModel>
                {
                        new MenuItemViewModel { Header = "Building 4",
                        MenuItems = new ObservableCollection<MenuItemViewModel>
                            {
                                new MenuItemViewModel { Header = "< 500",
                                    MenuItems = new ObservableCollection<MenuItemViewModel>
                                    {
                                        new MenuItemViewModel {Header = "Executives" },
                                        new MenuItemViewModel {Header = "Engineers" },
                                        new MenuItemViewModel {Header = "Sales" },
                                        new MenuItemViewModel {Header = "Marketing"},
                                        new MenuItemViewModel {Header = "Support"}
                                      }
                                },
                                new MenuItemViewModel { Header = "500 - 999",
                                    MenuItems = new ObservableCollection<MenuItemViewModel>
                                    {
                                        new MenuItemViewModel {Header = "Executives" },
                                        new MenuItemViewModel {Header = "Engineers" },
                                        new MenuItemViewModel {Header = "Sales" },
                                        new MenuItemViewModel {Header = "Marketing"},
                                        new MenuItemViewModel {Header = "Support"}
                                     }
                                }
                             }
                    }
          }

I am trying to capture the value of each selection the user makes and display them in a listbox. For example a user selects "Building 4" then "500 - 999" then "support" as those values are selected they populated the list box. I have a function in MenuItemViewModel that is called Execute(), this will get the Header of the last value selected, I.e "support" but I cannot figure out how to get that value to the listbox.

Here is the ViewModel

public class MenuItemViewModel
{
    private readonly ICommand _command;
    string Value;

    public ObservableCollection<Cafe> Cafes
    {
        get;
        set;
    }

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

    public string Header { get; set; }

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

    public ICommand Command
    {
        get
        {
            return _command;
        }
    }

    public void Execute()
    {

        MessageBox.Show("Clicked at " + Header);

    }

}

And finally the xaml for the MenuItem and ListBox:

<Menu x:Name="buildingMenu" Margin="0,15,0,0" HorizontalAlignment="Left" Height="20" Width="200" ItemsSource="{Binding MenuItems}" >
        <Menu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="Command" Value="{Binding Command}" />
                </Style>
            </Menu.ItemContainerStyle>
        <Menu.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
                <TextBlock Text="{Binding Header}"/>
            </HierarchicalDataTemplate>
        </Menu.ItemTemplate>
        </Menu>

<ListBox Name="selectionListBox" HorizontalAlignment="Right" Height="179" Margin="0,177,55,0" VerticalAlignment="Top" Width="500" />

I have tried to add the Header to a List in the ViewModel but I cannot get anything work. Is there something similar to a combobox's SelectedValue that can be used here? Thanks for the help


Solution

  • This is actually a lot harder to do than you might expect, because you're using menus in a way they're not really designed to be used.

    First of all you're going to need a list of items in your view model to bind to, eg:

    public ObservableCollection<string> ListItems { get; set; }
    

    And the corresponding binding in your ListBox:

    <ListBox ItemsSource="{Binding ListItems}" Name="selectionListBox" />
    

    Next, you'll need to populate this list with items as they're being clicked. This is tricky with the way you've done things at the moment because ListItems will need to be in your main view model, yet your command handler is in MenuItemViewModel. This means you'll either need to add a way for MenuItemViewModel to call it's parent, or better yet move the command handler to the main view model (so that it's now shared) and have the MenuItems pass in their data context object. I use MVVM Lite and in that framework you do something like this:

    private ICommand _Command;
    public ICommand Command
    {
        get { return (_Command = (_Command ?? new RelayCommand<MenuItemViewModel>(Execute))); }
    }
    
    public void Execute(MenuItemViewModel menuItem)
    {
        // ... do something here...
    }
    

    You don't seem to be using MVVM Lite, but whatever your framework it is it will have support for passing a parameter into your execute function, so I'll leave that for you to look up. Either way your MenuItem Command bindings will now all need to be modified to point to this common handler:

    <Style TargetType="{x:Type MenuItem}">
        <Setter Property="Command" Value="{Binding ElementName=buildingMenu, Path=DataContext.Command}" />
    </Style>
    

    Your other requirement is for the list to populate as each submenu item is selected, and this is where things get nasty. Submenus don't trigger commands when they close, they trigger SubmenuOpened and SubmenuClosed events. Events can't bind to the command handlers in your view model, so you have to use code-behind handlers instead. If you were declaring your MenuItems explicitly in XAML you could just set them directly there, but your Menu is binding to a collection, so your MenuItems are being populated by the framework and you'll have to set the handler by adding an EventSetter to your style:

    <Style TargetType="{x:Type MenuItem}">
        <EventSetter Event="SubmenuOpened" Handler="OnSubMenuOpened" />
        <Setter Property="Command" Value="{Binding ElementName=buildingMenu, Path=DataContext.Command}" />
    </Style>
    

    This works, in that calls the specified handler function in your MainWindow code-behind:

        private void OnSubmenuOpened(object sender, RoutedEventArgs e)
        {
            // uh oh, what now?
        }
    

    The problem of course is that event handlers exist in the view, but in MVVM we need it in the view model. If you're happy to corrupt your views like this for the sake of getting your application to work then check this SO question for some example code showing how to get it working. However, if you're a hard-core MVVM purist like myself you'll probably want a solution that doesn't involve code-behind, and in that case you'll again need to refer to your framework. MVVM Lite provides a behavior that allows you to convert events to command, you typically use it like this:

    <MenuItem>
            <i:Interaction.Triggers xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
                <i:EventTrigger EventName="OnSubmenuOpened">
                    <cmd:EventToCommand Command="{Binding SubmenuOpenedCommand}"  />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    

    The problem with behaviours though is that you can't set them in a style. Styles are applied to all objects, which is why EventSetter works fine. Behaviours have to be created for each object that they're used for.

    So the final piece of the puzzle is that if you have cases in MVVM where you need to set a behaviour in a style then you'll need to use the solution posted by vspivak in his article Using System.Windows.Interactivity Behaviors and Actions in WPF/Silverlight styles. I've used this myself in commercial projects, and it's a nice general solution to the bigger problem of redirecting event handlers to command handlers in styles.

    Hope that answers your question. I'm sure it seems ridiculously convoluted, but what you're trying to do is a fairly pathological scenario well outside how WPF is typically used.