Chcę utworzyć prostą przeglądarkę obrazów w WPF, która umożliwi użytkownikowi:
- Przesuwaj (przeciągając obraz myszą).
- Zoom (z suwakiem).
- Pokaż nakładki (na przykład wybór prostokąta).
- Pokaż oryginalny obraz (w razie potrzeby z paskami przewijania).
Czy możesz wyjaśnić, jak to zrobić?
Nie znalazłem dobrej próbki w sieci. Czy powinienem używać ViewBox? Lub ImageBrush? Czy potrzebuję ScrollViewer?
Odpowiedzi:
Sposób, w jaki rozwiązałem ten problem, polegał na umieszczeniu obrazu w Border z właściwością ClipToBounds ustawioną na True. RenderTransformOrigin na obrazie jest następnie ustawiana na 0,5,0,5, więc obraz zacznie się powiększać w środku obrazu. RenderTransform jest również ustawiona na TransformGroup zawierającą ScaleTransform i TranslateTransform.
Następnie obsłużyłem zdarzenie MouseWheel na obrazie, aby zaimplementować powiększanie
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; }
Aby obsłużyć panoramowanie, pierwszą rzeczą, którą zrobiłem, było obsłużenie zdarzenia MouseLeftButtonDown na obrazie, przechwycenie myszy i zarejestrowanie jej lokalizacji, zapisuję również bieżącą wartość TranslateTransform, która jest aktualizowana w celu zaimplementowania panoramowania.
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); }
Następnie obsłużyłem zdarzenie MouseMove, aby zaktualizować 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; } }
Wreszcie nie zapomnij zwolnić przechwytywania myszy.
private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { image.ReleaseMouseCapture(); }
Jeśli chodzi o uchwyty wyboru do zmiany rozmiaru, można to zrobić za pomocą adornera, zapoznaj się z tym artykułem, aby uzyskać więcej informacji.
źródło
Po użyciu próbek z tego pytania stworzyłem pełną wersję aplikacji do przesuwania i powiększania z odpowiednim powiększeniem względem wskaźnika myszy. Cały kod przesuwania i powiększania został przeniesiony do oddzielnej klasy o nazwie 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(); } } }
źródło
double zoomCorrected = zoom*st.ScaleX; st.ScaleX += zoomCorrected; st.ScaleY += zoomCorrected;
Odpowiedź została zamieszczona powyżej, ale nie była kompletna. oto pełna wersja:
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>
Kod za
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; } } }
Mam przykład pełnego projektu WPF używającego tego kodu na mojej stronie internetowej: Jot the sticky notes app .
źródło
Wypróbuj tę kontrolę powiększenia: http://wpfextensions.codeplex.com
użycie kontrolki jest bardzo proste, odniesienie do zestawu wpfextensions niż:
<wpfext:ZoomControl> <Image Source="..."/> </wpfext:ZoomControl>
W tej chwili paski przewijania nie są obsługiwane. (Będzie w następnym wydaniu, które będzie dostępne za tydzień lub dwa).
źródło
źródło
@Anothen i @ Number8 - Klasa Vector nie jest dostępna w Silverlight, więc aby działała, wystarczy zapisać ostatnią pozycję zaobserwowaną podczas ostatniego wywołania zdarzenia MouseMove i porównać dwa punkty, aby znaleźć różnicę ; następnie dostosuj transformację.
XAML:
<Border Name="viewboxBackground" Background="Black"> <Viewbox Name="viewboxMain"> <!--contents go here--> </Viewbox> </Border>
Za kodem:
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; }
Pamiętaj również, że nie potrzebujesz grupy TransformGroup ani kolekcji, aby zaimplementować przesuwanie i powiększanie; zamiast tego CompositeTransform załatwi sprawę z mniejszym kłopotem.
Jestem prawie pewien, że jest to naprawdę nieefektywne pod względem wykorzystania zasobów, ale przynajmniej działa :)
źródło
Aby powiększyć w stosunku do pozycji myszy, potrzebujesz tylko:
var position = e.GetPosition(image1); image1.RenderTransformOrigin = new Point(position.X / image1.ActualWidth, position.Y / image1.ActualHeight);
źródło
@ Merk
Dla rozwiązania ur zamiast wyrażenia lambda możesz użyć następującego kodu:
//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]; }
ten kod może być używany tak jak w przypadku .Net Frame w wersji 3.0 lub 2.0
Mam nadzieję, że ci to pomoże :-)
źródło
Jeszcze inna wersja tego samego rodzaju kontroli. Ma podobną funkcjonalność jak inne, ale dodaje:
Użycie jest proste:
<Controls:ImageViewControl ImagePath="{Binding ...}" />
A kod:
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); } } }
źródło
if (image.ActualWidth*(st.ScaleX + zoom) < 200 || image.ActualHeight*(st.ScaleY + zoom) < 200) //don't zoom out too small. return;
w obrazie.MouseWheelSpowoduje to powiększanie i pomniejszanie, a także przesuwanie, ale obraz pozostanie w granicach kontenera. Zapisany jako kontrolka, więc dodaj styl
App.xaml
bezpośrednio lub za pośrednictwemThemes/Viewport.xaml
.Dla czytelności wrzuciłem to również na gist i github
Zapakowałem to również w 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(); } }
./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>
Stosowanie:
<viewers:Viewport> <Image Source="{Binding}"/> </viewers:Viewport>
Jakieś problemy, daj mi znać.
Miłego kodowania :)
źródło