Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/wpf/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 泛美航空;变焦图像_C#_Wpf_Xaml_Zooming_Pan - Fatal编程技术网

C# 泛美航空;变焦图像

C# 泛美航空;变焦图像,c#,wpf,xaml,zooming,pan,C#,Wpf,Xaml,Zooming,Pan,我想在WPF中创建一个简单的图像查看器,使用户能够: 平移(通过鼠标拖动图像) 缩放(使用滑块) 显示覆盖(例如矩形选择) 显示原始图像(如果需要,使用滚动条) 你能解释一下怎么做吗 我在网上找不到好的样品。 我应该使用ViewBox吗?还是画笔? 我需要ScrollViewer吗? 平移:将图像放在画布内。实现鼠标向上、向下和移动事件以移动Canvas.Top、Canvas.Left属性。向下时,将IsDragingFlag标记为true,向上时,将该标志设置为false。在移动时,检查是

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

  • 平移(通过鼠标拖动图像)
  • 缩放(使用滑块)
  • 显示覆盖(例如矩形选择)
  • 显示原始图像(如果需要,使用滚动条)
你能解释一下怎么做吗

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

  • 平移:将图像放在画布内。实现鼠标向上、向下和移动事件以移动Canvas.Top、Canvas.Left属性。向下时,将IsDragingFlag标记为true,向上时,将该标志设置为false。在移动时,检查是否设置了标志,如果设置了标志,则在画布内的图像上偏移Canvas.Top和Canvas.Left属性
  • 缩放:将滑块绑定到画布的缩放变换
  • 显示覆盖:在包含图像的画布上添加没有背景的其他画布
  • 显示原始图像:ViewBox内的图像控件

我解决此问题的方法是将图像放置在边框内,并将其ClipToBounds属性设置为True。然后将图像上的RenderTransferMorigin设置为0.5,0.5,以便图像开始在图像中心缩放。RenderTransform还设置为包含ScaleTransform和TranslateTransform的TransformGroup

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_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();
}
至于用于调整大小的选择句柄,可以使用装饰器来完成,请查看以获取更多信息

试试这个缩放控制:

控件的使用非常简单,引用wpfextensions程序集比:

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


此时不支持滚动条。(将在一到两周后发布的下一个版本中发布)。

答案已在上面发布,但尚未完成。以下是完整的版本:

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>

我的网站上有一个完整wpf项目的示例,使用以下代码:。

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

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

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

XAML:

还请注意,您不需要TransformGroup或collection来实现平移和缩放;相反,a将以更少的麻烦完成这项任务

我很确定这在资源使用方面确实是低效的,但至少它是有效的:)

@Merk

对于替代lambda表达式的ur解决方案,您可以使用以下代码:

//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];
        }
//var tt=(TranslateTransform)((TransformGroup)image.RenderTransform).Children.First(tr=>tr是TranslateTransform);
TranslateTransform tt=null;
TransformGroup TransformGroup=(TransformGroup)grid.RenderTransform;
for(int i=0;i
此代码可以按原样用于.Net Framework 3.0或2.0


希望它能帮助你:-)

在使用了这个问题中的示例后,我制作了完整版本的平移和缩放应用程序,并对鼠标指针进行了适当的缩放。所有平移和缩放代码都已移动到称为ZoomOrder的单独类中

ZoomBorder.cs


同样类型控件的另一个版本。它的功能与其他功能类似,但它增加了:

  • 触摸支持(拖动/挤压)
  • 可以删除映像(通常,映像控件会锁定磁盘上的映像,因此您无法删除它)
  • 内边框子对象,因此平移图像不会与边框重叠。对于带有圆角矩形的边框,请查找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);
            }
        }
    }
    
    公共类ImageViewControl:边框
    {
    私人点源;
    私人点启动;
    私有图像;
    公共图像视图控件()
    {
    cliptobunds=true;
    已加载+=已加载;
    }
    #区域图像路径
    /// 
    ///ImagePath依赖属性
    /// 
    公共静态只读DependencyProperty ImagePathProperty=DependencyProperty.Register(“ImagePath”、typeof(string)、typeof(ImageViewControl)、new FrameworkPropertyMetadata(string.Empty、OnImagePathChanged));
    /// 
    ///获取或设置ImagePath属性。此依赖项属性
    ///指示图像文件的路径。
    /// 
    公共字符串映像路径
    {
    get{return(string)GetValue(ImagePathProperty);}
    set{SetValue(ImagePathProperty,value);}
    }
    /// 
    ///处理对ImagePath属性的更改。
    /// 
    ImagePathChanged上的私有静态无效(DependencyObject d、DependencyPropertyChangedEventArgs e)
    {
    var target=(ImageViewControl)d;
    var oldImagePath=(字符串)e.OldValue;
    var newImagePath=target.ImagePath;
    target.ReloadImage(newImagePath);
    target.OnImagePathChanged(oldImagePath、newImagePath);
    }
    /// 
    ///为派生类提供处理ImagePath属性更改的机会。
    /// 
    受保护的虚拟void OnImagePath已更改(字符串oldImagePath、字符串newImagePath)
    {
    }
    #端区
    已加载专用void(对象发送方,RoutedEventArgs RoutedEventArgs)
    {
    图像=新
    
    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
        }
    }
    
    <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>
    
    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();
            }
        }
    }
    
    <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);
            }
        }
    }
    
    PM > Install-Package Han.Wpf.ViewportControl
    
    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();
        }
    }
    
    <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>
    
    <Application ... >
        <Application.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
    
                    <ResourceDictionary Source="./Themes/Viewport.xaml"/>
    
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </Application.Resources>
    </Application>
    
    <viewers:Viewport>
        <Image Source="{Binding}"/>
    </viewers:Viewport>
    
    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();
            }
        }
    
    <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>
    
    
    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; }
    
        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
        }