WPF Sample Series – Databound HierarchicalDataTemplate Menu Sample

application

applicationtwo

Notice the menu separator below Print.

This is the next sample in the WPF Sample Applications Series. The purpose of the Sample Series is to provide concise code solutions for specific programming tasks. This sample provides a brief description of the problem, the solution and full source code.

In this WPF Sample, I have adopted the Code Project style of marking code and variable names within the text of the article with the color #990000.  Please leave a comment on this styling and let me know if I should keep using it or not.  Thank you for your comments and suggestions.

I have a previous post on using the HierarchicalDataTemplate to create a disk explorer that you can read.

In this WPF Sample, I want to demonstrate using the HierarchicalDataTemplate with data drawn from a business object to dynamically populate a WPF Menu Control.  A lot of Internet WPF menu examples populate the Menu Control in XAML markup.  This is fine for applications with static menus, but what about business applications that need to display menus based on the user or group that the user belongs to?

This coding technique also allows you to have unlimited hierarchical menu nesting.  Many WPF Internet examples have hard coded the number of levels their menus can have.  When working in the “real world” with business applications, we need to be able to add additional levels to the menu without having to modify the UI code.  This code accomplishes that goal.

Dynamically loading menus for business applications also allows you to customize your menu content based not only on the user, but also on the customer.  Let’s say that you have an application that spans several vertical areas like, AP, AR, GL, Tax, etc.  Some customers many not require all the vertical areas that other customers do.  Dynamically loading the menu from a database allows great flexibility for application deployment to various customers.

All my WPF business applications load their menus dynamically after the user logs into the application.  I thought I would share this coding technique with you.

Downloads

After downloading, you must rename the following download by changing the file extension from .doc to .zip. This is a requirement of WordPress.com.

HierarchicalDataTemplate

The HierarchicalDataTemplate is a super WPF feature.  This data template allows you to take a flat data source, like a collection of menu data items that was populated from a SQL query and render it hierarchically.  This process requires very little code.

Big Picture Of The Menu Loading
  • Window code calls the business layer and gets the data for the menu.
    • The result of this call contains the entire menu stored in a collection.
    • This collection is then referenced by the read-only property MenuDataItems on the Window object.  This is an important step.
  • Menu Control has its DataContext property set to the top level items.  In our example the top level items are, File, Administration and Finance.
  • When the user opens one of the top level menus the following occurs:
    • The sub-items of that menu are then loaded.  Notice, the sub-menu items do not load until they are required.
How The HierarchicalDataTemplate Process Data Items
  • Menu Control processes one MenuDataItem from its DataContext at a time.
  • WPF finds that the MenuDataItem Type has a matching data template that has its DataType property set to MenuDataItem, so it selects this data template for rendering the MenuDataItem.
  • HierarchicalDataTemplate process the data item
    • Since this data template has a Style Selector, this code runs first.
      • A resource in the data template XAML markup, points to a class that derives from StyleSelector
      • In this application, that class is, MenuSeparatorStyleSelector.
        • This code gets a reference to the MenuDataItem and if the IsSeparator property is True, it provides a custom style for this MenuItem
        • In this application the resource is, menuSeparatorStyle.  This style provides a control template that only has a Separator control in it.  This control template is used instead of the control template in our HierarchicalDataTemplate.
    • The ContentPresenter.Loaded event fires, calling the OnMenuDataItemLoaded event handler code.
      • This code locates the MenuItem in the VisualTree.
      • Then gets a reference to the MenuItem.DataContext and casts it to a MenuDataItem.
      • The MenuDataItem properties can then be used to populate other MenuItem properties.
      • In this application, the MenuDataItem.Icon property is used to add an icon to the MenuItem.
      • Note: It is in this event handler that you can populate the MenuItem Command and CommandParameter properties.  These are the typical properties used in the WPF RoutedEvent and RoutedCommand handlers to process menu commands.
    • The MenuItem ContentPresenter has its content property bound to the MenuDataItem.MenuText property.
    • The RecognizedAccessKey property is set to True.  This is the secret to allows the MenuDataItem.MenuText property to contain “_File” and the underscore will be hidden until the <ALT> key is pressed.
    • Here is the magic.  Each item created by the HierarchicalDataTemplate has its own ItemsSource.  A collection of child items if you will.  The application then uses a binding converter and passes it the MenuDataItem.MenuItemID property.
      • The converter code that gets called is the MenuDataItemConverter.
      • This converter then gets a reference to the ApplicationMainWindow and runs executes the GetChildItems function, passing the MenuItemID property value.
        • The GetChildItems function returns a List(Of MenuDataItem).  This list is populated with any MenuDataItem from the MenuDataItems collection where their MenuItemParentID is equal to the MenuItemID that was passed into the converter.  In effect, we are simply getting all the children of the MenuItem that was clicked by the user.
  • The above looks complex but it’s really not.  Just several pieces of code working together to render a multi-level, data driven, dynamic menu with text, icons, separators.

ApplicationMainWindow.xaml

A pattern that I have adopted, I always name my main application window, ApplicationMainWindow.

You can now use the above explanation and follow through this simple XAML.

