How to add the zooming feature for your Silverlight Page turn application with Silverlight 1.0


The "Page turn" sample is very famous for Silverlight community.  I think there are much people who want to have zooming feature for the sample with client side script only (without using AJAX) solution.


I've done for the enhancement on creating Silverlight viewer for IT intersection magazine at http://www.microsoft.com/japan/powerpro/magazine/viewer/ .


I'd like to introduce my solution to you with step by step approach.  Hope this help you.


(1) Adds outer canvas and Silder control for MainCanvas of Scene.xaml
In the original Scene.xaml, you have the "MainCanvas" at the top of the UI element tree.  To implement the scaling feature, we can put the new Canvas as "RootCanvas" for the parent of "MainCanvas" and the slider control as "sliderControl" in the Scene.xaml.  You'll see that MainCanvas have the ScaleTransform object for implementing the zooming feature.


Modified Scene.xaml is here.



<Canvas
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="RootCanvas">


 <Canvas x:Name="MainCanvas" Canvas.Top="5">
 <Canvas.Resources>
 <!-- Timer Storyboard -->
 <Storyboard BeginTime="0" Duration="0:0:0" x:Name="timerStoryboard"/>


 </Canvas.Resources>


   <Canvas Canvas.Top="5" x:Name="PageCanvas">
     <!-- shadow behind book -->
     <Image Canvas.Top="25" Canvas.Left="455" Source="assets/shadowPage01.png" Opacity="0.8"/>
     <Image x:Name="shadowBehindPage01" Canvas.Top="25" Canvas.Left="36" Opacity="0" IsHitTestVisible="false" Source="assets/shadowPage01.png"/>
     <Canvas x:Name="evenPageCanvas" Canvas.Top="30" Canvas.Left="460" Opacity="1"/>
     <Polygon x:Name="shadowOnEvenPage" Canvas.Top="30" Canvas.Left="460" Points="420,570 420,570 420,570 420,570" Fill="Black" Opacity="0.25"/>
     <Canvas x:Name="oddPageCanvas" Canvas.Top="30" Canvas.Left="40" Opacity="1"/>
     <Canvas x:Name="mouseCaptureCanvas" Canvas.Top="30" Canvas.Left="40" Opacity="0" IsHitTestVisible="false" Background="transparent" Width="840" Height="570"/>
   </Canvas>


   <!-- ***************************** -->
   <!-- THUMBNAILS FOR BROWSING PAGES -->
   <!-- ***************************** -->
   <Canvas x:Name="pageBrowserControl" Canvas.Top="617">
     <Canvas.Resources>
       <!-- Open Page Browser Animation -->
       <Storyboard BeginTime="0" x:Name="openPageBrowserSB">
         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="lines">
           <SplineDoubleKeyFrame KeySpline="0.7,0,0.4,1" Value="1" KeyTime="00:00:00.2"/>
         </DoubleAnimationUsingKeyFrames>
         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="X" Storyboard.TargetName="lineLeft">
           <SplineDoubleKeyFrame KeySpline="0.7,0,0.4,1" Value="0" KeyTime="00:00:00.6"/>
         </DoubleAnimationUsingKeyFrames>
         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.6" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="pageBrowserWindow">
           <SplineDoubleKeyFrame KeySpline="0.7,0,0.4,1" Value="1" KeyTime="00:00:00.4"/>
         </DoubleAnimationUsingKeyFrames>
       </Storyboard>
     </Canvas.Resources>
     <!-- the pageBrowserWindow clips the pageBrowser to visible area -->
     <Canvas x:Name="pageBrowserWindow" Canvas.Left="74" Canvas.Top="0" Opacity="0" IsHitTestVisible="false">
       <Canvas.Clip>
         <RectangleGeometry Rect="0,-300 770 500"/>
       </Canvas.Clip>


       <Rectangle Height="44" Width="770" Fill="#30000000" Opacity="0"/>


       <!-- pageBrowser is where all the Thumbnails are added -->
       <Canvas x:Name="pageBrowser"/>
     </Canvas>


     <!-- Open/Close Thumbnails -->
     <Canvas x:Name="pageBrowserButton" Canvas.Top="3" Canvas.Left="855">
       <Image x:Name="unchecked_normal" Height="19" Width="27" Source="assets/pb01.jpg" Opacity="1"/>
       <Image x:Name="unchecked_over" Height="19" Width="27" Source="assets/pb02.jpg" Opacity="0" IsHitTestVisible="False"/>
       <Image x:Name="unchecked_down" Height="19" Width="27" Source="assets/pb03.jpg" Opacity="0" IsHitTestVisible="False"/>
       <Image x:Name="checked_normal" Height="19" Width="27" Source="assets/px01.jpg" Opacity="0" IsHitTestVisible="False"/>
       <Image x:Name="checked_over" Height="19" Width="27" Source="assets/px02.jpg" Opacity="0" IsHitTestVisible="False"/>
       <Image x:Name="checked_down" Height="19" Width="27" Source="assets/px03.jpg" Opacity="0" IsHitTestVisible="False"/>
     </Canvas>


     <!-- Line Thumbnails limit -->
     <Canvas x:Name="lines" Opacity="0.01">
       <Image x:Name="lineRight" Source="assets/whiteLine.jpg" Height="44" Width="1" Canvas.Left="849"/>
       <Canvas Canvas.Left="69">
         <Canvas.RenderTransform>
           <TransformGroup>
             <TranslateTransform x:Name="lineLeft" X="780" Y="0"/>
           </TransformGroup>
         </Canvas.RenderTransform>
         <Image Source="assets/whiteLine.jpg" Height="44" Width="1"/>
       </Canvas>
     </Canvas>
   </Canvas>


   <!-- Donwload Progress UI -->
   <Canvas x:Name="downloadUI" Canvas.Top="250" Canvas.Left="230">
     <Canvas.Resources>
       <Storyboard x:Name="fadeDownloadUI">
         <DoubleAnimationUsingKeyFrames Storyboard.TargetName="downloadUI" Storyboard.TargetProperty="Opacity">
           <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="0" KeySpline="0.5,0 1,0.5"/>
         </DoubleAnimationUsingKeyFrames>
       </Storyboard>
     </Canvas.Resources>
     <Path Opacity=".8" Fill="Gray" Stretch="Fill" Width="500" Height="100" Data="M0,20 L20,0 L500,0 L500,150 L0,150 z"/>
     <TextBlock x:Name="progressText" Text="" Foreground="white" Canvas.Top="30" Canvas.Left="25" />
     <Rectangle x:Name="progressRect" Fill="#ff23A3E0" Width="0" Height="12" Canvas.Top="60" Canvas.Left="25" />
   </Canvas>


   <!-- Annotate, Clear Ink Buttons    -->
   <Canvas x:Name="inkButtonCanvas" Canvas.Top="670" Visibility="Collapsed"></Canvas>
 </Canvas>


 <Canvas x:Name="sliderControl" Canvas.Left="480" Width="250" Height="50">
   <Canvas x:Name="slider" Canvas.Left ="20" Canvas.Top="0" Width="200" Height="45" Background="transparent" Loaded="slider_Loaded" RenderTransformOrigin="0.5,0.5">
     
  <Canvas.RenderTransform>
   <TransformGroup>
    <ScaleTransform ScaleX="1" ScaleY="0.6"/>
    <SkewTransform AngleX="0" AngleY="0"/>
    <RotateTransform Angle="0"/>
    <TranslateTransform X="0" Y="0"/>
   </TransformGroup>
  </Canvas.RenderTransform>
     
  <Line x:Name="slider_line"
   StrokeThickness="5"
   X1="0" Y1="25" X2="200" Y2="25" >
   <Line.Stroke>
    <LinearGradientBrush EndPoint="0.509,-3.136" StartPoint="0.491,4.136">
     <GradientStop Color="#FF000000" Offset="0"/>
     <GradientStop Color="#FFEAECE0" Offset="1"/>
    </LinearGradientBrush>
   </Line.Stroke>
   <Line.Fill>
    <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
     <GradientStop Color="#FF000000" Offset="0"/>
     <GradientStop Color="#FFF2E8E8" Offset="1"/>
    </LinearGradientBrush>
   </Line.Fill>
  </Line>


  <Rectangle
   Fill="Transparent"
   Width="200" Height="45"
   MouseLeftButtonDown="slider_MouseLeftButtonDown" />
   
  <Rectangle x:Name="slider_thumb" Width="12" Height="41" RadiusX="5" RadiusY="5" MouseLeftButtonUp="slider_thumb_MouseLeftButtonUp"
   MouseMove="slider_thumb_MouseMove"
   MouseLeftButtonDown="slider_thumb_MouseLeftButtonDown">
   <Rectangle.Fill>
    <LinearGradientBrush EndPoint="0.526,0.442" StartPoint="0.554,1.04">
     <GradientStop Color="#FF000000" Offset="0"/>
     <GradientStop Color="#FF0ECFF8" Offset="1"/>
    </LinearGradientBrush>
   </Rectangle.Fill>
   <Rectangle.Stroke>
    <LinearGradientBrush EndPoint="0.945,0.364" StartPoint="0.055,0.636">
     <GradientStop Color="#FF000000" Offset="0"/>
     <GradientStop Color="#FFFFFFFF" Offset="1"/>
    </LinearGradientBrush>
   </Rectangle.Stroke>
  </Rectangle>      


 


 </Canvas>
 <Canvas x:Name="slider_down" Width="42" Height="42" Canvas.Left="-20" Canvas.Top="4" MouseLeftButtonUp="slider_toDown" Cursor="Hand" RenderTransformOrigin="0.5,0.5">
  <Canvas.RenderTransform>
   <TransformGroup>
    <ScaleTransform ScaleX="0.6" ScaleY="0.6"/>
    <SkewTransform AngleX="0" AngleY="0"/>
    <RotateTransform Angle="0"/>
    <TranslateTransform X="0" Y="0"/>
   </TransformGroup>
  </Canvas.RenderTransform>
  <Ellipse Width="35" Height="35" >
   <Ellipse.Fill>
    <LinearGradientBrush EndPoint="0.495,0" StartPoint="0.476,1.571">
     <GradientStop Color="#FF260EFB" Offset="0"/>
     <GradientStop Color="#FFFFFFFF" Offset="1"/>
    </LinearGradientBrush>
   </Ellipse.Fill>
  </Ellipse>
  <Path Width="26" Height="2" Stretch="Fill" Stroke="#FF000000" StrokeThickness="2" Data="M8,-25 L32,-25" Canvas.Left="3.5" Canvas.Top="17">
   <Path.Fill>
    <LinearGradientBrush EndPoint="0.495,0" StartPoint="0.476,1.571">
     <GradientStop Color="#FF260EFB" Offset="0"/>
     <GradientStop Color="#FFFFFFFF" Offset="1"/>
    </LinearGradientBrush>
   </Path.Fill>
  </Path>
 </Canvas>  
 <Canvas x:Name="slider_up" Width="42" Height="42" Canvas.Left="230" Canvas.Top="4" MouseLeftButtonUp="slider_toUp" Cursor="Hand" RenderTransformOrigin="0.5,0.5">


  <Canvas.RenderTransform>
   <TransformGroup>
    <ScaleTransform ScaleX="0.6" ScaleY="0.6"/>
    <SkewTransform AngleX="0" AngleY="0"/>
    <RotateTransform Angle="0"/>
    <TranslateTransform X="0" Y="0"/>
   </TransformGroup>
  </Canvas.RenderTransform>


  <Ellipse  Width="35" Height="35">
   <Ellipse.Fill>
    <LinearGradientBrush EndPoint="0.495,0" StartPoint="0.476,1.571">
     <GradientStop Color="#FF260EFB" Offset="0"/>
     <GradientStop Color="#FFFFFFFF" Offset="1"/>
    </LinearGradientBrush>
   </Ellipse.Fill>
  </Ellipse>
  <Path Width="26" Height="2" Stretch="Fill" Stroke="#FF000000" StrokeThickness="2" Data="M8,-32 L32,-32" Canvas.Left="4.5" Canvas.Top="17">
   <Path.Fill>
    <LinearGradientBrush EndPoint="0.495,0" StartPoint="0.476,1.571">
     <GradientStop Color="#FF260EFB" Offset="0"/>
     <GradientStop Color="#FFFFFFFF" Offset="1"/>
    </LinearGradientBrush>
   </Path.Fill>
  </Path>
  <Path Width="26" Height="2" Stretch="Fill" Stroke="#FF000000" StrokeThickness="2" Data="M265,-32 L289,-32" RenderTransformOrigin="0.5,0.5" Canvas.Left="4.5" Canvas.Top="17">
   <Path.Fill>
    <LinearGradientBrush EndPoint="0.495,0" StartPoint="0.476,1.571">
     <GradientStop Color="#FF260EFB" Offset="0"/>
     <GradientStop Color="#FFFFFFFF" Offset="1"/>
    </LinearGradientBrush>
   </Path.Fill>
   <Path.RenderTransform>
    <TransformGroup>
     <ScaleTransform ScaleX="1" ScaleY="1"/>
     <SkewTransform AngleX="0" AngleY="0"/>
     <RotateTransform Angle="90"/>
     <TranslateTransform X="0" Y="0"/>
    </TransformGroup>
   </Path.RenderTransform>
  </Path>
 </Canvas>
    </Canvas>


