![]() |
Web Development »
Silverlight »
General
Intermediate
License: The Code Project Open License (CPOL)
DeepZoomBy Sacha BarberAn article showing how to do DeepZoom in Silverlight 2.0 |
C# (C# 3.0), .NET (.NET 3.5), ASP.NET, Dev, Design
|
||||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

Since I wrote my first article on Silverlight (Silverlight 1.1 Fun and Games) which was based on the Silverlight 1.1 Alpha, things have a changed a bit, and now a Silverlight 2.0 Beta is available.
There have been some real improvements since the 1.1 Alpha, for example within the 1.1 Alpha there were no input/layout controls to speak of. Ok there was Canvas and that was about it. So I for one am quite pleased with the new controls that the Silverlight 2.0 Beta has available.
For example if I use Expression Blend Beta 2.5, to create a new Silverlight 2.0 Beta project, I get to use all of the following controls (believe it or not there never used to be Buttons in 1.1, you had to write those yourself...Ouch)
Now this is all cool, but I am still not that interested in writing another
Silverlight article just to show these new controls. What does interest me (and
has interested me) enough to write this article is the new MultiScaleImage
which doesn't exist in the full WPF toolset.
The new MultiScaleImage is a strange thing. From what I can tell
from my googling, this owes its existance to an old product called Sea Dragon,
which Microsoft bought and rebranded DeepZoom.
As the name suggested the MultiScaleImage more than likely allows
multiple scale images to be viewed in a single image container. This is the
case. The DeepZoom (aka Sea Dragon) allows a composition to be made where the
image (or more typically collage of images) have various different stages of
information stored which allows the MultiScaleImage to work correctly.
In order to create the required information there is a DeepZoom composer which
aids in the design of DeepZoom compositions. If you want to see an online version
have a look at Vertigos
hardrock.com site
Before we proceed, I'll just list what I think youll need to construct Silverlight 2.0 apps that use DeepZoom
Oh and Visual Studio 2008 is always required
I am going to outline the steps that I followed in order to get DeepZoom working (I hope this helps, cos I had to jump through a few hoops)
The 1st step is to create a composer project, so once you have downloaded and installed the Deep Zoom Composer, simply create a new project, and import and compose some images.

The only thing that I had to do here was give the Export a name, and press the export button.
I called the export "composedoutput" so I got (oh and I didnt click create collection)
OutputSdi
I then started VS2008 and created a new Silverlight project

And chose to let VS2008 create a new web site for the Silverlight App.

I then cleaned up the web project a bit, to remove unwanted files. The ones below with lines through them I didnt need.

I then built the Silverlight app, which created a ClientBin directory in the web project.

Also of note is the single .xap file. This is actually a compiled Silverlight file (you can rename this .Zip and examine its contents), and it should contain the referenced Dlls, and the actual App Dll and a manifest file.