<Window x:Class="ApplicationMainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:HierarchicalMenuSample"
    Title="HierarchicalDataTemplate Menu Sample"
    Height="300"
    Width="400"
    >
  <Window.Resources>

    <Style TargetType="{x:Type MenuItem}" x:Key="menuSeparatorStyle">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate
                  TargetType="{x:Type MenuItem}">
                    <Separator HorizontalAlignment="Stretch" IsEnabled="false"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <HierarchicalDataTemplate DataType="{x:Type local:MenuDataItem}">

      <ContentPresenter Content="{Binding Path=MenuText}"
          Loaded="OnMenuDataItemLoaded" RecognizesAccessKey="True" />

        <HierarchicalDataTemplate.ItemsSource>
            <Binding Path="MenuItemID">
                <Binding.Converter>
                    <local:MenuDataItemConverter />
                </Binding.Converter>
            </Binding>
        </HierarchicalDataTemplate.ItemsSource>
    </HierarchicalDataTemplate>

    <local:MenuSeparatorStyleSelector x:Key="menuSeparatorStyleSelector"/>

  </Window.Resources>

  <DockPanel>

    <Menu x:Name="mnuMainMenu" ItemsSource="{Binding}" VerticalAlignment="Top" DockPanel.Dock="Top"
        ItemContainerStyleSelector="{StaticResource menuSeparatorStyleSelector}"/>

    </DockPanel>
</Window>

ApplicationMainWindow.xaml.vb

Partial Public Class ApplicationMainWindow
  Inherits System.Windows.Window

#Region " Declarations "

  Private _objMenuDataItems As List(Of MenuDataItem)

#End Region

#Region " Properties "

  Public ReadOnly Property MenuDataItems() As List(Of MenuDataItem)
    Get
      Return _objMenuDataItems
    End Get
  End Property

#End Region

#Region " Constructors & Loaded "

  Public Sub New()
    InitializeComponent()

  End Sub

  Private Sub ApplicationMainWindow_Loaded(ByVal sender As Object, ByVal e As _
    System.Windows.RoutedEventArgs) Handles Me.Loaded

    _objMenuDataItems = MenuItems.Load
    Me.mnuMainMenu.DataContext = GetChildItems(0)

  End Sub

#End Region

#Region " Menu "

Private Sub OnMenuDataItemLoaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs)

 Dim objMenuItem As MenuItem = FindAncestores.FindAncestorMenuItem(CType(sender, DependencyObject))

    If objMenuItem IsNot Nothing Then

      Dim objMenuDataItem As MenuDataItem = TryCast(objMenuItem.DataContext, MenuDataItem)

      If objMenuDataItem IsNot Nothing Then

        If objMenuDataItem.Icon.Length > 0 Then

          'You have a number of options here.
          '  1.  you could assign each menu icon a resource name
          '  2.  or you could switch the directory based on the skin.
          '  3.  or ...
          'I have hard coded the menu icon directory for this short demo
          Dim img As Image
          img = New Image
          img.Source = New BitmapImage(New Uri(String.Concat("Images\", objMenuDataItem.Icon), _
              UriKind.Relative))
          objMenuItem.Icon = img
          objMenuItem.Margin = New Thickness(0, 8, 0, 0)
        End If

        'TODO developers this is where you assign the
        '   MenuItem Command and CommandParameter
        '
        ' Depending on your application architecture, you'll need to
        '  expand the MenuDataItem class to support your menu commands.
      End If

    End If

  End Sub

  'scope must be Friend so that the MenuDataItemConverter
  '  can use this function
  Friend Function GetChildItems(ByVal intParentID As Integer) As List(Of MenuDataItem)

    Dim obj As New List(Of MenuDataItem)

    For Each i As MenuDataItem In Me.MenuDataItems

      If i.MenuItemParentID = intParentID Then
        obj.Add(i)
      End If

    Next

    Return obj

  End Function

#End Region

End Class

MenuSeparatorStyleSelector.vb

This is the StyleSelector code that is used to swap the ControlTemplate for any MenuDataItems that have their IsSeparator property equal to True.

Public Class MenuSeparatorStyleSelector
  Inherits StyleSelector

    Public Overrides Function SelectStyle(ByVal item As Object, _
      ByVal container As System.Windows.DependencyObject) As System.Windows.Style

        If DirectCast(item, MenuDataItem).IsSeparator Then
            Return DirectCast(DirectCast(container, FrameworkElement).FindResource( _
                "menuSeparatorStyle"), Style)

        Else
            Return MyBase.SelectStyle(item, container)
        End If

    End Function

End Class

After you download the source, step through the code as it runs.  You’ll be able to see how the pieces fit together.

Hope you can learn just a little bit more about WPF from this article and the Sample Series.

Just a grain of sand on the worlds beaches.

4 Responses to “WPF Sample Series – Databound HierarchicalDataTemplate Menu Sample”

  1. jimbeam451 Says:

    I enjoyed the article, this is exactly what I need. Any chance you have this with C# code?

  2. jimbeam451 Says:

    I appreciate the lead and I have now successfully converted it to C#.

    One last question – can you show the modifications needed to use databinding based on a unique user ID. Suppose I have 2 users, unique IDs and I want to show two different menu bars. I already have the DB side of this, I would like some more color on the databinding portion where the menu “refreshes” based on a new user ID.

Leave a Reply

You must be logged in to post a comment.