QGraphicsView Zoom avant et arrière en vertu de la position de la souris à l'aide de la molette souris

j'ai une application avec un QGraphicsView fenêtre au milieu de l'écran. Je veux pouvoir zoomer et zoomer en utilisant un rouleau de souris.

actuellement j'ai re-implémenté QGraphicsView et remplace la fonction de défilement de la souris pour qu'elle ne fasse pas défiler l'image (comme elle le fait par défaut).

void MyQGraphicsView::wheelEvent(QWheelEvent *event)
{
    if(event->delta() > 0)
    {
        emit mouseWheelZoom(true);
    }
    else
    {
        emit mouseWheelZoom(false);
    }
}

donc quand je fais défiler, j'émets un signal true si la roue de la souris avance false si la roue de la souris recule.

j'ai alors connecté ce signal à une fente (fonction zoom voir ci-dessous) dans la classe qui gère mes trucs de GUI. Maintenant, fondamentalement, je pense que ma fonction de zoom n'est tout simplement pas la meilleure façon de le faire du tout, j'ai vu quelques exemples de personnes utilisant la fonction overriden wheelevent pour mettre les échelles, mais je ne pouvais pas vraiment trouver une réponse complète.

au lieu de cela j'ai fait ceci mais ce n'est pas parfait du tout donc je cherche à ce que cela soit légèrement modifié ou pour un exemple de travail en utilisant scale dans l'événement de la roue fonction.

j'initialise m_zoom_level0 dans le constructeur.

void Display::zoomfunction(bool zoom)
{
    QMatrix matrix;

    if(zoom && m_zoom_level < 500)
    {
        m_zoom_level = m_zoom_level + 10;
        ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        matrix.scale(m_zoom_level, m_zoom_level);

        ui->graphicsView->setMatrix(matrix);
        ui->graphicsView->scale(1,-1);
    }
    else if(!zoom)
    {
        m_zoom_level = m_zoom_level - 10;
        ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        matrix.scale(m_zoom_level, m_zoom_level);

        ui->graphicsView->setMatrix(matrix);
        ui->graphicsView->scale(1,-1);
    }
}

comme vous pouvez le voir ci-dessus, j'utilise un QMatrix et en le mettant à l'échelle et en le réglant à la vue graphique et en plaçant l'ancre de transformation sous la souris, mais son tout simplement pas fonctionner parfaitement parfois si je défile charges, il va juste commencer à zoomer (ce qui je pense est à voir avec la boucle de l'int ou quelque chose).

comme je l'ai dit aide avec ceci ou un bon exemple d'échelle en vertu de la souris serait génial.

20
demandé sur Grade 'Eh' Bacon 2013-10-01 14:12:20

9 réponses

un tel zoom est un peu délicat. Laissez-moi partager ma propre classe pour avoir fait ça.

Header:

#include <QObject>
#include <QGraphicsView>

/*!
 * This class adds ability to zoom QGraphicsView using mouse wheel. The point under cursor
 * remains motionless while it's possible.
 *
 * Note that it becomes not possible when the scene's
 * size is not large enough comparing to the viewport size. QGraphicsView centers the picture
 * when it's smaller than the view. And QGraphicsView's scrolls boundaries don't allow to
 * put any picture point at any viewport position.
 *
 * When the user starts scrolling, this class remembers original scene position and
 * keeps it until scrolling is completed. It's better than getting original scene position at
 * each scrolling step because that approach leads to position errors due to before-mentioned
 * positioning restrictions.
 *
 * When zommed using scroll, this class emits zoomed() signal.
 *
 * Usage:
 *
 *   new Graphics_view_zoom(view);
 *
 * The object will be deleted automatically when the view is deleted.
 *
 * You can set keyboard modifiers used for zooming using set_modified(). Zooming will be
 * performed only on exact match of modifiers combination. The default modifier is Ctrl.
 *
 * You can change zoom velocity by calling set_zoom_factor_base().
 * Zoom coefficient is calculated as zoom_factor_base^angle_delta
 * (see QWheelEvent::angleDelta).
 * The default zoom factor base is 1.0015.
 */
class Graphics_view_zoom : public QObject {
  Q_OBJECT
public:
  Graphics_view_zoom(QGraphicsView* view);
  void gentle_zoom(double factor);
  void set_modifiers(Qt::KeyboardModifiers modifiers);
  void set_zoom_factor_base(double value);

private:
  QGraphicsView* _view;
  Qt::KeyboardModifiers _modifiers;
  double _zoom_factor_base;
  QPointF target_scene_pos, target_viewport_pos;
  bool eventFilter(QObject* object, QEvent* event);

signals:
  void zoomed();
};

Source:

#include "Graphics_view_zoom.h"
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <qmath.h>

Graphics_view_zoom::Graphics_view_zoom(QGraphicsView* view)
  : QObject(view), _view(view)
{
  _view->viewport()->installEventFilter(this);
  _view->setMouseTracking(true);
  _modifiers = Qt::ControlModifier;
  _zoom_factor_base = 1.0015;
}

