Perspective : Dynamic page management of a Silverlight application

Perspective : Dynamic page management of a Silverlight application

Silverlight 2.0 has no high-level feature to manage the pages of a consistent application. Loading a large application can be very long, and it is necessary to establish a mechanism for application assemblies progressive loading.

The Perspective for Silverlight framework offers a solution to this problem.

Created in the same educational spirit as Perspective for WPF this framework offers the following features :

  • On-demand packages, assemblies and pages loading,
  • Simple but powerful visual navigation system among the pages of the application,
  • Application parameters management.

Application sources organization

To optimize the loading time, the features of a Silverlight application should be divided into several packages.

Each package is itself made of a Silverlight application and deployed as a .xap file gathering application assemblies and reference assemblies. The features with a dependency on assemblies that are not in the runtime (like Silverlight Toolkit) shoud be gathered in a package.

The first loaded package should be as light as possible. His role will be to display a splash screen containing a Silverlight animation, and to launch the application main user interface, potentially from another package.

App.xaml files will be deleted from packages (except from the first).

The referenced assembly already loaded by previous packages should not be embedded in .xap files (for that, assign their "Copy Local" property to false).

Thus, the PerspectiveDemo application (the Perspective example application) is organized as follow :

  • PerspectiveDemo.xap package : application's entry point (App.xaml) and splash screen (Splash.xaml). Reference on Perspective.Core.Deployment.dll.
  • PerspectiveDemo.UI.xap package : MainPage.xaml, application's main page, which handles pages navigation. Reference on Perspective.Core.Deployment.dll, Perspective.Wpf.dll and on Microsoft.Windows.Controls (Silverlight Toolkit).
  • PerspectiveDemo.UI.Pages.xap package : application feature pages.

PackageManager

The PackageManager class allows to dynamically load a package (.xap file), its assemblies and a Silverlight object (typically a UserControl).

This class is contained in the Perspective.Core.Deployment.dll assembly, which weighs only 13 Kb and should be referenced by the main application package. Its LoadObjectAsync() method loads a Silverlight object from a given package. It is used in the Application.Startup event (App.xaml.cs) to load the main page of the application, after displaying the splash screen. This method is asynchronous. It takes as arguments the namespace and the class name. The ObjectStateChanged event is fired when the object is loaded.

private void Application_Startup(object sender, StartupEventArgs e)
{
    // loads the splash screen
    this.RootVisual = new ContentControl();
    (this.RootVisual as ContentControl).HorizontalContentAlignment = HorizontalAlignment.Stretch;
    (this.RootVisual as ContentControl).VerticalContentAlignment = VerticalAlignment.Stretch;
    (this.RootVisual as ContentControl).Content = new Splash();
...
    // loads the main page
    PackageManager mainPackageManager = new PackageManager();
    mainPackageManager.ObjectStateChanged += new EventHandler<ObjectInfoEventArgs>(mainPackageManager_ObjectStateChanged);
    mainPackageManager.LoadObjectAsync("PerspectiveDemo.UI", "MainPage");
}
void mainPackageManager_ObjectStateChanged(object sender, ObjectInfoEventArgs e)
{
    // displays the main page
    (this.RootVisual as ContentControl).Content = e.ObjectInfo.Instance;
}

Navigation among application pages

The navigation features of Perspective are the following ones :

  • Only one page can be displayed at once, in a visual container which also displays a list of opened pages. Each element of this list holds a button that can close the page.
  • Pages are standard Silverlight UserControl. It is not necessary to implement an interface or to implement a mechanism at their level.
  • Navigation can be synchronized with a menu system (i.e. a TreeView).

2 kinds of visual containers are provided :

  • ComboFrame : container managing the list of opened pages in a ComboBox.

ComboFrame

  • ListFrame : container managing the list of opened pages in a ListBox.

ListFrame

These classes have a LoadPageAsync() method and a PageStateChanged event that encapsulate corresponding PackageManager features (LoadObjectAsync() and ObjectStateChanged). The PageStateChanged event is also fired when the page becomes active or when it is unloaded.

MainPage.xaml should contain one of these 2 containers and a menu system:

