webentwicklung-frage-antwort-db.com.de

QGraphicsView Zoomen mit dem Mausrad unter der Mausposition

Ich habe eine Anwendung mit einem QGraphicsView-Fenster in der Mitte des Bildschirms. Ich möchte in der Lage sein, mit einem Mausrad zu zoomen. 

Derzeit habe ich QGraphicsView neu implementiert und die Maus-Scroll-Funktion überschrieben, sodass das Bild nicht durchgeblättert wird (wie dies standardmäßig der Fall ist). 

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

wenn ich also scrolle, sende ich ein wahres Signal, wenn das Mausrad falsch ist, wenn das Mausrad zurück ist.

Ich habe dieses Signal dann an einen Steckplatz (Zoomfunktion siehe unten ) in der Klasse angeschlossen, die mein GUI-Zeug behandelt. Im Grunde denke ich, dass meine Zoomfunktion einfach nicht der beste Weg ist, um überhaupt etwas zu tun. Ich habe einige Beispiele von Leuten gesehen, die die Overriden-Wheelevent-Funktion zum Einstellen von Skalen verwendet haben, aber ich konnte keine vollständige Antwort finden.

Stattdessen habe ich dies getan, aber es ist auf keinen Fall perfekt, also suche ich, dass dies ein wenig geändert wird oder ein Arbeitsbeispiel mit einer Skala in der Radereignisfunktion.

Ich initialisiere m_zoom_level im Konstruktor auf 0.

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

Wie Sie oben sehen können, verwende ich eine QMatrix und skaliere diese und setze sie auf GraphicsView und den Transformationsanker unter Maus, aber sie funktioniert manchmal nicht perfekt, wenn ich mit dem Scrollen scrolle, beginnt sie nur zu zoomen (was ich denke, ist mit dem Int-Looping oder etwas zu tun). 

Wie gesagt, Hilfe mit dieser oder einem guten Beispiel für Maßstab unter der Maus wäre toll.

22
AngryDuck

Solches Zoomen ist ein bisschen schwierig. Lassen Sie mich meine eigene Klasse dafür teilen.

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

Quelle:

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

Anwendungsbeispiel:

Graphics_view_zoom* z = new Graphics_view_zoom(ui->graphicsView);
z->set_modifiers(Qt::NoModifier);
33
Pavel Strakhov

Hier ist eine Lösung mit 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())
18
rengel

Hier ist die Python-Version für mich. Kommt aus der Kombination der Antworten von @Stefan Reinhardt und @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())
9
veslam

Nach viel Frust scheint das zu funktionieren. Das Problem scheint zu sein, dass QGraphicsViews transform nichts mit seiner Bildlaufposition zu tun hat, daher hängt das Verhalten von QGraphicsView::mapToScene(const QPoint&) const sowohl von der Bildlaufposition als auch von der Transformation ab. Ich musste die Quelle für mapToScene betrachten, um dies zu verstehen.

In diesem Sinne hat sich Folgendes bewährt: Erinnern Sie sich an den Szenenpunkt, auf den die Maus zeigt, skalieren Sie, ordnen Sie den Szenenpunkt auf Mauskoordinaten zu und passen Sie die Bildlaufleisten so an, dass dieser Punkt unter der Maus angezeigt wird:

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
Ben

Sie können einfach die eingebaute Funktionalität AnchorUnderMouse oder AnchorViewCenter verwenden, um den Fokus unter der Maus oder in der Mitte beizubehalten

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);
        }
    }
5
Alex Maystrenko

Hier ist eine komprimierte Version der Lösung oben; Nur mit dem Code, den Sie in das Radereignis einfügen müssen. Das funktioniert perfekt mit/ohne Scrollbalken;)

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

Es ist ein bisschen spät, aber ich bin heute nur mit Pyside durch dasselbe gegangen, sollte aber genauso sein ...

Der Ansatz ist "sehr einfach", hat mich allerdings etwas Zeit gekostet ... Zuerst alle Anker auf NoAnchor setzen, dann den Punkt des Radevents nehmen, der Szene zuordnen und die Szene nach diesem Wert übersetzen , skalieren und schließlich zurückübersetzen:

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

Dies war die einzige Lösung, die für meinen Zweck funktionierte.

5

Glatterer Zoom

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
e.n.shirokov

PyQt beantwortet die Frage gut, stellen Sie hier eine c ++ - Funktion zur Verfügung, falls jemand sie in Zukunft benötigt.

