DeepZoom






4.91/5 (45投票s)
本文介绍如何在 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 应用程序所需要的东西
- Microsoft Silverlight Tools Beta 1 for Visual Studio 2008
- Silverlight 2 Beta 1 SDK
- Expression Blend 2.5 Preview
- Deep Zoom Composer
哦,而且 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。