Silverlight 3 SDK and non-ENU Visual Studio editions

by gleblanc 18. July 2009 23:23

Silverlight 3 is now officially RTW and it's great, with tools to make great LOB (Line of Business) applications and no more just eye-catching animations (moreover mainly done by programmers...).

Great but right now, there is no way to create such applications with non-ENU Visual Studio or VWD Express ! Silverlight 3 tools that accommodate French, Italian, German, Spanish, Korean, Japanese and Chinese VS or VWD will be released end of July, with doc (partially in these languages) available end of August.

In the meantime, you can install Visual Web  Developer Express, ENU version (ie English) that can cohabit with Visual Studio, french edition for instance. So, there is a way to prepare yourself for Silverlight 3 applications.

If a blogger, moreover an author, becomes silent for a while, there is a reason : he is dead (I am still alive, thanks), he is no more interested in the subject (I am more than ever interested in Silverlight 3), he fall in love (no change there, still same wife, no reason to change) or he was too busy for some reason. Though still very busy, I will soon restart my blogging activity. 

  

Tags:

Master pages, part VI. Progressive download with web service, datagrid, etc. : declaratively specifying downloadable modules

by gleblanc 28. March 2009 18:37

Let's see how to declaratively specify all modules (XAP files, images, etc.) that we decide to load asynchronously in order to reduce wait time for the user.

Our application is made of a master page, a first page (initially displayed with the master page) and a second page, downloaded asynchronously. In the first page, we have a datagrid that gets its data from a web service. Due to the heavy footprint of the datagrid, we will also load it asynchronously.

I am afraid only veterans among the veterans read one day the books mentionned here (you can click on items and get info in tooltips). Believe it or not, there was a time when "serious" programmers (those working in PL/I and CICS on 360/370 mainframes) considered PCs as toys without any future. Reading these books was considered as politically incorrect in the very very large, air-conditioned, computer rooms with very large windows, just near the main entrance (people were so proud to work there!). Background images are paintings from Piet Mondrian (Mondriaan before he decided to americanize his name).


Project / source code

We first build the Web Service (here an Asmx web service), named BooksWS, in the Web part of the solution. Nothing new and in relation with this post : since the web service is executed on the server, it is not downloaded to the client.

The initial project (SilverlightApplication6) contains the master page. In this project, we add Page1 (Page1.xaml). In the solution, we create a project for Page2 (Proj4Page2), a project to hold data common to the different pages (Proj4Data), a project for the datagrid (Proj4DataGrid) and a project to refer to the web service (Proj4UseBooksWS). Five XAP files are thus created in the ClientBin folder. Four of them will be downloaded asynchronously, as well as background images, other images and some text information. The initial XAP file is only 22 KB and will thus be displayed quickly.

Let's come to the part that presents interest : declaratively specifying the modules that have to be downloaded asynchronously (though in sequence).

In the initial project, we add a class named Action :

 public class Action
 {
  public delegate void Del(OpenReadCompletedEventArgs e);
  public string File { get; set; }
  public Del Fct { get; set; }
  public object Dest { get; set; }
  public string Comment { get; set; }
 }

