Görüntüyü Kaydır ve Yakınlaştır


131

WPF'de kullanıcının şunları yapmasını sağlayacak basit bir resim görüntüleyici oluşturmak istiyorum:

  • Kaydırma (görüntüyü fareyle sürükleyerek).
  • Yakınlaştır (kaydırıcıyla).
  • Bindirmeleri göster (örneğin dikdörtgen seçimi).
  • Orijinal resmi gösterin (gerekirse kaydırma çubuklarıyla).

Nasıl yapılacağını açıklayabilir misin?

İnternette iyi bir örnek bulamadım. ViewBox kullanmalı mıyım? Veya ImageBrush? ScrollViewer'a ihtiyacım var mı?


WPF için profesyonel bir Yakınlaştırma Kontrolü elde etmek için ZoomPanel'e bakın . Ücretsiz değildir, ancak kullanımı çok kolaydır ve birçok özelliğe sahiptir - hareketli yakınlaştırma ve kaydırma, ScrollViewer desteği, fare tekerleği desteği, dahil ZoomController (taşıma, yakınlaştırma, uzaklaştırma, dikdörtgen yakınlaştırma, sıfırlama düğmeleri ile). Aynı zamanda birçok kod örneği ile birlikte gelir.
Andrej Benedik

Codeproject.com'da WPF için yakınlaştırma ve kaydırma kontrolünün uygulanması hakkında bir makale yazdım. codeproject.com/KB/WPF/zoomandpancontrol.aspx
Ashley Davis

İyi bul. Denemesi bedava ve eğer onunla yazılım geliştirmeyi düşünüyorsanız bir lisans için bilgisayar başına 69 $ istiyorlar. Kullanılması gereken bir DLL dosyasıdır, bu yüzden sizi durduramazlar, ancak bir müşteri için ticari olarak oluşturuyorsanız, özellikle de herhangi bir üçüncü taraf yardımcı programın beyan edilmesini ve ayrı ayrı lisanslanmasını gerektiriyorsa, ödeme yapmanız gerekir. geliştirme ücreti. EULA'da "başvuru başına" esasına göre olduğunu söylemedi, ancak satın alma işleminizi kaydettirir kaydetmez, oluşturduğunuz tüm uygulamalar için "ücretsiz" olacak ve ücretli lisans dosyanızı kopyalayabilir satın almayı temsil etmek için onunla.
vapcguy

Yanıtlar:


116

Bu sorunu çözme şeklim, görüntüyü ClipToBounds özelliği True olarak ayarlanmış bir Sınır içine yerleştirmekti. Görüntü üzerindeki RenderTransformOrigin daha sonra 0.5,0.5 olarak ayarlanır, böylece görüntü, görüntünün merkezine yakınlaştırmaya başlar. RenderTransform ayrıca bir ScaleTransform ve bir TranslateTransform içeren bir TransformGroup olarak ayarlanmıştır.

Daha sonra yakınlaştırmayı uygulamak için görüntüdeki MouseWheel olayını işledim

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

Kaydırma işlemini gerçekleştirmek için yaptığım ilk şey, görüntüdeki MouseLeftButtonDown olayını işlemek, fareyi yakalamak ve konumunu kaydetmek için TranslateTransform'un mevcut değerini de saklarım, bu da kaydırmayı uygulamak için güncellenmiştir.

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

Sonra TranslateTransform'u güncellemek için MouseMove olayını işledim.

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

Son olarak, fare yakalamayı bırakmayı unutmayın.

private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    image.ReleaseMouseCapture();
}

Yeniden boyutlandırma seçim tutamaçlarına gelince, bu bir süsleyici kullanılarak gerçekleştirilebilir, daha fazla bilgi için bu makaleye göz atın .


9
Yine de bir gözlem, image_MouseLeftButtonDown'da CaptureMouse'u çağırmak, image_MouseMove'a kaynak henüz başlatılmamış bir çağrı ile sonuçlanacaktır - yukarıdaki kodda, tamamen şans eseri sıfır olacaktır, ancak kaynak (0,0) dışında ise görüntü kısa bir sıçrama yaşayacak. Bu nedenle, bu sorunu gidermek için image_MouseLeftButtonDown'un sonunda image.CaptureMouse () 'u çağırmanın daha iyi olacağını düşünüyorum.
Andrei Pana

2
İki şey. 1) image_MouseWheel ile ilgili bir hata var, TranslateTransform'a benzer şekilde ScaleTransform'u almanız gerekiyor. Yani, onu bir TransformGroup'a Yayınlayın ve ardından uygun Alt Öğeyi seçin ve yayınlayın. 2) Hareketiniz Jittery ise, farenizin konumunu (dinamik olduğu için) elde etmek için görüntüyü kullanamayacağınızı unutmayın, statik bir şey kullanmanız gerekir. Bu örnekte bir sınır kullanılmıştır.
Dave

