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.

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

  1. WPF Sample Series - Databound HierarchicalDataTemplate Menu Sample | Internet Directory Says:

    [...] post by Karl On WPF – .Net Share and Enjoy: These icons link to social bookmarking sites where readers can share and [...]

  2. donpoke Says:

    Hi Karl, its possible to use a XmlDocument as the dynamic source for the menu items? I found this sample in the msdn forums (http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2827907&SiteID=1) but there is a limit about the numbers of child elements. Any samples or reference would be appreciated. Regards Rodrigo

  3. Karl Shifflett Says:

    Don,

    Yes, you can use and XML file as your source. The very cool feature of the HierarchicalDataTemplate is that is has no limit to the number of levels.

    This solution takes a flat structure and expands it to menu levels.

    If, your XML file already has the levels defined, I know that you “can” use it, I just have not done this yet.

    Here is a post that I “think” does what you want. http://blogs.msdn.com/llobo/rss.xml

    I hope this is some help. Have a great day!

    Cheers,

    Karl

  4. Karl Shifflett Says:

    Here is another example I just found:

    http://blogs.msdn.com/wpfsdk/archive/2008/01/29/a-sample-and-a-question.aspx

    Karl

  5. Interesting Finds: February 24, 2008 « Hank Wallace Says:

    [...] Part 2 – Form Notification Control That Binds To IDataErrorInfo.Error PropertyKarl Shifflett – WPF Sample Series – Databound HierarchicalDataTemplate Menu SamplePete Brown – Silverlight, WPF, Windows Forms, Ajax – Which One is for Me? Technorati tags: Smart [...]

  6. thamaluk Says:

    Hi Karl

    I have a question about MVC, I thought that I could use this post and turn it into a MVC application, I created two additional projects, Model & Controller. In the Model project I moved the MenuData items and the Helpers. Into the Controller I was going to move the Converters folder, however the MenuDataItemConverter has a reference to ApplicationMenuWindow. I have been studying Josh’s posting about MVC & WPF it seems that this reference wouldn’t be a good thing to have in a MVC style app.

    Do you have any suggestions as how best to make changes to avoid this ?

    Would I be better off just leaving the converter as part of the View ?

    Thanks in advance for any advise

  7. Karl Shifflett Says:

    The windows is intended to be shell for other user controls that are loaded by the menu.

    I would implement MVC for the user controls (those are the forms, “User Maintenance” “Group Maintenance” etc.

    When I write my applications, I code the shell as is and leave the MVC for the application forms, but that’s just me. This shell has a model, but the control code is in the code behind for the window for the exact reasons you have mentioned.

    I hope this helps!

    Cheers,

    Karl

  8. Building a Databound WPF Menu Using a HierarchicalDataTemplate - Joe's Blog Says:

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

  9. irfanamd Says:

    Instead of using the ItemContainerStyleSelector=”{StaticResource menuSeparatorStyleSelector}”, wouldn’t it be better to define a data trigger on IsSeparator property like

    <ContextMenu x:Key=”MyContextMenu” ItemsSource=”{Binding MenuOptions}”>
    <ContextMenu.ItemContainerStyle>
    <Style TargetType=”{x:Type MenuItem}”>
    <Style.Triggers>
    <DataTrigger Binding=”{Binding IsSeparator}” Value=”True”>
    <Setter Property=”Template”>
    <Setter.Value>
    <ControlTemplate TargetType=”{x:Type MenuItem}”>
    <Separator HorizontalAlignment=”Stretch” IsEnabled=”false”/>
    </ControlTemplate>
    </Setter.Value>
    </Setter>
    </DataTrigger>
    </Style.Triggers>
    &t;/Style >
    </ContextMenu.ItemContainerStyle>
    </extMenu>

  10. Karl Shifflett Says:

    irfanamd,

    This is why I love WPF. There are many ways to accomplished the same task.

    I have not yet profiled the two solutions, do you think your solution would perform better?

    Have a great day,

    Karl

  11. franklingray Says:

    Love the example and would like to use this approach but I CAN’T see how to do the work. I don’t see anything executing when a user clicks on a menu item and I don’t know how to bind the menuitem to a method to execute. Please fill in that piece of the puzzle.

  12. franklingray Says:

    Nevermind….I figured it out.

  13. Karl Shifflett Says:

    Frank,

    Glad you sorted it out.

    Cheers,

    Karl

  14. mlblack Says:

    Hi Karl. Very nice! I’m still wrapping my head around WPF–how would we get the child items if building a user control or if building this in a browser app? I can’t cast the container as a System.Windows.DependencyObject.

    Thanks!

  15. mlblack Says:

    Sorry–it’s late at night and I’m not thinking straight. I mean this is the line I don’t know how to duplicate with a User Control or browser app:
    Dim objWindow As ApplicationMainWindow = CType(App.Current.MainWindow, ApplicationMainWindow)

Leave a Reply

You must be logged in to post a comment.