nRoute: Let's borrow some style

Posted by Rishi on 07-Apr-09 3:20 PM - Comments (5)

One of the cool thing about the Silverlight 3 Navigation Framework (SL3NF) is that out-of-box it comes with an usable default template. But what is even cooler, is that Microsoft will provide a number of swappable themes for the default template. Now, I am no Microsoft and if you were to choose nRoute I wouldn't want you to miss out on all the fun, so in this post I'll show you how we can "borrow" some style. Alternatively, you can use this as a guide to consume these themes in any Silverlight 2 (SL2) projects even without nRoute.

What I will do is go over each file in the SL3NF template, and translate each SL3NF specific construct into its SL2/nRoute equivalent. I kick off with a blank SL2 Application, to which I add an nRoute assembly reference.

The Main Page

I start by renaming the default Page.xaml in the SL2 project into MainPage.xaml as per it's equivalent in SL3NF template. Next I copy over the xaml from the SLN3F template, and ignore the code behind. Now, I'll make a couple of changes to MainPage's copied xaml, as itemized below:

  • Introduce two nRoute related XML namespaces (nav for nRoute.Behaviours.Navigation and cntr for nRoute.Controls) also I remove the SL3 specific navigation namespaces
  • Next I remove all the Event Handling attributes (like Click="NavButton_Click") and also the Tag attributes (like Tag="/Views/HomePage.xaml")
  • I change the navigation:Frame into cntr:NavigationContainer and remove it's "Source" attribute
  • On each of the buttons I introduce a navigation behaviour, with something like nav:Click.NavigateUrl="Views/HomePage/"

Below, are the xamls for the MainPage.xaml file, first from SL3NF and then SL2 Project:

<UserControl x:Class="SilverlightApplication.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation">
    <Grid x:Name="LayoutRoot" Background="{StaticResource ApplicationBackgroundColorBrush}">

        <Grid Style="{StaticResource NavigationContainerStyle}">

            <Border Style="{StaticResource NavigationBorderStyle}">

                <StackPanel Style="{StaticResource NavigationPanelStyle}">

                    <Button Click="NavButton_Click" Tag="/Views/HomePage.xaml" Content="name list" 
                            Style="{StaticResource PageLinkStyle}"/>
                    <Button Click="NavButton_Click" Tag="/Views/AboutPage.xaml" Content="detail list" 
                            Style="{StaticResource PageLinkStyle}"/>

                </StackPanel>

            </Border>

            <Border Style="{StaticResource BrandingBorderStyle}">

                <StackPanel Style="{StaticResource BrandingPanelStyle}">

                    <TextBlock Text="your." Style="{StaticResource BrandingTextNormalStyle}"/>
                    <TextBlock Text="application." Style="{StaticResource BrandingTextHighlightStyle}"/>
                    <TextBlock Text="name" Style="{StaticResource BrandingTextNormalStyle}"/>

                </StackPanel>

            </Border>

        </Grid>

        <Border Style="{StaticResource FrameContainerStyle}">

            <Border Style="{StaticResource FrameInnerBorderStyle}">

                <navigation:Frame x:Name="Frame" Source="/Views/HomePage.xaml"
                                  HorizontalContentAlignment="Stretch"
                                  VerticalContentAlignment="Stretch"
                                  Padding="15,10,15,10"
                                  Background="White"/>

            </Border>

        </Border>

    </Grid>
</UserControl>
<UserControl x:Class="nRouteNavigationTemplate.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cntr="clr-namespace:nRoute.Controls;assembly=nRoute.Silverlight"  
    xmlns:nav="clr-namespace:nRoute.Behaviours.Navigation;assembly=nRoute.Silverlight">
    <Grid x:Name="LayoutRoot" Background="{StaticResource ApplicationBackgroundColorBrush}">
       
        <Grid Style="{StaticResource NavigationContainerStyle}">

            <Border Style="{StaticResource NavigationBorderStyle}">

                <StackPanel Style="{StaticResource NavigationPanelStyle}">

                    <Button nav:Click.NavigateUrl="Views/HomePage/" Content="home" 
                            Style="{StaticResource PageLinkStyle}"/>
                    <Button nav:Click.NavigateUrl="/Views/AboutPage/" Content="about" 
                            Style="{StaticResource PageLinkStyle}"/>

                </StackPanel>

            </Border>
            
            <Border Style="{StaticResource BrandingBorderStyle}">

                <StackPanel Style="{StaticResource BrandingPanelStyle}">

                    <TextBlock Text="your." Style="{StaticResource BrandingTextNormalStyle}"/>
                    <TextBlock Text="application." Style="{StaticResource BrandingTextHighlightStyle}"/>
                    <TextBlock Text="name" Style="{StaticResource BrandingTextNormalStyle}"/>

                </StackPanel>

            </Border>

        </Grid>

        <Border Style="{StaticResource FrameContainerStyle}">

            <Border Style="{StaticResource FrameInnerBorderStyle}">

                <cntr:NavigationContainer x:Name="Frame"
                                  HorizontalContentAlignment="Stretch"
                                  VerticalContentAlignment="Stretch"
                                  Padding="15,10,15,10"
                                  Background="White"/>

            </Border>

        </Border>

    </Grid>
