潘&放大图片

时间:2009-04-12 15:48:15

标签: c# wpf xaml zoom pan

我想在WPF中创建一个简单的图像查看器,使用户能够:

  • 平移(通过鼠标拖动图像)。
  • 缩放(使用滑块)。
  • 显示叠加层(例如矩形选区)。
  • 显示原始图片(如果需要,可使用滚动条)。

你能解释一下怎么做吗?

我在网上找不到好的样本。 我应该使用ViewBox吗?还是ImageBrush? 我需要ScrollViewer吗?

谢谢!

14 个答案:

答案 0 :(得分:140)

在使用这个问题的样本后,我已经制作完整版的pan&相对于鼠标指针适当缩放缩放应用程序。所有pan&缩放代码已移至名为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 abosuluteX;
                double abosuluteY;

                abosuluteX = relative.X * st.ScaleX + tt.X;
                abosuluteY = relative.Y * st.ScaleY + tt.Y;

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

                tt.X = abosuluteX - relative.X * st.ScaleX;
                tt.Y = abosuluteY - 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();
        }
    }
}

答案 1 :(得分:104)

我解决这个问题的方法是将图像置于Border中,并将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();
}

至于调整大小的选择句柄可以使用装饰器完成,请查看this article以获取更多信息。

答案 2 :(得分:43)

答案在上面发布但未完成。这是完成的版本:

<强> 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;
        }
    }
}

我在我的网站上使用此代码的完整wpf项目示例:Jot the sticky note app

答案 3 :(得分:10)

尝试此缩放控件:http://wpfextensions.codeplex.com

控件的用法非常简单,参考wpfextensions程序集比:

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

目前不支持滚动条。 (它将在下一个版本中提供,将在一到两周内发布。)

答案 4 :(得分:9)

  • Pan:将图像放入Canvas中。实现Mouse Up,Down和Move事件以移动Canvas.Top,Canvas.Left属性。向下时,将isDraggingFlag标记为true,向上时将标志设置为false。在移动时,检查是否设置了标志,如果是,则在画布中的图像上偏移Canvas.Top和Canvas.Left属性。
  • 缩放:将滑块绑定到“画布的缩放变换”
  • 显示叠加层:在包含图像的画布上添加没有背景的其他画布。
  • 显示原始图像:ViewBox内的图像控件

答案 5 :(得分:4)

答案 6 :(得分:3)

我也试过 this answer 但对结果并不完全满意。我一直在谷歌搜索,终于找到了一个帮助我管理我想要的结果的 Nuget Package,anno 2021。我想与 Stack Overflow 的前开发人员分享。

我使用了通过 this Nuget Package 找到的 this Github Repository Gu.WPF.Geometry。开发的所有功劳都应归功于此软件包的所有者 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; }

此外,我希望在加载时立即执行统一填充命令,这是我在代码隐藏中设法做到的事情{{1 }} 。你看我只在执行命令时将 Zoombox 设置为可见,以避免在加载用户控件时“闪烁”。

MachineLayoutControl.xaml.cs

Result

答案 7 :(得分:2)

要相对于鼠标位置进行缩放,您只需要:

var position = e.GetPosition(image1);
image1.RenderTransformOrigin = new Point(position.X / image1.ActualWidth, position.Y / image1.ActualHeight);

答案 8 :(得分:2)

@ Mark

对于您的解决方案而不是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 Frame work 3.0或2.0

希望它可以帮助你: - )

答案 9 :(得分:2)

同一种控制的另一个版本。它具有与其他功能类似的功能,但它增加了:

  1. 触摸支持(拖动/捏合)
  2. 可以删除图像(通常,Image控件会将图像锁定在磁盘上,因此您无法删除它。)
  3. 内边框子项,因此平移图像不会与边框重叠。如果是带有圆角矩形的边框,请查找ClippedBorder类。
  4. 用法很简单:

    <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);
            }
        }
    }
    

答案 10 :(得分:1)

我的London Underground示例执行此操作,但使用的是地图而不是静态图像。

答案 11 :(得分:1)

要获得专业的WPF缩放控制,请查看ZoomPanel

它不是免费的,但是非常易于使用并且具有许多功能 - 动画缩放和平移,支持ScrollViewer,鼠标滚轮支持,包括ZoomController(带移动,放大,缩小,矩形缩放,重置按钮) 。它还附带了许多代码示例。

答案 12 :(得分:1)

这将放大和缩小以及平移,但将图像保持在容器的边界内。写为控件,直接将样式添加到App.xaml或通过Themes/Viewport.xaml添加。

为了便于阅读,我还在gistgithub

上传了此内容

我还在nuget

打包了这个
PM > Install-Package Han.Wpf.ViewportControl

./ Controls / Viewport.cs:

public class Viewport : ContentControl
{
    private bool _capture;
    private FrameworkElement _content;
    private Matrix _matrix;
    private Point _origin;

    public static readonly DependencyProperty MaxZoomProperty =
        DependencyProperty.Register(
            nameof(MaxZoom),
            typeof(double),
            typeof(Viewport),
            new PropertyMetadata(0d));

