Multiple level navigation in Silverlight

Note: The code for this article can be found at http://johanleino.codeplex.com/

This is actually my first post which concerns Silverlight. I have for the last couple of weeks started to get my hands dirty with some new Silverlight 4 stuff for a new client. I am building the UI (completely in Silverlight) and this article is about a problem that I had with the navigation.

In asp.net (and SharePoint) I have many times build a navigation scenario with multiple levels and a sort of breadcrumb that displays the paths the user has gone down. This has been made with a couple of repeaters for the html/CSS and the sitemap provider/s.
In Silverlight though I had never done this sort of stuff so it was (at least for me) a bit of a challenge. I didn´t find a lot of stuff around displaying navigation in an asp.net repeater way so I thought I might write this article so that other can benefit from my experiences.

So, to start off with…this is how I want my application to look like:

The user has clicked on the Product tab and then from there of the Product Categories. You can see the breadcrumbing drawn out on the screen (not so pretty but the effect is there).

Navigation.xml

Inside my solution tree I have an assets folder that contains a file containing the actual sitemap which will look like this:

<SiteMap>
 <MainMenu name="Product" url="/Product/ProductList">
 <SubMenu name="Products" url="/Product/ProductList" />
 <SubMenu name="ProductCategories" url="/Product/ProductCategoryList" />
 </MainMenu>
 <MainMenu name="Customer" url="/Customer/CustomerList">
 <SubMenu name="Customers" url="/Customer/CustomerList" />
 <SubMenu name="CustomerCategories" url="/Customer/CustomerCategoryList" />
 </MainMenu>
</SiteMap>

Then I have a class that reads that xml file and parses it into a list of MenuItem class instances. That code looks like this:

 public class Navigation : List<MenuItem>
 {
 public Navigation()
 : base()
 {
 XDocument document = XDocument.Load("Assets/Navigation.xml");

 foreach (var MainMenu in document.Descendants("MainMenu"))
 {
 Add(new MenuItem
 {
   Name = MainMenu.Attribute("name").Value,
   Url = MainMenu.Attribute("url").Value,
   SubMenuItems = new List<MenuItem>(
     from SubMenu in MainMenu.Descendants("SubMenu")
     select new MenuItem
     {
       Name = SubMenu.Attribute("name").Value,
       Url = SubMenu.Attribute("url").Value
     })
 });
 }
 }

 }

With some LINQ the code will read the xml file and then use the content of that file to construct MenuItem instances.

MainPage.xaml

This page work in a Silverlight way like the master page in asp.net so this is where I have my navigation elements. The picture above displays the main navigation (green) and the sub navigation (red).
And a picture of the xaml looks like this:

Firstly, I have an xmlns declaration called local which points to the default namespace inside my assembly.
Secondly, I have a resource declared that points to my Navigation class which will parse the xml in its constructor. This resource is called NavigationDataSource.
Thirdly, I use that resource as an ItemSource to the ItemsControl which works much like the repeater in asp.net inside a stackpanel. The hyperlink button has a click event at which point I handle the datasource to the submenu items.

private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
 {
 HyperlinkButton link = e.OriginalSource as HyperlinkButton;

 if (link != null)
 {
 if (link.DataContext != null)
 {
 MenuItem item = link.DataContext as MenuItem;
 if (item != null && item.SubMenuItems != null)
 {
 SubMenu.ItemsSource = item.SubMenuItems;
 SubMenu.Visibility = Visibility.Visible;
 }
 }
 }
 }

The hyperlink has a datacontext which in fact is an instance of the MenuItem and when I have lifted that from the hyperlink I can get to its SubMenuItems and databind them to the SubMenu control.

The breadcrumb

The navigation frame has an event called navigated in which I will handle the breadcrumb. Let´s have a look at the code first:

 private void ContentFrame_Navigated(object sender, NavigationEventArgs e)
 {
 foreach (var item in SubMenu.Items)
 {
 var hb = SubMenu.ItemContainerGenerator.ContainerFromItem(item).FindVisualChild<HyperlinkButton>();

 if (hb.NavigateUri.ToString().Equals(e.Uri.ToString()))
 {
 VisualStateManager.GoToState(hb, "ActiveLink", true);
 }
 else
 {
 VisualStateManager.GoToState(hb, "InactiveLink", true);
 }
 }

 foreach (var item in MainMenu.Items.OfType<MenuItem>())
 {
 var hb = MainMenu.ItemContainerGenerator.ContainerFromItem(item).FindVisualChild<HyperlinkButton>();

 if (item.SubMenuItems.Any(mnu => mnu.Url == e.Uri.ToString()))
 {
 VisualStateManager.GoToState(hb, "ActiveLink", true);
 }
 else
 {
 VisualStateManager.GoToState(hb, "InactiveLink", true);
 }
 }
 }

The code decides which submenu item has the same url as the current xaml page that is displayed and show that as the active link. The code will then find the mainmenu item that has the active link in its collection and display that as the active link too.

Finally there is an extension method that takes care of finding the visual representation of an item in the ItemsControl.Items collection which is called FindVisualChild and has the following look:

<pre> public static T FindVisualChild<T>(this DependencyObject instance) where T : DependencyObject
 {
 T control = default(T);

 for (int i = 0; i < VisualTreeHelper.GetChildrenCount(instance); i++)
 {
 if ((control = VisualTreeHelper.GetChild(instance, i) as T) != null)
 {
 break;
 }

 control = FindVisualChild<T>(VisualTreeHelper.GetChild(instance, i));
 }

 return control;

 }

This example is not perfect but it might work as an example…

UPDATE 2010-03-03: An nice looking alternative which uses the sitemap provider architecture (which I originally wanted to use) can be found here.

, , ,

  1. #1 by Elvis Munzer on March 7, 2010 - 04:15

    Hi, I found this post while looking for help with Microsoft Silverlight. I have recently switched internet browser from Opera to IE. Just recently I seem to have a issue with loading websites that use Microsoft Silverlight. Everytime I browse page that needs Microsoft Silverlight, my browser crashes and I get a “npctrl.dll” error. I can’t seem to find out how to fix it. Any help getting Microsoft Silverlight to work is very appreciated! Thanks

  2. #3 by Satish on May 6, 2010 - 21:59

    Hello,
    In my custom list I have Send to List. First I want user to put email address in that and using workflow I’m sending mail to that address. But then after I want to clear that value as “Null” so that each time user edit that list my workflow don’t send email to that person again and again.

    Please Help.

  3. #4 by Mickie Stamm on May 6, 2010 - 22:13

    Hi – I’m working on an implementation of tabs and sub-tabs in Silverlight and am also using hyperlinks displayed through an ItemsControl. I was looking for a way to get at the hyperlinks in the ItemsControl to apply the Active/Inactive link formatting and found your post on this. Just wanted to say thanks very much for your time in posting the solution you have with the FindVisualChild extension method. This works perfectly – thanks!

    • #5 by Johan Leino on May 7, 2010 - 06:19

      Thx!
      That´s why I blog, so that others can benefit from my learnings (just as I do). Feedback is always nice!

  4. #6 by kamran on July 10, 2010 - 17:45

    You have provided one of the only resources I could find on implementing multilevel navigation in Silverlight. Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: