【问题标题】:QGraphicsView Zooming in and out under mouse position using mouse wheelQGraphicsView使用鼠标滚轮在鼠标位置下放大和缩小
【发布时间】:2013-10-07 10:57:01
【问题描述】:

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

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

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

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

然后,我将此信号连接到处理我的 GUI 内容的类中的插槽(缩放功能见下文)。现在基本上我认为我的缩放功能根本不是最好的方法。我已经看到一些人使用覆盖的 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 循环或其他事情有关)。

正如我所说,这方面的帮助或鼠标下缩放的一个很好的例子会很棒。

【问题讨论】:

  • 很高兴能得到这个问题的明确答案,因为有太多像这样的问题都有不同的答案,而且大多数问题似乎只解决了一半,然后才被单独留下......当然这个并不太复杂,它只是正确缩放的问题以及如何应用它..

标签: c++ qt qgraphicsview


【解决方案1】:

这种缩放有点棘手。让我分享我自己的课程。

标题:

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

【讨论】:

  • 干杯这工作非常好,当我尝试编译它时标记了一些错误QMouseEvent* mouse_event = static_cast&lt;QMouseEvent*&gt;(event);我不得不更改为QMouseEvent* mouse_event = (QMouseEvent *) event;除此之外,所有工作都谢谢你。你介意解释一些硬编码值的用途吗,这样我就可以确切地理解它在做什么以及为什么,再次感谢我很快就会接受
  • 非常感谢@Pavel!
  • 这不是将视图重新定位到鼠标指向的位置而不是放大/缩小鼠标光标吗?
  • 提示:使用item-&gt;setTransformationMode(Qt::SmoothTransformation);:对我来说,它修复了使用拖动模式view-&gt;setDragMode(QGraphicsView::ScrollHandDrag);时的重绘故障。此外,它将使用 AA,有助于在放大时消除大像素。
  • 谢谢先生,太棒了,在上次编辑 6 年后,它在复制/粘贴后直接工作。
【解决方案2】:

这是一个使用 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())

【讨论】:

    【解决方案3】:

    这是适用于我的 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())
    

    【讨论】:

    • 是的,这些固定锚解决了问题。
    • Qt5: event.delta() -> event.angleDelta().y()
    【解决方案4】:

    您可以简单地使用内置功能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);
            }
        }
    

    【讨论】:

    • 这是真正的解决方案,让 Qt 完成工作,而不是重新发明轮子。伟大的贡献!
    • @ypnos 很好的双关语,在那里重新发明了轮子 :)
    • 我尝试了很多其他答案,我发现这是正确的,谢谢!
    【解决方案5】:

    这是上述解决方案的精简版;只需将代码放入车轮事件中即可。在我的测试中,这适用于有/没有滚动条,完美;)

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

    【讨论】:

      【解决方案6】:

      在经历了很多挫折之后,这似乎奏效了。问题似乎是QGraphicsViewtransform 与其滚动位置无关,因此QGraphicsView::mapToScene(const QPoint&amp;) 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());
      }
      

      【讨论】:

      • 我实际上根本没有使用 scrollbr,所以不幸的是,实际上重置位置的最后一点不起作用,我不喜欢重新发布,但我已经实现了最初我认为有效但实际上并没有想到的值得提出一个新问题,不知道您是否有任何想法? stackoverflow.com/questions/21134446/…
      • 这对我不起作用。然而,维斯勒姆的回答确实如此。
      【解决方案7】:

      有点晚了 但我今天只用 Pyside 走过了同样的路,但应该是一样的......

      这种方法“非常简单”,虽然花了我一些时间...... 首先将所有Anchors设置为NoAnchor,然后取wheelevent的点,映射到场景中, 通过这个值翻译场景,缩放并最终翻译回来:

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

      这是唯一适合我的解决方案。 恕我直言,这也是最合乎逻辑的解决方案...

      【讨论】:

        【解决方案8】:

        更平滑的缩放

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

        【讨论】:

          【解决方案9】:

          简单示例:

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

          【讨论】:

            【解决方案10】:

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

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

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

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

            Qt SDK 似乎没有针对不常见场景中的鼠标移动事件进行很好的测试,例如使用鼠标滚轮进行缩放。

            【讨论】:

              【解决方案11】:

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

              围绕点矩阵缩放公式:rotate around point,与缩放相同。

              【讨论】:

                【解决方案12】:

                将@veslam:s 解决方案与来自 QT Wiki (https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView) 的 Smooth Zoom 代码结合起来似乎效果很好:

                来源:

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

                【讨论】:

                  【解决方案13】:

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2020-03-16
                    • 2018-11-14
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-11-12
                    • 2018-03-20
                    • 1970-01-01
                    相关资源
                    最近更新 更多