void Graphics_view_zoom::gentle_zoom(double factor) {
  _view->scale(factor, factor);
  _view->centerOn(target_scene_pos);
  QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0,
                                                             _view->viewport()->height() / 2.0);
  QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos;
  _view->centerOn(_view->mapToScene(viewport_center.toPoint()));
  emit zoomed();
}

void Graphics_view_zoom::set_modifiers(Qt::KeyboardModifiers modifiers) {
  _modifiers = modifiers;

}

void Graphics_view_zoom::set_zoom_factor_base(double value) {
  _zoom_factor_base = value;
}

bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
  if (event->type() == QEvent::MouseMove) {
    QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
    QPointF delta = target_viewport_pos - mouse_event->pos();
    if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) {
      target_viewport_pos = mouse_event->pos();
      target_scene_pos = _view->mapToScene(mouse_event->pos());
    }
  } else if (event->type() == QEvent::Wheel) {
    QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
    if (QApplication::keyboardModifiers() == _modifiers) {
      if (wheel_event->orientation() == Qt::Vertical) {
        double angle = wheel_event->angleDelta().y();
        double factor = qPow(_zoom_factor_base, angle);
        gentle_zoom(factor);
        return true;
      }
    }
  }
  Q_UNUSED(object)
  return false;
}

exemple d'Utilisation:

Graphics_view_zoom* z = new Graphics_view_zoom(ui->graphicsView);
z->set_modifiers(Qt::NoModifier);
28
répondu Pavel Strakhov 2013-10-01 13:25:27

Voici une solution en utilisant PyQt:

def wheelEvent(self, event):
    """
    Zoom in or out of the view.
    """
    zoomInFactor = 1.25
    zoomOutFactor = 1 / zoomInFactor

    # Save the scene pos
    oldPos = self.mapToScene(event.pos())

    # Zoom
    if event.angleDelta().y() > 0:
        zoomFactor = zoomInFactor
    else:
        zoomFactor = zoomOutFactor
    self.scale(zoomFactor, zoomFactor)

    # Get the new position
    newPos = self.mapToScene(event.pos())

    # Move scene to old position
    delta = newPos - oldPos
    self.translate(delta.x(), delta.y())
17
répondu rengel 2014-10-26 09:08:31

Voici la version python qui fonctionne pour moi. Vient de la combinaison des réponses de @Stefan Reinhardt et @rengel .

class MyQGraphicsView(QtGui.QGraphicsView):

def __init__ (self, parent=None):
    super(MyQGraphicsView, self).__init__ (parent)

def wheelEvent(self, event):
    # Zoom Factor
    zoomInFactor = 1.25
    zoomOutFactor = 1 / zoomInFactor

    # Set Anchors
    self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor)
    self.setResizeAnchor(QtGui.QGraphicsView.NoAnchor)

    # Save the scene pos
    oldPos = self.mapToScene(event.pos())

    # Zoom
    if event.delta() > 0:
        zoomFactor = zoomInFactor
    else:
        zoomFactor = zoomOutFactor
    self.scale(zoomFactor, zoomFactor)

    # Get the new position
    newPos = self.mapToScene(event.pos())

    # Move scene to old position
    delta = newPos - oldPos
    self.translate(delta.x(), delta.y())
8
répondu veslam 2015-03-13 07:29:39

Après beaucoup de frustration, cela semble fonctionner. Le problème semble être que l' QGraphicsViewtransform n'a rien à voir avec sa position de défilement, donc le comportement de QGraphicsView::mapToScene(const QPoint&) const dépend à la fois de la position de défilement et de la transformation. J'ai dû regarder à la source pour l' mapToScene pour le comprendre.

en gardant cela à l'esprit, voici ce qui a fonctionné: se souvenir du point de la scène pointé par la souris, échelle, mapper ce point de la scène aux coordonnées de la souris, puis ajuster les barres de défilement pour faire ce point liquidation en vertu de la souris:

void ZoomGraphicsView::wheelEvent(QWheelEvent* event)
{
   const QPointF p0scene = mapToScene(event->pos());

   qreal factor = std::pow(1.01, event->delta());
   scale(factor, factor);

   const QPointF p1mouse = mapFromScene(p0scene);
   const QPointF move = p1mouse - event->pos(); // The move
   horizontalScrollBar()->setValue(move.x() + horizontalScrollBar()->value());
   verticalScrollBar()->setValue(move.y() + verticalScrollBar()->value());
}
6
répondu Ben 2013-12-27 15:43:30

C'est un peu tard mais je suis passé par la même chose aujourd'hui seulement avec Pyside, mais devrait être le même...

L'approche est "très simple", bien chiffré moi un peu de temps... D'abord mettre toutes les ancres à NoAnchor, puis prendre le point de l'eventail, la carte à la scène, traduisez la scène par cette valeur, échelle et enfin traduisez-la en arrière:

def wheelEvent(self, evt):
    #Remove possible Anchors
    self.widget.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor)
    self.widget.setResizeAnchor(QtGui.QGraphicsView.NoAnchor)
    #Get Scene Pos
    target_viewport_pos = self.widget.mapToScene(evt.pos())
    #Translate Scene
    self.widget.translate(target_viewport_pos.x(),target_viewport_pos.y())
    # ZOOM
    if evt.delta() > 0:
        self._eventHandler.zoom_ctrl(1.2)
    else:
        self._eventHandler.zoom_ctrl(0.83333)
    # Translate back
    self.widget.translate(-target_viewport_pos.x(),-target_viewport_pos.y())

