Click here to Skip to main content
6,629,885 members and growing! (23,000 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Presentation Foundation » General     Intermediate License: The Code Project Open License (CPOL)

WPF Data Bound Menus

By Michael L Perry

Use a combination of data-binding techniques to create WPF menus that change with your application
C# (C# 3.0), .NET (.NET 3.5), WPF, Dev
Version:2 (See All)
Posted:3 Jul 2009
Views:3,668
Bookmarked:15 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
7 votes for this article.
Popularity: 4.23 Rating: 5.00 out of 5

1

2

3

4
7 votes, 100.0%
5
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.

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

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.

<Separator/>
<MenuItem Header="E_xit" Click="Exit_Click"/>
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.

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

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

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. 

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

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

About the Author

Michael L Perry


Member
Michael blogs at http://adventuresinsoftware.com, where he discusses lessons learned on the road to quality computing. The blog is all about observations, mistakes, and a-ha moments that have lead to improvements in his career.

Michael also created a new way of data binding at http://updatecontrols.net. This open source library automatically discovers dependencies and notifies the UI. You never have to see INotifyPropertyChanged again.
Occupation: Architect
Location: United States United States

Other popular Windows Presentation Foundation articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 1 of 1 (Total in Forum: 1) (Refresh)FirstPrevNext
GeneralExcellent topic Pinmemberorouit16:54 27 Sep '09  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 3 Jul 2009
Editor: Deeksha Shenoy
Copyright 2009 by Michael L Perry
Everything else Copyright © CodeProject, 1999-2009
Web11 | Advertise on the Code Project