140 votes

Image panoramique et zoom

Je veux créer une visionneuse d'images simple dans WPF qui permettra à l'utilisateur de :

  • Panoramique (en faisant glisser l'image avec la souris).
  • Zoom (avec un curseur).
  • Afficher les superpositions (sélection de rectangle par exemple).
  • Afficher l'image originale (avec des barres de défilement si nécessaire).

Pouvez-vous m'expliquer comment faire ?

Je n'ai pas trouvé de bon exemple sur le web. Devrais-je utiliser ViewBox ? Ou ImageBrush ? Ai-je besoin de ScrollViewer ?

0 votes

Pour obtenir un Zoom Control professionnel pour WPF, consultez l'adresse suivante ZoomPanel . Il n'est pas gratuit, mais il est très facile à utiliser et possède de nombreuses fonctionnalités - zoom et panoramique animés, prise en charge de ScrollViewer, prise en charge de la molette de la souris, ZoomController inclus (avec des boutons de déplacement, de zoom avant, de zoom arrière, de zoom rectangulaire et de réinitialisation). Il est également fourni avec de nombreux exemples de code.

0 votes

J'ai écrit un article sur codeproject.com sur l'implémentation d'un contrôle de zoom et de pan pour WPF. codeproject.com/KB/WPF/zoomandpancontrol.aspx

0 votes

Bonne trouvaille. L'essai est gratuit, mais ils demandent 69 dollars par ordinateur pour une licence si vous avez l'intention de créer un logiciel avec. Il s'agit d'une DLL à utiliser, donc ils ne peuvent pas vous en empêcher, mais c'est là que, si vous le construisez commercialement pour un client, en particulier un qui exige que tout utilitaire tiers soit déclaré et fasse l'objet d'une licence individuelle, vous devrez payer les frais de développement. Dans le CLUF, il n'est pas dit que c'est sur une base "par application", cependant, de sorte que dès que vous avez enregistré votre achat, il serait alors "libre" pour toutes les applications que vous avez créées, et vous pourriez copier votre fichier de licence payée avec elle pour représenter l'achat.

197voto

Wiesław Šoltés Points 451

Après avoir utilisé les exemples de cette question, j'ai réalisé une version complète de l'application pan & zoom avec un zoom correct par rapport au pointeur de la souris. Tout le code de pan & zoom a été déplacé dans une classe séparée appelée 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();
        }
    }
}

15 votes

Malheureusement, je ne peux pas vous donner plus de points. Ça marche vraiment très bien.

9 votes

Avant que les commentaires ne soient bloqués pour "Nice Job !" ou "Great Work", je veux juste dire "Nice Job" et "Great Work". C'est un bijou de WPF. Il a soufflé le wpf ext zoombox hors de l'eau.

7 votes

OUT debout. Je pourrais encore rentrer chez moi ce soir... +1000

120voto

Ian Oakes Points 5796

J'ai résolu ce problème en plaçant l'image à l'intérieur d'une bordure dont la propriété ClipToBounds est définie sur True. La propriété RenderTransformOrigin de l'image est ensuite définie sur 0.5,0.5 afin que l'image commence à zoomer sur le centre de l'image. La RenderTransform est également définie comme un TransformGroup contenant une ScaleTransform et une TranslateTransform.

J'ai ensuite géré l'événement MouseWheel sur l'image pour implémenter le zoom.

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

Pour gérer le panoramique, la première chose que j'ai faite a été de gérer l'événement MouseLeftButtonDown sur l'image, pour capturer la souris et enregistrer sa position, je stocke également la valeur actuelle de la TranslateTransform, c'est ce qui est mis à jour pour mettre en œuvre le panoramique.

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

Ensuite, j'ai géré l'événement MouseMove pour mettre à jour la 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;
    }
}

Enfin, n'oubliez pas de relâcher la capture de la souris.

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

Quant aux poignées de sélection pour le redimensionnement, elles peuvent être réalisées à l'aide d'un ornement. cet article pour plus d'informations.

10 votes

Une observation cependant, appeler CaptureMouse dans image_MouseLeftButtonDown résultera en un appel à image_MouseMove où l'origine n'est pas encore initialisée - dans le code ci-dessus, elle sera zéro par pure chance, mais si l'origine est différente de (0,0), l'image subira un court saut. Par conséquent, je pense qu'il est préférable d'appeler image.CaptureMouse() à la fin de image_MouseLeftButtonDown pour résoudre ce problème.

2 votes

Deux choses. 1) Il y a un bug avec image_MouseWheel, vous devez obtenir le ScaleTransform de la même manière que vous obtenez le TranslateTransform. C'est à dire, le couler dans un TransformGroup puis sélectionner et couler l'enfant approprié. 2) Si votre mouvement est instable, rappelez-vous que vous ne pouvez pas utiliser l'image pour obtenir la position de votre souris (puisqu'elle est dynamique), vous devez utiliser quelque chose de statique. Dans cet exemple, une bordure est utilisée.

47voto

Kelly Points 2004

La réponse a été postée ci-dessus mais n'était pas complète. Voici la version complétée :

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>

Code Behind

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

J'ai un exemple de projet wpf complet utilisant ce code sur mon site web : Jot, l'application de notes adhésives .

1 votes

Avez-vous des suggestions sur la façon de rendre cela utilisable dans Silverlight 3 ? J'ai des problèmes avec Vector et la soustraction d'un point à un autre... Merci.

0 votes

@Number8 A posté une implémentation qui fonctionne dans Silverlight 3 pour vous ci-dessous :)

4 votes

Un petit inconvénient - l'image grandit avec la frontière, et non à l'intérieur de la frontière

10voto

Palesz Points 1288

Essayez ce Zoom Control : http://wpfextensions.codeplex.com

L'utilisation du contrôle est très simple, il suffit de faire référence à l'assemblage wpfextensions :

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

Les barres de défilement ne sont pas prises en charge pour le moment. (Elle le sera dans la prochaine version qui sera disponible dans une ou deux semaines).

0 votes

Ouaip, j'aime ça. Le reste de la bibliothèque est assez banal.

0 votes

Il ne semble pas y avoir de prise en charge directe de l'option "Afficher les superpositions (sélection de rectangle, par exemple)", mais pour le comportement de zoom et de panoramique, c'est une excellente commande.

10voto

markti Points 2191
  • Pan : Placez l'image à l'intérieur d'un Canvas. Mettez en œuvre les événements de la souris vers le haut, le bas et le déplacement pour déplacer les propriétés Canvas.Top, Canvas.Left. Lorsque vous descendez, vous mettez un drapeau isDraggingFlag à true, lorsque vous montez, vous mettez le drapeau à false. Lors du déplacement, vous vérifiez si le drapeau est activé, si c'est le cas, vous décalez les propriétés Canvas.Top et Canvas.Left de l'image dans le canevas.
  • Zoom : Lier le curseur à la transformation d'échelle du canevas.
  • Afficher les superpositions : ajoutez des canevas supplémentaires sans arrière-plan au-dessus du canevas contenant l'image.
  • afficher l'image originale : contrôle d'image à l'intérieur d'un ViewBox

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X