<UserControl x:Class="PerspectiveDemo.UI.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:pc="clr-namespace:Perspective.Wpf.Controls;assembly=Perspective.Wpf"
    xmlns:mwc="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="3*"/>
        </Grid.ColumnDefinitions>
        <mwc:TreeView Name="tvMenu" Grid.Column="0">
            <mwc:TreeViewItem Header="Page1" IsExpanded="True" Selected="TreeViewItem_Selected"/>
            <mwc:TreeViewItem Header="Page2" IsExpanded="True" Selected="TreeViewItem_Selected"/>
            <mwc:TreeViewItem Header="Page3" IsExpanded="True" Selected="TreeViewItem_Selected"/>
        </mwc:TreeView>
        <Border Grid.Column="1" BorderBrush="Red" BorderThickness="4" CornerRadius="4">
            <pc:ComboFrame 
                Name="frame" 
                Margin="5"
                PageStateChanged="Frame_PageStateChanged" />
        </Border>
    </Grid>
</UserControl>

When a menu item is selected, the corresponding page is loaded from a package:

private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
{
    TreeViewItem tvi = (TreeViewItem)sender;
    string namespaceBase = "PerspectiveDemo.UI.Pages";
    frame.LoadPageAsync(
        namespaceBase, 
        tvi.Header.ToString(), 
        tvi.Header.ToString());
}

MainPage

A key can be passed as argument to the LoadPageAsync() method to identify the page. The key is used as the title of the page in the container's list. A page call with a key already used triggers the PageStateChanged event with an ObjectState argument having the Activated value. In this case, the existing object is activated, i.e. redisplayed. The code for the example application replies to this event by resynchronizing the TreeView with the page.

If the passed key is null, the ID matches the class name.

To enable multiple instantiations of the same class, it is necessary to use a different key for each call :

private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
{
    TreeViewItem tvi = (TreeViewItem)sender;
    string namespaceBase = "PerspectiveDemo.UI.Pages";
    frame.LoadPageAsync(
        namespaceBase, 
        tvi.Header.ToString(), 
        String.Format("{0} - {1}", tvi.Header.ToString(), DateTime.Now));
}

Multiinstantiation

Application parameters management

The ParametersHelper class helps to manage arguments passed to the Silverlight application.

The custom plug-in settings can be specified in the HTML object element's initParms chain, or in the InitParameters property of the Silverlight ASP.NET server control. In the Silverlight application, these settings can only be get in the Startup event of the Application object. The LoadInitParametersFrom() method of ParametersHelper class makes it possible to get them to keep them available throughout the life of the application in the InitParameters dictionary property.

private void Application_Startup(object sender, StartupEventArgs e)
{
...
    ParametersHelper.LoadInitParametersFrom(e.InitParams);
...
}

Parameters can also be specified in the URL.

The ParametersHelper.ReadParameterValueFromUrlFirst() method reads a parameter value looking first in the URL, and the in the InitParameters dictionary. Thus, HTML hard-coded parameters may be overriden by URL parameters.

Predefined parameters names are specially managed by ParametersHelper for easy reading through properties by using ReadParameterValueFromUrlFirst() :

  • ns (NamespaceValue property) : package base namespace,
  • page (PageValue property) : page class name, including the partial namespace inside the assembly,
  • key (ElementKeyValue property) : object key (optional).

Thus, The URL http://localhost:49658/PerspectiveDemoTestPage.aspx?page=Page1&ns=PerspectiveDemo.UI.Pages can directly display the specified page (Page1.xaml) with the following code in the MainPage constructor :

public MainPage()
{
    InitializeComponent();
    string partialClassName = ParametersHelper.PageValue;
    string namespaceBase = ParametersHelper.NamespaceValue;
    string elementKey = ParametersHelper.ElementKeyValue;
    if (!String.IsNullOrEmpty(partialClassName)
        && !String.IsNullOrEmpty(namespaceBase))
    {
        frame.LoadPageAsync(namespaceBase, partialClassName, elementKey);
    }
}

About this article

Author : Olivier Dewit.

History :

  • January 26, 2009 : 1st publication (Perspective for Silverlight, version 0.9)