Master pages in Silverlight, part IV : progressive download in the background

by gleblanc 30. January 2009 19:32

Something is killing some RIA applications : the time it sometimes takes to see the first page. So, let's see how to keep the master page as small as possible (a few KB for the master page and the initial content page) and progressively load (in the background) other content pages and all high resolution images. We will also show how to pass information between pages.  


Project / source code

As in previous posts dedicated to master pages, we create a solution that initially (as usual) contains just one project. This initial project contains the master page (Page.xaml). In this project, we add the initial content page (Page1.xaml). Images are not included as resources in the project to restreint the initial XAP file to a very reasonable size : 11 KB, that dramatically decreases the initial load time. These images (in high resolution) have to be copied in the ClientBin folder and will be loaded from the server, in the background. They will be displayed as soon as they become available on the client machine.

Page2 and Page3 content pages are created as projects in the solution : right-click on the solution (first line in the Solution Explorer window), Add New Project, Silverlight Application. Check the "Link this Silverlight control into an existing web site" checkbox but uncheck the "Add a test page" checkbox. The two projects newly created are named Proj4Page2 and Proj4Page3. Both projects contain Page.xaml and Page.xaml.cs. Feel free to change names. Prepare Page2 and Page3 as usual. After compilation, Proj4Page2.xap and Proj4Page3.xap files are created in the ClientBin folder. These files will have to be copied to the server.

How to pass information between pages ? We create another new project named Proj4Data. In the Page.xaml file, remove the Grid tag, though it is not mandatory (the goal is to keep Proj4Data.xap as small as possible). Let's assume Page2 and Page3 have to pass a list of strings to other pages. In Page.xaml.cs file of the Proj4Data project, we add :

 public static List<string> Page2Lst;
 public static List<string> Page3Lst;

In all projects but Proj4Data, we add a reference to Proj4Data : right-click on a project, Add Reference, Projects. This increases each XAP file by 4 KB, quite acceptable though it could still be possible to decrease that footprint. In our case, the master page makes the status bar accessible from any page. To do so, we also add (the control - a TextBlock- is named stBar in the master page) :

 public static TextBlock statusBar;

Now, let's link things and progressively load high resolution images and pages in the background (while the initial page is already displayed). In Page.xaml.cs of the main project (the one containing the master page and the initial content page), we add :

 int nStep = 1;
 WebClient client;
 public Page()
 {
  InitializeComponent();
  page1 = new Page1();
  PageContainer.Children.Add(page1);
  client = new WebClient();
  client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
  client.OpenReadAsync(new Uri("Clouds.jpg", UriKind.Relative));
  Proj4Data.Page.statusBar = stBar;
 }

Page1 is included in the initial XAP file. There is thus no need to explicitly load it from the server. The different loads are performed in the background. We start with the background image for the master page (the Clouds.jpg is not inserted as resource but just copied in the ClientBin folder). The last line means that Proj4Data.Page.statusBar is another way to reference the stBar control in the master page. The OpenReadCompleted function is the same as seen in the previous post :

 using System.Windows.Media.Imaging;
 using System.Windows.Resources;
 using System.Reflection;
 ..... 
 void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
 {
  BitmapImage img;
  StreamResourceInfo sri, sri4dll;
  AssemblyPart asmPart;
  Assembly asm;
  UserControl uc;
  switch (nStep)
  {
   case 1 :        // got background image for masterpage
      nStep++;
      .....        // initiate async read for Page1 background image
      break;
   case 2 :        // get background image for Page1
      .....
      nStep++;
      .....        // initiate async read for Page2
      break;
   case 3 :        // got Page3
      .....
      nStep++;
      .....        // initiate async read for Page3
      break;
   case 4:         // got Page3
      .....
      break;
  }
 }

There is no need to repeat what was explained in the previous post. Moreover, source code is provided, as usual.

See you soon for another improvement, with the dynamic load of controls that have an heavy footprint.  

Tags:

Master pages in Silverlight, Part III : Dynamically loading content pages

by gleblanc 20. January 2009 22:37

In posts I and II of this serie, the pages (to be inserted in the page-holder cell of the master page) were coming from the same XAP file (our Silverlight application had only one XAP file). This may seem convenient but can lead to a XAP file whose size could discourage users at load time. You know that users are becoming a bit nervous whenever they have to wait more than a few seconds... You also know that initial bad feelings are hard to change ! 

