程序要實現的目的是通過鼠標來控制圖片的縮放和移動的效果,也就是說可以鼠標在程序界面上拖動圖片,通過鼠標滾輪放大和縮小圖片。這種功能在圖片瀏覽程序裏面再普通不過了,一般來說,如果是在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;
}
}
}