Share on Facebook
This demo is updated to V3.5 - you can view updated html/silverlight menu posting if you click here.
For this ecommerce admin site we're building, I need to implement a menu system in Silverlight.
The requirements for the menu are this: If the client doesn't support Silverlight ( bots, clients who haven't installed it yet, and uh, that's all I've got, sorry not three this list.) If the client does support Silverlight, load this menu from the exact same sitemap that the ASP AJAX menu is using.
To be able to complete this task, I have several unanswered questions:
- How do I get my sitemap xml into the Silverlight Control before it is instantiated?
- How do I use the sitemap xml once I get it there?
- How do I format the menu using "menuItem" and "menuTitle" of the sitemap?
- How do I build the Silverlight control so that it's skinnable? (I don't want to build it again and again every time I do a new ecommerce web site)
How do I switch between the Silverlight menu and the HTML menu if my visitor's browser doesn't support Silverlight?
After about 8 hours at this task, I have something that works. I bet you can find a better way. But, it works and that's a great first step. This Silverlight Sitemap Menu can be seen here: http://www.faxt.com/silverlight/silverlightislands/Default.aspx.
SOURCE? Source to the SiteMapMenu silverlight (SL2B2) is here.
I'll go over my questions again, and how I answered them one at a time.
How do I get my sitemap xml into the Silverlight Control before it is instantiated?
At <a href=""michaelsync">michael sync's blog</a>, I learned how to set the InitParameters in my .aspx page. And how to grab the value from the InitParameters in my Silverlight control.
Here's my code I ended up with, This piece is in Page_Load of my .ASPX page:
string sitemappath = HttpContext.Current.Server.MapPath(".") + "/Web.sitemap"string sitem2 = File.ReadAllText(sitemappath);Xaml1.InitParameters = "SiteMap=" + sitem2;
This piece is in Application_Startup, in the App.xaml file.
private void Application_Startup(object sender, StartupEventArgs e){// Load the main control string sitemap = e.InitParams["SiteMap"];this.RootVisual = new Page(sitemap); }
How do I use the sitemap xml once I get it there?
I'll build some business objects to represent the Silverlight menu using LINQ over Xml ( Xlinq )
One thing I ran into while working with Xlinq - that is namespaces can complicate the code, in my case, since it was my site map, I just removed the namespace which was in the first node.
Note: This article I found on CodePlex is a wonderful resource for working with Xlinq in WPF, http://www.codeproject.com/KB/vista/LINQ_3.aspx, that's good - since they're related you know.
To you get the Xdocument to load up in my Silverlight control from my Sitemap XML, I created two new Business Object classes in my project. The first class represents a SiteMapNode. The second class represents a MenuGroup.
XLINQ:
XDocument xdoc = XDocument.Parse(sitemapXML);IEnumerable menuitems = from menuitem in _xdoc.Descendants("siteMapNode")select new SiteMapBO{url = (string)menuitem.Attribute("url"),title = (string)menuitem.Attribute("title"),description = (string)menuitem.Attribute("title"),menugroup = (string)menuitem.Attribute("menugroup"),itemType = (string)menuitem.Attribute("itemType")};
SiteMapBO.cs:
public class SiteMapBO{public string url{get;set;}public string title { get; set; }public string description { get; set; }public string menugroup { get; set; }public string itemType { get; set; }}
How do I format the menu using "menuItem" and "menuTitle" of the sitemap?
I want my menu titles to be independently skinnable from my menu items. That's what the MenuGroups class I mentioned above is for. After getting a list of SiteMapBO's from the Xdocument, I then re-traverse the list to load a new Ienumerable list of MenuGroups - and this is the list that gets bound to my ListBox.
MenuGroups.cs:
public class MenuGroups { public string title { get; set; } public List itemsList {get;set;} public MenuGroups() { itemsList = new List(); } }
Code which builds the ItemsSource of my ListBox:
if (menuitems.Count() > 0) { //break out the menu titles and menu items into a // specially formatted list. int ictr = 0; List menus = new List(); for (ictr = 0 ; ictr <= menuitems.Count(); ictr++) { SiteMapBO smu = menuitems.ElementAt( ictr ); MenuGroups mnu = new MenuGroups(); if (smu.itemType.Equals("menuTitle")) { mnu.title = smu.title; string menuGroup = smu.menugroup; ictr++; while (ictr < menuitems.Count()) { SiteMapBO smb = menuitems.ElementAt(ictr); if (smb.itemType.Equals("menuTitle")) { ictr--; break; } if (smb.menugroup.Equals( menuGroup )) mnu.itemsList.Add(smb); else break; ictr++; }; } menus.Add(mnu); } this.MenuList.ItemsSource = menus; //YEA!!! } }
How do I build the Silverlight control so that it's skinnable? (I don't want to build it again and again every time I do a new ecommerce web site)
I don't have enough space for this! Just for the sake of giving the solution (which in actual time took probably 2/3 of my working time to come up with) I'll just show you the XAML. (notice that this solution allows for individual formatting of the header and the items)
This is the menu:
<stackpanel x:name="MenuGrid">
<img stretch="Fill" source="images/bdlogo.png" />
<listbox x:name="MenuList">
</listbox>
</stackpanel>
Here is the menuTitleStyle:
<style x:key="menuTitleStyle" targettype="ListBox">
<setter property="ItemTemplate">
<setter.value>
<datatemplate>
<stackpanel>
<textblock text="{Binding title}"
style="{StaticResource menuTitle}" />
<listbox itemssource="{Binding itemsList}" selectionchanged="MenuList_SelectionChanged"
style="{StaticResource menuItemsStyle}"/>
</stackpanel>
</datatemplate>
</setter.value>
</setter>
</style>