In this post, the first two pages (Page1 and Page2) are coming from the initial XAP file (as in previous posts) but we are now adding a third page that is dynamically loaded when needed. Doing so, our Silverlight application is made of two (or more) XAP files. Each XAP file becomes reasonable in size and only needed pages are loaded in memory. Once a page is loaded, there is no need to reload it from the server when needed again : we detect that the page is in memory. We'll also show how to pass information from page to page (initially or dynamically loaded).   


Project / source code

We start from the previous project, with Page as master page and Page1 and Page2 as content pages (these two pages being loaded at start-up time, since the three pages are included in the same XAP file). The project is named SilverlightApplication3. The background image for Page1 is a drawing from Henri Matisse and the one for Page2 is a drawing from Pablo Picasso. After compilation, the file SilverlightApplication3.xap is created in the ClientBin directory. This XAP file will be automatically loaded at application start-up time. 

Now, let's create our second XAP file (that will contain Page3). In the solution (first line in Solution Explorer), we create a new project that we name Project4Page3. There is no need to request a test page (so, uncheck the corresponding check box) and link the Silverlight application to the existing web application :

Project creation 

In the project named Project4Page3, Visual Studio automatically creates a Page.xaml file (and the corresponding Page.xaml.cs file). We remove Width and Height attributes as usual. In Page.xaml, we add a few Silverlight controls and respond to Click events. Business as usual... Of course, we could choose better names. The background image is "Babel Tower" from Brueghel (in high resolution, so it will take more time to load the XAP file). 

After compilation, the file Project4Page3.xap is created in the ClientBin folder (this folder already contains the initial XAP file). As you know, a XAP file is a ZIP file that contains (here) two elements : a manifest (App.Manifest.xaml) that describes the XAP content and a DLL (named here Project4Page3.dll) that contains the description of our Page3 as well as the associated code (in MSIL format).

We need to be able to dynamically load Page3, here following a click on the "To Page3" hyperlink in the master page. The code presented hereafter is thus part of the master page.

At the beginning of the master page (Page.xaml.cs), we first add :

 using System.Windows.Resources;
 using System.IO;
 using System.Reflection;

 Our Page3 page is a Silverlight user control, as any Silverlight page. It is thus an object of the UserControl class. To keep a reference to Page3 in the master page, we declare :

 public UserControl page3;

Initially, page3 is null. After a dynamic load, page3 will contain a value.

In the function that handles the click on the "To Page3" hyperlink, we check whether Page3 is already in memory or not (in this case, we need to initiate a dynamic load of Project4Page3.xap) :

 private void bToPage3_Click(object sender, RoutedEventArgs e)
 {
  if (page3 == null)
  {
   .....   // page not yet in memory, we now load the XAP file for Page3
  }
  else
  {
   .....   // page already in memory, no need to reload from server
  }
 }

 If the page is already in memory, we do as we did before : we check whether a page switching is really needed (a user could click twice on the same button). Also, remember that PageContainer is a reference in the master-page for the page-holder cell :

 UserControl currentPage = PageContainer.Children[0] as UserControl;
 if (currentPage != page3) PageContainer.Children[0] = page3;

If Page3 is not yet in memory, we initiate the download (from the server to our application) for Project4Page3.xap :

 WebClient client = new WebClient();
 client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
 client.OpenReadAsync(new Uri("Project4Page3.xap", UriKind.Relative));   

The completion function is just a few lines, that we will explain : 

 void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
 {
  StreamResourceInfo sri = new StreamResourceInfo(e.Result, null);
  StreamResourceInfo sri4dll = Application.GetResourceStream(sri,
                                    new Uri("Project4Page3.dll", UriKind.Relative));
  AssemblyPart asmPart = new AssemblyPart();
  Assembly asm = asmPart.Load(sri4dll.Stream);
  UserControl uc = asm.CreateInstance("Project4Page3.Page") as UserControl;
  page3 = uc;
  PageContainer.Children[0] = page3;
  stBar.Text = "Page3 loaded from server at " + DateTime.Now.ToLongTimeString();
 }

When the transfer is finished, the function client_OpenReadCompleted is executed and e.Result references what was transferred from the server, ie Project4Page3.xap. We know our XAP contains a manifest (that describes what needs to be deployed to the Silverlight application) and the Project4Page3.dll (that itself contains our Page3 page, description and code).

