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.

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

    • Jim,

      Best way is to select data from the database based on the user, then only bind that selected users data to the menu. In that case, the data will be dynamic as opposed to static.

      Cheers,

      Karl

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

  4. gmanuel40 says:

    I was able to use this great sample you provided and apply my own data and have a working menu. Since I’m new to VB.Net & WPF, I’m hoping you can provide me with guidance for my next step. Maybe I’m going about this wrong…I would like to put this code into a DLL so I can simply create an instance of this menu and pass it the menu data to load…maybe even spawn more than one menu from the same program. I tried changing the application type to a WPF class library, but then I’m note sure how to fix all the errors…what goes in the dll and what goes in the exe.
    Any help ia appreciated.
    Thanks,
    Gary

    • Gary,

      I would strongly suggest getting a VB.NET book so that you can get all the basics down with respect to .NET before heading into WPF land. Without a good background in a .NET language you’ll run into all sorts of problems.

      Chances are you are missing references in the library that the .exe has. Your error messages will inform you what assemblies or names spaces are not found, add those and you should be back up.

      Cheers,

      Karl

      • gmanuel40 says:

        Thanks Karl,
        I did get a book on VB.Net and I’m using all the great info on the web. I’m stuck on the following issue that I’m hopping you can help. Taking your example code, I did modify it just enough to have it compile as a DLL and I’m trying to load it into the AutoCAD application. I’m getting an exception error in the Convert function. If I put a Try Catch statement in the function to handle the error…the menu susseccfully displays in my application, however I only get the main top level headers in my menu with no pulldown.

        Without the Try Catch in there, the code bails on the following line.

        Dim objWindow As ApplicationMainWindow = CType(Application.Current.MainWindow, ApplicationMainWindow)

        Note I replaced app.Current.MainWindow with Application.Current.MainWindow because I removed the app.xaml.vb from the project.

        I changed the application compile type to a WPF Class Library and I added the following class to launch an instance of the menu from the external application:

        Imports Autodesk.AutoCAD.Runtime
        Imports System.Windows

        Public Class Class1
        Inherits Application
        Public Shared frmWPF As New ApplicationMainWindow
        Public Sub StartWPF()

        frmWPF.Show()

        End Sub
        End Class

        Can you please help…I’ve been stuck on this one and I can’t seem to get passed it.

        Gary

        • Gary,

          Put a breakpoint on the line of code with this, “Application.Current”

          From what I can tell, Application.Current will probably be null which is why you are getting an exception.

          Why do you need this in a Converter? This does not sound correct.

          Post your converter code for me to see.

          Karl

        • gmanuel40 says:

          Thanks for the reply Karl,

          Here is the Converter code….it is unchanged from your example other than replacing “app” with “application”…here I’m showing the Try Catch statments, without it, it cratches on the “Return objWindow.GetChildItems(DirectCast(value, Integer))” line. The immediate window gives the following exception error and the program bails.

          A first chance exception of type ‘System.NullReferenceException’ occurred in HierarchicalMenuSample.dll

          While stepping through the code…Before it crashes…the window does appear and the first item “File” appears…it does not seemm to go any further.

          Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
          Try
          If TypeOf value Is Integer Then

          Dim objWindow As ApplicationMainWindow = CType(Application.Current.MainWindow, ApplicationMainWindow)

          Return objWindow.GetChildItems(DirectCast(value, Integer))

          Else
          Return Nothing
          End If
          Catch
          Return Nothing
          Finally
          End Try

          End Function

        • Gary,

          What object is actually null?

          Separate each object in this line of code so that its not nested on one line.

          Return objWindow.GetChildItems(DirectCast(value, Integer))

          Then using the debugger, find out exactly which object is null.

          The code I put in this demo is not a one size fits all. It works in the provided scenario but may not work in yours. You may have to change the structure of the code and how the nested items are resolved.

          Cheers,

          Karl

        • gmanuel40 says:

          Karl,

          I put the following checks in the code. and MsgBox(“application is nothing”) runs the 3 times that the code gets called. I thought that “application” would return the current application…is it trying to retrieve MainWindow from the host application instead…how do I specify the instance of the WPF menu?
          In this case…I get the WPF Menu with the 3 main headers but no pulldown…but it does not crash.

          Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
          Try
          If TypeOf value Is Integer Then
          If Application.Current.MainWindow IsNot Nothing Then
          Dim objWindow As ApplicationMainWindow = CType(Application.Current.MainWindow, ApplicationMainWindow)
          If objWindow IsNot Nothing Then
          Return objWindow.GetChildItems(DirectCast(value, Integer))
          Else
          MsgBox(“objWindow is nothing”)
          Return Nothing
          End If
          Else
          MsgBox(“application is nothing”)
          Return Nothing
          End If

          Else
          Return Nothing
          End If
          Catch
          Return Nothing
          Finally
          End Try

          End Function

        • I’m not sure what you are trying to do.

          You can always set up a static (Shared for VB) data source and access that directly from within the converter or change the way the data is accessed.

          This converter is only accessing the MainWindow so that it can get to the data source. So set up a Shared class that exposes the same data and it will work.

          Karl

        • gmanuel40 says:

          Thanks Karl,

          With your sample in mind, could you give me an example of what you mean by setting up a shared class to access MainWindow data?

        • Gary,

          Sorry, I don’t have the time right now to rewrite this demo to fit in your scenario.

          There are several ways to solve this issue, one is a Shared property on a class that your converter can access the data, another is to add a dependency property to your converter so that you can data bind your data collection to the dependecy property thus enabling your converter to have access to the data.

          I hope this helps,

          Karl

      • gmanuel40 says:

        Karl,
        I was able to get it working. I brought back the original app.vb class and in the ApllicationMainWindow code I put the following:

        Public Shared Sub Main()
        Dim app As app = New app()
        app.Run(New wpfMenu())
        app = Nothing
        End Sub
        I’m calling the main sub from my application and by putting back app.current.mainwindow in the converter function…it worked!!
        I may not be doing it quite right but at least menu does come up as expected.

        Your comments were very helpful and I appreciate you taking the time.

        Thank you!
        Gary

  5. mangeshshukla says:

    HI,
    I was trying to implement the “Open Recent” menu Item, which requires the file list to be dynamically loaded not only during application load, but also after the user opens or saves a file. The last accessed filepath should be at the top, and the remaining files should be reordered.
    Using this example, I was able to load the recent file list menu items at loading, but I am facing a problem with ordering the menu items after user accesses a file( saves or opens).
    Any idea on how to get this working.

    • I would dynamically add the recent items to its sub-menu each time the sub-menu is displayed at runtime. You can use any sorting routing or use a CollectionViewSource and assign required SortDescriptions.

      Each time a file is opened, you can store that information in the registry, user settings file, etc. Then when the menu opens, it just gets this data and displays it.

      Does this help?

      Cheers,

      Karl

  6. rrossney says:

    I don’t think you need any code-behind whatsoever to do this, not if you use the ItemContainerStyle.

    Here’s a HierarchicalDataTemplate that creates dynamic menus, binding the Command property of the source object to each MenuItem’s Command, swapping out the control template for item separators, etc. I did need to use Expression Blend to get the template that menu separators use, but the rest of this is extremely straightforward:

  7. hardywang says:

    Nice work.

    One more question, is there a way to turn it into MVVM binding? Because I see it it pretty tight to the UI controls.

Follow

Get every new post delivered to your Inbox.

Join 246 other followers