使用Qt缩放功能

时间:2017-12-08 05:30:34

标签: c++ qt qgraphicsview

当前实现,缩放到View的中心,以便当我们放大时,左上角或当前鼠标指针中显示的项目不可见。

我想要基于当前鼠标指针的缩放功能,因此当前鼠标指针上的项目会缩放到视图的中心。

缩放基础视图区域的代码

void csGuiView::wheelEvent(QWheelEvent *event)
{

    if ((event->modifiers()&Qt::ControlModifier) == Qt::ControlModifier
        && event->angleDelta().x() == 0)
    {
    QPoint  pos  = event->pos();
    QPointF posf = this->mapToScene(pos);

    double angle = event->angleDelta().y();

    double scalingFactor;

    if(angle > 0)
    {
        scalingFactor = 1 + ( angle / 360 * 0.1);
    }else if (angle < 0)
    {
        scalingFactor = 1 - ( -angle / 360 * 0.1);
    } else
    {
        scalingFactor = 1;
    }

    m_pvtData->m_scale = scalingFactor;

    this->scale(scalingFactor, scalingFactor);

    double w = this->viewport()->width();
    double h = this->viewport()->height();

    double wf = this->mapToScene(QPoint(w-1, 0)).x()
            - this->mapToScene(QPoint(0,0)).x();
    double hf = this->mapToScene(QPoint(0, h-1)).y()
            - this->mapToScene(QPoint(0,0)).y();

    double lf = posf.x() - pos.x() * wf / w;
    double tf = posf.y() - pos.y() * hf / h;

    /* try to set viewport properly */
    this->ensureVisible(lf, tf, wf, hf, 0, 0);


    QPointF newPos = this->mapToScene(pos);


    this->ensureVisible(QRectF(QPointF(lf, tf) - newPos + posf,
                               QSizeF(wf, hf)), 0, 0);

    }

    if ((event->modifiers()&Qt::ControlModifier) != Qt::ControlModifier) {
    QGraphicsView::wheelEvent(event);
    }

    event->accept();
}

1 个答案:

答案 0 :(得分:3)

要始终以鼠标指针为中心进行缩放 - 鼠标指针的位置必须成为缩放的原点。

这听起来很简单,但我有点努力准备演示。 (对不起,我对线性代数不太好。)但是,我终于让它运行了。

我的示例代码testQWidget-Zoom.cc

#include <vector>
#include <QtWidgets>

// class for widget to demonstrate zooming
class Canvas: public QWidget {
  // types:
  private:
    struct Geo {
      QRectF rect; QColor color;
      Geo(const QRectF &rect, const QColor &color):
        rect(rect), color(color)
      { }
    };
  // variables:
  private:
    bool _initDone : 1; // flag: true ... sample geo created
    std::vector<Geo> _scene; // contents to render
    QMatrix _mat; // view matrix
  // methods:
  public: 
    // constructor.
    Canvas(): QWidget(), _initDone(false) { }
    // destructor.
    virtual ~Canvas() = default;
    // disabled:
    Canvas(const Canvas&) = delete;
    Canvas& operator=(const Canvas&) = delete;
  private:
    // initializes sample geo
    void init()
    {
      if (_initDone) return;
      _initDone = true;
      // build scene (with NDC i.e. view x/y range: [-1, 1])
      _scene.emplace_back(Geo(QRectF(-1.0f, -1.0f, 2.0f, 2.0f), QColor(0x000000u)));
      _scene.emplace_back(Geo(QRectF(-0.2f, -0.2f, 0.4f, 0.4f), QColor(0x00ff00u)));
      _scene.emplace_back(Geo(QRectF(-0.8f, -0.8f, 0.4f, 0.4f), QColor(0xff0000u)));
      _scene.emplace_back(Geo(QRectF(-0.8f, 0.4f, 0.4f, 0.4f), QColor(0x0000ffu)));
      _scene.emplace_back(Geo(QRectF(0.4f, 0.4f, 0.4f, 0.4f), QColor(0xff00ffu)));
      _scene.emplace_back(Geo(QRectF(0.4f, -0.8f, 0.4f, 0.4f), QColor(0xffff00u)));
      // get initial scaling
      const int wView = width(), hView = height();
      _mat.scale(wView / 2, hView / 2);
      _mat.translate(1, 1);
    }
  protected:
    virtual void paintEvent(QPaintEvent *pQEvent) override
    {
      init();
      // render
      QPainter qPainter(this);
#if 0 // This scales line width as well:
      qPainter.setMatrix(_mat);
      for (const Geo &geo : _scene) {
        qPainter.setPen(geo.color);
        qPainter.drawRect(geo.rect);
      }
#else // This transforms only coordinates:
      for (const Geo &geo : _scene) {
        qPainter.setPen(geo.color);
        QRectF rect(geo.rect.topLeft() * _mat, geo.rect.bottomRight() * _mat);
        qPainter.drawRect(rect);
      }
#endif // 0
    }
    virtual void wheelEvent(QWheelEvent *pQEvent) override
    {
      //qDebug() << "Wheel Event:"
      //qDebug() << "mouse pos:" << pQEvent->pos();
      // pos() -> virtual canvas
      bool matInvOK = false;
      QMatrix matInv = _mat.inverted(&matInvOK);
      if (!matInvOK) {
        qDebug() << "View matrix not invertible!";
        return;
      }
      QPointF posNDC
        = QPointF(pQEvent->pos().x(), pQEvent->pos().y()) * matInv;
      //qDebug() << "mouse pos (NDC):" << posNDC;
      float delta = 1.0f + pQEvent->angleDelta().y() / 1200.0f;
      //qDebug() << "angleDelta:" << pQEvent->angleDelta().y();
      //qDebug() << "scale factor:" << delta;
      _mat.translate(posNDC.x(), posNDC.y()); // origin to spot
      _mat.scale(delta, delta); // scale
      _mat.translate(-posNDC.x(), -posNDC.y()); // spot to origin
      update();
      pQEvent->accept();
    }
};


