In my recent post I went through various design aspects of nRoute Containers, albeit without a single line of code - as a counter, I showed a demo specifically for Containers dubbed Office® 2010 (or more correctly Officer 2010). In this post I'll go through the Officer demo app, and like with my previous "step in" post, I'll enumerate various items in a point-by-point fashion, relating them to Container related use and design concerns.
Note even though we are aping Microsoft Office 2010 visuals, the demo app is not designed to reproduce office type functionality - it is just an exercise in learning. Screenshot for Microsoft Excel 2010, below:
And below are the Silverlight 3 version screenshots, click here to run the demo app (requires Silverlight 3).
Application Structure
One traditional but important type of "separation of concerns" is componentization, and assemblies are often the physical manifestation of such separation. Because the Officer app is rather shallow, there is no great story as far as componentization goes, still I've separated the app into three projects - shell, content and infrastructure related projects. The infrastructure is basically controls and components common to all, the shell is the silverlight "application" that hosts the root visual and styling content, and the content project quite self-descriptively houses the content.
However the point of having three projects was to highlight the possibility of dependency-mitigation using the Url-based mediator. For example, apart from a single line of code there is no dependency between the shell and the content project. The single line of code (line 18, below), is just used to register the navigation mappings but can equally be rigged in multiple other non-dependent ways. For example, you could dynamically load the content assembly and then ask it register the mappings, or a better approach would be to use something like Prism which offers bootstrapping and module-based initialization entrapments within which you could set the mappings.
The Application Class
The application class as I have mentioned multiple times is conveniently hijacked in nRoute to host a so-called application-wide default container - which by definition handles any navigation without a specified handler. In this case we use the build-in StatefullApplication class, onto which we set the RootVisual and the NaivgationContainer properties as shown below.
public partial class App : nRoute.Navigation.Application.StatefullApplication
{
private void OnStartup(object sender, StartupEventArgs e)
{
// we create the shell
var _shell = new Shell();
this.RootVisual = _shell;
// we set the default container and set the shell's data context
this.NavigationContainer = _shell.PresistentContainer;
_shell.DataContext = this.GetApplicationLinks();
// we can customize this using deep-linking or something - but for now it is static
_shell.LinksListBox.SelectedIndex = 1;
// and we map the assemblies - this is strongly typed, but you could do it many other ways
ActionService.MapAssemblyActions(this.GetType().Assembly);
NavigationService.MapAssemblyRoutes(typeof(Officer2010.Content.Views.NewDialog).Assembly);
}
// .. other code
}
The code above should be self-explanatory apart from the GetApplicationLinks call and setting index on a list box - which I'll get to below. Note, here we not enabling deep-linking or browser-shell integration, both of which are normally setup at the application class level - though it is perfectly doable.
Navigation Structure
If you look at the navigation control (shown on the right), it has a set of primary nodes which are both navigable and selectable. However a bit unusual is that it also headlines a couple of links that seemingly are functional and not navigational in nature - like save or open. To represent this structure, I just created two POCO classes, with the primary nodes (Link class type) mapped to navigation Urls and its children (of LinkAction class type) mapped to action Urls. Further, a list of the primary nodes, yielded by in the GetApplicationLinks method in the App class, is set as the DataContext of the root visual (Line 11 above).
The navigation controls is just itself a common ListBox, with a custom ItemTemplate and importantly a customized ItemContainerStyle. And since we know the binding structure, the templates bind to the relevant properties, they also make use of nRoute behaviours attach the navigation and action Urls (see LinksListResources.xaml). Just as a side point, the ItemContainerStyle holds the Lists control's template for the mouse-over and selected visuals, though customizing it is a bit inaccessible in Blend - to customize use the Object > Edit Other Styles > Edit Item Container > . menu hierarchy.
The Shell Structure
The structure of Shell.xaml is really simple, it is basically 3x3 grid with basically a left-anchored list box, a flexible-width container, and a persuado back button up-top. The image below outlines the layout structure:
Custom Containers
With this demo app I wanted to highlight two customization points with Containers, styling and state-management. Because containers by definition model their state related behaviours, I wanted to create a container that could persist its state in Isolated Storage on exit and resurrect it on start - however that was not to be simply because in SL we can't arbitrarily serialize objects, so in a future release I'll do the necessary tinkering with the state management classes to hopefully get this scenario working (however this in entirely possible in WPF). Nonetheless, in the code you'll see how we can still use the isolated storage to persist data, however on a page-by-page basis.
The styling customization was easy pickings; firstly I wanted to put in place some navigation-induced transition effects for which I created a custom control-template that wrapped the presenter in Silverlight Toolkit's transition control, you could also easily use other libraries such as Silverlight FX.
<!-- TRANSITION CONTENT CONTROL WRAPPER -->
<toolkit:TransitioningContentControl RestartTransitionOnContentChange="True">
<ContentPresenter/>
</toolkit:TransitioningContentControl>
The other visual tweak I did was to use the SL3 specific child window for highlighting navigation failures, this has much better aesthetics than the MessageBox approach used earlier - try clicking the "Back" button. Lastly, one stupidity I've done with the Containers API is to expose ICommand based properties to trigger navigation, which ensured very clumsily use semantics; so in the custom container I've somewhat correct that by exposing a simple Url property that triggers navigation when changed.
Sub-Containers
One of the common user-cases involve having container like regions within a page, which itself is hosted in a container - that is nothing special really, but with nRoute you get nice-clean and loosely-coupled contraption to play such scenarios out. For example in the NewDialog.xaml page (shown below) we have two lists of document templates (highlighted with yellow diagonal boxes); selecting any item on either of the two ListBoxes triggers a command in the ViewModel, which in turn causes navigation in the sub-container (shown with red stripes) - that then shows the selected document template's details page. The commands are exposed as properties on the ViewModel, and bound to the ListBoxes' SelectionChanged event using nRoute behaviours, and the sub-container is bound to an "ActiveTemplate" property of the ViewModel.
One of the other useful aspects to sub-containers is that the DataContext of the "host page" flows in the sub-container and any of it's active content, unless specified otherwise. I've used this DataContext inheritance behaviour in OptionsDialog.xaml (shown below); the options page displays Common and Advanced Settings in a sub-container. To hold the settings data I create two POCO classes (the CommonSettings and AdvancedSettings types) and exposed these on the host page's ViewModel as properties. As these two properties "flowed" into the sub-pages, they were used to bind the settings on various UI controls - which really helped, as I didn't have to create extra ViewModels or manage extraneous data exchanges. Also, the POCO classes were earmarked with the DataContract and DataMemeber attributes to enable persistence using the Isolated store in SL, which enables your custom settings to survive multiple disconnected sessions - try it.
Fishing not the fish
In summary, I hope I've shown the use, design, and customization aspects of Navigation Containers - because they really help in logically breaking-down your application and compose it together as a loosely-couple but cohesive unit. So the real take-away should be how you model your application's visuals using containers as a fundamental building-block - in wiser words the fishing as opposed to the fish itself.
To run the Officer® 2010 app, click here (requires Silverlight 3 runtime).
To get nRoute and Officer® 2010 source code, click here.
PS: The detailed Officer demo app is not a showpiece for architecture or coding practices - it was specifically designed around highlighting certain nRoute concepts. Also, you will be pleased to know no code-behind was harmed in the making of this demo.
Update: Recompiled and updated the source for SL3 RTM version