    public static readonly DependencyProperty MinZoomProperty =
        DependencyProperty.Register(
            nameof(MinZoom),
            typeof(double),
            typeof(Viewport),
            new PropertyMetadata(0d));

    public static readonly DependencyProperty ZoomSpeedProperty =
        DependencyProperty.Register(
            nameof(ZoomSpeed),
            typeof(float),
            typeof(Viewport),
            new PropertyMetadata(0f));

    public static readonly DependencyProperty ZoomXProperty =
        DependencyProperty.Register(
            nameof(ZoomX),
            typeof(double),
            typeof(Viewport),
            new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public static readonly DependencyProperty ZoomYProperty =
        DependencyProperty.Register(
            nameof(ZoomY),
            typeof(double),
            typeof(Viewport),
            new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public static readonly DependencyProperty OffsetXProperty =
        DependencyProperty.Register(
            nameof(OffsetX),
            typeof(double),
            typeof(Viewport),
            new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public static readonly DependencyProperty OffsetYProperty =
        DependencyProperty.Register(
            nameof(OffsetY),
            typeof(double),
            typeof(Viewport),
            new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public static readonly DependencyProperty BoundsProperty =
        DependencyProperty.Register(
            nameof(Bounds),
            typeof(Rect),
            typeof(Viewport),
            new FrameworkPropertyMetadata(default(Rect), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public Rect Bounds
    {
        get => (Rect) GetValue(BoundsProperty);
        set => SetValue(BoundsProperty, value);
    }

    public double MaxZoom
    {
        get => (double) GetValue(MaxZoomProperty);
        set => SetValue(MaxZoomProperty, value);
    }

    public double MinZoom
    {
        get => (double) GetValue(MinZoomProperty);
        set => SetValue(MinZoomProperty, value);
    }

    public double OffsetX
    {
        get => (double) GetValue(OffsetXProperty);
        set => SetValue(OffsetXProperty, value);
    }

    public double OffsetY
    {
        get => (double) GetValue(OffsetYProperty);
        set => SetValue(OffsetYProperty, value);
    }

    public float ZoomSpeed
    {
        get => (float) GetValue(ZoomSpeedProperty);
        set => SetValue(ZoomSpeedProperty, value);
    }

    public double ZoomX
    {
        get => (double) GetValue(ZoomXProperty);
        set => SetValue(ZoomXProperty, value);
    }

    public double ZoomY
    {
        get => (double) GetValue(ZoomYProperty);
        set => SetValue(ZoomYProperty, value);
    }

    public Viewport()
    {
        DefaultStyleKey = typeof(Viewport);

        Loaded += OnLoaded;
        Unloaded += OnUnloaded;
    }

    private void Arrange(Size desired, Size render)
    {
        _matrix = Matrix.Identity;

        var zx = desired.Width / render.Width;
        var zy = desired.Height / render.Height;
        var cx = render.Width < desired.Width ? render.Width / 2.0 : 0.0;
        var cy = render.Height < desired.Height ? render.Height / 2.0 : 0.0;

        var zoom = Math.Min(zx, zy);

        if (render.Width > desired.Width &&
            render.Height > desired.Height)
        {
            cx = (desired.Width - (render.Width * zoom)) / 2.0;
            cy = (desired.Height - (render.Height * zoom)) / 2.0;

            _matrix = new Matrix(zoom, 0d, 0d, zoom, cx, cy);
        }
        else
        {
            _matrix.ScaleAt(zoom, zoom, cx, cy);
        }
    }

    private void Attach(FrameworkElement content)
    {
        content.MouseMove += OnMouseMove;
        content.MouseLeave += OnMouseLeave;
        content.MouseWheel += OnMouseWheel;
        content.MouseLeftButtonDown += OnMouseLeftButtonDown;
        content.MouseLeftButtonUp += OnMouseLeftButtonUp;
        content.SizeChanged += OnSizeChanged;
        content.MouseRightButtonDown += OnMouseRightButtonDown;
    }

    private void ChangeContent(FrameworkElement content)
    {
        if (content != null && !Equals(content, _content))
        {
            if (_content != null)
            {
                Detatch();
            }

            Attach(content);
            _content = content;
        }
    }

    private double Constrain(double value, double min, double max)
    {
        if (min > max)
        {
            min = max;
        }

        if (value <= min)
        {
            return min;
        }

        if (value >= max)
        {
            return max;
        }

        return value;
    }

    private void Constrain()
    {
        var x = Constrain(_matrix.OffsetX, _content.ActualWidth - _content.ActualWidth * _matrix.M11, 0);
        var y = Constrain(_matrix.OffsetY, _content.ActualHeight - _content.ActualHeight * _matrix.M22, 0);

        _matrix = new Matrix(_matrix.M11, 0d, 0d, _matrix.M22, x, y);
    }

    private void Detatch()
    {
        _content.MouseMove -= OnMouseMove;
        _content.MouseLeave -= OnMouseLeave;
        _content.MouseWheel -= OnMouseWheel;
        _content.MouseLeftButtonDown -= OnMouseLeftButtonDown;
        _content.MouseLeftButtonUp -= OnMouseLeftButtonUp;
        _content.SizeChanged -= OnSizeChanged;
        _content.MouseRightButtonDown -= OnMouseRightButtonDown;
    }

    private void Invalidate()
    {
        if (_content != null)
        {
            Constrain();

            _content.RenderTransformOrigin = new Point(0, 0);
            _content.RenderTransform = new MatrixTransform(_matrix);
            _content.InvalidateVisual();

            ZoomX = _matrix.M11;
            ZoomY = _matrix.M22;

            OffsetX = _matrix.OffsetX;
            OffsetY = _matrix.OffsetY;

            var rect = new Rect
            {
                X = OffsetX * -1,
                Y = OffsetY * -1,
                Width = ActualWidth,
                Height = ActualHeight
            };

            Bounds = rect;
        }
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _matrix = Matrix.Identity;
    }

    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);

        if (Content is FrameworkElement element)
        {
            ChangeContent(element);
        }
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        if (Content is FrameworkElement element)
        {
            ChangeContent(element);
        }

        SizeChanged += OnSizeChanged;
        Loaded -= OnLoaded;
    }

    private void OnMouseLeave(object sender, MouseEventArgs e)
    {
        if (_capture)
        {
            Released();
        }
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (IsEnabled && !_capture)
        {
            Pressed(e.GetPosition(this));
        }
    }

    private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (IsEnabled && _capture)
        {
            Released();
        }
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        if (IsEnabled && _capture)
        {
            var position = e.GetPosition(this);

            var point = new Point
            {
                X = position.X - _origin.X,
                Y = position.Y - _origin.Y
            };

            var delta = point;
            _origin = position;

            _matrix.Translate(delta.X, delta.Y);

            Invalidate();
        }
    }

    private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (IsEnabled)
        {
            Reset();
        }
    }

    private void OnMouseWheel(object sender, MouseWheelEventArgs e)
    {
        if (IsEnabled)
        {
            var scale = e.Delta > 0 ? ZoomSpeed : 1 / ZoomSpeed;
            var position = e.GetPosition(_content);

            var x = Constrain(scale, MinZoom / _matrix.M11, MaxZoom / _matrix.M11);
            var y = Constrain(scale, MinZoom / _matrix.M22, MaxZoom / _matrix.M22);

            _matrix.ScaleAtPrepend(x, y, position.X, position.Y);

            ZoomX = _matrix.M11;
            ZoomY = _matrix.M22;

            Invalidate();
        }
    }

    private void OnSizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (_content?.IsMeasureValid ?? false)
        {
            Arrange(_content.DesiredSize, _content.RenderSize);

            Invalidate();
        }
    }

    private void OnUnloaded(object sender, RoutedEventArgs e)
    {
        Detatch();

        SizeChanged -= OnSizeChanged;
        Unloaded -= OnUnloaded;
    }

    private void Pressed(Point position)
    {
        if (IsEnabled)
        {
            _content.Cursor = Cursors.Hand;
            _origin = position;
            _capture = true;
        }
    }

    private void Released()
    {
        if (IsEnabled)
        {
            _content.Cursor = null;
            _capture = false;
        }
    }

    private void Reset()
    {
        _matrix = Matrix.Identity;

        if (_content != null)
        {
            Arrange(_content.DesiredSize, _content.RenderSize);
        }

        Invalidate();
    }
}

./主题/ Viewport.xaml:

<ResourceDictionary ... >

