Pan & Zoom 图像

143
我希望在WPF中创建一个简单的图片浏览器,使用户可以:
  • 通过鼠标拖动图像来平移。
  • 使用滑块进行缩放。
  • 显示覆盖层(例如矩形选择)。
  • 显示原始图像(如果需要,带有滚动条)。
你能解释一下如何实现吗?
我在网上没有找到好的示例。 应该使用ViewBox吗?还是ImageBrush? 我需要ScrollViewer吗?

要获得WPF的专业Zoom Control,请查看ZoomPanel。它不是免费的,但非常易于使用,并具有许多功能-动画缩放和平移,支持ScrollViewer、鼠标滚轮支持,包括ZoomController(带有移动、缩放、矩形缩放、重置按钮)。它还附带了许多代码示例。 - Andrej Benedik
我在codeproject.com上写了一篇关于WPF缩放和平移控件实现的文章。http://www.codeproject.com/KB/WPF/zoomandpancontrol.aspx - Ashley Davis
1
不错的发现。可以免费试用,如果您打算使用它构建软件,则需要为每台计算机支付69美元的许可证费用。它是一个DLL库,所以他们无法阻止您使用它,但是如果您为客户商业构建软件,特别是要求声明和单独授权任何第三方工具的客户,您将不得不支付开发费用。在最终用户许可协议中,它没有说明是“按应用程序”计费的,因此,一旦您注册购买,它就会对您创建的所有应用程序“免费”,并且可以将已付费的许可文件复制到其中以代表购买。 - vapcguy
12个回答

232

使用这个问题的样本后,我制作了完整版本的缩放应用程序,并且相对于鼠标指针进行正确的缩放。所有平移和缩放代码已经移动到名为ZoomBorder的单独类中。

ZoomBorder.cs

using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace PanAndZoom
{
  public class ZoomBorder : Border
  {
    private UIElement child = null;
    private Point origin;
    private Point start;

    private TranslateTransform GetTranslateTransform(UIElement element)
    {
      return (TranslateTransform)((TransformGroup)element.RenderTransform)
        .Children.First(tr => tr is TranslateTransform);
    }

    private ScaleTransform GetScaleTransform(UIElement element)
    {
      return (ScaleTransform)((TransformGroup)element.RenderTransform)
        .Children.First(tr => tr is ScaleTransform);
    }

    public override UIElement Child
    {
      get { return base.Child; }
      set
      {
        if (value != null && value != this.Child)
          this.Initialize(value);
        base.Child = value;
      }
    }

    public void Initialize(UIElement element)
    {
      this.child = element;
      if (child != null)
      {
        TransformGroup group = new TransformGroup();
        ScaleTransform st = new ScaleTransform();
        group.Children.Add(st);
        TranslateTransform tt = new TranslateTransform();
        group.Children.Add(tt);
        child.RenderTransform = group;
        child.RenderTransformOrigin = new Point(0.0, 0.0);
        this.MouseWheel += child_MouseWheel;
        this.MouseLeftButtonDown += child_MouseLeftButtonDown;
        this.MouseLeftButtonUp += child_MouseLeftButtonUp;
        this.MouseMove += child_MouseMove;
        this.PreviewMouseRightButtonDown += new MouseButtonEventHandler(
          child_PreviewMouseRightButtonDown);
      }
    }

    public void Reset()
    {
      if (child != null)
      {
        // reset zoom
        var st = GetScaleTransform(child);
        st.ScaleX = 1.0;
        st.ScaleY = 1.0;

        // reset pan
        var tt = GetTranslateTransform(child);
        tt.X = 0.0;
        tt.Y = 0.0;
      }
    }

    #region Child Events

        private void child_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (child != null)
            {
                var st = GetScaleTransform(child);
                var tt = GetTranslateTransform(child);

                double zoom = e.Delta > 0 ? .2 : -.2;
                if (!(e.Delta > 0) && (st.ScaleX < .4 || st.ScaleY < .4))
                    return;

                Point relative = e.GetPosition(child);
                double absoluteX;
                double absoluteY;

                absoluteX = relative.X * st.ScaleX + tt.X;
                absoluteY = relative.Y * st.ScaleY + tt.Y;

                st.ScaleX += zoom;
                st.ScaleY += zoom;

                tt.X = absoluteX - relative.X * st.ScaleX;
                tt.Y = absoluteY - relative.Y * st.ScaleY;
            }
        }

        private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (child != null)
            {
                var tt = GetTranslateTransform(child);
                start = e.GetPosition(this);
                origin = new Point(tt.X, tt.Y);
                this.Cursor = Cursors.Hand;
                child.CaptureMouse();
            }
        }

        private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (child != null)
            {
                child.ReleaseMouseCapture();
                this.Cursor = Cursors.Arrow;
            }
        }

        void child_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.Reset();
        }

        private void child_MouseMove(object sender, MouseEventArgs e)
        {
            if (child != null)
            {
                if (child.IsMouseCaptured)
                {
                    var tt = GetTranslateTransform(child);
                    Vector v = start - e.GetPosition(this);
                    tt.X = origin.X - v.X;
                    tt.Y = origin.Y - v.Y;
                }
            }
        }

        #endregion
    }
}