int main(int argc, char **argv)
{
  QApplication app(argc, argv);
  Canvas canvas;
  canvas.resize(512, 512);
  canvas.show();
  // runtime loop
  return app.exec();
}

这三行是​​实际有趣的(Canvas::wheelEvent()):

      _mat.translate(posNDC.x(), posNDC.y()); // origin to spot
      _mat.scale(delta, delta); // scale
      _mat.translate(-posNDC.x(), -posNDC.y()); // spot to origin

这就是它的样子:

Snapshot of testQWidget-Zoom - initially

Snapshot of testQWidget-Zoom - after pointing into red rect and turning the wheel

第一张图片是应用程序启动后的快照。

然后我指着红色矩形的中心并稍微转动了轮子。红色矩形按照预期在鼠标指针周围生长。

1 st 更新:

而且,这是直接使用屏幕坐标的更新版本(而不是将所有内容转换为NDC):

#include <vector>
#include <QtWidgets>

// class for widget to demonstrate zooming
class Canvas: public QWidget {
  // types:
  private:
    struct Geo {
      QRectF rect; QColor color;
      Geo(const QRectF &rect, const QColor &color):
        rect(rect), color(color)
      { }
    };
  // variables:
  private:
    bool _initDone : 1; // flag: true ... sample geo created
    std::vector<Geo> _scene; // contents to render
    QMatrix _mat; // view matrix
  // methods:
  public: 
    // constructor.
    Canvas(): QWidget(), _initDone(false) { }
    // destructor.
    virtual ~Canvas() = default;
    // disabled:
    Canvas(const Canvas&) = delete;
    Canvas& operator=(const Canvas&) = delete;
  private:
    // initializes sample geo
    void init()
    {
      if (_initDone) return;
      _initDone = true;
      const int wView = width(), hView = height();
      // build scene (with NDC i.e. view x/y range: [-1, 1])
      _scene.emplace_back(Geo(QRectF(-1.0f, -1.0f, 2.0f, 2.0f), QColor(0x000000u)));
      _scene.emplace_back(Geo(QRectF(-0.2f, -0.2f, 0.4f, 0.4f), QColor(0x00ff00u)));
      _scene.emplace_back(Geo(QRectF(-0.8f, -0.8f, 0.4f, 0.4f), QColor(0xff0000u)));
      _scene.emplace_back(Geo(QRectF(-0.8f, 0.4f, 0.4f, 0.4f), QColor(0x0000ffu)));
      _scene.emplace_back(Geo(QRectF(0.4f, 0.4f, 0.4f, 0.4f), QColor(0xff00ffu)));
      _scene.emplace_back(Geo(QRectF(0.4f, -0.8f, 0.4f, 0.4f), QColor(0xffff00u)));
      // scale geometry to screen coordinates
      QMatrix mat;
      mat.scale(wView / 2, hView / 2);
      mat.translate(1, 1);
      for (Geo &geo : _scene) {
        geo.rect = QRectF(geo.rect.topLeft() * mat, geo.rect.bottomRight() * mat);
      }
    }
  protected:
    virtual void paintEvent(QPaintEvent *pQEvent) override
    {
      init();
      // render
      QPainter qPainter(this);
      qPainter.setMatrix(_mat);
      for (const Geo &geo : _scene) {
        qPainter.setPen(geo.color);
        qPainter.drawRect(geo.rect);
      }
    }
    virtual void wheelEvent(QWheelEvent *pQEvent) override
    {
      //qDebug() << "Wheel Event:";
      //qDebug() << "mouse pos:" << pQEvent->pos();
      float delta = 1.0f + pQEvent->angleDelta().y() / 1200.0f;
      //qDebug() << "angleDelta:" << pQEvent->angleDelta().y();
      //qDebug() << "scale factor:" << delta;
      _mat.translate(pQEvent->pos().x(), pQEvent->pos().y()); // origin to spot
      _mat.scale(delta, delta); // scale
      _mat.translate(-pQEvent->pos().x(), -pQEvent->pos().y()); // spot to origin
      update();
      pQEvent->accept();
    }
};

