PlaneProjection for 3D effects. Part III : carousels

by Administrator 17. August 2009 00:28

Let's make use of PlaneProjection to program other 3D effects : carousels in this post.

First a simple one, to show how easy it is : just a few lines of code !


Project / source code

The images are not included in the xap file as internal ressources, they are loaded from the web server at run time (to do so, the images must be copied in the ClientBin folder, the one that contains the xap file to be published).

We define a playing ground for the carousel :
 <Grid x:Name="ground" Width="400"  />

The code is self-explanatory (just a bit of very basic geometry to put each image at different angle) :
 string[] arImageNames = { "Amsterdam", "London", "Moskow", "Paris", "Pisa", "Roma",
                           "TajMahal", "Venice" };
 Image[] arImages;
 public MainPage()
 {
  InitializeComponent();
  InitializeCarrousel();
 }
 private void InitializeCarrousel()
 { 
  Storyboard stb = new Storyboard();
  int N = arImageNames.Length;       // number of images
  arImages = new Image[N];
  // for each image
  for (int i = 0; i < N; i++)
  {
   // load image first from the server
   arImages[i] = new Image();
   arImages[i].Width = 150; arImages[i].Height = 150;
   arImages[i].Source = new BitmapImage(new Uri(arImageNames[i] + ".jpg", UriKind.Relative));
   // prepare an image for the carousel
   Image img = new Image();
   img.Width = img.Height = 150;
   img.Source = arImages[i].Source;
   img.Stretch = Stretch.Fill;
   // prepare rotation around the Y-axis
   PlaneProjection pp = new PlaneProjection();
   pp.CenterOfRotationZ = -250;  // center behind the screen
   img.Projection = pp;
   // prepare the animation
   DoubleAnimation da = new DoubleAnimation();
   da.From = i * 360 / N;     // each image at different angle
   da.By=360; da.Duration = TimeSpan.FromSeconds(10);
   da.RepeatBehavior = RepeatBehavior.Forever;
   Storyboard.SetTarget(da, pp);
   Storyboard.SetTargetProperty(da, new PropertyPath("RotationY"));
   stb.Children.Add(da);
   
   // add image to the carousel
   ground.Children.Add(img);
  }
  this.Resources.Add("stb", stb);
  stb.Begin();
 }

Let's now create a more sophisticated carousel :


Project / source code

We replaced each image with a stack panel made of :
- a semi-transparent black rectangle,
- an image,
- another semi-transparent black rectangle.

A bit more geometry to compute the width of each semi-transparent rectangle.

To change speed, we handle the MouseMove event in a colored rectangle and change the Duration property of each animation (as many as images to be displayed). To change rotation, we need to stop the animation and restart it.


To end the day, another carousel :


Project / source code

Each city is displayed as a TextBlock inside a border. For each such border, we add 6 containers (again borders). Doing so, the figure looks more as a circle. The trick is to compute the height of each elementary border : we fixed the

height of each display panel, we know how many cities we have. And we know from basic geometry that a circonference is 2*PI*radius...

 

Tags:

PlaneProjection for 3D effects. Part II : flipping

by Administrator 11. August 2009 03:07

Let's make use of PlaneProjection to program 3D effects : flipping effects in this post (more effects to come in next posts).

As usual, let's start with the easiest one. Please, note that all images have a red circle in the upper-left corner : whenever displaying an image and playing with rotations, it's important to be sure the image is correctly displayed and not the inverted one (the "back side" or mirrored image if you prefer) and it's so easy to be confused without a mark.

Project / Source code

The images are not included in the xap file as internal ressources, they are loaded from the web server at run time. To do so, the images must be copied in the ClientBin folder (the one that contains the xap file to be published). To load these images at run-time (here at start-up time) :
 string[] arImageNames={"Amsterdam", "London", "Moskow", "Paris", "Roma" };
 Image[] arImages;
 .....
 public MainPage()
 {
  InitializeComponent();
  arImages = new Image[arImageNames.Length];
  for (int i=0; i<arImageNames.Length; i++)
  {
   arImages[i] = new Image();
   arImages[i].Source = new BitmapImage(new Uri(arImageNames[i]+".jpg", UriKind.Relative));
  }
  .....
 }