MainWindow.xaml

<Window x:Class="PanAndZoom.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:PanAndZoom"
        Title="PanAndZoom" Height="600" Width="900" WindowStartupLocation="CenterScreen">
    <Grid>
        <local:ZoomBorder x:Name="border" ClipToBounds="True" Background="Gray">
            <Image Source="image.jpg"/>
        </local:ZoomBorder>
    </Grid>
</Window>

MainWindow.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;

namespace PanAndZoom
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

16
很遗憾,我无法给你更多的分数。这真的很棒。 - Tobiel
10
在评论被封锁之前,有人说“干得好!”或“做得棒!”。我只想说,“干得好!”和“做得棒!”这是一颗WPF宝石。它比WPF ext缩放框更加优秀。 - Jesse Seger
8
在这里,"OUT standing"的意思是“杰出的”或“非常好的”。句子的后半部分表达了希望今晚能回家的可能性。最后的“+1000”可能是表示对此事的积极态度或乐观预期。 - Bruce Pierson
6
非常好的答案! 我对缩放因子进行了微小的修正,这样它就不会缩放得“更慢”了。 double zoomCorrected = zoom*st.ScaleX; st.ScaleX += zoomCorrected; st.ScaleY += zoomCorrected; - DELUXEnized
2
@Skaranjit 没有头绪,Reset() 在提供的示例代码中是有效的。你是否从 MainWindow 调用了 border.Reset()?你可以在这里尝试演示:https://github.com/wieslawsoltes/PanAndZoomDemo - Wiesław Šoltés
显示剩余17条评论

120
我解决这个问题的方法是将图像放在一个边框中,该边框的ClipToBounds属性设置为True。图像的RenderTransformOrigin也设置为0.5,0.5,以便图像从图像中心开始缩放。 RenderTransform还设置为包含ScaleTransform和TranslateTransform的TransformGroup。
然后,我处理了图像上的MouseWheel事件来实现缩放。
private void image_MouseWheel(object sender, MouseWheelEventArgs e)
{
    var st = (ScaleTransform)image.RenderTransform;
    double zoom = e.Delta > 0 ? .2 : -.2;
    st.ScaleX += zoom;
    st.ScaleY += zoom;
}

为了处理平移,我首先处理了图像上的MouseLeftButtonDown事件,以捕获鼠标并记录其位置,我还存储了TranslateTransform的当前值,这是更新以实现平移的内容。

Point start;
Point origin;
private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    image.CaptureMouse();
    var tt = (TranslateTransform)((TransformGroup)image.RenderTransform)
        .Children.First(tr => tr is TranslateTransform);
    start = e.GetPosition(border);
    origin = new Point(tt.X, tt.Y);
}