We will later on create an array of actions. In an Action item, we specify : 
- File : the name of what has to be downloaded (for instance Proj4Page2.xap or myImage.jpg),
- Dest : the destination (a page, an image somewhere or whatever you decide) ,
- Fct : the function (you have to write it) to be executed when the client gets the data (it's a delegate, in other words a pointer to a function that gets as parameter information on the asynchronous read at completion time),
- Comment : a comment used here to display information in the status bar.

In the C# file of the master page (Page.xaml), we build the array of actions (in the constructor) :

 List<Action> actions;
 .....
 actions = new List<Action>() {
    new Action(){File="Mondrian1.jpg", Fct=new Action.Del(LoadImage),
       Dest=0, Comment="Loading background image (Piet Mondrian) for master page..."},
    new Action(){File="Proj4Data.xap", Fct=new Action.Del(LoadPageForData),
       Comment="Loading xap file of project for common values..."},
    .....
  };

At the end of the constructor, we fire the first asynchronous download :
 int nStep = 0;
 .....
 client = new WebClient();
 client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
 // start serie of async loads
 client.OpenReadAsync(new Uri(actions[nStep].File, UriKind.Relative));
 stBar.Text = actions[0].Comment;

The client_OpenReadCompleted function becomes :

 void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
 {
  actions[nStep].Fct(e);
  nStep++;
  if (nStep < actions.Count)
  {
   stBar.Text = actions[nStep].Comment;
   client.OpenReadAsync(new Uri(actions[nStep].File, UriKind.Relative));
  }
  else stBar.Text = "";
 }

Every Fct function is specific to the termination of a specific asynchronous download. For instance, LoadPage2 terminates the asynchronous load of Page2 and is, as seen in a previous post :

 public void LoadPage2(OpenReadCompletedEventArgs e)
 {
  sri = new StreamResourceInfo(e.Result, null);
  sri4dll = Application.GetResourceStream(sri, new Uri("Proj4Page2.dll", UriKind.Relative));
  asmPart = new AssemblyPart();
  asm = asmPart.Load(sri4dll.Stream);
  uc = asm.CreateInstance("Proj4Page2.Page") as UserControl;
  page2 = uc;
 }

The Proj4Data project is directly referenced in other projects : it contains references to common data and is only 4 KB in size. In the Proj4UseBooksWS project, we add a service reference to the web service. After compilation, do not forget to copy the ServiceReferences.ClientConfig file from this project to the project of the master page (here SilverlightApplication6).

See source code, included as usual, for additional information. Enjoy Silverlight !

Tags:

Master pages, part V : progressive download of pages with a heavy footprint

by gleblanc 16. February 2009 01:29

In previous posts, we showed how to progressively load (in the background) the different pages of a Silverlight application, drastically reducing the wait time for the user. Here, we go a step further : some pages have a heavy footprint (ie their xap files are big), generally because they need support from one or more dll's (these dll's are then incorporated by Visual Studio into the xap file). 