int main(int argc, char **argv)
{
  QApplication app(argc, argv);
  Canvas canvas;
  canvas.resize(256, 256);
  canvas.show();
  // runtime loop
  return app.exec();
}

相关的三行没有太大变化 - 鼠标坐标直接应用于转换。

顺便说一下。我更改了渲染 - 它现在可以缩放线宽以及我使用

      qPainter.setMatrix(_mat);
Canvas::paintEvent()

而不是“手动”转换所有点。

在我指向蓝色矩形的中心并转动鼠标滚轮后,快照显示了应用程序:

Snapshot of testQWidget-Zoom - after pointing into blue rect and turning the wheel

2 nd 更新:

建议的矩阵操作也适用于QGraphicsView

#include <QtWidgets>

// class for widget to demonstrate zooming
class Canvas: public QGraphicsView {
  // methods:
  public: 
    // constructor.
    Canvas() = default;
    // destructor.
    virtual ~Canvas() = default;
    // disabled:
    Canvas(const Canvas&) = delete;
    Canvas& operator=(const Canvas&) = delete;

  protected:

    virtual void wheelEvent(QWheelEvent *pQEvent) override
    {
      //qDebug() << "Wheel Event:";
      // pos() -> virtual canvas
      QPointF pos = mapToScene(pQEvent->pos());
      //qDebug() << "mouse pos:" << pos;
      // scale from wheel angle
      float delta = 1.0f + pQEvent->angleDelta().y() / 1200.0f;
      //qDebug() << "angleDelta:" << pQEvent->angleDelta().y();
      //qDebug() << "scale factor:" << delta;
      // modify transform matrix
      QTransform xform = transform();
      xform.translate(pos.x(), pos.y()); // origin to spot
      xform.scale(delta, delta); // scale
      xform.translate(-pos.x(), -pos.y()); // spot to origin
      setTransform(xform);
      //qDebug() << "transform:" << xform;
      // force update
      update();
      pQEvent->accept();
    }
};

QRectF toScr(QWidget *pQWidget, float x, float y, float w, float h)
{
  const int wView = pQWidget->width(), hView = pQWidget->height();
  const int s = wView < hView ? wView : hView;
  return QRectF(
    (0.5f * x + 0.5f) * s, (0.5f * y + 0.5f) * s,
    0.5f * w * s, 0.5f * h * s);
}

int main(int argc, char **argv)
{
  QApplication app(argc, argv);
  // setup GUI
  Canvas canvas;
  canvas.setTransformationAnchor(QGraphicsView::NoAnchor);
  canvas.resize(256, 256);
  canvas.show();
  // prepare scene
  QGraphicsScene qGScene;
  qGScene.addRect(toScr(canvas.viewport(), -1.0f, -1.0f, 2.0f, 2.0f), QColor(0x000000u));
  qGScene.addRect(toScr(canvas.viewport(), -0.2f, -0.2f, 0.4f, 0.4f), QColor(0x00ff00u));
  qGScene.addRect(toScr(canvas.viewport(), -0.8f, -0.8f, 0.4f, 0.4f), QColor(0xff0000u));
  qGScene.addRect(toScr(canvas.viewport(), -0.8f, 0.4f, 0.4f, 0.4f), QColor(0x0000ffu));
  qGScene.addRect(toScr(canvas.viewport(), 0.4f, 0.4f, 0.4f, 0.4f), QColor(0xff00ffu));
  qGScene.addRect(toScr(canvas.viewport(), 0.4f, -0.8f, 0.4f, 0.4f), QColor(0xffff00u));
  canvas.setScene(&qGScene);
  // runtime loop
  return app.exec();
}

使用QGraphicsView简化了代码,因为不需要渲染代码 - 它已经内置。

由于我({1}}没有多少经验,另一个问题让我非常努力:QGraphicsView能够在应用转换后自动修复视图位置[cg] ally 。在我的情况下,这相当适得其反,显然我的转变和QGraphicsView似乎在相反的方向“拉”。

因此,我已经吸取了当天的教训:QGrapicsView::setTransformationAnchor(QGraphicsView::NoAnchor)

是必须关闭它(在我的情况下不是意图)自动居中

我发现值得注意的其他细节是QGraphicsView::mapToScene(),它可以用来方便地将窗口小部件坐标(例如鼠标坐标)转换为场景空间。

snapshot of testQWidget-Zoom after port to QGraphicsView