使用WPF Resource以及Transform等技术实现鼠标控制图片缩放和移动的效果
程序要实现的目的是通过鼠标来控制图片的缩放和移动的效果,也就是说可以鼠标在程序界面上拖动图片,通过鼠标滚轮放大和缩小图片。这种功能在图片浏览程序里面再普通不过了,一般来说,如果是在MFC或者Winform里面实现这两个功能的话,都是通过处理鼠标的移动和滚轮事件,在这两个事件处理函数里面,获取鼠标的位置和滚轮滚动的偏移量,然后针对性地更改图片的位置和高宽度来做的。
比如说,在Winform里面实现鼠标拖拽图片功能的话,代码看起来像下面这样:
// 上一次鼠标移动的位置
private Point m_PreviousMousePosition;
private void DoImageMove(object sender, MouseEventArgs e)
{
// 将sender转化成触发鼠标事件的控件,在Winform程序里面,
// 一般都是PictureBox控件。
PictureBox picture = sender as PictureBox;
Debug.Assert(picture != null);
// 或者鼠标在PictureBox控件的相对坐标,下面的GetMouseRelativePositionTo
// 函数是一个虚构的函数,具体的实现可以Google一下他人的实现方式。
Point mousePosition = GetMouseRelativePositionTo(picture);
// 移动图片,由于MouseMove事件会在我们移动鼠标的时候触发多次,
// 因此我们可以通过获取两次鼠标移动之间,鼠标指针位置的偏移量
// 来知道图片应该移动的偏移量。
picture.X += mousePosition.X - m_PreviousMousePosition.X;
picture.Y += mousePosition.Y - m_PreviousMousePosition.Y;
// 将这次鼠标的位置保存下来。
m_PreviousMousePosition = mousePosition;
}
对于通过滚动条来实现图片缩放的代码应该会是这样:
private void MasterImage_MouseWheel(object sender, MouseWheelEventArgs e)
{
PictureBox picture = sender as PictureBox;
Debug.Assert(picture != null);
// 强迫PictureBox控件在更改大小的时候,自动缩放图片
// 以便填充整个PictureBox控件
picture.SizeMode = PictureBoxSizeMode.StretchImage;
// 0.001是我随便取的一个值,因为滚轮的Delta值太大了
// 根据鼠标滚轮的偏移量来更改PictureBox宽度和高度。
picture.Width += e.Delta * 0.001;
picture.Height += e.Delta * 0.001;
}
上面两个代码也是很简洁,但问题是,如果程序需要显示多个图片用来作为比对,并且我们移动其中任何一个图片,其它图片的位置也会跟着有相同的位置变化;缩放其中任何一个图片,其它图片的也有同样程度的缩放比例。这样,我们就得在MasterImage_MouseMove和MasterImage_MouseWheel函数里面显示修改所有图片的位置。更糟糕地是,上面的代码很难适应的新的需求,上面的图片缩放代码,实际上是以图片最左上角的那个点为原点来进行横向和纵向缩放的。如果新需求是要求我们以图片中心的那个点为原点进行图片缩放的话,又该如何修改代码呢?
WPF提供了很多函数方便我们处理图片,例如各式各样的Transform类用来移动、缩放和旋转图片,有各式各样的Effect类来修改图片的外观。更难得的是,这些类都可以在XAML代码直接设置,而XAML为了提高代码的可维护性又为我们提供了Resource这么好的概念来将通用的代码和设置保存在一个中心位置,其它控件可以直接引用同一个Resource就可以获取同样的设置。因此,为什么我们不能将这两个工具结合起来编写尽量少的代码来实现图片移动和缩放的功能呢?
步骤如下:
1. 定义一个TranslateTransform实例来修改图片显示的起始位置。
2. 定义一个ScaleTransform实例来缩放图片的大小,你可以通过设置CenterX和CenterY的值来指定图片缩放的原点。
3. 将两个Transform放到一个TransformGroup里面,这样Image控件就可以在显示的时候综合使用两个Transform的效果了。你可以注意一下,TransformGroup的基类也是Transform,想一想这采用的是哪一个设计模式?不知道,呃……看样子我还需要写另外一篇文章解释一下这个设计模式……不过我最近有点忙,如果你等不及看到我下一篇文章的话,还是自己Google一下吧。
4. 将TransformGroup放到当前窗体的Resource里面,这样窗体里面所有的Image控件都可以引用到这个实例。
5. 在鼠标移动事件里面修改TranslateTransform对应的值。
6. 在鼠标滚轮事件里面修改ScaleTransform的ScaleX和ScaleY的值来缩放图片。
Window1.xaml
<Window x:Class="MouseMove.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MouseMove"
Title="Window1" Height="600" Width="800">
<Grid x:Name="MainPanel">
<Grid.Resources>
<TransformGroup x:Key="ImageTransformResource">
<ScaleTransform />
<TranslateTransform />
</TransformGroup>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" x:Name="MasterImage"
MouseLeftButtonDown="MasterImage_MouseLeftButtonDown"
MouseLeftButtonUp="MasterImage_MouseLeftButtonUp"
MouseMove="MasterImage_MouseMove"
MouseWheel="MasterImage_MouseWheel">
<Rectangle.Fill>
<VisualBrush Transform="{StaticResource ImageTransformResource}" Stretch="Uniform">
<VisualBrush.Visual>
<Image Source="C:"Windows"Web"Wallpaper"Architecture"Img15.jpg" />
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="1"
MouseLeftButtonDown="MasterImage_MouseLeftButtonDown"
MouseLeftButtonUp="MasterImage_MouseLeftButtonUp"
MouseMove="MasterImage_MouseMove"
MouseWheel="MasterImage_MouseWheel">
<Rectangle.Fill>
<VisualBrush Transform="{StaticResource ImageTransformResource}" Stretch="Uniform">
<VisualBrush.Visual>
<Image Source="C:"Windows"Web"Wallpaper"Architecture"Img14.jpg" />
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
namespace MouseMove
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
private bool m_IsMouseLeftButtonDown;
public Window1()
{
InitializeComponent();
}
private void MasterImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Rectangle rectangle = sender as Rectangle;
if (rectangle == null)
return;
rectangle.ReleaseMouseCapture();
m_IsMouseLeftButtonDown = false;
}
private Point m_PreviousMousePoint;
private void MasterImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Rectangle rectangle = sender as Rectangle;
if (rectangle == null)
return;
rectangle.CaptureMouse();
m_IsMouseLeftButtonDown = true;
m_PreviousMousePoint = e.GetPosition(rectangle);
}
private void MasterImage_MouseMove(object sender, MouseEventArgs e)
{
Rectangle rectangle = sender as Rectangle;
if (rectangle == null)
return;
if (m_IsMouseLeftButtonDown)
DoImageMove(rectangle, e);
}
private void DoImageMove(Rectangle rectangle, MouseEventArgs e)
{
//Debug.Assert(e.LeftButton == MouseButtonState.Pressed);
if (e.LeftButton != MouseButtonState.Pressed)
return;
TransformGroup group = MainPanel.FindResource("ImageTransformResource") as TransformGroup;
Debug.Assert(group != null);
TranslateTransform transform = group.Children[1] as TranslateTransform;
Point position = e.GetPosition(rectangle);
transform.X += position.X - m_PreviousMousePoint.X;
transform.Y += position.Y - m_PreviousMousePoint.Y;
m_PreviousMousePoint = position;
}
private void MasterImage_MouseWheel(object sender, MouseWheelEventArgs e)
{
TransformGroup group = MainPanel.FindResource("ImageTransformResource") as TransformGroup;
Debug.Assert(group != null);
ScaleTransform transform = group.Children[0] as ScaleTransform;
transform.ScaleX += e.Delta * 0.001;
transform.ScaleY += e.Delta * 0.001;
}
}
}