QGraphicsView使用鼠标滚轮在鼠标位置下放大和缩小

QGraphicsView Zooming in and out under mouse position using mouse wheel

本文关键字:鼠标 放大 缩小 位置 QGraphicsView      更新时间:2023-10-16

我有一个应用程序,屏幕中间有一个QGraphicsView窗口。我希望能够使用鼠标滚轮滚动放大和缩小。

目前,我已经重新实现了QGraphicsView,并覆盖了鼠标滚动功能,这样它就不会滚动图像(就像默认情况下一样)。

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

所以当我滚动时,如果鼠标滚轮向前,我会发出一个信号true如果鼠标滚轮向后,我会发射一个信号false。

然后,我将这个信号连接到类中处理GUI内容的插槽(缩放函数请参见下面的)。现在基本上,我认为我的缩放功能根本不是最好的方法。我见过一些人使用overriden wheelevent功能设置缩放比例的例子,但我真的找不到完整的答案。

所以我做了这个,但无论如何都不完美,所以我希望对它进行一些调整,或者作为一个在轮子事件函数中使用缩放的工作示例。

我在构造函数中将m_zoom_level初始化为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);
}
}

正如你在上面看到的,我使用了一个QMatrix,并对其进行缩放,将其设置为Graphicsview,并将转换锚点设置为鼠标下,但有时它并不完美,如果我滚动加载,它只会开始放大(我认为这与int循环或其他操作有关)。

正如我所说,这方面的帮助或鼠标下缩放的一个好例子将是伟大的。

这样的缩放有点棘手。让我分享一下我自己的课。

标题:

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

来源:

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

用法示例:

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

以下是使用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())

您可以简单地使用内置功能AnchorUnderMouseAnchorViewCenter来在鼠标下或中心保持焦点。这适用于我在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);
}
}

以下是适用于我的python版本。来自@Stefan Reinhardt和@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())

有点晚了但我今天只和派塞德走过了同样的路,但应该是一样的。。。

这个方法"非常简单",虽然花了我一些时间。。。首先将所有锚点设置为NoAnchor,然后获取轮子事件的点,将其映射到场景,按此值平移场景,缩放并最终将其平移回:

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

这是唯一符合我目的的解决方案。IMHO这也是最合乎逻辑的解决方案。。。

这是上面解决方案的浓缩版本;只需将您需要放入轮子事件中的代码。在我的测试中,这可以完美地使用/不使用滚动条;)

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

在经历了很多挫折之后,这似乎奏效了。问题似乎是QGraphicsViewtransform与其滚动位置无关,因此QGraphicsView::mapToScene(const QPoint&) const的行为取决于滚动位置和变换。我必须查看mapToScene的来源才能理解这一点。

考虑到这一点,以下是行之有效的方法:记住鼠标指向的场景点,缩放,将该场景点映射到鼠标坐标,然后调整滚动条,使该点最终位于鼠标下方:

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

更平滑的缩放

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

简单示例:

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

PyQt回答工作良好,这里提供了一个c++函数,以备将来有人需要。

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

绕点缩放矩阵公式:绕点旋转,与缩放相同。

在Mac操作系统上,使用QGraphicsView::setTransformationAnchor(AnchorUnderMouse):时,此处引用的解决方案有时会失败

1-当窗口没有焦点时,Qt不会更新lastMouseMoveScenePoint。因为缩放是在失去焦点时使用鼠标位置执行的,而不是当前位置。(https://bugreports.qt.io/browse/QTBUG-73033)

当使用任务控制切换窗口时,2-Qt有时会停止传播鼠标移动事件,因此缩放也会像#1中那样表现不佳。(https://bugreports.qt.io/browse/QTBUG-73067)。我制作了这个视频,因为没有调用mouseMoveEvent,所以第二次单击窗口时芯片没有突出显示。我知道这不是我的应用程序中的一个错误,因为这是Qt提供的40000芯片的例子。我在这里发布了这个问题的解决方法。

3-setInteractive(false)不能与AnchorUnderMouse一起使用,因为用作转换中心的鼠标位置没有更新:https://bugreports.qt.io/browse/QTBUG-60672

QtSDK似乎没有很好地测试在不常见的场景中的鼠标移动事件,比如用鼠标滚轮缩放。

将@veslam:solution与QT Wiki中的Smooth Zoom代码相结合(https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView)似乎工作得很好:

来源:

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

标题:

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