</Canvas>


 


Note that you can change the UI elements of the sliderControl.


(2) Puts some scripts for controlling the display scale and Slider event handlers
You can manage the additional scripts in extra.js, in the file, we have some variables for zooming and Slider.
Most of the code for this slider, I just refer to the slider sample in Silverlight Quickstarts at http://silverlight.net/quickstarts/silverlight10/controls.aspx#sliderexample .


To apply the scaling, we can use the ScaleTransform object.  In this code, the object is created on runtime by createFromXaml method.



// extra.js
var bInit, SLcontrol;
var mainPage, transformGroupObject, scaleCanvas, scaleFactor=1.0;
var translateCanvas;


// Slider implementation 


var mouseDownPosition = 0;
var mouseDownValue = -1;
var thumbCenter = 6;


function slider_Loaded(sender, args) {
    slider_SetValue(sender, 0);
}



function slider_toUp(sender, args)
{
  var slider = sender.findName("slider");
  var newPosition = slider_GetValue(slider)+10;
  slider_SetValue(slider, newPosition );


}


function slider_toDown(sender, args)
{
  var slider = sender.findName("slider");
  var newPosition = slider_GetValue(slider)-10;
  slider_SetValue(slider, newPosition );


}


function slider_MouseLeftButtonDown(sender, args) {
   
    var coordinate = args.getPosition(null).x;
    var sliderControl = sender.findName("sliderControl");
    coordinate -= sliderControl["Canvas.Left"]+20;
    var slider = sender.findName("slider");
    slider_SetValue(slider, coordinate - thumbCenter); 
}