So far it's all cool. At this point you should be able to run the web app and see the Silverlight app embedded in the web page just fine.
Ok so now comes some code.
So we have a nice Silverlight app, but this article is about DeepZoom. So we need to make some changes to our Silverlight app to DeepZoom'ify it. Lets look at the XAML required to do this.
<UserControl x:Class="DeepZoomApp.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800" Height="420">
<Grid x:Name="LayoutRoot" Background="White">
<MultiScaleImage x:Name="image" UseSprings="False"
ViewportWidth="1.0"
Loaded="image_Loaded"
MotionFinished="image_InitialMotionFinished" />
</Grid>
</UserControl>
And here is the FULL code behind for this Silverlight page.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace DeepZoomApp
{
/// <summary>
/// A very simple silverlight control demonstrating how to wire up mouse
/// interactions to a MultiScaleImage instance.
/// </summary>
public partial class Page : UserControl
{
/// <summary>
/// Stores an instance of the wrapper class that handles mouse interactions
/// (panning, zooming, etc)
/// </summary>
private MultiScaleImageWrapper mouseInteractionWrapper;
/// <summary>
/// Indicates if the first motion has completed yet
/// (the initial zoom behavior is disabled)
/// </summary>
private bool isInitialMotionFinished;
public Page()
{
InitializeComponent();
// initialize the wrapper class to enable mouse interactions
mouseInteractionWrapper = new MultiScaleImageWrapper(image);
}
/// <summary>
/// Wires up an absolute Uri to the MultiScaleImage collection path
/// (for some reason, relative values are not currently supported).
/// This should make this example work, no matter what port is auto-assigned
/// by Cassini.
/// </summary>
/// <param name="sender">The MultiScaleImage instance.</param>
/// <param name="e">Unused RoutedEvent arguments.</param>
private void image_Loaded(object sender, RoutedEventArgs e)
{
Uri collectionUri;
if (Uri.TryCreate(App.Current.Host.Source,
"OutputSdi/composedoutput/info.bin", out collectionUri))
image.Source = collectionUri;
}
/// <summary>
/// Handles the "MotionFinished" event fired by the MultiScaleImage,
/// but only re-enables the "UseSprings" property after the first
/// motion completes (a little trick to properly bypass the initial "zoom in
/// from nowhere" animation when first loading)
/// </summary>
/// <param name="sender">The MultiScaleImage instance.</param>
/// <param name="e">Unused RoutedEvent arguments.</param>
void image_InitialMotionFinished(object sender, RoutedEventArgs e)
{
if (!isInitialMotionFinished)
{
isInitialMotionFinished = true;
image.UseSprings = true;
}
}
}
}
The more eagle eyed amongst you may notice that there is a class used here
called "MultiScaleImageWrapper". I can take no credit what so ever
for this class. Thats all down to the most excellent folk at vertigo,
who were kind enough to create a codeplex
page with this class. This class allows the users to use the MouseScrollWheel
in a browser to interact with the DeepZoom MultiScaleImage. This
is done by hooking into the hosting element events. Lets have a look at the
code shall we.
using System;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;
namespace DeepZoomApp
{
/// <summary>
/// Provides mouse panning and zooming support for a MultiScaleImage.
/// </summary>
public class MultiScaleImageWrapper
{
/// <summary>
/// Specifies the zoom factor when the MultiScaleImage receives a mouse click event.
/// </summary>
private const double ZOOM_FACTOR_CLICK = 2;
/// <summary>
/// Specifies the zoom factor when the MultiScaleImage receives a mouse wheel event.
/// </summary>
private const double ZOOM_FACTOR_WHEEL = 1.25;
/// <summary>
/// Listens to mouse wheel events raised by the HTML document.
/// </summary>
private static MouseWheelListener mouseWheelListener = new MouseWheelListener();
/// <summary>
/// Indicates whether or not the mouse is over the MultiScaleImage.
/// </summary>
private bool isMouseOver;
/// <summary>
/// Indicates whether or not the left mouse button is pressed for the MultiScaleImage.
/// </summary>
private bool isMouseDown;
/// <summary>
/// Indicates whether or not the mouse is being dragged over the MultiScaleImage.
/// </summary>
private bool isMouseDrag;
/// <summary>
/// Specifies the initial mouse drag origin for the MultiScaleImage.
/// </summary>
private Point dragOrigin;
/// <summary>
/// Specifies the initial mouse drag position for the MultiScaleImage.
/// </summary>
private Point dragPosition;
/// <summary>
/// Specifies the current mouse cursor position for the MultiScaleImage.
/// </summary>
private Point cursorPosition;
/// <summary>
/// Gets the wrapped MultiScaleImage instance.
/// </summary>
public MultiScaleImage Image { get; private set; }
/// <summary>
/// Initializes a new instance of the MultiScaleImageWrapper class to provide mouse panning and zooming support for the specified MultiScaleImage.
/// </summary>
/// <param name="image">The MultiScaleImage instance to wrap.</param>
public MultiScaleImageWrapper(MultiScaleImage image)
{
Image = image;
Image.MouseEnter += Image_MouseEnter;
Image.MouseLeave += Image_MouseLeave;
Image.MouseMove += Image_MouseMove;
Image.MouseLeftButtonDown += Image_MouseLeftButtonDown;
Image.MouseLeftButtonUp += Image_MouseLeftButtonUp;
mouseWheelListener.MouseWheel += Image_MouseWheel;
}
/// <summary>
/// Handles the MouseEnter event for the MultiScaleImage. Sets the isMouseOver flag to true and saves the currentPosition of the cursor.
/// </summary>
/// <param name="sender">The wrapped MultiScaleImage instance.</param>
/// <param name="e">The mouse event arguments.</param>
private void Image_MouseEnter(object sender, MouseEventArgs e)
{
isMouseOver = true;
cursorPosition = e.GetPosition(Image);
}
/// <summary>
/// Handles the MouseLeave event for the MultiScaleImage. Sets the isMouseOver flag to false.
/// </summary>
/// <param name="sender">The wrapped MultiScaleImage instance.</param>
/// <param name="e">The mouse event arguments.</param>
private void Image_MouseLeave(object sender, MouseEventArgs e)
{
isMouseOver = false;
}
/// <summary>
/// Handles the MouseMove event for the MultiScaleImage. Saves the currentPosition of the cursor and pans when the mouse is dragged.
/// </summary>
/// <param name="sender">The wrapped MultiScaleImage instance.</param>
/// <param name="e">The mouse event arguments.</param>
private void Image_MouseMove(object sender, MouseEventArgs e)
{
cursorPosition = e.GetPosition(Image);
if (isMouseDown)
{
isMouseDrag = true;
Point origin = new Point();
origin.X = dragOrigin.X - (cursorPosition.X - dragPosition.X) / Image.ActualWidth * Image.ViewportWidth;
origin.Y = dragOrigin.Y - (cursorPosition.Y - dragPosition.Y) / Image.ActualWidth * Image.ViewportWidth;
Image.ViewportOrigin = origin;
}
}
/// <summary>
/// Handles the MouseLeftButtonDown event for the MultiScaleImage. Saves the initial dragOrigin and dragPosition in case the user begins to pan.
/// </summary>
/// <param name="sender">The wrapped MultiScaleImage instance.</param>
/// <param name="e">The mouse button event arguments.</param>
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Image.CaptureMouse();
mouseWheelListener.IsEnabled = false;
isMouseDown = true;
isMouseDrag = false;
dragOrigin = Image.ViewportOrigin;
dragPosition = e.GetPosition(Image);
}
/// <summary>
/// Handles the MouseLeftButtonUp event for the MultiScaleImage. Zooms in if the user is not completing a pan and resets the mouse.
/// </summary>
/// <param name="sender">The wrapped MultiScaleImage instance.</param>
/// <param name="e">The mouse button event arguments.</param>
private void Image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!isMouseDrag && isMouseDown)
{
bool isShiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
double factor = isShiftDown ? 1 / ZOOM_FACTOR_CLICK : ZOOM_FACTOR_CLICK;
Zoom(factor, cursorPosition);
}
isMouseDown = false;
isMouseDrag = false;
Image.ReleaseMouseCapture();
mouseWheelListener.IsEnabled = true;
}
/// <summary>
/// Handles the MouseWheel event for the MultiScaleImage. Zooms in or out depending on the mouse wheel direction.
/// </summary>
/// <param name="sender">The MouseWheelWrapper instance.</param>
/// <param name="e">The mouse wheel event arguments.</param>
private void Image_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (!isMouseOver || e.Delta == 0)
return;
double factor = e.Delta > 0 ? ZOOM_FACTOR_WHEEL : 1 / ZOOM_FACTOR_WHEEL;
Zoom(factor, cursorPosition);
e.Handled = true;
}
/// <summary>
/// Zooms in or out of the MultiScaleImage.
/// </summary>
/// <param name="factor">The zoom factor.</param>
/// <param name="point">The zoom center point.</param>
public void Zoom(double factor, Point point)
{
Point logicalPoint = Image.ElementToLogicalPoint(point);
Image.ZoomAboutLogicalPoint(factor, logicalPoint.X, logicalPoint.Y);
}
/// <summary>
/// Provides data for a mouse wheel event.
/// </summary>
private class MouseWheelEventArgs : EventArgs
{
/// <summary>
/// Gets a value that specifies the delta and direction of the mouse wheel event. This value is normalized across browsers.
/// </summary>
public double Delta { get; private set; }
/// <summary>
/// Gets or sets a value that indicates whether or not the mouse wheel event has been handled.
/// </summary>
public bool Handled { get; set; }
/// <summary>
/// Initializes a new instance of the MouseWheelEventArgs class with the specified delta.
/// </summary>
/// <param name="delta">The delta calculated for the mouse wheel event.</param>
public MouseWheelEventArgs(double delta)
{
Delta = delta;
}
}
/// <summary>
/// Provides cross-browser support for the mouse wheel.
/// </summary>
private class MouseWheelListener
{
/// <summary>
/// Indicates whether or not the mouse wheel is enabled.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// Occurs when a mouse wheel event is fired.
/// </summary>
public event EventHandler<MouseWheelEventArgs> MouseWheel;
/// <summary>
/// Initializes a new instance of the MouseWheelListener class that listens to mouse wheel events fired by the HTML document.
/// </summary>
public MouseWheelListener()
{
if (HtmlPage.IsEnabled)
{
HtmlPage.Document.AttachEvent("DOMMouseScroll", Plugin_MouseWheelFirefox);
HtmlPage.Document.AttachEvent("onmousewheel", Plugin_MouseWheelOther);
IsEnabled = true;
}
}
/// <summary>
/// Handles mouse wheel events for Firefox.
/// </summary>
/// <param name="sender">The HTML element for the plug-in.</param>
/// <param name="e">The HTML event arguments.</param>
private void Plugin_MouseWheelFirefox(object sender, HtmlEventArgs e)
{
if (!IsEnabled)
{
e.PreventDefault();
return;
}
double delta = (double)e.EventObject.GetProperty("detail") / -3;
MouseWheelEventArgs args = new MouseWheelEventArgs(delta);
MouseWheel(this, args);
if (args.Handled)
e.PreventDefault();
}
/// <summary>
/// Handles mouse wheel events for browsers other than Firefox.
/// </summary>
/// <param name="sender">The HTML element for the plug-in.</param>
/// <param name="e">The HTML event arguments.</param>
private void Plugin_MouseWheelOther(object sender, HtmlEventArgs e)
{
if (!IsEnabled)
{
e.EventObject.SetProperty("returnValue", false);
return;
}
double delta = (double)e.EventObject.GetProperty("wheelDelta") / 120;
MouseWheelEventArgs args = new MouseWheelEventArgs(delta);
MouseWheel(this, args);
if (args.Handled)
e.EventObject.SetProperty("returnValue", false);
}
}
}
}
I then copied the folder that contained the following files
So for me that was the whole OutputSdi folder, so my ClientBin folder now looked like this

The very last step was to change the place the MultiScaleImage
looked for its source. This is done by the line within the MultiScaleImage
loaded event
if (Uri.TryCreate(App.Current.Host.Source,
"OutputSdi/composedoutput/info.bin", out collectionUri))
image.Source = collectionUri;
And thats it. Job done.
v1.0 13/05/08
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 24 Feb 2009 Editor: |
Copyright 2008 by Sacha Barber Everything else Copyright © CodeProject, 1999-2009 Web09 | Advertise on the Code Project |