</UserControl>
Next, have a look at the code-behinds, also first from the SL3NF followed by the SL2 project.
namespace SilverlightApplication
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void NavButton_Click(object sender, RoutedEventArgs e)
        {
            Button navigationButton = sender as Button;
            String goToPage = navigationButton.Tag.ToString();
            this.Frame.Navigate(new Uri(goToPage, UriKind.Relative));
        }

    }
}
namespace nRouteNavigationTemplate
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }
    }
}

Now, if you wanted to consume these designs in SL2 without nRoute, you can replace the NavigationContainer with a plain ContentContainer and put in your own mechanism to switch its content.

Home Page

In the SL3NF the home page is based on a newly introduced type called Page, well, for the SL2 project I go for plain old UserControl. And also we don't need the navigation namespace or the title property, but the main grid from the template is copied intact. The xaml of both projects are show below, the first one is from the SL3NF.

<navigation:Page x:Class="SilverlightApplication.HomePage" 
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           Title="HomePage Page">
    <Grid x:Name="LayoutRoot" Background="White">
        
        <StackPanel> 
            
            <TextBlock Text="Home" Style="{StaticResource HeaderTextStyle}"/>
            
            <StackPanel Style="{StaticResource ContentTextPanelStyle}">
            
                <TextBlock Text="To learn more about Silverlight visit " Style="{StaticResource ContentTextStyle}"/>
                <HyperlinkButton Content="http://www.silverlight.net" NavigateUri="http://www.silverlight.net" Style="{StaticResource HyperlinkButtonStyle}"/>
            
            </StackPanel>
        </StackPanel>
    </Grid>
</navigation:Page>
<UserControl x:Class="nRouteNavigationTemplate.Views.HomePage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="LayoutRoot" Background="White">

        <StackPanel>

            <TextBlock Text="Home" Style="{StaticResource HeaderTextStyle}"/>

            <StackPanel Style="{StaticResource ContentTextPanelStyle}">

                <TextBlock Text="To learn more about Silverlight visit " Style="{StaticResource ContentTextStyle}"/>
                <HyperlinkButton Content="http://www.silverlight.net" NavigateUri="http://www.silverlight.net" Style="{StaticResource HyperlinkButtonStyle}"/>

            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>
Next are the code behinds, the first one is again from the SL3NF project.
namespace SilverlightApplication
{
    public partial class HomePage : Page
    {
        public HomePage()
        {
            InitializeComponent();
        }

        // Executes when the user navigates to this page.
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

    }
}
namespace nRouteNavigationTemplate.Views
{
    public partial class HomePage : UserControl
    {
        public HomePage()
        {
            InitializeComponent();
        }
    }
}
About Page

The about page is almost exactly like home page - no need to show it all over again.

Error Page

The error page in the SL3NF is based on a new type called ChildWindow, which has no equivalent in SL2 so I'll skip importing this. However, nRoute does shows a modal error message, using the good-old MessageBox.

Application Class (App.xaml)

In the App.xaml file I basically copied all the content form the SL3NF, which was really the application.resources element. Now, the code-behind file in SL3NF is vanilla SL3 App class, though it notably uses a new ErrorWindow type to handle/show exceptions. However in the SL2 project we need to make a couple of change to the standard App class file, particulars enumerated below:

  • We inherit from a custom application class, of type NavigationApplication (Line 5)
  • We set attributes to the app class to map navigation content, specifically for the Home Page and the About Page. Note, we could have set the attributes on the UserControls themselves but this is another way, perhaps a neater way.  (Line 3 & 4)
  • We set the Application-Level Container to be the Main Page's Navigation Control (Line 19)
  • We set the Application-Level Container's Initial Url to load (Line 20)
  • And we register for Browser Integration Hooks specifically (Line 18)

The code behind files from both projects are show below, the first one like before belongs to the SL3NF.

namespace SilverlightApplication
{
    public partial class App : Application
    {

        public App()
        {
            this.Startup += this.Application_Startup;
            this.UnhandledException += this.Application_UnhandledException;

            InitializeComponent();
        }

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            this.RootVisual = new MainPage();
        }

        private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
            // If the app is running outside of the debugger then report the exception using
            // the browser's exception mechanism. On IE this will display it a yellow alert 
            // icon in the status bar and Firefox will display a script error.
            if (!System.Diagnostics.Debugger.IsAttached)
            {

                // NOTE: This will allow the application to continue running after an exception has been thrown
                // but not handled. 
                // For production applications this error handling should be replaced with something that will 
                // report the error to the website and stop the application.
                e.Handled = true;
                ChildWindow ErrorWin = new ErrorWindow(e.ExceptionObject);
                ErrorWin.Show();
            }
        }
    }
}
namespace nRouteNavigationTemplate
{
    [nRoute.Navigation.MapNavigationContent("Views/HomePage/", "HomePage Page", typeof(Views.HomePage))]
    [nRoute.Navigation.MapNavigationContent("Views/AboutPage/", "AboutPage Page", typeof(Views.AboutPage))]
    public partial class App : nRoute.Navigation.Application.NavigationApplication
    {

