平移和变焦图像

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

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

你能解释一下怎么做吗?

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

153272 次浏览
  • 把图像放在画布里。实现鼠标向上、向下和移动事件来移动画布。上面,画布。留下的财产。向下时,将 isDragingFlag 标记为 true,向上时将标记设置为 false。在移动时,检查是否设置了标志,是否偏移了画布。Top 和 Canvas。在画布中的图像上留下属性。
  • 缩放: 将滑块绑定到画布的比例变换
  • 显示覆盖: 添加额外的画布上没有背景的画布包含图像。
  • 显示原始图像: 视窗内的图像控制

我解决这个问题的方法是将图像放置在边框中,并将其 ClipToBound 属性设置为 True。然后将图像上的渲染变换起源设置为0.5,0.5,这样图像将开始在图像的中心缩放。渲染转换也被设置为包含 ScaleTransform 和 transateTransform 的 TransformGroup。

然后我处理了图像上的鼠标滚轮事件来实现缩放

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 事件,捕捉鼠标并记录它的位置,我还存储了 TransateTransform 的当前值,这个值被更新以实现平移。

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 事件,以更新 transateTransform。

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

至于用于调整大小的选择句柄,这可以通过使用装饰器来完成,有关更多信息,请查看 这篇文章

试试这个变焦控制: http://wpfextensions.codeplex.com

该控件的用法非常简单,参考 wpf 下面的程序集:

<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>

暗号

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

我有一些演示这个 记下便利贴应用程序的源代码。

要相对于鼠标位置进行缩放,所需的全部操作是:

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

@ 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 或集合来实现平移和缩放; 相反,使用 复合变换可以更轻松地完成这项工作。

我非常肯定这在资源使用方面是非常低效的,但至少它是有效的:)

@ 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 Frame work 3.0或2.0

希望对你有所帮助: -)

在使用了这个问题的例子之后,我已经制作了完整版的平移和缩放应用程序,相对于鼠标指针进行了适当的缩放。所有的平移和缩放代码已被移动到单独的类称为缩放边界。

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

这是同类控件的另一个版本,功能与其他版本相似,但它补充道:

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

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

为了便于阅读,我还在 大意Github上传了这篇文章

我还把这个打包在 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>

有问题就告诉我。

快乐编码:)

对@Wies aw oltés 提供的超级解决方案的一个补充回答 以上

现有的代码使用右键重置图像位置,但我更习惯于使用双击。只需替换现有的 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();
}
}

我也尝试了 这个答案,但并不完全满意的结果。我一直在谷歌上搜索,终于找到了一个 Nuget 包,它帮助我管理我想要的结果,anno 2021。我想与 Stack Overflow 的前开发人员分享它。

我使用的是通过 这个 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