사용자가 다음을 수행 할 수 있도록 WPF에서 간단한 이미지 뷰어를 만들고 싶습니다.
- 팬 (이미지를 마우스로 드래그하여).
- 확대 / 축소 (슬라이더 사용)
- 오버레이 표시 (예 : 사각형 선택).
- 원본 이미지를 표시합니다 (필요한 경우 스크롤 막대 포함).
어떻게하는지 설명해 주실 수 있습니까?
웹에서 좋은 샘플을 찾지 못했습니다. ViewBox를 사용해야합니까? 아니면 ImageBrush? ScrollViewer가 필요합니까?
사용자가 다음을 수행 할 수 있도록 WPF에서 간단한 이미지 뷰어를 만들고 싶습니다.
어떻게하는지 설명해 주실 수 있습니까?
웹에서 좋은 샘플을 찾지 못했습니다. ViewBox를 사용해야합니까? 아니면 ImageBrush? ScrollViewer가 필요합니까?
답변:
이 문제를 해결하는 방법은 ClipToBounds 속성이 True로 설정된 이미지를 Border 내에 배치하는 것입니다. 그런 다음 이미지의 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();
}
크기 조정을위한 선택 핸들은 adorner를 사용하여 수행 할 수 있습니다 . 자세한 내용은 이 기사 를 확인하십시오.
이 질문의 샘플을 사용한 후 마우스 포인터를 기준으로 적절한 확대 / 축소를 사용하여 전체 버전의 팬 및 확대 / 축소 앱을 만들었습니다. 모든 이동 및 확대 / 축소 코드가 ZoomBorder라는 별도의 클래스로 이동되었습니다.
ZoomBorder.cs
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace PanAndZoom
{
public class ZoomBorder : Border
{
private UIElement child = null;
private Point origin;
private Point start;
private TranslateTransform GetTranslateTransform(UIElement element)
{
return (TranslateTransform)((TransformGroup)element.RenderTransform)
.Children.First(tr => tr is TranslateTransform);
}
private ScaleTransform GetScaleTransform(UIElement element)
{
return (ScaleTransform)((TransformGroup)element.RenderTransform)
.Children.First(tr => tr is ScaleTransform);
}
public override UIElement Child
{
get { return base.Child; }
set
{
if (value != null && value != this.Child)
this.Initialize(value);
base.Child = value;
}
}
public void Initialize(UIElement element)
{
this.child = element;
if (child != null)
{
TransformGroup group = new TransformGroup();
ScaleTransform st = new ScaleTransform();
group.Children.Add(st);
TranslateTransform tt = new TranslateTransform();
group.Children.Add(tt);
child.RenderTransform = group;
child.RenderTransformOrigin = new Point(0.0, 0.0);
this.MouseWheel += child_MouseWheel;
this.MouseLeftButtonDown += child_MouseLeftButtonDown;
this.MouseLeftButtonUp += child_MouseLeftButtonUp;
this.MouseMove += child_MouseMove;
this.PreviewMouseRightButtonDown += new MouseButtonEventHandler(
child_PreviewMouseRightButtonDown);
}
}
public void Reset()
{
if (child != null)
{
// reset zoom
var st = GetScaleTransform(child);
st.ScaleX = 1.0;
st.ScaleY = 1.0;
// reset pan
var tt = GetTranslateTransform(child);
tt.X = 0.0;
tt.Y = 0.0;
}
}
#region Child Events
private void child_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (child != null)
{
var st = GetScaleTransform(child);
var tt = GetTranslateTransform(child);
double zoom = e.Delta > 0 ? .2 : -.2;
if (!(e.Delta > 0) && (st.ScaleX < .4 || st.ScaleY < .4))
return;
Point relative = e.GetPosition(child);
double absoluteX;
double absoluteY;
absoluteX = relative.X * st.ScaleX + tt.X;
absoluteY = relative.Y * st.ScaleY + tt.Y;
st.ScaleX += zoom;
st.ScaleY += zoom;
tt.X = absoluteX - relative.X * st.ScaleX;
tt.Y = absoluteY - relative.Y * st.ScaleY;
}
}
private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (child != null)
{
var tt = GetTranslateTransform(child);
start = e.GetPosition(this);
origin = new Point(tt.X, tt.Y);
this.Cursor = Cursors.Hand;
child.CaptureMouse();
}
}
private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (child != null)
{
child.ReleaseMouseCapture();
this.Cursor = Cursors.Arrow;
}
}
void child_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
this.Reset();
}
private void child_MouseMove(object sender, MouseEventArgs e)
{
if (child != null)
{
if (child.IsMouseCaptured)
{
var tt = GetTranslateTransform(child);
Vector v = start - e.GetPosition(this);
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
}
}
#endregion
}
}
MainWindow.xaml
<Window x:Class="PanAndZoom.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PanAndZoom"
Title="PanAndZoom" Height="600" Width="900" WindowStartupLocation="CenterScreen">
<Grid>
<local:ZoomBorder x:Name="border" ClipToBounds="True" Background="Gray">
<Image Source="image.jpg"/>
</local:ZoomBorder>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PanAndZoom
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
double zoomCorrected = zoom*st.ScaleX; st.ScaleX += zoomCorrected; st.ScaleY += zoomCorrected;
답변이 위에 게시되었지만 완료되지 않았습니다. 완성 된 버전은 다음과 같습니다.
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 .
이 줌 컨트롤을 사용해보십시오 : http://wpfextensions.codeplex.com
컨트롤의 사용법은 매우 간단합니다. wpfextensions 어셈블리를 참조하는 것보다
<wpfext:ZoomControl>
<Image Source="..."/>
</wpfext:ZoomControl>
현재 스크롤바는 지원되지 않습니다. (다음 릴리스에서는 1 ~ 2 주 후에 제공 될 예정입니다).
@Anothen 및 @ Number8-Vector 클래스는 Silverlight에서 사용할 수 없으므로 제대로 작동하려면 MouseMove 이벤트가 마지막으로 호출 된 마지막 위치를 기록한 다음 두 지점을 비교하여 차이점을 찾아야합니다. ; 그런 다음 변환을 조정하십시오.
XAML :
<Border Name="viewboxBackground" Background="Black">
<Viewbox Name="viewboxMain">
<!--contents go here-->
</Viewbox>
</Border>
코드 숨김 :
public Point _mouseClickPos;
public bool bMoving;
public MainPage()
{
InitializeComponent();
viewboxMain.RenderTransform = new CompositeTransform();
}
void MouseMoveHandler(object sender, MouseEventArgs e)
{
if (bMoving)
{
//get current transform
CompositeTransform transform = viewboxMain.RenderTransform as CompositeTransform;
Point currentPos = e.GetPosition(viewboxBackground);
transform.TranslateX += (currentPos.X - _mouseClickPos.X) ;
transform.TranslateY += (currentPos.Y - _mouseClickPos.Y) ;
viewboxMain.RenderTransform = transform;
_mouseClickPos = currentPos;
}
}
void MouseClickHandler(object sender, MouseButtonEventArgs e)
{
_mouseClickPos = e.GetPosition(viewboxBackground);
bMoving = true;
}
void MouseReleaseHandler(object sender, MouseButtonEventArgs e)
{
bMoving = false;
}
또한 이동 및 확대 / 축소를 구현하기 위해 TransformGroup 또는 컬렉션이 필요하지 않습니다. 대신 CompositeTransform 은 번거 로움없이 트릭을 수행합니다.
나는 이것이 리소스 사용 측면에서 실제로 비효율적이라고 확신하지만 적어도 작동합니다 :)
마우스 위치를 기준으로 확대 / 축소하려면 필요한 것은 다음과 같습니다.
var position = e.GetPosition(image1);
image1.RenderTransformOrigin = new Point(position.X / image1.ActualWidth, position.Y / image1.ActualHeight);
@ 머크
람다 식을 넣은 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];
}
이 코드는 .Net Frame work 3.0 또는 2.0에서 그대로 사용할 수 있습니다.
희망 :-)
같은 종류의 제어의 또 다른 버전. 다른 기능과 비슷한 기능을 가지고 있지만 다음을 추가합니다.
사용법은 간단합니다.
<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);
}
}
}
if (image.ActualWidth*(st.ScaleX + zoom) < 200 || image.ActualHeight*(st.ScaleY + zoom) < 200) //don't zoom out too small. return;
image.MouseWheel에
이렇게하면 확대 / 축소 및 축소되지만 이미지는 컨테이너 경계 안에 유지됩니다. 컨트롤로 작성되었으므로 스타일을 App.xaml
직접 또는를 통해 추가하십시오 Themes/Viewport.xaml
.
가독성을 위해 이것을 gist 및 github 에 업로드했습니다.
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();
}
}
./Themes/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>
어떤 문제라도, 나에게 소리 지르십시오.
행복한 코딩 :)