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 :
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 !