The image is declared in xaml as follows (with an initial -90° rotation along the Y-axis to render it initially invisible) :
 <Image x:Name="img" Width="150" Height="150" Stretch="Fill" ..... >
  <Image.Projection>
   <PlaneProjection x:Name="pp" RotationY="-90" />
  </Image.Projection>
 </Image>

We animate the plane projection from -90° to 90° (we will change image at the end of each elementary animation) :
 <Grid.Resources>
  <Storyboard x:Name="stb"  Completed="stb_Completed" >
   <DoubleAnimation x:Name="da" Storyboard.TargetName="pp" Storyboard.TargetProperty="RotationY" Duration="0:0:4" From="-90" To="90"  />

  </Storyboard>
 </Grid.Resources>

The DoubleAnimation line could be written, without giving a name (here pp) to the PlaneProjection :
 <DoubleAnimation x:Name="da" Storyboard.TargetName="img" Storyboard.TargetProperty="(Image.Projection).RotationY" Duration="0:0:4" From="-90" To="90" />

In the function that handles the Completed event (N being the index of the image being displayed) :
 private void stb_Completed(object sender, EventArgs e)
 {
  N = ++N % arImageNames.Length;
  img.Source = arImages[N].Source;
  stb.Begin();
 }
Really, nothing complicated !


And now another way to flip pages :

Project / source code

We use three panels and each panel is made of two images :
- a small image of the spirals (just half of it), inserted as a ressource,
- the displayed image (not always the same, for instance Amsterdam.jpg or the first or the last image), these images being kept on the web server (in the same folder as the xap file).

These three panels (all with CenterOfRotationX equal to 0) are :
- one panel for the left side : fixed, with RotationY of 180° (so, we can see a full spiral from two half-sized images) and image initially invisible,
- one panel for the right side : fixed, with RotationY of 0° and an image (title image) initially present,
- a moving panel, initially invisible (it will become visible when the users clicks the Next or Previous link button).

When the user clicks Next or Previous and the action is valid (they are more images to be displayed), we :
- prepare the moving panel (copy of an image and preparation of the storyboard),
- prepare the left panel (if click on Previous) or the right panel (if click on Next) with a copy of an image that will become more and more visible,
- make the moving panel visible and start the animation.

Whenever we flip page from left-to-right or right-to-left, the animation is performed in two steps : - for a right-to-left-flip (Next action), a first animation from 0° to 85° (we then change image in the moving panel) and then a second animation from 85° to 180° (the moving panel becomes then invisible),
- for a left-to-right flip (Previous action), a first animation from 180° to 85° (with then a change of image in the moving panel) and then a second animation from 85° to 0° (the moving panel becomes then invisible).

More details in the source code file available.
 

Flipping is not restricted to images. We can flip between data forms :

Project / source code

This animation is similar (even easier) to the previous one : we have again three panels. We just added :
- RotationX="-340" and RotationY="-40" to the PlaneProjection of the back panel
- RotationX="-20" and RotationY="-40" to the PlaneProjection of the front panel.

Since we accept to see the back side of data forms during animations, the animations can be performed in only one step (a RotationX from -20° to -340° for Next and -340° to -20° for Previous).

See soon for other effects.

 

Tags:

Silverlight 3 Tools now available for non-ENU versions of development tools

by Administrator 4. August 2009 14:17

Silverlight 3 Tools may now be installed with non English versions of Visual Studio and Visual Web Developer Express. Available languages are : French, German, Italian, Japanese, Korean, Spanish and Chinese' (both simplified and traditional).

Download these tools here

Those using non-ENU versions of development tools can now enjoy Silverlight 3 development.

 

Tags:

PlaneProjection for 3D effects. Part I

by Administrator 31. July 2009 07:20

Silverlight 3 is not fully but somewhat 3D. It means you cannot expect the capabilities of Direct3D (though some libraries from 3rd parties are in progress and promising) but there is enough today in SL3 to easily implement eye-catching 3D effects. In this serie, we will implement a bunch of them.

