I had a colleague ask me how he could solve a databinding problem while building a WPF Menu. The immediate answer wasn’t apparent to me, though it’s actually quite simple. A while back Karl Shifflett wrote up an excellent example of doing something similar. I take his example one step further by showing how you can easily bind properties to the MenuItem instead of assigning one-time values.
Download Source: WPFDataBoundMenu.zip
We’re going to use a simple Business Object which represents our MenuItem. It has four properties: MenuText, IsEnabled, Command, and Children.
public class MyBusinessObject : INotifyPropertyChanged
{
private bool _isEnabled;
private string _menuText;
private ICommand _command;
private ObservableCollection<MyBusinessObject> _children;
public event PropertyChangedEventHandler PropertyChanged;
public MyBusinessObject()
{
Children = new ObservableCollection<MyBusinessObject>();
}
public string MenuText
{
get { return _menuText; }
set
{
_menuText = value;
RaisePropertyChanged("MenuText");
}
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
RaisePropertyChanged("IsEnabled");
}
}
public ICommand Command
{
get { return _command; }
set
{
_command = value;
RaisePropertyChanged("Command");
}
}
public ObservableCollection<MyBusinessObject> Children
{
get { return _children; }
set
{
_children = value;
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
We’ll build our window to include a Menu and a Button. When we click the button we want to toggle the IsEnabled property of one of our Business Objects.
<Window
x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1"
Height="400"
Width="700">
<Window.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:MyBusinessObject}"
ItemsSource="{Binding Path=Children}">
<ContentPresenter
Content="{Binding MenuText}"
Loaded="ContentPresenter_Loaded"
RecognizesAccessKey="True" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid
Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Menu
ItemsSource="{Binding}"/>
<StackPanel
Grid.Row="1">
<Button
Content="Toggle Enable"
Click="Enable_Click"/>
</StackPanel>
</Grid>
</Window>
In our code-behind for the window we build a collection of MyBusinessObject’s. We then assign that collection to the DataContext of our Window. This allows the Menu control to bind its ItemsSource to the current value of the DataContext (ItemsSource="{Binding}"). Where we do the magic is in the ContenPresenter_Loaded event handler. This is fired when the MenuItems are being built. Inside the handler we get a reference to the ContenPresenter then walk up the tree to it’s Parent MenuItem. UtilityMethods.FindParent accomplishes this task by using the VisualTreeHelper and recursively walking the tree. We then can get a reference to our MyBusinessObject through the DataContext of the MenuItem. This allows us to build bindings, in code, to bind to various properties. It’s important to remember that we can do lots of the same things in code in which we can do in XAML. XAML is just usually funner. =]
public partial class Window1 : Window
{
private ObservableCollection<MyBusinessObject> _options;
public Window1()
{
InitializeComponent();
_options = new ObservableCollection<MyBusinessObject>();
// build simple menu structure
MyBusinessObject menuTwo = new MyBusinessObject
{
MenuText = "Two",
};
menuTwo.Children.Add(
new MyBusinessObject
{
MenuText = "Child 1"
}
);
menuTwo.Children.Add(
new MyBusinessObject
{
MenuText = "Child 2",
IsEnabled = false
}
);
_options.Add(
new MyBusinessObject
{
MenuText = "One"
}
);
_options.Add(menuTwo);
DataContext = _options;
}
private void ContentPresenter_Loaded(object sender, RoutedEventArgs e)
{
// get the content presenter
ContentPresenter contentPresenter =
sender as ContentPresenter;
// get reference to menu item
MenuItem menuItem =
UtilityMethods.FindParent<MenuItem>(contentPresenter);
// get reference to business object
MyBusinessObject myBusinessObject =
menuItem.DataContext as MyBusinessObject;
// create bindings
Binding isEnabledBinding = new Binding("IsEnabled");
isEnabledBinding.Mode = BindingMode.TwoWay;
isEnabledBinding.Source = myBusinessObject;
menuItem.SetBinding(MenuItem.IsEnabledProperty, isEnabledBinding);
Binding commandBinding = new Binding("Command");
commandBinding.Mode = BindingMode.TwoWay;
commandBinding.Source = myBusinessObject;
menuItem.SetBinding(MenuItem.CommandProperty, commandBinding);
}
private void Enable_Click(object sender, RoutedEventArgs e)
{
// toggle IsEnabled
_options[1].Children[1].IsEnabled =
!_options[1].Children[1].IsEnabled;
}
}
And that’s it! I hope that helps!
Download Source: WPFDataBoundMenu.zip
Joe
Read the complete post at http://xamlcoder.com/cs/blogs/joe/archive/2008/11/25/building-a-databound-wpf-menu-using-a-hierarchicaldatatemplate.aspx