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.