每周源代码18 – Deep Zoom(SeaDragon)Silverlight 2 MultiScaleImage鼠标滚轮缩放及平移
[原文发表时间] 2008-03-08 04:14
亲爱的读者,请阅读我的系列博文《每周源代码》的第十八篇。以下是我本周在Mix阅读及编写的代码。
我在研究Silverlight 2中的Deep Zoom,却发现居然没有支持鼠标滑轮框外的平移,缩放功能的“Hello DeepZoom World!”的样本这简直太令人讨厌了。而Vertigo的样本示例则和我想要的效果一样。你可以在这里查看我做的Deep Zoom示例样本,或者点击右边的图片。
我们可以通过好几种方法让Silverlight支持鼠标滑轮。鼠标滑轮事件源于浏览器而非Silverlight本身(在我看来,这正如同你希望的那样,因为Silverlight内置于浏览器,它不能取代其特性)。
所以,你可以使用Adomas的Javascript代码,辅以Jeff Prosise编写好的代码进入Silverlight,调用方法。事件会在JavaScript中处理,Zoom函数也通过JavaScript的桥调用,转换成Silverlight的托管代码。
当然,你也可以调用内部托管代码,为DOM(JavaScript事件)设置管理处理,就像Pete Blois使用鼠标滑轮帮助器类进行操作那样。我直接从Pete的博客上下载了这个类并将其添加到了我的项目中。这非常棒,无需任何外部JavaScript文件。所有的事件都是由托管代码处理的。
1: if (HtmlPage.IsEnabled) {
2: HtmlPage.Window.AttachEvent("DOMMouseScroll", this.HandleMouseWheel);
3: HtmlPage.Window.AttachEvent("onmousewheel", this.HandleMouseWheel);
4: HtmlPage.Document.AttachEvent("onmousewheel", this.HandleMouseWheel);
5: }
此外,我还在Yasser博客中Yasser Makram和John的评论里截取了我最需要的代码段。
我看过很多用鼠标点击或者用键盘下键来实现缩放效果的示例,但我还想支持鼠标滑轮事件,就像在Mix中显示的那样。
这个更完整的样本将提供你:
• 拖动以平移
• 点击以放大,Shift点击以缩小
• 用鼠标滑轮来进行缩放
• 无需依靠JavaScript – 所有操作都是在管理代码中完成的。
首先,从DeepZoom的输出编辑器开始(我用这台机器上的Windows Wallpapers在编辑器中编写DeepZoom图像),然后将结果导出的文件夹结构拷贝至某处(为方便起见,我把它放在bin/debug下,不过你可以随意,只要源属性在XAML中整齐地排列):
1: <UserControl
2: xmlns="https://schemas.microsoft.com/client/2007"
3: xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
4: x:Class="SilverlightApplication1.Page"
5: Width="800" Height="600" >
6:
7: <Grid
8:
9: x:Name="LayoutRoot"
10: Background="Gray">
11: <MultiScaleImage
12: x:Name="msi"
13: ViewportWidth="1.0"
14: Source="https://www.yourdomain.com/foo/items.bin" />
15: </Grid>
16: </UserControl>
注意事项:你可以在MultiScaleImage上设置一个很酷的转换。设置UseSprings="false",它可以关掉缩放的动画效果。为什么要这么操作呢?因为特定的放大缩小动画让DeepZoom有机会能展示它的轻巧视觉操控和图像间的转换。当动画产生时,你不太会注意到平铺视图间的转换。当然,我很想搞清楚这一切到底是怎么实现的,所以我很喜欢看其中的转换。
你要知道现在是早上4点,所以这个代码有点儿靠不住,考虑也许不周全,因为我只在上面花了一个小时的时间。我想知道读者怎样利用这段代码,将其改进为一个经典的范本。这个示例还不够完善,我其实有点迟疑要不要把它贴在这里,因为它很杂乱,不过这也很有趣,不是吗?至少我觉得它行之有效。
有一个需要指出的是, control的名称是“msi”,它通过x:name=”msi”在上述的XAML中设定,这样你就可以看到我引用属性,比如下面XAML代码中的msi.thisandthat。还有,下面的“using akadia”是从我页面引用的Pete的MouseHandler代码的命名空间。
我的代码通过匿名委托连接了一连串constructor里的事件,它们一同调用了单一Zoom()辅助方法。
1: using System;
2: using System.Windows;
3: using System.Windows.Controls;
4: using System.Windows.Documents;
5: using System.Windows.Ink;
6: using System.Windows.Input;
7: using System.Windows.Media;
8: using System.Windows.Media.Animation;
9: using System.Windows.Shapes;
10: using System.Windows.Threading;
11: using akadia;
12:
13: namespace SilverlightApplication1
14: {
15: public partial class Page : UserControl
16: {
17: Point lastMousePos = new Point();
18:
19: double _zoom = 1;
20: bool mouseButtonPressed = false;
21: bool mouseIsDragging = false;
22: Point dragOffset;
23: Point currentPosition;
24:
25: public double ZoomFactor
26: {
27: get { return _zoom; }
28: set { _zoom = value; }
29: }
30:
31: public Page()
32: {
33: this.InitializeComponent();
34:
35: this.MouseMove += delegate(object sender, MouseEventArgs e)
36: {
37: if (mouseButtonPressed)
38: {
39: mouseIsDragging = true;
40: }
41:
42: this.lastMousePos = e.GetPosition(this.msi);
43:
44: };
45:
46: this.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e)
47:
48: {
49:
50: mouseButtonPressed = true;
51: mouseIsDragging = false;
52: dragOffset = e.GetPosition(this);
53: currentPosition = msi.ViewportOrigin;
54:
55: };
56:
57: this.msi.MouseLeave += delegate(object sender, MouseEventArgs e)
58: {
59: mouseIsDragging = false;
60:
61: };
62:
63: this.MouseLeftButtonUp += delegate(object sender, MouseButtonEventArgs e)
64: {
65: mouseButtonPressed = false;
66: if (mouseIsDragging == false)
67: {
68: bool shiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
69:
70: ZoomFactor = 2.0;
71: if(shiftDown) ZoomFactor = 0.5; //back out when shift is down
72: Zoom(ZoomFactor, this.lastMousePos);
73:
74: }
75:
76: mouseIsDragging = false;
77:
78: };
79:
80: this.MouseMove += delegate(object sender, MouseEventArgs e)
81: {
82: if (mouseIsDragging)
83: {
84:
85: Point newOrigin = new Point();
86: newOrigin.X = currentPosition.X - (((e.GetPosition(msi).X - dragOffset.X) / msi.ActualWidth) * msi.ViewportWidth);
87: newOrigin.Y = currentPosition.Y - (((e.GetPosition(msi).Y - dragOffset.Y) / msi.ActualHeight) * msi.ViewportWidth);
88: msi.ViewportOrigin = newOrigin;
89: }
90: };
91:
92: new MouseWheelHelper(this).Moved += delegate(object sender, MouseWheelEventArgs e)
93: {
94:
95: e.Handled = true;
96: if (e.Delta > 0)
97: ZoomFactor = 1.2;
98: else
99: ZoomFactor = .80;
100:
101: Zoom(ZoomFactor, this.lastMousePos);
102:
103: };
104:
105: }
106:
107: public void Zoom(double zoom, Point pointToZoom)
108:
109: {
110:
111: Point logicalPoint = this.msi.ElementToLogicalPoint(pointToZoom);
112: this.msi.ZoomAboutLogicalPoint(zoom, logicalPoint.X, logicalPoint.Y);
113:
114: }
115:
116: }
117:
118: }
我也碰到一个麻烦,不过因为我还没安装Silverlight 2 Beta 1工具安装程序(Silverlight 2 Beta 1工具安装程序故障排除),我还没在调试器上试过。我刚在Expression Blend 2.5和Notepad2中创建了它。我太享受这过程了,都不想停下来去安装其他东西了。
- 我发现当我应该用一个absolute factor的时候,我却用了相对计算的ZoomFactor。
- 第三,我想避免图像平移出可视范围(就是说我不想大家迷失方向),而且我希望能给缩小放大设置合理的上限/下限。
- 不要忘了在主机IIS上将 .xap文件设置为mime 类型应用 / x-silverlight-app
这一切实在太棒了,而且也不难。我也会在上午再琢磨下还有没有其他办法实现此操作。