function slider_thumb_MouseLeftButtonDown(sender, args) {
    var slider = sender.findName("slider");
    sender.captureMouse();
    mouseDownValue = slider_GetValue(slider);
    mouseDownPosition = args.getPosition(null).x;
}


function slider_thumb_MouseLeftButtonUp(sender, args) {
    var slider = sender.findName("slider");
    slider.releaseMouseCapture();
    mouseDownValue = -1;
}


function slider_thumb_MouseMove(sender, args) {
    var slider = sender.findName("slider");
    if (mouseDownValue != -1) {
        var newValue = mouseDownValue + (args.getPosition(null).x - mouseDownPosition);   
        slider_SetValue(slider, newValue);
    }  
}


function slider_GetValue(sender) {
    var thumb = sender.findName("slider_thumb");
    return thumb["Canvas.Left"];
}


function slider_SetValue(sender, newValue) {
   
    if (newValue > sender.width ) {
        newValue = sender.width;
        mouseDownValue = -1;
    }
    if (newValue < 0) {
        newValue = 0;
        mouseDownValue = -1;
    }
    var thumb = sender.findName("slider_thumb");
   
    thumb["Canvas.Left"] = newValue;
   
    ApplyZoom(newValue);  // The main point
}


function ApplyZoom(nValue)
{
    if (bInit == null) return;
    currentScale = 1.0 + nValue / 200;
    // Change the scale ratio for MainCanvas
    scaleCanvas.ScaleX = parseFloat(currentScale);
    scaleCanvas.ScaleY = parseFloat(currentScale);


}