170

Bu sorudaki örnekleri kullandıktan sonra, fare imlecine göre uygun yakınlaştırma ile pan & zoom uygulamasının tam sürümünü yaptım. Tüm kaydırma ve yakınlaştırma kodu, ZoomBorder adlı ayrı bir sınıfa taşındı.

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

10
Maalesef sana daha fazla puan veremem. Bu gerçekten harika çalışıyor.
Tobiel

6
Yorumlar "Güzel İş!" İçin engellenmeden önce veya "Harika İş" Sadece Güzel İş ve Harika İş demek istiyorum. Bu bir WPF cevheri. Wpf ext zoombox'ı sudan dışarı üfler.
Jesse Seger

4
Muhteşem. Henüz bu gece eve gidebilirim ... +1000
Bruce Pierson

1
HARİKA. Böyle bir uygulamayı düşünmedim ama gerçekten çok güzel! Çok teşekkür ederim!
Noel Widmer

3
mükemmel cevap! Yakınlaştırma faktörüne ufak bir düzeltme ekledim, bu yüzden "daha yavaş" yakınlaştırmıyordouble zoomCorrected = zoom*st.ScaleX; st.ScaleX += zoomCorrected; st.ScaleY += zoomCorrected;
DELUX

46

Cevap yukarıda yayınlandı ancak tamamlanmadı. işte tamamlanmış versiyon:

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>

Arkasındaki Kod

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

Web sitemde bu kodu kullanan tam bir wpf projesi örneğim var : Yapışkan not uygulamasını not edin .


1
Silverlight 3'te bunu nasıl kullanılabilir hale getireceğiniz konusunda herhangi bir öneriniz var mı? Vector ile ve bir Noktayı diğerinden çıkarırken sorun yaşıyorum ... Teşekkürler.
Sayı8

@ Number8 Aşağıda sizin için Silverlight 3'te çalışan bir uygulama yayınladı :)
Henry C

4
Küçük bir dezavantajı - görüntü büyür ile sınırında değil, sınırında
itsho

aynı şeyi windows 8 metro tarzı uygulamada nasıl uygulayacağınıza dair bir şeyler önerebilir misiniz? c # üzerinde çalışıyorum, xaml windows8
raj

1
İmage_MouseWheel'da transform.ScaleX ve ScaleY değerlerini test edebilirsiniz ve bu değerler + zoom> limitiniz ise + = zoom çizgilerini uygulamayın.
Kelly

10

Bu Yakınlaştırma Kontrolünü deneyin: http://wpfextensions.codeplex.com

kontrolün kullanımı çok basittir, wpfextensions derlemesine atıfta bulunulduğunda:

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

Kaydırma çubukları şu anda desteklenmiyor. (Bir veya iki hafta içinde piyasaya çıkacak olan sonraki sürümde olacak).


Evet, zevk alıyorum. Kütüphanenin geri kalanı oldukça önemsiz.
EightyOne Unite

Bununla birlikte, 'Kaplamaları göster (örneğin dikdörtgen seçimi)' için doğrudan destek yok gibi görünse de, yakınlaştırma / kaydırma davranışı için bu harika bir kontrol.
jsirr13

9
  • Kaydır: Resmi bir Kanvasın içine yerleştirin. Canvas.Top, Canvas.Left özelliklerini taşımak için Fare Yukarı, Aşağı ve Taşı olaylarını uygulayın. Aşağıdayken isDraggingFlag'i true olarak işaretlersiniz, up bayrağı false olarak ayarladığınızda. Hareket halindeyken, tuval içindeki görüntüdeki Canvas.Top ve Canvas.Left özelliklerini kaydırırsanız, bayrağın ayarlanıp ayarlanmadığını kontrol edersiniz.
  • Yakınlaştır: Kaydırıcıyı Kanvasın Ölçek Dönüştürmesine bağlayın
  • Bindirmeleri göster: görüntüyü içeren tuvalin üstüne arka planı olmayan ek tuvaller ekleyin.
  • orijinal görüntüyü göster: bir ViewBox içindeki görüntü kontrolü

4

@Anothen ve @ Number8 - Vector sınıfı Silverlight'ta mevcut değildir, bu nedenle çalışmasını sağlamak için MouseMove etkinliği en son çağrıldığında görülen son konumun kaydını tutmamız ve farkı bulmak için iki noktayı karşılaştırmamız gerekir ; sonra dönüşümü ayarlayın.

XAML:

    <Border Name="viewboxBackground" Background="Black">
            <Viewbox Name="viewboxMain">
                <!--contents go here-->
            </Viewbox>
    </Border>  

