QGraphicsView Acercar y alejar la imagen debajo de la posición del mouse con la rueda del mouse

Tengo una aplicación con una ventana QGraphicsView en el medio de la pantalla. Quiero poder acercar y alejar con la rueda del mouse.

Actualmente he vuelto a implementar QGraphicsView y QGraphicsView la función de desplazamiento del mouse para que no se desplace la imagen (como lo hace de forma predeterminada).

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

así que cuando me desplazo, estoy emitiendo una señal verdadera si la rueda del mouse hacia adelante es falsa si la rueda del mouse retrocede.

A continuación, conecté esta señal a una ranura (función de zoom, ver más abajo ) en la clase que maneja mi interfaz gráfica de usuario. Ahora, básicamente, creo que mi función de zoom simplemente no es la mejor manera de hacerlo. He visto algunos ejemplos de personas que usan la función anulada de Wheelevent para establecer escalas, pero no pude encontrar una respuesta completa.

Así que, en cambio, he hecho esto, pero no es perfecto de ninguna manera, así que estoy buscando que esto se modifique un poco o para un ejemplo de trabajo usando la escala en la función de evento de rueda.

Inicializo m_zoom_level a 0 en el constructor.

 void Display::zoomfunction(bool zoom) { QMatrix matrix; if(zoom && m_zoom_level 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); } } 

Como puede ver arriba, estoy usando una QMatrix y escalando eso y configurándola en Graphicsview y estableciendo el anclaje de transformación debajo del mouse, pero simplemente no funciona a la perfección, a veces, si estoy desplazando cargas, solo comenzará a acercarse solo. (que creo que tiene que ver con el int looping over o algo así).

Como dije, ayuda con esto o un buen ejemplo de escala debajo del mouse sería genial.

Tal acercamiento es un poco complicado. Permítanme compartir mi propia clase para hacer eso.

Encabezamiento:

 #include  #include  /*! * 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(); }; 

Fuente:

 #include "Graphics_view_zoom.h" #include  #include  #include  #include  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(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(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; } 

Ejemplo de uso:

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

Aquí hay una solución usando 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()) 

Aquí está la versión de Python que funciona para mí. Viene de la combinación de respuestas de @Stefan Reinhardt y @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()) 

Después de mucha frustración, esto parece funcionar. El problema parece ser que la transform QGraphicsView no tiene nada que ver con su posición de desplazamiento, por lo que el comportamiento de QGraphicsView::mapToScene(const QPoint&) const depende tanto de la posición de desplazamiento como de la transformación. Tuve que mirar la fuente de mapToScene para entender esto.

Con esto en mente, esto es lo que funcionó: recuerde el punto de la escena al que apunta el mouse, escala, correlaciona el punto de escena con las coordenadas del mouse, luego ajuste las barras de desplazamiento para que ese punto termine debajo del mouse:

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

Es un poco tarde, pero caminé por el mismo hoy solo con Pyside, pero debería ser el mismo …

El enfoque es “muy simple”, aunque me costó un poco de tiempo … Primero coloque todos los Anclas en NoAnchor, luego tome el punto del evento, métalo en la escena, traduzca la escena por este valor, escale y finalmente tradúzcalo espalda:

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

Esta fue la única solución que funcionó para mi propósito. En mi humilde opinión también es la solución más lógica …

Simplemente puede usar la funcionalidad incorporada AnchorUnderMouse o AnchorViewCenter para mantener el foco bajo el mouse o en el centro. Esto funciona para mí en 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); } } 

Aquí hay una versión condensada de la solución anterior; con solo el código que necesita poner en el evento de la rueda. Esto funciona con / sin barras de desplazamiento en mi prueba, perfectamente;)

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

Zoom más suave

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

La combinación de la solución @veslam: s con el código Smooth Zoom de QT Wiki ( https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView ) parece funcionar muy bien:

Fuente:

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

Encabezamiento:

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