// For zooming feature implementation


function createTransformObjects(target, scale, x, y)
{
    var newScale = scale*scaleFactor;
    var xamlString = '<TransformGroup><ScaleTransform ScaleX="'+newScale.toString()+'" ScaleY="'+newScale.toString()+'" />';
    xamlString = xamlString + '<TranslateTransform X="'+x.toString()+'" Y="'+y.toString()+'" /></TransformGroup>';


    transformGroupObject = SLcontrol.content.createFromXaml(xamlString);
    scaleCanvas = transformGroupObject.children.getItem(0);
    translateCanvas = transformGroupObject.children.getItem(1);
    target.RenderTransform = transformGroupObject;
}


(3) Changes the plug-in size in createSilverlight.js



function createSilverlight()
{
    var scene = new PageTurn(24);
    Silverlight.createObjectEx({
        source: "xaml/Scene.xaml",
        parentElement: document.getElementById("SilverlightControlHost"),
        id: "SilverlightControl",
        properties: {
            width: "2700",
            height: "2220",
            version: "0.9",
            background: "black"
        },
        events: {
             onError: null,
             onLoad: Silverlight.createDelegate(scene, scene.handleLoad)
// onLoad: Silverlight.createDelegate(scene, scene.handleLoad)
        }
    });
}


(4) Modifies the handleLoad in mainpage.js to integrate zooming feature for MainCanvas