C'était la seule solution qui fonctionnait pour mon but. IMHO it est également la solution la plus logique...

5
répondu Stefan Reinhardt 2014-03-11 13:20:27

vous pouvez simplement utiliser la fonctionnalité builtin AnchorUnderMouse ou AnchorViewCenter pour maintenir la mise au point sous la souris ou au centre. Cela fonctionne pour moi dans Qt 5.7

void SceneView::wheelEvent(QWheelEvent *event)
    {
        if (event->modifiers() & Qt::ControlModifier) {
            // zoom
            const ViewportAnchor anchor = transformationAnchor();
            setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
            int angle = event->angleDelta().y();
            qreal factor;
            if (angle > 0) {
                factor = 1.1;
            } else {
                factor = 0.9;
            }
            scale(factor, factor);
            setTransformationAnchor(anchor);
        } else {
            QGraphicsView::wheelEvent(event);
        }
    }
4
répondu Alex Maystrenko 2017-01-17 03:32:55

Voici une version condensée de la solution ci-dessus; avec juste le code que vous devez mettre dans l'événement de roue. Cela fonctionne avec/sans les barres de défilement dans mes tests, parfaitement ;)

void MyGraphicsView::wheelEvent(QWheelEvent* pWheelEvent)
{
    if (pWheelEvent->modifiers() & Qt::ControlModifier)
    {
        // Do a wheel-based zoom about the cursor position
        double angle = pWheelEvent->angleDelta().y();
        double factor = qPow(1.0015, angle);

        auto targetViewportPos = pWheelEvent->pos();
        auto targetScenePos = mapToScene(pWheelEvent->pos());

        scale(factor, factor);
        centerOn(targetScenePos);
        QPointF deltaViewportPos = targetViewportPos - QPointF(viewport()->width() / 2.0, viewport()->height() / 2.0);
        QPointF viewportCenter = mapFromScene(targetScenePos) - deltaViewportPos;
        centerOn(mapToScene(viewportCenter.toPoint()));

        return;
    }
4
répondu cmaughan 2017-06-07 20:17:09

zoom plus lisse

void StatusView::wheelEvent(QWheelEvent * event)
{
    const QPointF p0scene = mapToScene(event->pos());

    qreal factor = qPow(1.2, event->delta() / 240.0);
    scale(factor, factor);

    const QPointF p1mouse = mapFromScene(p0scene);
    const QPointF move = p1mouse - event->pos(); // The move
    horizontalScrollBar()->setValue(move.x() + horizontalScrollBar()->value());
    verticalScrollBar()->setValue(move.y() + verticalScrollBar()->value());

}
3
répondu e.n.shirokov 2016-04-05 09:41:13
https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView) seems to work very well:

Source:

QGraphicsViewMap::QGraphicsViewMap(QWidget *parent) : QGraphicsView(parent)
{
    setTransformationAnchor(QGraphicsView::NoAnchor);
    setResizeAnchor(QGraphicsView::NoAnchor);
}

void QGraphicsViewMap::wheelEvent(QWheelEvent* event)
{
    wheelEventMousePos = event->pos();

    int numDegrees = event->delta() / 8;
    int numSteps = numDegrees / 15; // see QWheelEvent documentation
    _numScheduledScalings += numSteps;
    if (_numScheduledScalings * numSteps < 0) // if user moved the wheel in another direction, we reset previously scheduled scalings
        _numScheduledScalings = numSteps;

    QTimeLine *anim = new QTimeLine(350, this);
    anim->setUpdateInterval(20);

    connect(anim, SIGNAL (valueChanged(qreal)), SLOT (scalingTime(qreal)));
    connect(anim, SIGNAL (finished()), SLOT (animFinished()));
    anim->start();
 }

void QGraphicsViewMap::scalingTime(qreal x)
{
    QPointF oldPos = mapToScene(wheelEventMousePos);

    qreal factor = 1.0+ qreal(_numScheduledScalings) / 300.0;
    scale(factor, factor);

    QPointF newPos = mapToScene(wheelEventMousePos);
    QPointF delta = newPos - oldPos;
    this->translate(delta.x(), delta.y());
}

void QGraphicsViewMap::animFinished()
{
    if (_numScheduledScalings > 0)
        _numScheduledScalings--;
    else
        _numScheduledScalings++;

    sender()->~QObject();
}

Header:

class QGraphicsViewMap : public QGraphicsView
{
    Q_OBJECT

private:
    qreal _numScheduledScalings = 0;
    QPoint wheelEventMousePos;
public:
    explicit QGraphicsViewMap(QWidget *parent = 0);

signals:

public slots:
    void wheelEvent(QWheelEvent* event);
    void scalingTime(qreal x);
    void animFinished();
};
0
répondu Johannes Bergmark 2018-05-24 19:47:34