然后我处理了MouseMove事件来更新TranslateTransform。

private void image_MouseMove(object sender, MouseEventArgs e)
{
    if (image.IsMouseCaptured)
    {
        var tt = (TranslateTransform)((TransformGroup)image.RenderTransform)
            .Children.First(tr => tr is TranslateTransform);
        Vector v = start - e.GetPosition(border);
        tt.X = origin.X - v.X;
        tt.Y = origin.Y - v.Y;
    }
}

最后别忘了释放鼠标捕获。

private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    image.ReleaseMouseCapture();
}

关于调整大小的选择手柄,可以使用装饰器来完成,详细信息请参见此文章


10
然而需要注意的是,在image_MouseLeftButtonDown中调用CaptureMouse会导致调用image_MouseMove时origin变量尚未初始化 - 在上面的代码中,由于纯粹的巧合,它将为零,但如果origin不是(0,0),则图像将出现短暂跳动。因此,我认为最好在image_MouseLeftButtonDown的末尾调用image.CaptureMouse()来解决这个问题。 - Andrei Pana
2
两件事。 1)image_MouseWheel 存在一个错误,你必须以类似获取 TranslateTransform 的方式获取 ScaleTransform。也就是将其强制转换为 TransformGroup,然后选择并转换适当的 Child 。 2)如果你的移动不流畅,请记住你不能使用 image 获取鼠标位置(因为它是动态的),你必须使用一些静态的东西。在这个例子中,使用了一个边框。 - Dave

49

上面已经发布了答案,但不完整。这是完整版本:

XAML

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MapTest.Window1"
x:Name="Window"
Title="Window1"
Width="1950" Height="1546" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Controls="clr-namespace:WPFExtensions.Controls;assembly=WPFExtensions" mc:Ignorable="d" Background="#FF000000">

<Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition Height="52.92"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Border Grid.Row="1" Name="border">
        <Image Name="image" Source="map3-2.png" Opacity="1" RenderTransformOrigin="0.5,0.5"  />
    </Border>

</Grid>

后台代码

using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace MapTest
{
    public partial class Window1 : Window
    {
        private Point origin;
        private Point start;

        public Window1()
        {
            InitializeComponent();

            TransformGroup group = new TransformGroup();

            ScaleTransform xform = new ScaleTransform();
            group.Children.Add(xform);

            TranslateTransform tt = new TranslateTransform();
            group.Children.Add(tt);

            image.RenderTransform = group;

            image.MouseWheel += image_MouseWheel;
            image.MouseLeftButtonDown += image_MouseLeftButtonDown;
            image.MouseLeftButtonUp += image_MouseLeftButtonUp;
            image.MouseMove += image_MouseMove;
        }

        private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            image.ReleaseMouseCapture();
        }

        private void image_MouseMove(object sender, MouseEventArgs e)
        {
            if (!image.IsMouseCaptured) return;

            var tt = (TranslateTransform) ((TransformGroup) image.RenderTransform).Children.First(tr => tr is TranslateTransform);
            Vector v = start - e.GetPosition(border);
            tt.X = origin.X - v.X;
            tt.Y = origin.Y - v.Y;
        }

        private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            image.CaptureMouse();
            var tt = (TranslateTransform) ((TransformGroup) image.RenderTransform).Children.First(tr => tr is TranslateTransform);
            start = e.GetPosition(border);
            origin = new Point(tt.X, tt.Y);
        }

        private void image_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            TransformGroup transformGroup = (TransformGroup) image.RenderTransform;
            ScaleTransform transform = (ScaleTransform) transformGroup.Children[0];

            double zoom = e.Delta > 0 ? .2 : -.2;
            transform.ScaleX += zoom;
            transform.ScaleY += zoom;
        }
    }
}

