技术开发 频道

在WPF下实现以鼠标为中心图片缩放范例

  【IT168 技术】用鼠标将浏览器中的图片进行移动和缩放是经常会遇到的应用。一般情况下,我可以通过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为我们提供了方便,WPF中包含了很多处理图片的函数,例如,用于移动、缩放和旋转图片的Transform类,修改图片外观的Effect类。这些类都可以在XAML代码中直接设置,因此可以将Resource和XAML相结合使用,以此来压缩代码量并且实现更丰富的应用。

  步骤如下:

  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;

        }

    }

}

  实现了以图片中心为基点的缩放拖拽之后,又想实现以图片焦点为基点的缩放拖拽。下面让一起通过实例来进行学习吧。

  下图里面右下方方块是一个WPF程序里面的一个图片,大小是40 x 40,里面的黑点是预备缩放的原点,假设黑点的坐标是(10, 10),在运行程序的时候,用户首先将方块移动到左边的位置,当然原点(黑点)也移动了,假如这个时候图片移动了50个像素。

图1 移动图片

  接着用户在移动后的位置上,将图片缩放,比如说放大了2倍,这个操作也会移动原点(黑点)在最终图片的位置。因为放大图片,实际上就是将原始图片的各个像素移动到新的位置(红点),这个时候,新的原点(红点)的坐标应该是(20, 20),相邻两个像素的空间使用插值的方法填充。这个时候,

  ScaleTransform.ScaleX = 2;

  ScaleTransform.ScaleY = 2;

图2 移动后缩放

  这个时候,用户打算放大图片当中的另外一个区域,再放大一倍(即放大到原图的3倍),下图里是蓝点,假设坐标是(50, 50),因为无论图片缩放与否,用户只会以他在实际图片看到的内容来判断新的缩放焦点:

图3 再次放大

  如果我们直接盲目地将ScaleTransform的各个属性设置为类似下面的值的话:

  ScaleTransform.ScaleX = 3;

  ScaleTransform.ScaleY = 3;

  ScaleTransform.CenterX = 50;

  ScaleTransform.CenterY = 50;

  就发生问题了, 因为ScaleX = 3表示新图是原图的3倍,然而我们的原点却是在2倍图片上设置的—原图的大小只有40 x 40。解决方案当然是将蓝点的位置转换回在原始图片的位置,注意原始图片应该是下图右下方的图片,而不是左边的—用户最初已经移动了图片。

图4 注意原始图片位置

  看起来转换起来有点麻烦,不过WPF提供了一个 函数TransformGroup.Inverse,可以把转换后图片上的坐标转换会在原始图片的坐标。当然啦,如果你熟悉图形学和线性代数的话,实际上,图片的缩放和移动就是将原始图片乘上一个矩阵,而TransformGroup.Inverse函数就是执行矩阵求逆操作。

  下面就是关键代码:

  XAML代码:

<Grid.Resources>

    
<TransformGroup x:Key="ImageCompareResources">

        
<ScaleTransform />

        
<TranslateTransform/>

    
</TransformGroup>

</Grid.Resources>                



<ScrollViewer HorizontalScrollBarVisibility="Disabled"

              VerticalScrollBarVisibility
="Disabled" Grid.Row="0" Grid.Column="0" x:Name="MasterScrollViewer" Margin="5" Background="WhiteSmoke">

    
<ContentControl x:Name="TestContentControl1"

       MouseLeftButtonDown
="MasterImage_MouseLeftButtonDown"

       MouseLeftButtonUp
="MasterImage_MouseLeftButtonUp"

       MouseMove
="MasterImage_MouseMove"

       MouseWheel
="MasterImage_MouseWheel">

        
<Image RenderOptions.BitmapScalingMode="NearestNeighbor"                                          

               x:Name
="MasterImage" Source="{Binding Path=MasterImagePath}" Stretch="Uniform"

               RenderTransform
="{StaticResource ImageCompareResources}"/>

    
</ContentControl>

</ScrollViewer>

 

  C#代码:

        private void MasterImage_MouseWheel(object sender, MouseWheelEventArgs e)

        {

            ContentControl image
= sender as ContentControl;

            
if (image == null)

            {

                
return;

            }

            

            TransformGroup group
= ImageComparePanel.FindResource("ImageCompareResources") as TransformGroup;

            Debug.Assert(group
!= null, "Can't find transform group from image compare panel resource");

            Point point
= e.GetPosition(image);

            
double scale = e.Delta * 0.001;

            ZoomImage(group, point, scale);

        }

        
private static void ZoomImage(TransformGroup group, Point point, double scale)

        {

            Debug.Assert(group
!= null, "Oops, ImageCompareResources is removed from current control's resouce");



            Point pointToContent
= group.Inverse.Transform(point);

            ScaleTransform transform
= group.Children[0] as ScaleTransform;

            
if (transform.ScaleX + scale < 1)

            {

                
return;

            }



            transform.ScaleX
+= scale;

            transform.ScaleY
+= scale;

            TranslateTransform transform1
= group.Children[1] as TranslateTransform;

            transform1.X
= -1 * ((pointToContent.X * transform.ScaleX) - point.X);

            transform1.Y
= -1 * ((pointToContent.Y * transform.ScaleY) - point.Y);

        }                  

        
private void MasterImage_MouseMove(object sender, MouseEventArgs e)

        {

            ContentControl image
= sender as ContentControl;

            
if (image == null)

            {

                
return;

            }



            
if (this.isMouseLeftButtonDown && e.LeftButton == MouseButtonState.Pressed)

            {

                
this.DoImageMove(image, e.GetPosition(image));

            }

        }



        
private void DoImageMove(ContentControl image, Point position)

        {

            TransformGroup group
= ImageComparePanel.FindResource("ImageCompareResources") as TransformGroup;

            Debug.Assert(group
!= null, "Can't find transform group from image compare panel resource");

            TranslateTransform transform
= group.Children[1] as TranslateTransform;

            transform.X
+= position.X - this.previousMousePoint.X;

            transform.Y
+= position.Y - this.previousMousePoint.Y;

            
this.previousMousePoint = position;

        }
0
相关文章