    <Style TargetType="{x:Type controls:Viewport}"
           BasedOn="{StaticResource {x:Type ContentControl}}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:Viewport}">
                    <Border BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Background="{TemplateBinding Background}">
                        <Grid ClipToBounds="True"
                              Width="{TemplateBinding Width}"
                              Height="{TemplateBinding Height}">
                            <Grid x:Name="PART_Container">
                                <ContentPresenter x:Name="PART_Presenter" />
                            </Grid>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

./ App.xaml中

<Application ... >
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="./Themes/Viewport.xaml"/>

            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

用法:

<viewers:Viewport>
    <Image Source="{Binding}"/>
</viewers:Viewport>

任何问题,请给我一个大喊。

快乐编码:)

答案 13 :(得分:0)

@WiesławŠoltés回答above

提供的一流解决方案的另一项补充

现有代码可以通过右键单击来重置图像位置,但是我更习惯于双击鼠标。只需替换现有的child_MouseLeftButtonDown处理程序:

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_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if ((e.ChangedButton == MouseButton.Left && e.ClickCount == 1))
        {
            if (child != null)
            {
                var tt = GetTranslateTransform(child);
                start = e.GetPosition(this);
                origin = new Point(tt.X, tt.Y);
                this.Cursor = Cursors.Hand;
                child.CaptureMouse();
            }
        }

        if ((e.ChangedButton == MouseButton.Left && e.ClickCount == 2))
        {
            this.Reset();
        }
    }