void CanvasView::zoomAt(const QPoint &centerPos, double factor)
{
    //QGraphicsView::AnchorUnderMouse uses ::centerOn() in it's implement, which must need scroll.
    //transformationAnchor() default is AnchorViewCenter, you need set NoAnchor while change transform, 
    //and combine all transform change will work more effective
    QPointF targetScenePos = mapToScene(centerPos);
    ViewportAnchor oldAnchor = this->transformationAnchor();
    setTransformationAnchor(QGraphicsView::NoAnchor);

    QTransform matrix = transform();
    matrix.translate(targetScenePos.x(), targetScenePos.y())
            .scale(factor, factor)
            .translate(-targetScenePos.x(), -targetScenePos.y());
    setTransform(matrix);

    setTransformationAnchor(oldAnchor);
}

void CanvasView::wheelEvent(QWheelEvent *event)
{
    if(event->modifiers().testFlag(Qt::ControlModifier))
    {
        double angle = event->angleDelta().y();

        double factor = qPow(1.0015, angle);    //smoother zoom
        zoomAt(event->pos(), factor);
        return;
    }

    QGraphicsView::wheelEvent(event);
}

Skalieren um Punktmatrixformel: m Punkt drehen , was mit der Skalierung identisch ist.

0
Shun

Die Kombination von @veslam: s Lösung mit dem Smooth Zoom-Code von QT Wiki ( https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView ) scheint sehr gut zu funktionieren:

Quelle:

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

Einfaches Beispiel:

class CGraphicsVew : public QGraphicsView
{
    Q_OBJECT

protected:
    void wheelEvent(QWheelEvent *event)
    {
        qreal deltaScale = 1;
        deltaScale += event->delta() > 0 ? 0.1 : -0.1;
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        scale(deltaScale, deltaScale);
    }
};
0
cherrerr

Unter Mac OS schlagen die hier genannten Lösungen manchmal fehl, wenn Sie QGraphicsView :: setTransformationAnchor (AnchorUnderMouse) verwenden:

1 - Qt aktualisiert lastMouseMoveScenePoint nicht, wenn das Fenster keinen Fokus hat. Aus diesem Grund wird der Zoom mit der Mausposition ausgeführt, wenn der Fokus verloren geht, und nicht mit dem aktuellen. ( https://bugreports.qt.io/browse/QTBUG-73033 )

2 - Qt stoppt manchmal das Weiterleiten von Mausbewegungsereignissen, wenn die Fenster mit der Missionssteuerung umgeschaltet werden, so dass sich der Zoom auch wie in # 1 verhält. ( https://bugreports.qt.io/browse/QTBUG-73067 ). Ich habe dieses video erstellt, bei dem Chips beim zweiten Klicken auf das Fenster nicht markiert sind, weil mouseMoveEvent nicht aufgerufen wird. Ich weiß, dass dies kein Fehler in meiner Anwendung ist, da dies das von Qt bereitgestellte 40000-Chip-Beispiel ist. Ich habe den Workaround für dieses Problem hier veröffentlicht.

3 - setInteractive (false) kann nicht mit AnchorUnderMouse verwendet werden, da die als Transformationszentrum verwendete Mausposition nicht aktualisiert wird: https://bugreports.qt.io/browse/QTBUG-60672

Es scheint, dass das Qt-SDK nicht gut auf Mausbewegungsereignisse in ungewöhnlichen Situationen wie dem Zoomen mit dem Mausrad getestet wurde.

0
Adriel Jr
void GraphicsView::wheelEvent(QWheelEvent* event)
{
    switch (event->modifiers()) {
    case Qt::ControlModifier:
        if (event->angleDelta().x() != 0)
            QAbstractScrollArea::horizontalScrollBar()->setValue(QAbstractScrollArea::horizontalScrollBar()->value() - (event->delta()));
        else
            QAbstractScrollArea::verticalScrollBar()->setValue(QAbstractScrollArea::verticalScrollBar()->value() - (event->delta()));
        break;
    case Qt::ShiftModifier:
        QAbstractScrollArea::horizontalScrollBar()->setValue(QAbstractScrollArea::horizontalScrollBar()->value() - (event->delta()));
        break;
    case Qt::NoModifier:
        if (abs(event->delta()) == 120) {
            if (event->delta() > 0)
                zoomIn();
            else
                zoomOut();
        }
        break;
    default:
        QGraphicsView::wheelEvent(event);
        return;
    }
    event->accept();
}

const double zoomFactor = 1.5;

void GraphicsView::zoomIn()
{
    scale(zoomFactor, zoomFactor);
}

void GraphicsView::zoomOut()
{
    scale(1.0 / zoomFactor, 1.0 / zoomFactor);
}
</ code>
0
X-Ray