A manifest is described as XML, with a Deployment tag that contains a Deployment.Parts tag that itself contains one or more AssemblyPart tags. Here, we have just one AssemblyPart tag since our XAP file contains just one DLL :

 <Deployment ..... >
  <Deployment.Parts>
   <AssemblyPart ..... Source="Project4Page3.dll" />
  </Deployment.Parts>
 </Deployment>

We first create a StreamResourceInfo object (sri) to analyze Project4Page3.xap. From sri, we then create another StreamResourceInfo object (sri4dll) to get access to Project4Page3.dll. In a XAP file, we could find several DLLs. In .NET parliance, we say it could contain several assemblies. What is important to us is to get the DLL (Project4Page3.dll). To do so, we create an AssemblyPart object and an Assembly object (asm), that we load with the code in the DLL.

The code just loaded corresponds to our Project4Page3 page (an object of the Project4Page3.Page class). We know it's a UserControl object. So, we create it using asm.CreateInstance. This object is our page3 object ! We can load it in the page-holder cell of the master page.

To access public fields in the master-page from the dynamically loaded content page, add a reference to the main project (here SilverlightApplication3) in the Project4Page3 project :

  SilverlightApplication3.Page page = Application.Current.RootVisual
as SilverlightApplication3.Page;

See you soon for other improvements ! 

Tags:

Master pages in Silverlight - Part II

by gleblanc 9. January 2009 19:29

In Part II of this serie dedicated to master pages in Silverlight, we will improve the previous program by adding an animation that is executed at page switching time.


Project / source code

First, we are dynamically adding an animation to each content page (here Page1 and Page2) : 

 public Page()
 {
  InitializeComponent();
  page1 = new Page1();
  page2 = new Page2();
  PageContainer.Children.Add(page1);

  // animation for Page1 (Scale animation on X)
  ScaleTransform scaleTrans = new ScaleTransform();
  scaleTrans.SetValue(NameProperty, "scale");
  page1.RenderTransform = scaleTrans;
  page1.RenderTransformOrigin = new Point(0.5, 0.5);
  Storyboard stb = new Storyboard();
  stb.Duration = new Duration(TimeSpan.FromSeconds(1));
  DoubleAnimation daX = new DoubleAnimation {From=0.1, To=1 };
  Storyboard.SetTargetName(daX, "scale");
  Storyboard.SetTargetProperty(daX, new PropertyPath("ScaleX"));
  stb.Children.Add(daX);
  page1.Resources.Add("AnimPage", stb);

  // animation for Page2 (Scale animation on Y)
  scaleTrans = new ScaleTransform();
  scaleTrans.SetValue(NameProperty, "scale");
  page2.RenderTransform = scaleTrans;
  page2.RenderTransformOrigin = new Point(0.5, 0.5);
  stb = new Storyboard();
  stb.Duration = new Duration(TimeSpan.FromSeconds(1));
  DoubleAnimation daY = new DoubleAnimation {From=0.1, To=1 };
  Storyboard.SetTargetName(daY, "scale");
  Storyboard.SetTargetProperty(daY, new PropertyPath("ScaleY"));
  stb.Children.Add(daY);
  page2.Resources.Add("AnimPage", stb);
 }

Each Storyboard for animation is named AnimPage and is added as resource in the page. An animation takes some time, here one second. To disable page switching while an animation is active, we declare a boolean :

 bool bRunningAnim;

Our SwitchToPage function becomes :

 public void SwitchToPage(UserControl p)
 {
  if (bRunningAnim) return;   // animation in progress
 
  Page masterPage = Application.Current.RootVisual as Page;
  UserControl currentPage = masterPage.PageContainer.Children[0] as UserControl;
  if (currentPage != p)
  {
   masterPage.PageContainer.Children.Add(p);
   Storyboard stb = p.Resources["AnimPage"] as Storyboard;
   stb.Completed += new EventHandler(stb_Completed);
   stb.Begin();
   bRunningAnim = true;
  }
 }

The new page is first added to the children of the content area grid cell (PageContainer, see previous post). Two pages are now displayed (more precisely, the second one will soon be displayed). We retrieve the AnimPage animation and launch it.

In the Completed function, we remove the old page from the content area and mark the animation as terminated :

 void stb_Completed(object sender, EventArgs e)
 {
  if (!bRunningAnim) return;
 
  Page masterPage = Application.Current.RootVisual as Page;
  masterPage.PageContainer.Children.RemoveAt(0);
  bRunningAnim = false;
 }