        public App()
        {
            this.Startup += this.Application_Startup;
            this.UnhandledException += this.Application_UnhandledException;
            InitializeComponent();
        }

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            this.RootVisual = new MainPage();
            NavigationService.MapLoadedAssembliesRoutes();
            this.NavigationContainer = ((MainPage)this.RootVisual).Frame;
            ((MainPage)this.RootVisual).Frame.InitialUrl = this.GetCurrentBookmarkOrDefaultUrl("Views/HomePage/");
            this.RegisterBrowserNavigationHooks(this.GetCurrentBookmarkOrDefaultUrl("Views/HomePage/"));
        }

        private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
            // If the app is running outside of the debugger then report the exception using
            // the browser's exception mechanism. On IE this will display it a yellow alert 
            // icon in the status bar and Firefox will display a script error.
            if (!System.Diagnostics.Debugger.IsAttached)
            {

                // NOTE: This will allow the application to continue running after an exception has been thrown
                // but not handled. 
                // For production applications this error handling should be replaced with something that will 
                // report the error to the website and stop the application.
                e.Handled = true;
                Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); });
            }
        }
        private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e)
        {
            try
            {
                string errorMsg = e.ExceptionObject.Message + e.ExceptionObject.StackTrace;
                errorMsg = errorMsg.Replace('"', '\'').Replace("\r\n", @"\n");

                System.Windows.Browser.HtmlPage.Window.Eval(
                    "throw new Error(\"Unhandled Error in Silverlight 2 Application " + errorMsg + "\");");
            }
            catch (Exception)
            {
            }
        }
    }
}
The xaml files are basically the same, please see the attached samples.

Borrowing Styles

That's it, we are done - we have captured the same look and feel as the default template in SL3NF (see Sample 1 below). What's more, we can now get all the goodness of swappable themes, in fact I borrowed two themes Brad Adam posted on his blog as shown below (click to open).

Click to open Sample 1 
Sample 1: Download Code (12 KB)

Click to open Sample 2
Sample 2: Download Code (12 KB) 

Click to open Sample 3
Sample 3: Download Code (12 KB)

Would you like to see this as the default template for nRoute too, or one that highlights the full range of the features in nRoute?

Differences

I wanted to point out some differences between the two projects:

  • The nRoute version has absolutely zero code behind, that is not the case with SL3NF
  • nRoute has a relatively involved application code-behind file, but in defence the reason is that nRoute doesn't make a lot of assumptions that SL3NF implicitly makes for you. For example, we don't assume browser integration so you have to call for it explicitly, note you can provided your own implementation too. Similarly, we don't assume your content mapping/loading strategy because we give a number of options, which are further extensible, hence the need for explicit calling. And we also don't presume the presence of application-level containers, this has to be deliberately specified - but the advantage is that it gives you more flexibility - consider the demo app, each blade/workspace can provisionally take on the role of the default-container, which is a very useful and powerful concept
  • The nRoute version should work in SL2, SL3 and also on WPF
  • SL3NF has nicer error-handling facilities, build atop the newer components in SL3

Despite the apparent differences, going forward I will try and adopt the navigation related controls introduced in SL3. This is possible because nRoute was designed with extensibility in mind, and most of the new types can possibly be co-opted by slapping an interface or two on them. This will hopefully give you an even richer experience, and first-class toolset options.

Potential Problems

Let me point out two problems you might run into with nRoute, as it stands today:

  • The browser-integration script in nRoute has a problem with the standards-mode in IE, though it works fine in all other browsers. As a temporary fix for IE, you can remove the DOCTYPE statement in the host html/aspx file
  • By default or when you edit App.xaml file, VS will automatically make the App class inherit from System.Windows.Application type, which you'll need to remove if you have inherited from a custom application type. To do so, right-click and select "Go To Definition" on the "InitializeComponent' call in the App class's constructor method, where you can remove the offending inheritance call.

I hope this gives you have a bit of an idea about nRoute's standing vis-à-vis SL3NF. Going further, I will have a more direct one-to-one comparison between the two in an upcoming post.

Comments

trackback
DotNetKicks.com
on 27-Mar-09 2:21 PM
Trackback from DotNetKicks.com

nRoute: Let's borrow some style

trackback
DotNetShoutout
on 27-Mar-09 2:24 PM
Trackback from DotNetShoutout

nRoute: Let's borrow some style

Xaml Templates
Xaml Templates Romania
on 28-Mar-09 5:43 PM
Hi,
at http://www.xamltemplates.net/sl you can see a complete theme for all the Silverlight 2.0 controls

Levin
Levin United States
on 30-Mar-09 2:58 AM
Wow!
biggest discovery today~
Nice job,

Rishi
Rishi
on 30-Mar-09 10:19 AM
@Levin, Thankz..

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading