Click here to Skip to main content
15,868,141 members
Articles / Desktop Programming / WPF

WPF Data Bound Menus

Rate me:
Please Sign up or sign in to vote.
5.00/5 (16 votes)
3 Jul 2009CPOL3 min read 81.7K   2.6K   46   6
Use a combination of data-binding techniques to create WPF menus that change with your application
DataBoundMenus

Introduction

There is not just one way to make a WPF menu work. There are several techniques that you have to combine. Much of an application's menu is static. Some of it is context sensitive, and only appears in certain conditions. And in a few places, the menu items are dynamic, like recently opened files or currently open windows.  This article demonstrates techniques for each case.

The sample code uses and includes an open source library called Update Controls that helps with data binding. Although the sample code depends upon it, these techniques themselves will work without Update Controls, except where otherwise stated.

Declarative Menu Structure

For the static menus, you want to declare the structure entirely in XAML. This gives you the greatest design/code separation, and the best tool support. Bind each of the menu items to an ICommand property in your view model.

XML
<Menu DockPanel.Dock="Top">
    <MenuItem Header="_File">
        <MenuItem Header="_New" Command="{Binding FileNewCommand}"/>
        <MenuItem Header="_Open" Command="{Binding FileOpenCommand}"/>
        <MenuItem Header="_Save" Command="{Binding FileSaveCommand}"/>
        <MenuItem Header="_Close" Command="{Binding FileCloseCommand}"/>
    </MenuItem>
</Menu> 

You can use update controls MakeCommand to create all of the bindable ICommand properties. The When clause will enable and disable the menu item.

C#
public ICommand FileSaveCommand
{
    get
    {
        // We can only save a file when one is open.
        return MakeCommand
            .When(() => _dataModel.OpenFileName != null)
            .Do(() => _dataModel.LastAction = "Save");
    }
} 

Some of the menu items are not application actions, but window actions. These can be handled in code-behind.

XML
<Separator/>
<MenuItem Header="E_xit" Click="Exit_Click"/>
C#
private void Exit_Click(object sender, RoutedEventArgs e)
{
    Close();
}

Context Sensitive Menus

You want context sensitive menus to appear under certain conditions. WPF has a mechanism for that: the DataTrigger. A DataTrigger sets a control property when a data property is equal to a specific value. In this case, we want to set a MenuItem's Visibility property to Hidden when the data property IsFileOpen is False.

XML
 <Window.Resources>
    <Style x:Key="VisibleWhenFileIsOpen" TargetType="MenuItem">
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsFileOpen}" Value="False">
                <Setter Property="Visibility" Value="Hidden"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources> 

We apply this style to any menu that is sensitive to this context. 

XML
<MenuItem Header="_Edit" Style="{StaticResource VisibleWhenFileIsOpen}">
    <MenuItem Header="Cu_t"/>
    <MenuItem Header="_Copy"/>
    <MenuItem Header="_Paste"/>
</MenuItem>

DataTriggers automatically reset. When the IsFileOpen data property is no longer False, the Visibility control property will go back to the default Visible. There is no need to create another trigger for that rule.

Dynamic Menus

For recently opened files or currently open windows, you want each menu item to represent a data object. You want to bind the menu to a list.

If you bind to the raw data objects, you will have a hard time getting exactly the behavior that you want in the view. XAML is declarative, and is easiest to use when the data is already in the right format. That's where the View Model comes in.

C#
public class RecentFileViewModel
{
    private int _index;
    private string _fileName;
    private IFileHandler _fileHandler;

    public RecentFileViewModel(int index, string fileName, IFileHandler fileHandler)
    {
        _index = index;
        _fileName = fileName;
        _fileHandler = fileHandler;
    }

    public string FileName
    {
        get { return string.Format("_{0} - {1}", _index + 1, _fileName); }
    }

    public ICommand Open
    {
        get
        {
            return MakeCommand
                .Do(() => _fileHandler.Open(_fileName));
        }
    }
}

The recent file view model presents the file name in a format suitable for the menu item. It even adds the underscore to turn the 1-based index into a hot key.

The view model also provides the command to open the file. It doesn't actually perform the operation; it delegates to a file handler and provides the context.

We provide a list of these view models based on the list of recently opened files. 

C#
public IEnumerable<RecentFileViewModel> RecentFiles
{
    get
    {
        // Create a RecentFileViewModel for each recent file.
        // The view model serves the menu item.
        return _dataModel.RecentFiles
            .Select((fileName, index) =>
                new RecentFileViewModel(index, fileName, this));
    }
}

Please note that this pattern does not work with ObservableCollection. Once you call .Select() on an ObservableCollection, it is no longer observable. This pattern only works with Update Controls.

Now we need to bind MenuItems to this collection. My first instinct was to set the ItemTemplate of the parent MenuItem to a DataTemplate containing a child MenuItem. The problem with that is that a DataTemplate controls the content of the child item, not the child item itself. So instead of setting ItemTemplate, you need to set the ItemContainerStyle

XML
<MenuItem Header="_Recent Files" ItemsSource="{Binding RecentFiles}">
    <MenuItem.ItemContainerStyle>
        <Style>
            <Setter Property="MenuItem.Header" Value="{Binding FileName}"/>
            <Setter Property="MenuItem.Command" Value="{Binding Open}"/>
        </Style>
    </MenuItem.ItemContainerStyle>
</MenuItem>

A WPF menu should not be defined using just one technique. If you choose something too simple, you won't be able to handle the more interactive requirements. If you choose something too complex, you lose tool support and put too much of your design in code. With this combination of techniques, you can create interactive menus with ease.

History

  • 3rd July, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Improving Enterprises
United States United States
Code is math. Michael writes about techniques for using mathematics to prove the correctness of code at http://qedcode.com. He also maintains two open-source projects that let you write verifiable user interface code (Update Controls) and collaborative applications (Correspondence).

Comments and Discussions

 
PraiseThank you Pin
Member 109607896-Mar-16 18:34
Member 109607896-Mar-16 18:34 
GeneralMy vote of 5 Pin
Iftikhar Ali14-Oct-12 0:17
Iftikhar Ali14-Oct-12 0:17 
GeneralMy vote of 5 Pin
DrABELL9-Oct-12 8:55
DrABELL9-Oct-12 8:55 
GeneralMy vote of 5 Pin
Eric Ouellet13-Jun-11 3:01
professionalEric Ouellet13-Jun-11 3:01 
GeneralThanks a lot + one comment ! Pin
Eric Ouellet13-Jun-11 2:58
professionalEric Ouellet13-Jun-11 2:58 
GeneralExcellent topic Pin
orouit27-Sep-09 15:54
professionalorouit27-Sep-09 15:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.