Wednesday 7 October 2009

Silverlight 3; Building a navigation tree from static and dynamic data

This post has moved to http://www.scottleckie.com/2009/10/silverlight-3-building-a-navigation-tree-from-static-and-dynamic-data/

The problem

Build a navigation tree that allows users to navigate to specific static pages, from static entries in the menus, and to pages that are built dynamically based on selections from the menu.

The Solution in a picture

image

The solution in code

Build the static menu selections in xaml, being sure to name each of the major headings with “Tag” entries;
<Grid x:Name="LayoutRoot">
<controls:TreeView x:Name="treeview"
BorderThickness="0" 
Margin="0" 
SelectedItemChanged="treeview_SelectedItemChanged">
<controls:TreeView.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="AntiqueWhite" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</controls:TreeView.Background>
<controls:TreeViewItem Header="Machines" Tag="Machines">
<controls:TreeViewItem Header="By type" x:Name="machinesByTypeTree">
</controls:TreeViewItem>
<controls:TreeViewItem Header="By O/S" x:Name="machinesByOsTree">
</controls:TreeViewItem>
</controls:TreeViewItem>
<controls:TreeViewItem Header="Software" Tag="Software">
<controls:TreeViewItem Header="By product" x:Name="softwareByProductTree">
</controls:TreeViewItem>
</controls:TreeViewItem>
<controls:TreeViewItem Header="Manufacturers" Tag="Manufacturers">
</controls:TreeViewItem>
<controls:TreeViewItem Header="Audit log">
<controls:TreeViewItem Header="Today" Tag="AuditToday" />
<controls:TreeViewItem Header="This week" Tag="AuditThisWeek" />
<controls:TreeViewItem Header="This month" Tag="AuditThisMonth" />
</controls:TreeViewItem>
</controls:TreeView>
</Grid>





Note that the “Tag=” entries define the major menu categories.


Next, build the dynamic entries, ensuring that we fill the planned dynamic menus. In the above example, we are going to complete the menus for “machinesByTypeTree” and “machinesByOsTree”;


internal class DynamicMenuEntry
{
public string MainMenuName;
public Object Dto;
}
private OperatingSystemTypeDomainContext osdc = new OperatingSystemTypeDomainContext();
private DeviceTypeDomainContext dtdc = new DeviceTypeDomainContext();
public TreeviewNavigator()
{
InitializeComponent();
osdc.Load(osdc.GetOperatingSystemTypesQuery(), LoadOsTypes, null);
dtdc.Load(dtdc.GetDeviceTypesQuery(), LoadDeviceTypes, null);
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
private void LoadOsTypes(LoadOperation lo)
{
machinesByOsTree.Items.Clear();
if (lo.Entities == null || lo.Entities.Count() < 1)
return;
foreach (OperatingSystemTypeDto os in lo.Entities)
{
TreeViewItem i = new TreeViewItem()
{
Header = os.Vendor + " " + os.Name + " " + os.Version,
Tag = new DynamicMenuEntry { MainMenuName = "MachinesByOsType", Dto = os }
};
machinesByOsTree.Items.Add(i);
}
}
private void LoadDeviceTypes(LoadOperation lo)
{
machinesByTypeTree.Items.Clear();
if (lo.Entities == null || lo.Entities.Count() < 1)
return;
foreach (DeviceTypeDto dt in lo.Entities)
{
TreeViewItem i = new TreeViewItem()
{
Header = dt.Name,
Tag = new DynamicMenuEntry { MainMenuName = "MachinesByDeviceType", Dto = dt }
};
machinesByTypeTree.Items.Add(i);
}
}


Note that, for each of the dynamic entries, we are recording a custom type “DynamicMenuEntry” that defines what the major menu category is. In other words, that is how we decide which menu to display, and the dynamic data is the parameter we pass to it.


The next stage is that we need to act on when the user clicks on a menu entry. We do this via the SelectedItemChanged event on the TreeView control which, in XAML, looks like this;


<controls:TreeView x:Name="treeview"
BorderThickness="0" 
Margin="0" 
SelectedItemChanged="treeview_SelectedItemChanged">
<controls:TreeView.Background>


And the code behind looks like this;


private void treeview_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
try
{
TreeView view = sender as TreeView;
if (view == null)
return;
TreeViewItem item = (TreeViewItem)view.SelectedItem;
if (item == null)
return;
if (item.Tag == null)
return;
if (item.Tag is string)
{
RunMenu((string)item.Tag, null);
return;
}
if (item.Tag is DynamicMenuEntry)
{
DynamicMenuEntry dme = (DynamicMenuEntry)item.Tag;
RunMenu(dme.MainMenuName, dme.Dto);
return;
}
}
catch (Exception exc)
{
ChildWindow err = new ErrorWindow("Unable to process navigation menu command", exc.Message);
err.Show();
return;
}
}


Here, we decode which menu we need to call, based on whether the tag is a string (“this is the page I need you to run”) or whether it is a DynamicMenuEntry (“this is the page I need you to run, and this is the parameter to pass”).


So, there we have it. For static menu entries, we use “.tag” to define the single page to call and for dynamic entries, the .tag defines the page to call and the parameter to pass (typically some kind of entity ID).

No comments:

Post a Comment