我有一些演示此功能的源代码,显示在Jot the sticky note app中。


1
有没有关于如何在Silverlight 3中使用它的建议?我在向量和从另一个点减去一个点方面遇到了问题...谢谢。 - Number8
4
一个小缺点是,图像会随着边框的增加而增大,而不是在边框内部增大。 - itsho
你们能否建议一些如何在Windows 8 Metro风格应用程序中实现相同功能的方法?我正在使用C#、XAML在Windows 8上工作。 - raj
1
在 image_MouseWheel 中,您可以测试 transform.ScaleX 和 ScaleY 的值,如果这些值 + 缩放 > 您的限制,请不要应用 += 缩放行。 - Kelly
请更正您的链接到“Jot the sticky note app”。 - NoWar
显示剩余3条评论

11
  • Pan: 将图像放置在 Canvas 中。实现鼠标按下、抬起和移动事件以移动 Canvas.Top 和 Canvas.Left 属性。当按下鼠标时,将一个isDraggingFlag标记为true,当抬起时,将其设置为false。在移动时,检查标志是否已设置,如果是,则在画布内的图像上偏移Canvas.Top和Canvas.Left属性。
  • Zoom: 将滑块绑定到 Canvas 的缩放变换。
  • Show overlays: 在包含图像的画布上方添加额外的没有背景的画布。
  • Show original image: 在 ViewBox 内使用图像控件。

9

请尝试使用这个缩放控件:http://wpfextensions.codeplex.com

这个控件的使用非常简单,只需要引用wpfextensions程序集即可:

<wpfext:ZoomControl>
    <Image Source="..."/>
</wpfext:ZoomControl>

目前不支持滚动条。(在接下来的版本中将会支持,预计一到两周内发布)。


是的,很享受。不过,其余的库都相当琐碎。 - EightyOne Unite
似乎没有直接支持“显示覆盖层(例如矩形选择)”的功能,但对于缩放/平移行为来说,它是一个很棒的控件。 - jsirr13

4

@Anothen 和 @Number8 - Vector 类在 Silverlight 中不可用,因此为了使其工作,我们只需要记录上一次调用 MouseMove 事件时看到的最后一个位置,并比较这两个点找到差异;然后调整转换。

XAML:

    <Border Name="viewboxBackground" Background="Black">
            <Viewbox Name="viewboxMain">
                <!--contents go here-->
            </Viewbox>
    </Border>  

代码后台:

    public Point _mouseClickPos;
    public bool bMoving;


    public MainPage()
    {
        InitializeComponent();
        viewboxMain.RenderTransform = new CompositeTransform();
    }

    void MouseMoveHandler(object sender, MouseEventArgs e)
    {

        if (bMoving)
        {
            //get current transform
            CompositeTransform transform = viewboxMain.RenderTransform as CompositeTransform;

            Point currentPos = e.GetPosition(viewboxBackground);
            transform.TranslateX += (currentPos.X - _mouseClickPos.X) ;
            transform.TranslateY += (currentPos.Y - _mouseClickPos.Y) ;

            viewboxMain.RenderTransform = transform;

            _mouseClickPos = currentPos;
        }            
    }

    void MouseClickHandler(object sender, MouseButtonEventArgs e)
    {
        _mouseClickPos = e.GetPosition(viewboxBackground);
        bMoving = true;
    }

    void MouseReleaseHandler(object sender, MouseButtonEventArgs e)
    {
        bMoving = false;
    }

请注意,您不需要TransformGroup或集合来实现平移和缩放;相反,CompositeTransform将以更少的麻烦完成任务。
我相当确定这在资源使用方面非常低效,但至少它能工作 :)

3

我也尝试了这个答案,但对结果并不完全满意。我继续搜索,最终在2021年找到了一个Nuget包,帮助我实现了想要的效果。我想与Stack Overflow的前任开发人员分享。

