Two people doing their own thing.

Microsoft Certified Partner Soul Solutions
Mar 24

Written by: Soul Solutions
Monday, 24 March 2008

johnWeeGo.jpgThe Expression team have posted about how to manipulate collections within the Silverlight 2 multiscaleimage control. So what a great time to revisit my little sample code and make some improvements. Let make sure we have mouse, mouse wheel, keyboard, bounds checks and resorting.

See my updated sample here. Use the "R" key to resort the collection.

DeepZoomThai2.jpg

First up you should read my first post that explains creating the image assets and setting up your project. In this post we will cover the following:

  1. Move event handlers into anonymous methods
  2. Use Pete Blois' mouse scroll wheel code that requires no JavaScript
  3. Add zoom bounds code for minimum and maximum zoom
  4. Resorting the images into a randomly ordered grid

As the expression blog mentions the first step is to ensure you export your assets as a collection:

ExportAsCollection.png

Anonymous Methods for Events

The concept here is to move all the event handlers into the constructor and implement them up as very simple code. I understand the reason why you would want to remove the handlers from the XAML. If you were working in tandem with a designer who was creating the XAML for the UI this does keep the code very separated. Our new XAML is cut down to this:


<UserControl x:Class="SoulSolutions.DeepZoom.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <Grid x:Name="LayoutRoot" Background="Black">
        <MultiScaleImage x:Name="deepZoomObject" Source="thai2007/items.bin" />
    </Grid>
</UserControl>

While we hook up the events directly to the event in the constructor as follows:


public Page()
{
    InitializeComponent();
    deepZoomObject.MouseMove += delegate(object sender, MouseEventArgs e)
    {
        if (mouseButtonPressed)
        {
            mouseIsDragging = true;
        }
        lastMousePos = e.GetPosition(deepZoomObject);
    };

    deepZoomObject.MouseLeftButtonDown 
        += delegate(object sender, MouseButtonEventArgs e)
    {
        mouseButtonPressed = true;
        mouseIsDragging = false;
        dragOffset = e.GetPosition(this);
        currentPosition = deepZoomObject.ViewportOrigin;
    };

    deepZoomObject.MouseLeave += delegate
    {
        mouseIsDragging = false;
    };

    deepZoomObject.MouseLeftButtonUp += delegate
    {
        mouseButtonPressed = false;
        if (mouseIsDragging == false)
        {
            if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
            {
                Zoom(0.5, lastMousePos);
            }
            else
            {
                Zoom(2, lastMousePos);
            }
            
        }else
        {
            mouseIsDragging = false;
        }
    };

    deepZoomObject.MouseMove += delegate(object sender, MouseEventArgs e)
    {
        if (mouseIsDragging)
        {
            Point newOrigin = new Point();
            newOrigin.X = currentPosition.X - 
                (((e.GetPosition(deepZoomObject).X - dragOffset.X) 
                / deepZoomObject.ActualWidth) * deepZoomObject.ViewportWidth);
            newOrigin.Y = currentPosition.Y - 
                (((e.GetPosition(deepZoomObject).Y - dragOffset.Y) 
                / deepZoomObject.ActualHeight) * deepZoomObject.ViewportWidth);
            deepZoomObject.ViewportOrigin = newOrigin;
        }
    };

    new MouseWheelHelper(deepZoomObject).Moved 
        += delegate(object sender, MouseWheelEventArgs e)
    {
        e.Handled = true;
        if (e.Delta > 0)
        {
            Zoom(1.2, lastMousePos);
        }
        else
        {
            Zoom(.80, lastMousePos);
        }
    };

    KeyDown += delegate(object sender, KeyEventArgs e)
    {
        Point p = new Point((deepZoomObject.Width/2),
                            ((deepZoomObject.Width/deepZoomObject.AspectRatio)/2));

       switch (e.Key)
       {
           case Key.I:
           case Key.C:
           case Key.Add:
               Zoom(1.1, p);
               break;
           case Key.O:
           case Key.Space:
           case Key.Subtract:
               Zoom(0.9, p);
               break;
           case Key.Left:
           case Key.A:
               deepZoomObject.ViewportOrigin = new Point(deepZoomObject.ViewportOrigin.X 
                - (0.1 * deepZoomObject.ViewportWidth), deepZoomObject.ViewportOrigin.Y);
               break;
           case Key.Right:
           case Key.D:
               deepZoomObject.ViewportOrigin = new Point(deepZoomObject.ViewportOrigin.X  
                + (0.1 * deepZoomObject.ViewportWidth), deepZoomObject.ViewportOrigin.Y);
               break;
           case Key.Up:
           case Key.W:
               deepZoomObject.ViewportOrigin = new Point(deepZoomObject.ViewportOrigin.X,
                deepZoomObject.ViewportOrigin.Y - (0.1 * deepZoomObject.ViewportWidth));
               break;
           case Key.Down:
           case Key.S:
               deepZoomObject.ViewportOrigin = new Point(deepZoomObject.ViewportOrigin.X,
                deepZoomObject.ViewportOrigin.Y + (0.1 * deepZoomObject.ViewportWidth));
               break;
           case Key.R:
               ArrangeIntoGrid();
               break;
           default:
               break;
       }
    };
}

For the Mouse scroll wheel event I have dropped the JavaScript helper in favour of Pete Blois' version.

Zoom Bounds

To tidy things up a little I've added a little check from Chip Aubry to the zoom method:


private void Zoom(double zoom, Point pointToZoom)
{
    if ((zoom >= 1.0 && deepZoomObject.ViewportWidth > 0.05) || 
        (zoom < 1.0 && deepZoomObject.ViewportWidth < 2))
    {
        Point logicalPoint = deepZoomObject.ElementToLogicalPoint(pointToZoom);
        deepZoomObject.ZoomAboutLogicalPoint(zoom, logicalPoint.X, logicalPoint.Y);
    }
}

Resorting the images

I've hooked up the "R" key to resort the collection into a simple grid. The code is from the expression team's blog and randomising the image order before laying out the images as a simple grid. The method animates the change of position for each image:


private void ArrangeIntoGrid()
{
    List<MultiScaleSubImage> randomList = RandomizedListOfImages();
    int numberOfImages = randomList.Count;
    int totalImagesAdded = 0;
    int totalColumns = (int)Math.Sqrt(numberOfImages) + 1;
    int totalRows = numberOfImages / (totalColumns - 1);

    for (int col = 0; col < totalColumns; col++)
    {
        for (int row = 0; row < totalRows; row++)
        {
            if (numberOfImages != totalImagesAdded)
            {
                MultiScaleSubImage currentImage = randomList[totalImagesAdded];

                Point currentPos = currentImage.ViewportOrigin;
                currentImage.ViewportWidth = totalColumns;
                Point futurePosition = new Point(-1.2*col, -1.6*row);

                // Set up the animation to layout in grid
                Storyboard moveStoryboard = new Storyboard();

                // Create Animation
                PointAnimationUsingKeyFrames moveAnimation 
                    = new PointAnimationUsingKeyFrames();

                // Create Keyframe
                SplinePointKeyFrame startKeyframe = new SplinePointKeyFrame();
                startKeyframe.Value = currentPos;
                startKeyframe.KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero);

                startKeyframe = new SplinePointKeyFrame();
                startKeyframe.Value = futurePosition;
                startKeyframe.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1));

                KeySpline ks = new KeySpline();
                ks.ControlPoint1 = new Point(0, 1);
                ks.ControlPoint2 = new Point(1, 1);
                startKeyframe.KeySpline = ks;
                moveAnimation.KeyFrames.Add(startKeyframe);

                Storyboard.SetTarget(moveAnimation, currentImage);
                Storyboard.SetTargetProperty(moveAnimation, "ViewportOrigin");

                moveStoryboard.Children.Add(moveAnimation);
                deepZoomObject.Resources.Add(moveStoryboard);

                // Play Storyboard
                moveStoryboard.Begin();

                totalImagesAdded++;
            }
            else
            {
                break;
            }
        }
    }
}

Download the source code here. (Excludes the export from Deep Zoom Composer, add your own!)

http://thailand.soulclients.com/SoulSolutions.DeepZoomPart2.zip (11.6KB)

Future Thoughts

This concept is really getting there, we can now not only show collections of images and navigate them, we can now manipulate the individual sub-images. The next step is to actually identify the sub images with metadata so we can do things like sort, filter and show information.

Tags:

11 comments so far...

Re: Silverlight Deep Zoom Sample Code Part 2

Controlling zoom bound using ViewportWidth isn't perfect. Try zoom out to the limit, zoom in slightly again. Now, you can zoom out even more!!!

I try to control it by calculating the current zoom factor, http://impressionsoft.blogspot.com/2008/03/silverlight-deepzoom-part-2.html,
but my approach, although better than ViewportWidth solution, isn't perfect either.

I wish Expression team can release more information sooner.

By ccchai on   Sunday, 30 March 2008

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application
# Geert van der Cruijsen

By TrackBack on   Thursday, 1 May 2008

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application
# Geert van der Cruijsen

By TrackBack on   Thursday, 1 May 2008

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application
# Geert van der Cruijsen

By TrackBack on   Thursday, 1 May 2008

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application
# Geert van der Cruijsen

By TrackBack on   Thursday, 1 May 2008

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application
# Geert van der Cruijsen

By TrackBack on   Thursday, 1 May 2008

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application

Display Picture Metadata in your Silverlight 2.0 Deepzoom Application
# Geert van der Cruijsen

By TrackBack on   Thursday, 1 May 2008

Re: Silverlight Deep Zoom Sample Code Part 2

is it possible do dinamicly load the images?

By Rui-Marinho on   Saturday, 31 May 2008

Re: Silverlight Deep Zoom Sample Code Part 2

I get a whole bunch of errors when I use your files. I was able to build a simple sample myself but I'd like to get it all dynamically working. these are the errors I'm getting :

1 time :
could not locate the assembly "System.Windows.controls" in file c:\WINDOWS\Microsoft.net\Framework\v3.5\Microsoft.Common.targets

2 times :

The name 'initializeComponent' does not exist in the current context in file App.xaml.cs and in Page.xaml.cs

20 to 25 times:

the name 'deepZoomObject' does not exist in the current context in file Page.xaml.cs

and some more errors but I think these are the most important ones...

any help would be appreciated

By harry on   Friday, 11 July 2008

Re: Silverlight Deep Zoom Sample Code Part 2

Hi, thanks for this blog.
Using: VS'08, Sl2B2, .NET 3.5, ...
got a couple of errors, fixed SetTargetProperty per:
http://msdn.microsoft.com/en-us/library/cc645049(VS.95,printer).aspx#important

But puzzling over:
deepZoomObject.Resources.Add(moveStoryboard) Err No overload for method 'Add' takes '1' arg...

By Art Scott on   Wednesday, 23 July 2008

Your name:
Title:
Comment:
Security Code
Enter the code shown above in the box below
Add Comment    Cancel