Change of plans. I was going to take you through my journey of creating the next version VRCyclist.com as a way of documenting the ways in which NavFx can be used. However the amount of work I needed to do just to get the point of having something to demonstrate in this and subsequent articles turned out to be too much so I decided to create new demo applications instead. I have designed the new demos to be useful as a learning tool as well as demonstrating how you might use NavFx. To be fair to both camps I have created C# and VB.NET versions of the demo, but I will only be using the C# one in these articles. I have commented both versions extensively so hopefully this won't be a problem for those whose preference is VB.NET.
So with this article I am going to cover how I envisaged the structure of applications that use NavFx and show how this is applied in the demo. This is by no means the only way to structure applications or use NavFx but it is the structure that drove the design. If anyone has a particular scenario in mind that they would like me to cover feel free to post feedback I will do my best to get it covered.
Silverlight Applications
With Silverlight 2.* Microsoft introduced native support for Applications. On the surface this may just appear a packaging mechanism as an Application file (.xap) is a zip file with a different extension. However I see it as much more because like a desktop application it always you to package up related components and libraries so that they can be downloaded as discrete packages to run in a browser. IMHO the Silverlight Application is not dissimilar to the JAVA Applet common on the internet for many years.
For the last few years I have noticed (as I am sure many of you have) a shift to browser based applications, more and more AJAX or similar technologies have been used to get away from postback driven web applications to Rich Interactive Applications(RIA) that run in a browser but feel more like a desktop application. One problem with these types of applications is that you need a wide range of skills to cover all aspects of development (whether a team or individual) X/HTML, JavaScript, .NET Language (C#, VB.NET etc) and SQL (accepted that LinQ and other technologies may eliminate the need for the latter). Whilst there are many of us who have all of these skills, it is still a maintenance issue for development shops. I believe many people are going to see Silverlight 2.* as means of reducing the range of skills required for developing browser applications, whilst increasing the possibilities for rich functionality. In short I can see Silverlight becoming a primary choice for Line of Business applcations, providing desktop like functionality in a browser.
It is this view point that drove the design of NavFx once I decided to create it, and this viewpoint is re-inforced by the fact that this is exactly what we are doing on my current contract. We are web enabling a desktop application by creating it entirely with Silverlight 2.* providing the UI and WCF providing the Business and Data services.
Application Structure
When I start out designing an application UI (web or desktop) the first thing I do is define what I like to call a Shell. The Shell typically provides the controlling and common features of the application. In a web application this would include things like a Header, Footer, Navigation and any side bars or panels that are going to appear on every page, and as I work with ASP.NET this would involve creating one or more Master Pages. In a desktop application the Shell would provide the Menubar, Toolbars and a Statusbar. Once my Shell is in place Icould then concentrate on providing functionality, in a web application this would be through Content Pages or dynamic content created through AJAX, in a desktop application it might be Child Windows, Panels or perhaps an Explorer type application. I could describe many different scenarios but I think you get the idea.
For a Silverlight Application I will take exactly the same approach. I will define my Shell then using NavFx dynamically load pages into one or more content panels. My Shell will contain the navigation elements that control the application and any common features that I want to be always available. The image below shows the Shell I have created for the NavFx Demos.
In this image everything except the centre scrolling pane is part of the Shell for the demo. The Shell comprises the following parts:
- Header - top panel
- Navigation Bar - left side panel
- Options Bar - top centre panel
- Watch Bar - righ side panel
- Output Window - bottom panel
- Content Panel - centre panel
All sizing is proportional and all elements that are not sized are set to Stretch so that the demo will always fill the browser and layout is dynamically adjusted. The purpose of the panels are described below.
Navigation Bar
This is a simple StackPanel with Orientation = "Vertical". It will provide a stack of buttons each loading a different page in the Content Panel designed to demonstrate a feature or usage scenario of NavFx.
Options Bar
This is empty at the moment but will be populated with controls as the demo evolves. These controls will allow you to control some of the NavFx features. For example when I get around to demonstrating the Transitions a list of available Transitors will be available in this panel.
Watch Bar
The Watch Bar is designed to give you some insight into what is happening within NavFx at the moment it shows a list of pages Registered with the Navigator instance used by the Shell, a checkbox that indicates whether the selected page is Pinned in the cache and a count of the registered pages.
Output Window
The grey panel at the bottom displays NavFx specific statements as they are executed. This is intended to help you learn the code that is required to achieve tasks with NavFx. I haven't figured out how to get the last TextBlock added to the panel to scroll into view after is is added yet, if anyone knows how to achieve this let me know through feedback and I will update the demos. The statements in the Output Window are in the appropriate language for the demo and include an indication of the file and method where they can be found in the source code.
How It Works
At this stage the demo comprises two projects NavFxDemo and NavFxDemoPages (NavFxDemoVB and NavFxDemoVBPages for the VB version). NavFxDemo was created from the NavFxApplication template and NavFxDemoPages was created from the Silverlight Libarary template.
NavFxDemo
The application project comprises four items:
- App.xaml
- Shell.xaml
- Home.xaml
- Extensions.cs
App.xaml
App.xaml was created from the NavFxApplication template so is an instance of NavFx.Application, which means it is already wired up to NavFx and has an instance of NavFx.Navigator available for use. As the template was used there is little for me to do (in fact if I weren't providing the Output Window there would be nothing to do at all, as the NavFxApplication template includes a Shell to get you started and it is wired up as the RootVisual ready to go. However for completenes I will cover what happens within the App.xaml.cs code and how this relates to NavFx.
In App.xaml.cs the App class is defined like this:
public partial class App: NavFx.Application
NavFx.Application derives from System.Windows.Application so App is a Silverlight Application with the addition of a GetNavigator() method added by NavFx. This method provides access to a private instance of NavFx.Navigator which is instantiated the first time you call GetNavigator(). This provides a psuedo Singleton Navigator for your application but I did not want to force this hence the reason for it being implemented as a "getter" function rather than following a strict Singleton pattern. In most cases I expect you will use a single Navigator instance but you are not forced to and I will demonstrate in my next article why you might want to use a seperate instance for a page.
The only NavFx code in App.xaml.cs is in the Application_Loaded handler, and to be honest even this does not need to be executed at this point. The code sets up a default Transitor, but this can be done anywhere as long as it done before the first call to one of the GoToPage overloads, I prefer to set up the default early so my handler contains the following:
this.GetNavigator().Transitor = new SimpleTransitor(App.Current);
So all I am doing is loading my Shell and setting up the SimpleTransitor provided by NavFx as my default page transition handler. Notice I am passing App.Current to the constructor for SimpleTransitor, this sets the Application as the default Target for the Transitor. NavFx uses the RootVisual property of the Application to work out how to display pages in this scenario. If the RootVisual implements IHostPage NavFx calls SetContent() on the interface to allow the HostPage to control where new content should be displayed, otherwise it replaces the Children of the RootVisual. There are two other statements in my handler this simply writes the previous NavFx statement to the Output Window using an extension method defined in Extensions.cs and look like this:
Shell.xaml
Shell.xaml is much more interesting than any other item in the demo projects at this time. I won't waste any time talking about the XAML since apart from the outer element being a navfx:HostPage it is all standard stuff.
Shell.xaml was created from the NavFxHostPage template so is an instance of the NavFx.HostPage, which means it is designed for dynamically loading and displaying pages that either fill the whole page or a specific content area of the page. As you have seen in previous sections my Shell is designed to display content pages in a panel with other panels surrounding it. So after defining my UI in the XAML and defining the contentPanel element I need to make sure that this is where content is displayed whenever I navigate to a page. The IHostPage of NavFx requires that my Shell implement a SetContent(), this will be called by Navigator whenever a call is made to a GoToPage method and the target is either an Application or implements IHostPage. The HostPage base class provides a default implementation that replaces all children of the layout root for the page with the new content. This is not the behaviour I want so I need to override the SetContent method like this:
public override void SetContent(System.Windows.Controls.UserControl newContent)
{
this.contentPanel.Children.Clear();
this.contentPanel.Children.Add(newContent);
}
The override replaces the contents of the contentPanel with the new content passed by Navigator.
The next thing I want to do with my Shell is have it load Home.xaml into the content panel as the first content page. I do this in the HostPage_Loaded event handler. Because Home.xaml is part of the application package the process is quite simple, instantiate the page, register then display it like this:
Home homePage = new Home();
this.Navigator.RegisterPage(homePage);
this.Navigator.GoToPage("Home");
Because the Target property of SimpleTransitor was set to App.Current earlier the GoToPage call will trigger a call to SetContent passing the homePage instance. Simple as that, I now have my Shell set up to load on startup, and my Home page as the first content page to be displayed. HostPage_Loaded includes more statements that write output to the OutputWindow but I will leave to investigate these in the source code. The remainder of the code in Shell.xaml.cs is to provide the functionality of the demo. Still in HostPage_Loaded there is a call to a helper method to populate the list of registered pages, I will go through this in a moment after I explain this statement:
this.Navigator.PageLoadCompleted += new EventHandler<PageLoadCompletedEventArgs>(Navigator_PageLoadCompleted);
When pages are loaded from external libraries they are loaded asynchronously, and as I worked on the demo I discovered that I needed to know when this completed so I could populate the list of registered pages, so I added a new PageLoadCompleted even to the NavFx.Navigator. In this statement I am wiring up a handler for this event that will make sure the list is updated after an asynchronous load completes. The handler simply calls the private helper method loadRegisteredPages(), which contains the following code:
this.registeredPages.Items.Clear();
foreach(IPage page in this.Navigator)
{
this.registeredPages.Items.Add(page.Path);
}
this.pageCount.Text = this.Navigator.PageCount.ToString();
This uses some more new features I added recently (these do not appear in the quick reference as yet, I will add them soon). After clearing the list of registered pages it repopulates it using the Enumerator I added recently to support just this kind of functionality, it then updates the count of pages displayed in the Watch Bar. Strictly speaking these features are not required for Navigation, but I figured if I needed them for something as simple as this demo, you may find them useful in your applications.
In the Watch Bar there is a checkbox that is read only and is intended to indicate whether the selected item in the registered pages list is Pinned or not. If a page is Pinned then it will only be cleared from the Page Cache if you explicitly request it. I will be adding a demonstration of this and writing about it in another article. In the SelectionChanged handler the registered pages list I have this statement:
this.isPinned.IsChecked = this.Navigator[this.registeredPages.SelectedIndex].IsPinned;
This uses another recently added feature, the Indexer provided by Navigator. There are two variants of this, the one I am using returns the IPage at a specified Index, the other accepts a string key and returns the IPage with a matching Path.
The final code I am going to cover is that in the handlers for the buttons in the Navigation Bar. The all follow a similar pattern, set the Target property of the Transitor then navigate to the page using one of the GoToPage overloads provided by Navigator. All three handlers home_Click, external1_Click and external2_click include this as the first statement:
this.Navigator.GetTransitor<SimpleTransitor>().Target = Application.Current;
Rather than relying on the Target still being the default set during startup I am explicitly setting prior to each navigation call, at this point it is not strictly necessary, but later I plan to add demonstrations that use different Transitors and I don't want to impose any requirements on how the application is used. This statement uses another new feature added recently the generic GetTransitor method this returns the current Transitor (if it is set) typed as specified. It doesn't do any checking so if you specify the wrong type it will throw an exception. If returns null if the Transitor is not set.
home_Click then calls the most basic GoToPage overload in the knowledge that the page is in the same package and is already registered (this latter assumption will have to change when I add the demonstration for ClearCache:
this.Navigator.GoToPage("Home");
external1_Click calls the overload of GoToPage designed to load and display a page in an external library in one statement:
Uri demoPagesUri = new Uri(@"NavFx.NavFxDemo.Pages.dll", UriKind.Relative);
this.Navigator.GoToPage("PanelPage1",false, demoPagesUri);
This overload of GoToPage expects a Uri instance as the third argument, the Uri must be relative and currently it only works with a dll which must be in the same folder as the .xap file. These file location and UriKind requirements are imposed by Silverlight not NavFx. Because of these requirements I added another overload that creates the Uri instance for you and only requires the path, i.e. the dll file name. This newest overload is the one used by external2_Click:
this.Navigator.GoToPage("PanelPage2",false, @"NavFx.NavFxDemo.Pages.dll");
Well that is all the NavFx specific code in the demos as they stand at the time of writing. In my next article I will be covering the implementation of a Wizard using NavFx and it's NextPage and PreviousPage methods. As always feel free to ask questions via the feedback feature here on my blog or the discussions feature on CodePlex. I am going to wind up with a tip that is not NavFx specific but saves a lot of bother when you are working with Silverlight Libraries.
Keeping Up To Date
When you wire up a Silverlight Application with a web site as I prefer to do, VS very helpfully makes sure the latest version of the .xap file is copied to the ClientBin folder of the web site project (or to the Debug or Release folder if you enable Configuration Specific Folder support), but it doesn't do this for Silverlight Libraries you have to set this up yourself or manually copy the files (forget the latter). The best way to do this is via the Post Build Event, that way every time you build your library the new build is copied to where it needs to be. Follow these steps to set up a post build event:
- Bring up the properties for the Silverlight Library project
- Select the Build Events tab
- Enter one of the statements provided below this list into the textbox labelled "Post-build event command line"
- Save the project properties
If you are using Configuration Specific folder use this post build command line
xcopy "$(TargetDir)$(TargetName).*" "$(SolutionDir)NavFxDemoWeb\ClientBin\$(ConfigurationName)\" /Y
Otherwise use this one
xcopy "$(TargetDir)$(TargetName).*" "$(SolutionDir)NavFxDemoWeb\ClientBin\" /Y
Previous Article Next Article