我使用了这个Nuget包Gu.WPF.Geometry,通过这个Github存储库发现。所有开发的功劳应归给Johan Larsson,该包的所有者。

我如何使用它?我想在缩放框下方将命令作为按钮显示,就像在MachineLayoutControl.xaml中所示。

<UserControl
   x:Class="MyLib.MachineLayoutControl"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:csmachinelayoutdrawlib="clr-namespace:CSMachineLayoutDrawLib"
   xmlns:effects="http://gu.se/Geometry">
   <UserControl.Resources>
       <ResourceDictionary Source="Resources/ResourceDictionaries/AllResourceDictionariesCombined.xaml" />
   </UserControl.Resources>

   <Grid Margin="0">
       <Grid.RowDefinitions>
           <RowDefinition Height="*" />
           <RowDefinition Height="Auto" />
       </Grid.RowDefinitions>

       <Border
           Grid.Row="0"
           Margin="0,0"
           Padding="0"
           BorderThickness="1"
           Style="{StaticResource Border_Head}"
           Visibility="Visible">
           <effects:Zoombox
               x:Name="ImageBox"
               IsManipulationEnabled="True"
               MaxZoom="10"
               MinZoom="0.1"
               Visibility="{Binding Zoombox_Visibility}">
               <ContentControl Content="{Binding Viewing_Canvas}" />
           </effects:Zoombox>
       </Border>
           <StackPanel
               Grid.Column="1"
               Margin="10"
               HorizontalAlignment="Right"
               Orientation="Horizontal">
               <Button
                   Command="effects:ZoomCommands.Increase"
                   CommandParameter="2.0"
                   CommandTarget="{Binding ElementName=ImageBox}"
                   Content="Zoom In"
                   Style="{StaticResource StyleForResizeButtons}" />

               <Button
                   Command="effects:ZoomCommands.Decrease"
                   CommandParameter="2.0"
                   CommandTarget="{Binding ElementName=ImageBox}"
                   Content="Zoom Out"
                   Style="{StaticResource StyleForResizeButtons}" />

               <Button
                   Command="effects:ZoomCommands.Uniform"
                   CommandTarget="{Binding ElementName=ImageBox}"
                   Content="See Full Machine"
                   Style="{StaticResource StyleForResizeButtons}" />

               <Button
                   Command="effects:ZoomCommands.UniformToFill"
                   CommandTarget="{Binding ElementName=ImageBox}"
                   Content="Zoom To Machine Width"
                   Style="{StaticResource StyleForResizeButtons}" />
   
           </StackPanel>

</Grid>
</UserControl>


在底层的Viewmodel中,我有以下相关代码:
public Visibility Zoombox_Visibility { get => movZoombox_Visibility; set { movZoombox_Visibility = value; OnPropertyChanged(nameof(Zoombox_Visibility)); } }
public Canvas Viewing_Canvas { get => mdvViewing_Canvas; private set => mdvViewing_Canvas = value; }

此外,我希望在加载时立即执行填充命令,这是我在 `MachineLayoutControl.xaml.cs` 的代码中实现的。您可以看到,只有在命令执行时才将 `Zoombox` 设置为可见状态,以避免用户控件加载时出现 "闪烁"。
    public partial class MachineLayoutControl : UserControl
    {
        #region Constructors

        public MachineLayoutControl()
        {
            InitializeComponent();
            Loaded += MyWindow_Loaded;
        }

        #endregion Constructors

        #region EventHandlers

        private void MyWindow_Loaded(object sender, RoutedEventArgs e)
        {
            Application.Current.Dispatcher.BeginInvoke(
               DispatcherPriority.ApplicationIdle,
               new Action(() =>
               {
                   ZoomCommands.Uniform.Execute(null, ImageBox);
                   ((MachineLayoutControlViewModel)DataContext).Zoombox_Visibility = Visibility.Visible;
               }));
        }

        #endregion EventHandlers
    }

Result