But first, in this part one, the basis illustrated by two programs you can play with, to experiment. These two programs help you to visualize the rendering of perspective transforms. The first program helps in the understanding of the basic plane transformations while the second allows you to animate projections.

To provide these 3D effects, you just need to implement a plane projection, ie a transformation that gives perspective to a 2D plane surface. For instance (here a PlaneProjection applied to an image but it's applicable to buttons, complex forms, grids, ie anything that has a visual 2D representation) :

 <Image Source=..... >
  <Image.Projection>
   <PlaneProjection ..... >
  </Image.Projection>
 </Image>


PlaneProjection has a few attributes you need to know, each with X, Y and Z variants : first Rotation (thus RotationX, RotationY and RotationZ) to specify rotation angles, then CenterOfRotation (thus CenterOfRotationX, CenterOfRotationY and CenterOfRotationZ) to specify axes of rotation. With just these two attributes (well, six...), you can already perform a lot. To go a bit further, there are LocalOffset and GlobalOffset (again, with the X, Y and Z variants) to specify two types of translation. Let's forget LocalOffset and GlobalOffset for the moment.

Before experimenting, a few words about the axis. The X-axis is horizontal, from left to right. The Y-axis is vertical, from top to bottom. The Z-axis is perpendicular, from inside to outside of the screen. With RotationX, we rotate a surface (for instance an image) around the (horizontal) X-axis. The rotation angle is expressed in degrees. As expected, with RotationY, we rotate the surface around the (vertical) Y-axis. With RotationZ (and with other default values), we rotate the surface around the center point, without leaving 2D space (by default, rotation around the center of the surface). Take time to experiment with the first program to feel at ease.

With CenterOfRotation, we change rotation axis. CenterOfRotationX and CenterOfRotationY are in the 0 to 1 range. CenterOfRotationX has an influence on RotationY while CenterOfRotationY has an influence on RotationX. Both have an influence on RotationZ.

0 in CenterOfRotationX corresponds to the left side of the surface (the one we apply the PlaneProjection) and 1 to the right side. A RotationY operation is then performed around a vertical axis that doesn't anymore traverse the surface center (though this new vertical rotation axis remains parallel to the Y-axis).

0 in CenterOfRotationY corresponds to the top side of the surface and 1 to the bottom side. RotationX is then performed around an horizontal axis that is parallel to the X-axis.

CenterOfRotationZ indicates a distance along the Z-axis. If attribute CenterOfRotationZ has a value of -200 (the point is then behind the screen) and you perform a RotationY operation, a rotation is performed around a vertical axis that traverses the point at -200 along the Z-axis. As the surface (an image or a form in the following program) is rotated, it appears smaller and smaller (the image is moving away) until RotationY reaches the value 180. If rotation continues, the surface appears larger and larger (the image is coming back to you), to its original size when RotationY reaches the value 360.

Let's experiment.

When a surface is rotated, the content disappears (it becomes very thin) and then, the back of the surface becomes visible ! Funny to discover the back side of pixels (as if the surface was drawn on a pane of glass) but unfortunately rarely realistic. This is a problem we will have to solve. Do not expect to solve the problem with a surface made of two consecutive images : the first one will not become the back side of the second one (Silverlight will only take the last one into account).

Now, let's explain LocalOffset and GlobalOffset. Both perform a translation (along the X, Y and Z axis, depending on the variant). GlobalOffset performs a translation along the X, Y and Z axis of the screen. These are fixed and unaffected by Rotation operations. X is always horizontal (from left to right) and Y always vertical (from top to bottom). GlobalOffset is always performed as the last transform, just before painting on the screen.

LocalOffset is performed after the rotation. This rotation (that depends on Rotation attributes) changes axis : if RotationZ is 90 (anti-clockwise rotation with positive values), the X-axis becomes vertical (from bottom to top) ! LocalOffsetX performs then a translation along the newly configured X-axis. GlobalOffsetX would always perform an horizontal translation. If no rotation is specified in the PlaneProjection attributes, LocalOffset and GlobalOffset have the same effect.

Experiment with the following program. Be careful, since some attributes are initialized (for the automatic animation), reset them to reinitialize with default values.

In the following posts, we will make use of these PlaneProjection.

Tags:

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:

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