Here, we are using the Chart control from the Toolkit (http://www.codeplex.com/Silverlight ) in the first page. To do so, we need to add a reference to Microsoft.Windows.Controls.DataVisualization.dll, which is a 280 KB file. Using the progressive download technique, our first page is only 12 KB and is thus displayed immediately after request. 

Background images are paintings from Georgia O'Keeffe, an American artist. 


Project / source code

As usual, we create a Silverlight project (SilverlightApplication5). The default page contains the master page, but not its background image, that will dynamically loaded (ie by program, later on) :

 <Grid>
  <Image x:Name="imgBG" Stretch="UniformToFill"  />
  <Grid x:Name="LayoutRoot" Background="LightGray" >
  .....  here the xaml for master page
  </Grid>
 </Grid>   

The master page contains a grid cell (named PageContainer) that will act as a place-holder for Page1, Page2 and Page3 (one at a time). 

Within the same project (the only one right now), we create Page1 (Add, New Item, Silverlight UserControl). The background image and the Chart control are not included (we just mention a grid cell as place-holder, named PlaceHolderForChart). The master page and Page1 are compiled into the same xap file (SilverlightApplication5.xap, whose size is only 12 KB).

In the solution, we add a new project (right click on the Solution line, Add, New Project), first for Page2 (project named Proj4Page2). Then for Page3 (Proj4Page3). Page2 and Page3 are build as usual. After compilation, Proj4Page2.xap and Proj4Page3.xap are created (10 KB each) in the ClientBin folder.   

To keep data available to and from any page, we create another project (named Proj4Data) in the solution. We keep data as public static fields in Proj4Data.Page.xaml.cs. Upon confirmation in Page2 and Page3 (button Confirm), legends and slider values are saved in fields of Proj4Data (for instance Proj4Data.Page.LegendPage2, of type string).

Now the most interesting part for this post : we create a new project named Proj4Chart. We add a reference to Microsoft.Windows.Controls.DataVisualization.dll (a 280 KB file downloaded with the toolkit). We insert a Chart control in Proj4Chart.xaml :

 <UserControl x:Class="Proj4Chart.Page"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
   xmlns:chart="clr-namespace:Microsoft.Windows.Controls.DataVisualization.Charting;
                assembly=Microsoft.Windows.Controls.DataVisualization"
  >
  <Grid x:Name="LayoutRoot" >
   <StackPanel Orientation="Vertical"  >
    <chart:Chart x:Name="chart" LegendTitle="Legend" BorderThickness="0"
                Width="250" Height="225" >
     <chart:Chart.Series>
      <chart:PieSeries DependentValueBinding="{Binding Path=Value}"
                       IndependentValueBinding="{Binding Path=Key}"
                       BorderThickness="0" />
     </chart:Chart.Series>
    </chart:Chart>
   </StackPanel>
  </Grid>
 </UserControl>

We add a using line in Proj4Chart.xaml.cs, as well as a function (DrawChart) :

 .....
 using Microsoft.Windows.Controls.DataVisualization.Charting;  namespace Proj4Chart
 {
  public partial class Page : UserControl
  {
   public Page()
   {
    InitializeComponent();
    .....    // initialisation of a delegate to DrawChart
   }
   public void DrawChart()
   {
    PieSeries serie = chart.Series[0] as PieSeries;
    serie.ItemsSource = new KeyValuePair<string, int>[] {
      new KeyValuePair<string, int>(Proj4Data.Page.LegendPage2,
                                    Proj4Data.Page.SliderValuePage2),
      new KeyValuePair<string, int>(Proj4Data.Page.LegendPage3,
                                    Proj4Data.Page.SliderValuePage3)
     };
   }
  }
 }

We will explain the initialisation of the delegate to DrawChart at the end of this post.

In the projects named SilverlightApplication5, Proj4Page2, Proj4Page3 and Proj4Chart, we add a reference (right click on the project line, Add Reference, Projects) to Proj4Data (Proj4Data.xap is a 5 KB file). 

In the constructor of the master page (SilverlightApplication5.xaml.cs), we initiate the progressive download (in the background) of (in sequence, in this order) : background image for master page, Proj4Page2.xap, Proj4Page3.xap, Proj4Chart, background image for Page1, background image for Page2 and background image for Page3. These two xap files and these four jpg files must be copied in the ClientBin folder.  

We explained in a previous post how to extract a UserControl out of Proj4Page2.xap and Proj4Page3.xap.

Proj4Chart.xap is a bit more complex since it contains an additional (and huge) file : Microsoft.Windows.Controls.DataVisualization.dll. For confirmation, rename Proj4Chart.xap as Proj4Chart.zip and unzip it. In AppManifest.xaml, you will find an additional line : 
 <AssemblyPart x:Name="Microsoft.Windows.Controls.DataVisualization"
               Source="Microsoft.Windows.Controls.DataVisualization.dll" />

The work for Proj4Chart is not so different (see previous posts) : 

 void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
 {
  .....
  StreamResourceInfo sri, sri4dll;
  AssemblyPart asmPart, asmPartChart;
  Assembly asm;
  UserControl uc;
  switch (nStep)
  {
   case 1:             // end of load for background image of masterpage
     .....
   case 2:             // end of load of Page2.xap
     .....
   case 3:             // end of load of Page3.xap
     .....
     nStep++;
     // load Proj4Chart.xap (includes Microsoft.Windows.Controls.DataVisualization.dll)
     client.OpenReadAsync(new Uri("Proj4Chart.xap", UriKind.Relative));
     break;
   case 4:             // end of load of Proj4Chart
     sri = new StreamResourceInfo(e.Result, null);
     sri4dll = Application.GetResourceStream(sri,
                  new Uri("Microsoft.Windows.Controls.DataVisualization.dll",
                          UriKind.Relative));
     asmPartChart = new AssemblyPart();
     asmPartChart.Load(sri4dll.Stream);
     sri4dll = Application.GetResourceStream(sri,
                 new Uri("Proj4Chart.dll", UriKind.Relative));
     asmPart = new AssemblyPart();
     asm = asmPart.Load(sri4dll.Stream);
     uc = asm.CreateInstance("Proj4Chart.Page") as UserControl;
     Proj4Data.Page.chart = uc;
     page1.PlaceHolderForChart.Children.Add(uc);
     nStep++;
     // load background image for Page1
     .....
     break;
   case 5:             // end of load for background image for page1
     .....
   case 6:             // end of load of background image for page2
     .....
   case 7:             // end of load of background image for page3
     .....
  }
 }

The xap file is first loaded into sri, of type StreamResourceInfo. From sri, we extract first DataVisualization.dll, that we load in a newly created object (asmPartChart) of type AssemblyPart. It's all, folks! For Proj4Chart.dll (that contains xaml and C# compiled code), nothing is changed from the previous post.

To end this post : how do we call the DrawChart function in Proj4Chart.Page.xam.cs from any page ? In Proj4Data.Page.xaml.cs (that contains shared variables), we declare a delegate :

 public delegate void DrawChartDel();
 public static DrawChartDel fDrawChart;   

A delegate is the C# (type-safe) equivalent of C function pointer, ie a variable that holds the address of a function. It's possible to call the function thru the function pointer. In the first line, we define a type : a variable of type DrawChartDel is (in C parliance) a variable that points to a function that accepts no argument and returns nothing (void). In the second line, we declare such a variable, here named fDrawChart.

Up to now, fDrawChart points to nothing. fDrawChart is initialized in the constructor in Proj4Chart.Page.xaml.cs :

 using Microsoft.Windows.Controls.DataVisualization.Charting; namespace Proj4Chart
 {
  public partial class Page : UserControl
  {
   public Page()
   {
    InitializeComponent();
    Proj4Data.Page.fDrawChart = new Proj4Data.Page.DrawChartDel(DrawChart);
   }
   public void DrawChart()
   {
    .....
   }
  }
 }

To call DrawChart from Page2, we just need to write :

 if (Proj4Data.Page.fDrawChart != null) Proj4Data.Page.fDrawChart(); 

In the forthcoming post, a similar example with a web service request. See you soon. A+, as we now say in the new sms-biaised French language. 

Tags:

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:

Synchronous animations

by gleblanc 3. December 2008 12:39

In Silverlight, animations are launched asynchronously. Great... But sometimes, this feature makes things more complicated than necessary, as experienced in the following program that graphically renders the behavior of a sort algorithm. So, let's explain how to simulate synchronous animations. Doing so, we can keep the algorithm as it is. We just have a few lines to add, to launch animations.


Project / source code

Our sort algorithm is the simplest one. We know it's not efficient but we don't care since our goal is just to present synchronous animations. This algorithm (to sort the array T of integers) is :
 private void Sort()
 {
  for (int i = 0; i < T.Length-1; i++)
  {
   int mini= i+1;
   for (int j = i + 2; j < T.Length; j++)
    if (T[j] < T[mini]) mini = j;
   if (T[i] > T[mini])
   {
    // exchange
    int c = T[i]; T[i] = T[mini]; T[mini] = c;
   }
  }
 } 

If we run the Sort function as it is and we add animations, the program will end in less than a millisecond. Nothing will be visible : since animations can only be run asynchronously, a lot of animations would be launched asynchronously, within that millisecond ! Adding Thread.Sleep instructions would slowdown the program but would also stop animations. In technical terms : we cannot run Sort from the main (also named UI) thread. We could create a thread (ie another path of code execution) and execute Sort on this thread but actions on UI elements (changing colors or animations for instance) must be run from the main thread.

So, let's explain how to solve this dilemma. We first create a thread (in the function that handles the Click event on the Sort button) and launch Sort on that secondary thread :
 using System.Threading;        
 .....
 Thread thread;
 .....
 private void bSort_Click(object sender, RoutedEventArgs e)
 {
  .....
  thread = new Thread(new ThreadStart(Sort));
  thread.Start();
 }

Now, in the Sort function, we need to add instructions to change colors of bars and launch animations. To do so, we write the Display function, that will also run on the secondary thread. Display accepts as arguments a string (that contains the operation),  two integers (to eventually specify concerned UI elements) and a boolean indicating a pause is needed :
 int[] T;
 .....
 private void Sort()
 {
  .....
  for (int i = 0; i < T.Length-1; i++)
  {
   Display("Candidate", i, 0, true);
   .....
   int mini= i+1;
   ..... 
   for (int j = i + 2; j < T.Length; j++)
   {
    Display("Comparing " + T[i] + " with " + T[j] , 0, 0, true);
    ......
    if (T[j] < T[mini])
    {
     .....
    }
   }
   if (T[i] > T[mini])
   {
    Display("Move1", i, mini, false);
    Display("Move2", mini, i, true);
    .....
  }
 }
 .....
 void Display(string s, int a, int b, bool bDelay)
 {
  .....
  if (bDelay) Thread.Sleep(DELAY);
 }
Sure, the Sort function has been modified but we just added lines. Some algorithms are so complicated that it's better not go further.

Accesses to UI elements are performed in the Display function. How ? First, we need to add a delegate. A delegate is nothing new for C programmers : it's a function pointer (in other words, a variable that holds the address of a function, enabling the function call thru that pointer). Here, our delegate "points to" a function with three arguments (a string and two integers) :
 private delegate void LaunchAnimationDelegate(string s, int a, int b);
 .....
 LaunchAnimationDelegate LaunchAnimDel;
 .....
 private void Sort()
 {
  LaunchAnimDel = new LaunchAnimationDelegate(LaunchAnimation);
  .....
 }
 void Display(string s, int a, int b, bool bDelay)
 {
  Dispatcher.BeginInvoke(LaunchAnimDel, new object[] { s, a, b });
  .....
 }
 .....
 private void LaunchAnimation(string s, int a, int b)
 {
  .....
 }

We first defined a type (LaunchAnimationDelegate), ie an information for the compiler. Then a variable (named LaunchAnimDel) of that type. That variable is initialized at the beginning of the Sort function. Finally, we write the function (named LaunchAnimation) to be called thru that delegate. That function will be executed on the main, UI, thread. To force execution on the main thread, the function call must be performed with Dispatcher.BeginInvoke. See also how parameters are passed to the function. In LaunchAnimation, we can have any instruction that modifies UI elements, for instance
 tabRect[a].Fill = new SolidColorBrush(Colors.Orange);

Concerning the visual interface, let's say we have a Grid split into four lines. The second line (with the animation) is a Canvas that contains 10 Rectangle (height and color are changing but not position). We created two other rectangles for the Move animations, with storyboards created dynamically (at Loaded time). To keep code as easy as possible but have the bars growing from bottom to top, a ScaleTransform (with Y equals to -1) is performed on the Canvas. Source code is available, as usual.

Tags:

Menus, part V : Sliding menu

by gleblanc 25. November 2008 02:06

We continue our serie of animated menus, this time : the sliding menu.


Project / source code

As usual, our sliding menu is declared in a XML file, Menu.xml, inserted as a resource :
  <?xml version="1.0" encoding="utf-8" ?>
 <Menu>
  <Item Text="Italy" >
   <Item Miniature="MiniVenice.jpg" Id="11" Text="Venice, Grand Canal"/>
   .....
   <Item Miniature="MiniVatican.jpg" Id="15" Text="Vatican, St. Peter's Square" />
  </Item>
  <Item Text="USA" >
   <Item Miniature="MiniYosemite.jpg" Id="21" Text="Yosemite National park" />
   .....
   <Item Miniature="MiniGrand Canyon.jpg" Id="23" Text="Grand Canyon National Park"/>
  </Item>
  .....
 </Menu>

The user has just one constraint : reserve space for the menu on top of anything else in the window (this explains the Canvas.ZIndex) . Miniatures must be included as resources (they are displayed in sub-menus). The user can then handle the Click event :
<Grid x:Name="LayoutRoot" Background="White" >
  <Grid x:Name="menuGrid" Canvas.ZIndex="99" Height="40" VerticalAlignment="Top" >
   <gl:Menu Click="menuGrid_Click" />
  </Grid>
  <Grid x:Name="mainGrid" >
   .....
  </Grid>
 </Grid>

As we did for other menu, we build a Silverlight user control named Menu.xaml in the glMenu namespace. Our menu as a Grid containing a Canvas for the sub-menus and an horizontal StackPanel for the main menu items. Why a Canvas for the sub-menus ? They are animated (down animation for opening and up animation for closing) and it is not (unfortunately) possible to animate the Margin property :
<Grid x:Name="MenuRoot" >
  .....
  <Canvas x:Name="canSubMenus" />
  <StackPanel x:Name="spMainMenu" Orientation="Horizontal" Loaded="spMainMenu_Loaded" >
   ....
  </StackPanel>
 </Grid>

Since main menu items and sub-menu items are quite different, we create two classes : MainMenuItem and SubMenuItem. In memory, we also keep a list of main menu items and a table for sub-menu items (the number of entries in this table depends on the number of main menu items) :
 List<MainMenuItem> lstMainMenuItems;
 List<SubMenuItem>[] tabSubMenuItems;

The main menu is first build in the spMainMenu_Loaded function. Thanks to Linq for Xml, we analyze the XML file to find the main menu items :
lstMainMenuItems = new List<MainMenuItem>();
 XElement xmlRoot = XElement.Load("Menu.xml");
 getMainMenuItems(xmlRoot);
 .....
 void getMainMenuItems(XElement xml)
 {
  var lstItems = from p in xml.Elements("Item") select p;
  foreach (var el in lstItems)
   lstMainMenuItems.Add(new MainMenuItem {Text=el.Attribute("Text").Value, xElem=el});
 }

The main menu is then build using Silverlight UI elements : a Grid including a TextBlock (indeed two TextBlock to give a 3D effect to the display). For each main menu item, we handle the MouseEnter event (to make visible the corresponding sub-menu), the MouseLeave (to hide it) but also the MouseMove (leaving the main menu item thru the bottom side must leave the sub-menu as it is).

The sub-menus are then build (at this time, we know the number of sub-menu items). All the sub-menus are placed in a Canvas (canSubMenus). Each sub-menus (three here) are build on a canvas. Each sub-menu item (we have 5 for Italy and 3 for USA) are also based on a canvas containing an image (the miniature). For each of these canvas, we handle the MouseEnter, MouseLeave and MouseLeftButtonDown events.

Source code is included, as usual.

Tags:

Page turner updated for Silverlight 2 RTM

by gleblanc 17. November 2008 21:57

Mitsu Furuta (from Microsoft France) updated his Page Turner to Silverlight 2 RTM. Let's explain how to use it.

Let's first demonstrate the page turner, with paintings from Pablo Picasso (the Spanish-born painter is currently honored in Paris with several outstanding exhibitions). Please note that some pages are more than just images.


Project / source code

You can find Mitsu's component at the following address : www.codeplex.com/wpfbookcontrol. Codeplex is really becoming a major player ! Download the zip-file and unzip it. You will find SLMitsuControls.dll (a 35 KB file!) in the SLMitsuControls/ClientBin folder. The component is under Ms-Pl licence, meaning you can use it in your own products, even commercial, for free (no royaltie to Microsoft, even if your product is not free) but, of course, you can't claim rights on ownership.

Let's now create a Silverlight application. Add a reference to SLMitsuControls.dll (in the Silverlight part of the project, right-click on the project, Add Reference and Browse to the folder containing the SLMitsuControls.dll file).

Let's first assume that our book-like control just contains images. These images need to be copied in the ClientBin folder (the one containing the XAP file). We first need to populate our book with these images. We keep file names (without extension) in an array of string (in Page.xaml.cs) :
 string[] ts = {"Picasso", "Head", "Blind man", "At the Lapin Agile", "Chicks from Avignon",
                "Woman crying", "Guitar player", "Auto portrait"};

In the UserControl tag (in Page.xaml), we add a xmlns tag for the control (any other name than local is OK), handle the Loaded event and insert our book in the first row of the main grid (the second row will contain a status bar implemented as a TextBlock) :
 <UserControl x:Class="TurnThePage2.Page"
    ..... 
    xmlns:local="clr-namespace:SLMitsuControls;assembly=SLMitsuControls"
    ..... 
    Loaded="UserControl_Loaded" >
  <Grid x:Name="LayoutRoot" Background="LightBlue" >
   <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition Height="25" />
   </Grid.RowDefinitions>
   <local:UCBook x:Name="book" Grid.Row="0" OnPageTurned="book_OnPageTurned" Margin="20" />
   .....
  </Grid>
 </UserControl>
In the Page.xaml.cs file, we need to add a using, declare that the Page class implements the IDataProvider interface and implement two functions (just two small lines, always the same) :
 .....
 using SLMitsuControls;
 .....
 namespace TurnThePage2
 {
  public partial class Page : UserControl, IDataProvider
  {
   ..... 
   public int GetCount()
   {
    return pages.Count;
   }
   public object GetItem(int index)
   {
    return pages[index];
   }
  }
 }
In case our pages are just images, it's possible to keep them in a List<Image>. But some of our pages will be more elaborate (a page could for instance be a complex grid). So, we prefer List<UIElement> since UIElement is the base class for UI elements. But here, our book is (to begin) just a collection of images (only two images here, no need to show more) :
 List<UIElement> pages;
 .....
 private void UserControl_Loaded(object sender, RoutedEventArgs e)
 {
  pages = new List<UIElement> {
   new Image {Source=new BitmapImage(new Uri(ts[1]+".jpg",
              UriKind.Relative)), Stretch=Stretch.UniformToFill },
   new Image {Source=new BitmapImage(new Uri(ts[2]+".jpg", UriKind.Relative)),
              Stretch=Stretch.UniformToFill }
   };
  book.SetData(this);
 }
It's enough to get a page turner for images ! It's possible to handle the OnPageTurned event :
 private void book_OnPageTurned(int leftPageIndex, int rightPageIndex)
 {
  .....
 }
After a page turn, the arguments give you the left and right pages that are visible : starting at 1 (be careful !) and with -1 for absence of page.

Let's now complicate a bit : our first page will present an animated text on top of a photo and the last page will present a button on top of an image (remember : the images must be copied in the ClientBin folder). Our downloadable project is still a bit more complex (with a list box). We dynamically specify attributes for the grid in first page and dynamically add a TextBlock :  
 private void UserControl_Loaded(object sender, RoutedEventArgs e)
 {
  pages = new List {
            new Grid (),    // first page
            new Image ..... // second page (image)
            .....
            new Image ....
            new Grid()      // last page
           };
  // specify first page
  Grid g = pages[0] as Grid;
  g.Background = new ImageBrush { ImageSource = new BitmapImage(new Uri("Picasso.jpg", 
                                  UriKind.Relative)),
                                  Stretch = Stretch.UniformToFill }; 
  TextBlock tb = new TextBlock { Text = "Pablo\nPicasso", FontSize = 70,
                    Foreground = new SolidColorBrush(Color.FromArgb(255, 173, 216, 230)),
                    TextWrapping = TextWrapping.Wrap,
                    VerticalAlignment = VerticalAlignment.Center, 
                    HorizontalAlignment = HorizontalAlignment.Center,
                    TextAlignment = TextAlignment.Center };
  .....    // here the animation on FontSize
  g.Children.Add(tb);
  ......
The code to animate the FontSize property is :
 Storyboard stbPage0 = new Storyboard();
 Storyboard.SetTarget(stbPage0, tb);
 DoubleAnimation daSize = new DoubleAnimation {From = 70, To = 0,
                                RepeatBehavior = RepeatBehavior.Forever,
                                AutoReverse = true };
 daSize.Duration = new Duration(new TimeSpan(0, 0, 5));
 Storyboard.SetTargetProperty(daSize, new PropertyPath("FontSize"));
 stbPage0.Children.Add(daSize);  
 stbPage0.Begin();

The last page (with a button on top of an image) is similar :
 g = pages[pages.Count - 1] as Grid;
 g.Background = new ImageBrush { ImageSource = new BitmapImage(new Uri("Picasso2.jpg",
                                 UriKind.Relative)), Stretch = Stretch.UniformToFill };
 Button b = new Button { Content = "Back to the first page", Width = 130, Height = 50 };
 b.Click += new RoutedEventHandler(b_Click); 
 g.Children.Add(b);

The function that handles the click being, as usual :
 void b_Click(object sender, RoutedEventArgs e)
 {
  ..... 
 }
To go back to the first page, we just need to write :
 book.CurrentSheetIndex = 0;
It's possible to launch an animation to switch to a next or previous page : 
 book.AnimateToNextPage(1000);
 .....
 book.AnimateToPreviousPage(1000);
where the argument is the duration of the animation, expressed in milliseconds.

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