See you soon for a next improvement.

Tags:

Master pages in Silverlight - Part I

by gleblanc 5. January 2009 14:22

One of best appreciated features of ASP.NET version 2 was the master page concept. There is no reason a Silverlight application could not benefit from master pages. So, let's see how to implement master pages in Silverlight : similar capabilities though different implementation.

Our first implementation will be basic but we will improve it in forthcoming posts.

A master page is a web page (here a Silverlight page) that consists of fixed parts and one (or more) content area. The content area can be changed, for instance following an action on a menu item.

In the following Silverlight application, the master page is made of a layer at the top (with an animated text), a menu (left part) and a status bar (bottom part). In the content area, it's possible to display (at one time) one of two XAML pages. We will show how to switch content in the content area and how to pass information from one XAML content page to another one.
Everything here is kept simple since our goal is just to present the master page concept applied to Silverlight. For your information, the background image in the first content page is a painting from Alfred Sisley while the background image in the second content page is a painting from Claude Monet.



Project / source code


The master page (Page.xaml) is implemented as a 3-row grid : a canvas (with animated text) in the first row, the menu (in a vertical StackPanel) + content area the second row and the status bar in the third row : 

 <Grid x:Name="LayoutRoot" Background="AliceBlue">
  <Grid.RowDefinitions>
   <RowDefinition Height="30" />
   <RowDefinition/>
   <RowDefinition Height="20" />
  </Grid.RowDefinitions>
  .....
  <Canvas x:Name="animatedText" Grid.Row="0" >
   .....
  </Canvas>
  <Grid Grid.Row="1" >
   <Grid.ColumnDefinitions>
    <ColumnDefinition Width="100" />
    <ColumnDefinition />
   </Grid.ColumnDefinitions>
   <StackPanel Grid.Column="0">
    .....
   </StackPanel>
   <Grid x:Name="PageContainer" Grid.Column="1" />
  </Grid>
  <TextBlock x:Name="stBar" Grid.Row="2" Text="This is the status bar"
             VerticalAlignment="Center" />
 </Grid>

The two content pages are created as normal Silverlight user controls : Page1.xaml and Page2.xaml. In these two Silverlight pages, we added some common controls, to make these pages more realistic. These three XAML files (Page.xaml for the master page and Page1.xaml and Page2.xaml for the content pages) are part of the same project. There are thus included in the same XAP file.
 
In the Page.xaml.cs file, we declare two properties, corresponding to the two content pages :

 public Page1 page1 { get; set; }
 public Page2 page2 { get; set; }

These two properties are initialized in the Page constructor. Page1 is then set as the initial content page :

 public Page()
 {
  InitializeComponent();
  page1 = new Page1();
  page2 = new Page2();
  PageContainer.Children.Add(page1);
 }

To make Page2 the current content page (thus to switch content page), we write :

 SwitchToPage(page2);

with SwitchToPage which is (additionally, we display a message in the status bar) :

 public void SwitchToPage(UserControl p)
 {
  Page masterPage = Application.Current.RootVisual as Page;
  UserControl currentPage = masterPage.PageContainer.Children[0] as UserControl;
  if (currentPage != p)
  {
   masterPage.PageContainer.Children[0] = p;
   stBar.Text = "At " + DateTime.Now.ToLongTimeString() + ", switch to page named " + p.Name;
  }
 }

To give a name to a content page, we just add a Name property in the UserControl tag.

To pass information from a content page to the master page or another content page, different solutions are possible. One of them consists in storing this information as a ressource. For instance, to initialize a field named InfoPage1 somewhere in Page1.xaml.cs

 Resources.Remove("InfoPage1");
 Resources.Add("InfoPage1", "new value of InfoPage1");

To read this information from another content page :

 Page p = Application.Current.RootVisual as Page;
 string s = p.page1.Resources["InfoPage1"] as string;


To switch to Page1 directly from Page2, we would write :

 Page p = Application.Current.RootVisual as Page;
 p.SwitchToPage(p.page1);
 

In the next posts, we will improve the page switching mechanism. See you soon.
 

Tags:

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen

About the author

Gerard Leblanc is the author of several books (in french) on C++, C#, .NET and Silverlight (Eyrolles, Paris as publisher). See www.gleblanc.eu as the companion web site for these books (included sample programs).
He is Microsoft MVP for Silverlight.

MVP logo