You can put five additional lines for PageTurn.prototype.handleLoad function.



PageTurn.prototype.handleLoad = function(control, userContext, rootElement) {
    this.plugIn = control;      // Store the host plug-in
    this.currentDownload = 0;   // Current resource to be downloaded 

    SLcontrol = control;
    mainPage = control.content.findname("MainCanvas");
    currentScale = 1.0;
    createTransformObjects(mainPage,currentScale, 0, 0);

   
    // create NavigationManager
    this.navigationManager = new NavigationManager(this.plugIn, this.maxNumPages);
   
    // create InkManager element that controls the mouseCaptureCanvas
    // InkManager = function(plugIn)
    this.inkManager = new InkManager(this.plugIn, this.navigationManager);
   
    // InkToggleButton = function(plugIn, text, checkedHandler, uncheckedHandler)
    var _annotateToggleButton = new InkToggleButton(this.plugIn, "Annotate", Silverlight.createDelegate(this.inkManager, this.inkManager.toggleInkMode), Silverlight.createDelegate(this.inkManager, this.inkManager.toggleInkMode));
    this.plugIn.content.findname("inkButtonCanvas").children.add(_annotateToggleButton.xamlElement);
   
    // InkButton = function(plugIn, text, clickedHandler)
    var _clearAnnotationButton = new InkButton(this.plugIn, "Clear Annotations", Silverlight.createDelegate(this.inkManager, this.inkManager.clearInk));
    _clearAnnotationButton.xamlElement["Canvas.Left"] = 74;
    this.plugIn.content.findname("inkButtonCanvas").children.add(_clearAnnotationButton.xamlElement);
   
    // create PageGenerator
    this.pageGenerator = new PageGenerator(this.maxNumPages);
   
    // begin downloading all assets
    this.downloadAssets();
   
    // Hook up thumbnail viewer (page browser control)
    // PageBrowserControl = function(plugIn, target, pageGenerator, this.maxNumPages)
    new PageBrowserControl(this.plugIn, rootElement.findName("pageBrowserControl"), this.pageGenerator, this.navigationManager, this.maxNumPages);
   
    bInit = true;
}




(5) Integrates everything in index.html



<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />


  <title> Your title here  </title>