3
为了相对于鼠标位置进行缩放,您只需要做以下操作:
var position = e.GetPosition(image1);
image1.RenderTransformOrigin = new Point(position.X / image1.ActualWidth, position.Y / image1.ActualHeight);

我正在使用PictureBox,RenderTransformOrigin已经不存在了。 - user1853517
@Switch RenderTransformOrigin 是用于 WPF 控件的。 - Xam

2

@ Merk

您可以使用以下代码来替代lambda表达式:

//var tt = (TranslateTransform)((TransformGroup)image.RenderTransform).Children.First(tr => tr is TranslateTransform);
        TranslateTransform tt = null;
        TransformGroup transformGroup = (TransformGroup)grid.RenderTransform;
        for (int i = 0; i < transformGroup.Children.Count; i++)
        {
            if (transformGroup.Children[i] is TranslateTransform)
                tt = (TranslateTransform)transformGroup.Children[i];
        }

这段代码可以直接在 .Net Framework 3.0 或 2.0 中使用。

希望对您有所帮助 :-)


2

这是同种类型控件的另一版本,与其他控件具有相似的功能,但增加了以下功能:

  1. 支持触摸操作(拖动/缩放)
  2. 可删除该图像(通常,Image控件会锁定磁盘上的图像,因此您无法删除它)。
  3. 内部边框子元素,使平移后的图像不会重叠在边框上。如果存在带有圆角矩形的边框,请查找ClippedBorder类。

使用方法很简单:

<Controls:ImageViewControl ImagePath="{Binding ...}" />

代码如下:

public class ImageViewControl : Border
{
    private Point origin;
    private Point start;
    private Image image;

    public ImageViewControl()
    {
        ClipToBounds = true;
        Loaded += OnLoaded;
    }

    #region ImagePath

    /// <summary>
    ///     ImagePath Dependency Property
    /// </summary>
    public static readonly DependencyProperty ImagePathProperty = DependencyProperty.Register("ImagePath", typeof (string), typeof (ImageViewControl), new FrameworkPropertyMetadata(string.Empty, OnImagePathChanged));

    /// <summary>
    ///     Gets or sets the ImagePath property. This dependency property 
    ///     indicates the path to the image file.
    /// </summary>
    public string ImagePath
    {
        get { return (string) GetValue(ImagePathProperty); }
        set { SetValue(ImagePathProperty, value); }
    }