Kod-arkasında:

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

Ayrıca, kaydırma ve yakınlaştırmayı uygulamak için bir TransformGroup veya koleksiyona ihtiyacınız olmadığını unutmayın; bunun yerine, bir CompositeTransform hile daha az güçlükle yapacaktır.

Bunun kaynak kullanımı açısından gerçekten verimsiz olduğuna eminim, ama en azından işe yarıyor :)


2

Fare konumuna göre yakınlaştırmak için ihtiyacınız olan tek şey:

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

PictureBox kullanıyorum, RenderTransformOrigin artık mevcut değil.
Anahtar

@Switch RenderTransformOrigin, WPF kontrolleri içindir.
Xam

2

@ Merk

Lambda ifadesinin uygulandığı çözümünüz için aşağıdaki kodu kullanabilirsiniz:

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

bu kod .Net Frame work 3.0 veya 2.0 için olduğu gibi kullanılabilir

Umarım sana yardımcı olur :-)


2

Yine aynı türden kontrolün başka bir versiyonu. Diğerleriyle benzer işlevselliğe sahiptir, ancak şunları ekler:

  1. Dokunma desteği (sürükle / kıstır)
  2. Görüntü silinebilir (normalde Görüntü kontrolü görüntüyü diskte kilitler, bu nedenle silemezsiniz).
  3. Bir iç kenarlık alt öğesi, böylece panlanan görüntü kenarlıkla örtüşmez. Yuvarlatılmış dikdörtgenlere sahip kenarlıklar olması durumunda, ClippedBorder sınıflarını arayın.

Kullanımı basittir:

<Controls:ImageViewControl ImagePath="{Binding ...}" />

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

1
Bulduğum tek sorun, XAML'de bir görüntünün yolu belirtilirse, görüntü nesnesi oluşturulmadan önce (yani OnLoaded çağrılmadan önce) onu oluşturmaya çalışmasıydı. Düzeltmek için, "image = new Image ..." kodunu onLoaded yönteminden yapıcıya taşıdım. Teşekkürler.
Mitch

Diğer bir sorun da, biz hiçbir şey yapmayana ve hiçbir şey if (image.ActualWidth*(st.ScaleX + zoom) < 200 || image.ActualHeight*(st.ScaleY + zoom) < 200) //don't zoom out too small. return;görmeyene kadar resmin küçültülmesi. Biraz sınırlama ekliyorum : in image.MouseWheel
huoxudong125

1

Bu, yakınlaştırmanın ve uzaklaştırmanın yanı sıra kaydırır ancak görüntüyü kabın sınırları içinde tutar. Bir kontrol olarak yazıldığından, stili App.xamldoğrudan veya Themes/Viewport.xaml.

Okunabilirlik için bunu gist ve github'a da yükledim

Bunu da nuget üzerine paketledim

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>

Kullanımı:

<viewers:Viewport>
    <Image Source="{Binding}"/>
</viewers:Viewport>

Herhangi bir sorun olursa bana seslen.

Mutlu kodlamalar :)


Harika, bu versiyonu seviyorum. Kaydırma çubukları eklemenin herhangi bir yolu var mı?
Etienne Charland

Bu arada bağımlılık özelliklerini yanlış kullanıyorsunuz. Yakınlaştırma ve Çevirme için, bağlama sırasında hiç çağrılmadığı için kodu özellik belirleyiciye koyamazsınız. Değişim ve Zorlama işleyicileri bağımlılık özelliğinin kendisine kaydetmeniz ve burada çalışmanız gerekir.
Etienne Charland

Ben kitlesel Ben daha sonra üretimde kullanmaktan yaşadım bazı sorunlarla ilgili düzeltmeler ile güncelleyin hasta, onu yazmaya beri bu cevabı değiştirdik
Adam H

Bu çözüm harika, ancak fare tekerleği kaydırma işlevinin, yakınlaştırma kaynağı olarak fare işaretçisi konumunu kullanmak yerine, bir görüntüyü yakınlaştırırken ve uzaklaştırırken neden tek bir yöne garip bir çekme yaptığını tam olarak anlayamıyorum. Deli miyim yoksa bunun mantıklı bir açıklaması var mı?
Paul Karkoska

Bunun bir ScrollViewer kontrolü içinde tutarlı bir şekilde çalışmasını sağlamaya çalışıyorum. Ölçek kaynağı olarak cusor konumunu kullanmak için biraz değiştirdim (fare konumunu kullanarak yakınlaştırmak ve uzaklaştırmak için), ancak bir ScrollViewer içinde nasıl çalışacağına dair gerçekten bazı girdiler kullanabilirim. Teşekkürler!
Paul Karkoska
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.