상하좌우 이동 및 확대/축소
WPF에서 사용자가 다음을 수행할 수 있는 간단한 이미지 뷰어를 만들고 싶습니다.
- 이동(마우스로 이미지를 끌어서 이동).
- 슬라이더를 사용하여 확대/축소합니다.
- 오버레이 표시(예: 직사각형 선택).
- 원본 이미지를 표시합니다(필요한 경우 스크롤 막대 포함).
어떻게 하는지 설명해 주시겠습니까?
저는 웹에서 좋은 샘플을 찾지 못했습니다.ViewBox를 사용해야 합니까?아니면 이미지 브러시?ScrollViewer가 필요합니까?
이 질문의 샘플을 사용한 후 마우스 포인터와 관련하여 적절한 확대/축소가 가능한 완전한 버전의 팬 & 줌 앱을 만들었습니다.모든 이동 및 확대/축소 코드가 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
}
}
주 창.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();
}
}
}
이 문제를 해결하는 방법은 ClipToBounds 속성이 True로 설정된 테두리 안에 이미지를 배치하는 것이었습니다.그런 다음 이미지의 RenderTransformOrigin(렌더 변환 원점)이 0.5,0.5로 설정되어 이미지가 이미지 중앙에서 확대축소되기 시작합니다.또한 렌더 변환은 축척 변환 및 변환 변환을 포함하는 변환 그룹으로 설정됩니다.
그리고 나서 나는 마우스를 다루었습니다.확대/축소를 구현하기 위한 이미지의 휠 이벤트
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;
}
패닝을 처리하기 위해 가장 먼저 한 일은 이미지에서 마우스 왼쪽 버튼다운 이벤트를 처리하고, 마우스를 캡처하고, 위치를 기록하기 위해 Translate Transform의 현재 값을 저장하는 것입니다. 이 값은 패닝을 구현하기 위해 업데이트되었습니다.
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);
}
그런 다음 번역 변환을 업데이트하기 위해 마우스 이동 이벤트를 처리했습니다.
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();
}
도너를 사용하여 크기를 조정할 수 있는 선택 핸들에 대한 자세한 내용은 이 문서를 참조하십시오.
위에 답변이 게시되었지만 완료되지 않았습니다.완성된 버전은 다음과 같습니다.
XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MapTest.Window1"
x:Name="Window"
Title="Window1"
Width="1950" Height="1546" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Controls="clr-namespace:WPFExtensions.Controls;assembly=WPFExtensions" mc:Ignorable="d" Background="#FF000000">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="52.92"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="1" Name="border">
<Image Name="image" Source="map3-2.png" Opacity="1" RenderTransformOrigin="0.5,0.5" />
</Border>
</Grid>
코드비하
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace MapTest
{
public partial class Window1 : Window
{
private Point origin;
private Point start;
public Window1()
{
InitializeComponent();
TransformGroup group = new TransformGroup();
ScaleTransform xform = new ScaleTransform();
group.Children.Add(xform);
TranslateTransform tt = new TranslateTransform();
group.Children.Add(tt);
image.RenderTransform = group;
image.MouseWheel += image_MouseWheel;
image.MouseLeftButtonDown += image_MouseLeftButtonDown;
image.MouseLeftButtonUp += image_MouseLeftButtonUp;
image.MouseMove += image_MouseMove;
}
private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
image.ReleaseMouseCapture();
}
private void image_MouseMove(object sender, MouseEventArgs e)
{
if (!image.IsMouseCaptured) return;
var tt = (TranslateTransform) ((TransformGroup) image.RenderTransform).Children.First(tr => tr is TranslateTransform);
Vector v = start - e.GetPosition(border);
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
image.CaptureMouse();
var tt = (TranslateTransform) ((TransformGroup) image.RenderTransform).Children.First(tr => tr is TranslateTransform);
start = e.GetPosition(border);
origin = new Point(tt.X, tt.Y);
}
private void image_MouseWheel(object sender, MouseWheelEventArgs e)
{
TransformGroup transformGroup = (TransformGroup) image.RenderTransform;
ScaleTransform transform = (ScaleTransform) transformGroup.Children[0];
double zoom = e.Delta > 0 ? .2 : -.2;
transform.ScaleX += zoom;
transform.ScaleY += zoom;
}
}
}
나는 이 Jot the sticky note 앱을 보여주는 소스 코드를 가지고 있습니다.
- 팬: 이미지를 캔버스 안에 넣습니다.마우스 위쪽, 아래쪽 및 이동 이벤트를 구현하여 캔버스를 이동합니다.위쪽, 캔버스.왼쪽 속성.다운된 경우 is DragingFlag를 true로 표시하고, 업된 경우 플래그를 false로 설정합니다.이동 중에 플래그가 설정되어 있는지 확인하고 캔버스를 오프셋합니다.상단 및 캔버스.캔버스 내 이미지에 속성을 남겼습니다.
- 확대/축소: 슬라이더를 캔버스의 축척 변환에 바인딩합니다.
- 오버레이 표시: 배경이 없는 캔버스를 이미지가 들어 있는 캔버스 위에 추가합니다.
- 원본 이미지 표시: ViewBox 내부의 이미지 제어
이 확대/축소 제어를 사용해 보십시오. http://wpfextensions.codeplex.com
컨트롤의 사용은 매우 간단합니다. wpf 확장 어셈블리를 참조하면 다음과 같습니다.
<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;
}
또한 이동 및 확대/축소를 구현하기 위해 Transform Group 또는 컬렉션이 필요하지 않으며, 대신 Composite Transform을 사용하면 번거로움이 줄어듭니다.
저는 이것이 자원 사용 측면에서 정말 비효율적이라고 꽤 확신하지만, 적어도 효과가 있습니다 :)
마우스 위치를 기준으로 확대/축소하려면 다음과 같이 하면 됩니다.
var position = e.GetPosition(image1);
image1.RenderTransformOrigin = new Point(position.X / image1.ActualWidth, position.Y / image1.ActualHeight);
저도 이 답변을 시도해 보았지만 결과에 전적으로 만족하지 못했습니다.저는 계속해서 검색했고 마침내 제가 원하는 결과를 관리하는 데 도움이 되는 Nuget Package를 찾았습니다. anno 2021.Stack Overflow의 이전 개발자들과 공유하고 싶습니다.
나는 이 Nuget Package Gu를 사용했습니다.이 Github Repository를 통해 WPF.Geometry를 찾았습니다.개발에 대한 모든 크레딧은 이 패키지의 소유자인 Johan Larsson에게 전달되어야 합니다.
어떻게 사용했죠?여기에 표시된 것처럼 명령을 확대/축소 상자 아래의 버튼으로 사용하고 싶었습니다.MachineLayoutControl.xaml
.
<UserControl
x:Class="MyLib.MachineLayoutControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:csmachinelayoutdrawlib="clr-namespace:CSMachineLayoutDrawLib"
xmlns:effects="http://gu.se/Geometry">
<UserControl.Resources>
<ResourceDictionary Source="Resources/ResourceDictionaries/AllResourceDictionariesCombined.xaml" />
</UserControl.Resources>
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border
Grid.Row="0"
Margin="0,0"
Padding="0"
BorderThickness="1"
Style="{StaticResource Border_Head}"
Visibility="Visible">
<effects:Zoombox
x:Name="ImageBox"
IsManipulationEnabled="True"
MaxZoom="10"
MinZoom="0.1"
Visibility="{Binding Zoombox_Visibility}">
<ContentControl Content="{Binding Viewing_Canvas}" />
</effects:Zoombox>
</Border>
<StackPanel
Grid.Column="1"
Margin="10"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Command="effects:ZoomCommands.Increase"
CommandParameter="2.0"
CommandTarget="{Binding ElementName=ImageBox}"
Content="Zoom In"
Style="{StaticResource StyleForResizeButtons}" />
<Button
Command="effects:ZoomCommands.Decrease"
CommandParameter="2.0"
CommandTarget="{Binding ElementName=ImageBox}"
Content="Zoom Out"
Style="{StaticResource StyleForResizeButtons}" />
<Button
Command="effects:ZoomCommands.Uniform"
CommandTarget="{Binding ElementName=ImageBox}"
Content="See Full Machine"
Style="{StaticResource StyleForResizeButtons}" />
<Button
Command="effects:ZoomCommands.UniformToFill"
CommandTarget="{Binding ElementName=ImageBox}"
Content="Zoom To Machine Width"
Style="{StaticResource StyleForResizeButtons}" />
</StackPanel>
</Grid>
</UserControl>
기본 View 모델에는 다음과 같은 관련 코드가 있었습니다.
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; }
또한 로드 시 즉시 Uniform to Fill 명령이 실행되기를 원했습니다. 이것은 코드백에서 할 수 있었던 일입니다. MachineLayoutControl.xaml.cs
사용자 컨트롤이 로드될 때 "깜빡임"을 방지하기 위해 명령이 실행되는 경우에만 확대/축소 상자를 표시하도록 설정했습니다.
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
}
머크
람다 식 대신 솔루션으로 다음 코드를 사용할 수 있습니다.
//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];
}
이 코드는 에 있는 그대로 사용할 수 있습니다.네트워크 프레임워크 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);
}
}
}
이렇게 하면 상하좌우 이동뿐만 아니라 확대 및 축소되지만 이미지는 용기의 범위 내에 유지됩니다.컨트롤로 작성되었으므로 스타일을 추가합니다.App.xaml
직통으로Themes/Viewport.xaml
.
가독성을 위해 이 온라인 게시판과 깃허브에도 올렸습니다.
나는 이것도 너겟에 포장했습니다.
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>
무슨 문제가 있으면 저에게 소리쳐 주세요.
해피 코딩 :)
@Wieswaw Scholté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();
}
}
언급URL : https://stackoverflow.com/questions/741956/pan-zoom-image
'programing' 카테고리의 다른 글
JDBC를 사용하여 저장 프로시저에서 Oracle 테이블 유형 가져오기 (0) | 2023.07.04 |
---|---|
Spring Boot/Angular 4 - 앱 내 라우팅이 서버를 강타합니다. (0) | 2023.07.04 |
printf를 사용하여 0 뒤에 오는 경우 소수점 이하의 자리 없이 부동 소수점 이하의 형식 지정 (0) | 2023.07.04 |
구문 오류: 가능하지 않습니다. 이 오류는 무엇입니까? (0) | 2023.07.04 |
MAX() 함수에서 SQL 행 반환 (0) | 2023.07.04 |