65.9K
CodeProject 正在变化。 阅读更多。
Home

DeepZoom

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (45投票s)

2008年5月13日

CPOL

4分钟阅读

viewsIcon

157790

downloadIcon

2242

本文介绍如何在 Silverlight 2.0 中实现 DeepZoom。

引言

自从我写了第一篇关于 Silverlight 的文章(Silverlight 1.1 Fun and Games),该文章基于 Silverlight 1.1 Alpha,之后事情发生了一些变化,现在 Silverlight 2.0 Beta 已经可用了。

与 1.1 Alpha 相比,有一些真正的改进;例如,在 1.1 Alpha 中,几乎没有输入/布局控件。好吧,只有一个 `Canvas`,就这么多了。所以,我个人对 Silverlight 2.0 Beta 提供的​​新控件感到非常满意。

例如,如果我使用 Expression Blend Beta 2.5 创建一个新的 Silverlight 2.0 Beta 项目,我就可以使用以下所有控件(信不信由你,1.1 中从来没有 Button,你必须自己写... 伤心)。

现在,这一切都很棒,但我仍然不太有兴趣写另一篇 Silverlight 文章来展示这些新控件。真正让我感兴趣(并且让我有动力写这篇文章)的是新的 `MultiScaleImage`,它在完整的 WPF 工具集中并不存在。

一点历史

新的 `MultiScaleImage` 是一个奇怪的东西。从我搜索的信息来看,它的存在归功于一个名为 Sea Dragon 的旧产品,微软购买了它并将其重新命名为 DeepZoom。

DeepZoom 能为我做什么?

顾名思义,`MultiScaleImage` 很可能允许在单个图像容器中查看多尺度图像。事实确实如此。DeepZoom(又名 Sea Dragon)允许创建一个组合,其中图像(或更典型地,图像的拼贴)包含不同阶段的信息,这使得 `MultiScaleImage` 能够正常工作。为了创建所需的信息,有一个 DeepZoom Composer 辅助 DeepZoom 组合的设计。如果您想查看在线版本,请查看 Vertigo 的 hardrock.com 网站

必备组件

在我们继续之前,我将列出我认为构建使用 DeepZoom 的 Silverlight 2.0 应用程序所需要的东西

哦,而且 Visual Studio 2008 总是必需的。

创建一个支持 DeepZoom 的 Silverlight 应用

我将概述我为使 DeepZoom 工作而遵循的步骤(我希望这会有所帮助,因为我不得不经历一番周折)。

步骤 1:创建 DeepZoom 组合项目

第一步是创建一个 Composer 项目,因此一旦您下载并安装了 Deep Zoom Composer,只需创建一个新项目并导入和组合一些图像。

我在这里唯一需要做的事情是给导出命名并按下导出按钮。

我将导出命名为“composedoutput”,所以我得到了(哦,而且我没有点击 Create Collection)

OutputSdi

  • composedoutput.sdi
  • composedoutput 文件夹
    • 许多子文件夹
    • info.bin
    • info.xml
  • SparseImageSceneGraph.xml

步骤 2:在 Visual Studio 2008 中创建一个 Silverlight 2.0 应用

然后我启动了 VS2008 并创建了一个新的 Silverlight 项目

并选择让 VS2008 为 Silverlight 应用创建一个新的网站

然后我稍微清理了一下网站项目,删除了不需要的文件。下面划线的部分是我不需要的

然后我构建了 Silverlight 应用,这在网站项目中创建了一个 `ClientBin` 目录。

另外值得注意的是单个 `.xap` 文件。这实际上是一个编译过的 Silverlight 文件(您可以将其重命名为 `.Zip` 并检查其内容),它应该包含引用的 DLL、实际的应用 DLL 和一个清单文件。

到目前为止,一切都很顺利。此时,您应该能够运行网站应用程序并在网页中看到嵌入的 Silverlight 应用程序。

好的,现在是一些代码。

步骤 3:将 DeepZoom 添加到 Silverlight 应用

我们有了一个不错的 Silverlight 应用,但本文是关于 DeepZoom 的。我们需要对我们的 Silverlight 应用进行一些更改,使其支持 DeepZoom。让我们看看所需的 XAML

<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>

这是此 Silverlight 页面的**完整**代码隐藏

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;
            }
        }
    }
}

那些眼尖的读者可能会注意到这里有一个名为“`MultiScaleImageWrapper`”的类。这个类我一点功劳也没有。这完全归功于 Vertigo 最出色的各位,他们很乐意创建了一个 CodePlex 页面,其中包含这个类。这个类允许用户使用浏览器中的 `MouseScrollWheel` 与 DeepZoom `MultiScaleImage` 进行交互。这是通过挂钩宿主元素事件来实现的。我们来看看代码,好吗?

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);
            }
        }
    }
}

步骤 4:将 DeepZoom Composer 输出复制到网站

然后我复制了包含以下文件的文件夹

  • composedoutput.sdi
  • composedoutput 文件夹
    • 许多子文件夹
    • info.bin
    • info.xml
  • SparseImageSceneGraph.xml

所以对我来说,那就是整个 `OutputSdi` 文件夹,所以我的 `ClientBin` 文件夹现在看起来是这样的

步骤 5:更改 Silverlight MultiScaleImage 的 URI

最后一步是更改 `MultiScaleImage` 查找其源的位置。这是通过 `MultiScaleImage` 加载事件中的这一行完成的

if (Uri.TryCreate(App.Current.Host.Source, 
    "OutputSdi/composedoutput/info.bin", out collectionUri))
    image.Source = collectionUri;

就是这样。工作完成了。

历史

  • v1.0: 13/05/08。
© . All rights reserved.