<script type="text/javascript" src="js/Silverlight.js">
</script>
<script type="text/javascript" src="js/createSilverlight.js">
</script>
<script type="text/javascript" src="js/inkManager.js">
</script>
<script type="text/javascript" src="js/inkButtons.js">
</script>
<script type="text/javascript" src="js/pageBrowserButton.js">
</script>
<script type="text/javascript" src="js/pageBrowserControl.js">
</script>
<script type="text/javascript" src="js/pageGenerator.js">
</script>
<script type="text/javascript" src="js/navigationManager.js">
</script>
<script type="text/javascript" src="js/thumbnail.js">
</script>
<script type="text/javascript" src="js/mainPage.js">
</script>
<script type="text/javascript" src="js/extra.js">
</script>
</head>


<body style="margin: 0px; overflow: scroll;">
  <table  cellspacing="0" cellpadding="0" width="100%"
  border="0" >
    <tbody>
      <tr>
        <td valign="middle" style="background-color: black; text-align: center">
          <div id="SilverlightControlHost">
            <script type="text/javascript">
//<![CDATA[
                            createSilverlight();
//]]>
</script>
          </div>
        </td>
      </tr>
    </tbody>
  </table>
</body>
</html>


That's it.  I hope you've just learned how to enhance the Page turn application with zooming feature.
You can try the result at http://www.microsoft.com/japan/powerpro/magazine/viewer/ .  Please enjoy.


(Added on 2007.11.30)


This is my response to http://silverlight.net/forums/t/5941.aspx to show you how to apply the zoom-in/zoom-out feature into your Silverlight Page turn sample.  The enhanced code is available as attached zip file.  You can try it soon once you put the files into your Web server.  I recommend you should set 'index.html' as the default document.

EnhancedSLPageTurn1.0.zip

Comments (12)

  1. Very good work Akira, i’m looking for that kind of  improvement too.

    Is it possible for you to share or to send to me thepageturn exemple you use, because actually thepageturn example on the silverlight official website doesn’t work. We can download it but it is bugged.

    After i will try to work on the thumbnails to be funnier and more functionnal.

    Thank you

  2. 大西 彰 says:

    Hi Nicolas,

    Yes, I’ll try to share the enhanced page turner on this blog in near future.  Also I’ll contact you via e-mail.

    As for the original page turn sample, we should set the event handler for onError as null because we’ll face with the error if we use the original handler.  Note that we can’t run the sample without Web Server because SL 1.0 doesn’t support to access our local resources for security reason.

    Thank you,

    -Akira

  3. 大西 彰 says:

    Now I share the sample as attachment.  Please download it and try it.

  4. 興味のある方の時間を短縮するために、添付ファイルとして公開してます。下記のURLを開き、ブログのattachmentをダウンロードしてください。元のサンプルで表示されない画像を削ってます。 http://blogs.msdn.com/aonishi/archive/2007/11/28/how-to-add-the-zooming-feature-for-your-silverlight-page-turn-application-with-silverlight-1-0.asp

  5. WynApse says:

    Silverlight Cream for November 30, 2007 — #139

  6. Nicolas Friceau says:

    Nice, Thank you to share it so quickly,

    It works well for me.

  7. Michael says:

    The download link is broken.

  8. 大西 彰 says:

    It seems to me it’s not broken.  Please try again to access the following URL.

    http://blogs.msdn.com/aonishi/attachment/6574315.ashx

  9. Petra says:

    It doesn’t work when you download and extract it. Brings up two error messages – AG_E_UNKNOWN_ERROR – Download Error – Error No. 1001. Every single version of this script I’ve looked at on the web does the same except the large and unwieldy Dr Dobbs’ page turn.

  10. 大西 彰 says:

    Hi Petra,

    Please deploy the sample into your Web server.  It doesn’t work if you run it local environment because the Page Turn sample uses Downloader object that require the HTTP communication.

    The Downloader is used for downloading the images files to make stability for initialization of the application.

  11. Manuel says:

    Hi, is it possible to change the dowloader object with other objet that works without web server?

    Thanks,

    Manuel.

Skip to main content