    /// <summary>
    ///     Handles changes to the ImagePath property.
    /// </summary>
    private static void OnImagePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (ImageViewControl) d;
        var oldImagePath = (string) e.OldValue;
        var newImagePath = target.ImagePath;
        target.ReloadImage(newImagePath);
        target.OnImagePathChanged(oldImagePath, newImagePath);
    }

    /// <summary>
    ///     Provides derived classes an opportunity to handle changes to the ImagePath property.
    /// </summary>
    protected virtual void OnImagePathChanged(string oldImagePath, string newImagePath)
    {
    }

    #endregion

    private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
        image = new Image {
                              //IsManipulationEnabled = true,
                              RenderTransformOrigin = new Point(0.5, 0.5),
                              RenderTransform = new TransformGroup {
                                                                       Children = new TransformCollection {
                                                                                                              new ScaleTransform(),
                                                                                                              new TranslateTransform()
                                                                                                          }
                                                                   }
                          };
        // NOTE I use a border as the first child, to which I add the image. I do this so the panned image doesn't partly obscure the control's border.
        // In case you are going to use rounder corner's on this control, you may to update your clipping, as in this example:
        // http://wpfspark.wordpress.com/2011/06/08/clipborder-a-wpf-border-that-clips/
        var border = new Border {
                                    IsManipulationEnabled = true,
                                    ClipToBounds = true,
                                    Child = image
                                };
        Child = border;

        image.MouseWheel += (s, e) =>
                                {
                                    var zoom = e.Delta > 0
                                                   ? .2
                                                   : -.2;
                                    var position = e.GetPosition(image);
                                    image.RenderTransformOrigin = new Point(position.X / image.ActualWidth, position.Y / image.ActualHeight);
                                    var st = (ScaleTransform)((TransformGroup)image.RenderTransform).Children.First(tr => tr is ScaleTransform);
                                    st.ScaleX += zoom;
                                    st.ScaleY += zoom;
                                    e.Handled = true;
                                };

        image.MouseLeftButtonDown += (s, e) =>
                                         {
                                             if (e.ClickCount == 2)
                                                 ResetPanZoom();
                                             else
                                             {
                                                 image.CaptureMouse();
                                                 var tt = (TranslateTransform) ((TransformGroup) image.RenderTransform).Children.First(tr => tr is TranslateTransform);
                                                 start = e.GetPosition(this);
                                                 origin = new Point(tt.X, tt.Y);
                                             }
                                             e.Handled = true;
                                         };

        image.MouseMove += (s, e) =>
                               {
                                   if (!image.IsMouseCaptured) return;
                                   var tt = (TranslateTransform) ((TransformGroup) image.RenderTransform).Children.First(tr => tr is TranslateTransform);
                                   var v = start - e.GetPosition(this);
                                   tt.X = origin.X - v.X;
                                   tt.Y = origin.Y - v.Y;
                                   e.Handled = true;
                               };

        image.MouseLeftButtonUp += (s, e) => image.ReleaseMouseCapture();

        //NOTE I apply the manipulation to the border, and not to the image itself (which caused stability issues when translating)!
        border.ManipulationDelta += (o, e) =>
                                       {
                                           var st = (ScaleTransform)((TransformGroup)image.RenderTransform).Children.First(tr => tr is ScaleTransform);
                                           var tt = (TranslateTransform)((TransformGroup)image.RenderTransform).Children.First(tr => tr is TranslateTransform);

                                           st.ScaleX *= e.DeltaManipulation.Scale.X;
                                           st.ScaleY *= e.DeltaManipulation.Scale.X;
                                           tt.X += e.DeltaManipulation.Translation.X;
                                           tt.Y += e.DeltaManipulation.Translation.Y;

                                           e.Handled = true;
                                       };
    }

    private void ResetPanZoom()
    {
        var st = (ScaleTransform)((TransformGroup)image.RenderTransform).Children.First(tr => tr is ScaleTransform);
        var tt = (TranslateTransform)((TransformGroup)image.RenderTransform).Children.First(tr => tr is TranslateTransform);
        st.ScaleX = st.ScaleY = 1;
        tt.X = tt.Y = 0;
        image.RenderTransformOrigin = new Point(0.5, 0.5);
    }

    /// <summary>
    /// Load the image (and do not keep a hold on it, so we can delete the image without problems)
    /// </summary>
    /// <see cref="http://blogs.vertigo.com/personal/ralph/Blog/Lists/Posts/Post.aspx?ID=18"/>
    /// <param name="path"></param>
    private void ReloadImage(string path)
    {
        try
        {
            ResetPanZoom();
            // load the image, specify CacheOption so the file is not locked
            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.UriSource = new Uri(path, UriKind.RelativeOrAbsolute);
            bitmapImage.EndInit();
            image.Source = bitmapImage;
        }
        catch (SystemException e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

1
我发现的唯一问题是,如果在XAML中指定了图像路径,则尝试在构造图像对象之前(即在调用OnLoaded方法之前)呈现它。为了解决这个问题,我把“image = new Image ...”代码从onLoaded方法移到了构造函数中。谢谢。 - Mitch
另一个问题是图像可以缩小到我们无法进行任何操作和查看。我添加了一些限制:在图像.MouseWheel中添加if (image.ActualWidth*(st.ScaleX + zoom) < 200 || image.ActualHeight*(st.ScaleY + zoom) < 200) //不要缩小得太小。 return; - huoxudong125

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接