使用Qt的缩放功能 [英] Zoom functionality using Qt

查看:76
本文介绍了使用Qt的缩放功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当前实现将视图缩放到视图的中心,因此当我们缩放它时,左上角或当前鼠标指针中出现的项目将不可见.

我想要基于当前鼠标指针的缩放功能,以便当前鼠标指针上显示的项目朝着视图的中心缩放.

缩放视图中心区域的代码

  void csGuiView :: wheelEvent(QWheelEvent * event){如果((event-> modifiers()& Qt :: ControlModifier)== Qt :: ControlModifier&&event-> angleDelta().x()== 0){QPoint pos = event-> pos();QPointF posf = this-> mapToScene(pos);双角= event-> angleDelta().y();双比例缩放因子;如果(角度> 0){scaleFactor = 1 +(角度/360 * 0.1);}否则,如果(angle< 0){scaleFactor = 1-(-angle/360 * 0.1);} 别的{scaleFactor = 1;}m_pvtData-> m_scale = scalingFactor;this-> scale(scalingFactor,scaleingFactor);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();双lf = posf.x()-pos.x()* wf/w;double tf = posf.y()-pos.y()* hf/h;/*尝试正确设置视口*/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-> accept();} 

解决方案

要进行缩放,始终以鼠标指针为中心–鼠标指针的位置只需成为缩放的起点即可.

听起来很简单,但我在准备演示时有些挣扎.(对不起,我的线性代数不是那么好.)但是,我终于开始运行它了.

我的示例代码 testQWidget-Zoom.cc :

  #include< vector>#include< QtWidgets>//用于演示缩放的小部件类画布类:公共QWidget {//类型:私人的:struct Geo {QRectF rect;QColor颜色;地理位置(const QRectF& rect,const QColor& color):rect(rect),颜色(color){}};//变量:私人的:bool _initDone:1;//标志:true ...已创建示例地理std :: vector< Geo>_场景;//要渲染的内容QMatrix _mat;//查看矩阵//方法:上市://构造函数.Canvas():QWidget(),_ initDone(false){}//析构函数虚拟〜Canvas()=默认值;//禁用:Canvas(const Canvas&)=删除;帆布&operator =(const Canvas&)=删除;私人的://初始化示例地理无效的init(){如果(_initDone)返回;_initDone = true;//构建场景(使用NDC,即x/y范围:[-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)));//获得初始缩放const int wView = width(),hView = height();_mat.scale(wView/2,hView/2);_mat.translate(1,1);}受保护的:虚拟void paintEvent(QPaintEvent * pQEvent)覆盖{在里面();//使成为QPainter qPainter(this);#if 0//也可以缩放线宽:qPainter.setMatrix(_mat);for(const Geo& geo:_scene){qPainter.setPen(geo.color);qPainter.drawRect(geo.rect);}#else//仅变换坐标:for(const Geo& geo:_scene){qPainter.setPen(geo.color);QRectF rect(geo.rect.topLeft()* _mat,geo.rect.bottomRight()* _mat);qPainter.drawRect(rect);}#endif//0}虚拟虚空wheelEvent(QWheelEvent * pQEvent)覆盖{//qDebug()<<车轮事件:"//qDebug()<<鼠标pos:"<<pQEvent-> pos();//pos()->虚拟画布bool matInvOK = false;QMatrix matInv = _mat.inverted(& matInvOK);如果(!matInvOK){qDebug()<<视图矩阵不可逆!";返回;}QPointF posNDC= QPointF(pQEvent-> pos().x(),pQEvent-> pos().y())* matInv;//qDebug()<<鼠标pos(NDC):"<<posNDC;浮点delta = 1.0f + pQEvent-> angleDelta().y()/1200.0f;//qDebug()<<"angleDelta:"<<pQEvent-> angleDelta().y();//qDebug()<<比例因子:"<<三角洲;_mat.translate(posNDC.x(),posNDC.y());//起点到终点_mat.scale(delta,delta);//规模_mat.translate(-posNDC.x(),-posNDC.y());//现货到原点更新();pQEvent-> accept();}};int main(int argc,char ** argv){QApplication app(argc,argv);帆布帆布;canvas.resize(512,512);canvas.show();//运行时循环返回app.exec();} 

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

  _mat.translate(posNDC.x(),posNDC.y());//起点到终点_mat.scale(delta,delta);//规模_mat.translate(-posNDC.x(),-posNDC.y());//现货到原点 

这是它的外观:

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

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


1 st 更新:

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

  #include< vector>#include< QtWidgets>//用于演示缩放的小部件类画布类:公共QWidget {//类型:私人的:struct Geo {QRectF rect;QColor颜色;地理位置(const QRectF& rect,const QColor& color):rect(rect),颜色(color){}};//变量:私人的:bool _initDone:1;//标志:true ...已创建示例地理std :: vector< Geo>_场景;//要渲染的内容QMatrix _mat;//查看矩阵//方法:上市://构造函数.Canvas():QWidget(),_ initDone(false){}//析构函数虚拟〜Canvas()=默认值;//禁用:Canvas(const Canvas&)=删除;帆布&operator =(const Canvas&)=删除;私人的://初始化示例地理无效的init(){如果(_initDone)返回;_initDone = true;const int wView = width(),hView = height();//构建场景(使用NDC,即x/y范围:[-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)));//将几何缩放到屏幕坐标QMatrix垫;mat.scale(wView/2,hView/2);mat.translate(1,1);对于(Geo& geo:_scene){geo.rect = QRectF(geo.rect.topLeft()* mat,geo.rect.bottomRight()* mat);}}受保护的:虚拟void paintEvent(QPaintEvent * pQEvent)覆盖{在里面();//使成为QPainter qPainter(this);qPainter.setMatrix(_mat);for(const Geo& geo:_scene){qPainter.setPen(geo.color);qPainter.drawRect(geo.rect);}}虚拟虚空wheelEvent(QWheelEvent * pQEvent)覆盖{//qDebug()<<车轮事件:";//qDebug()<<鼠标pos:"<<pQEvent-> pos();浮点delta = 1.0f + pQEvent-> angleDelta().y()/1200.0f;//qDebug()<<"angleDelta:"<<pQEvent-> angleDelta().y();//qDebug()<<比例因子:"<<三角洲;_mat.translate(pQEvent-> pos().x(),pQEvent-> pos().y());//起点到终点_mat.scale(delta,delta);//规模_mat.translate(-pQEvent-> pos().x(),-pQEvent-> pos().y());//现货到原点更新();pQEvent-> accept();}};int main(int argc,char ** argv){QApplication app(argc,argv);帆布帆布;canvas.resize(256,256);canvas.show();//运行时循环返回app.exec();} 

相关的三行变化不大–鼠标坐标直接应用于变换.

顺便说一句.我更改了渲染图–现在它可以缩放线条宽度,就像我以前使用的一样

  qPainter.setMatrix(_mat); 

Canvas :: paintEvent()中的

,而不是手动"转换所有点.

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


2 nd 更新:

建议的矩阵操作可在

Current implementation, zooms towards the center of View so items present in the top left corner or the current mouse pointer is not visible when we Zoom it.

I want zoom functionality based on the current mouse pointer so items present on the current mouse pointer zoom towards the center of the view.

Code for Zoom base don center of view area

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

解决方案

To zoom always centered at mouse pointer – the position of mouse pointer just has to become the origin of scaling.

It sounds that simple but I struggled a bit to prepare a demonstration. (I'm not that good in linear algebra, sorry.) However, I finally got it running.

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

and these three lines are the actual interesting ones (in 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

And this is how it looks:

The first image is a snapshot of the application just after starting it.

Then I pointed into the center of the red rectangle and turned the wheel slightly. The red rectangle grew around the mouse pointer as intended.


1st Update:

And, this is the updated version which uses screen coordinates directly (instead of converting everything to NDCs):

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

The relevant three lines didn't change much – the mouse coordinates are applied directly to transformation.

Btw. I changed the rendering – it now scales line width as well as I used

      qPainter.setMatrix(_mat);

in Canvas::paintEvent() instead of transforming all points "manually".

The snapshot shows the application after I pointed into the center of the blue rectangle and turned the mouse wheel:


2nd Update:

The suggested matrix manipulation works in a QGraphicsView as well:

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

Using a QGraphicsView simplifies code as no rendering code is needed – it's already built-in.

As I have not (yet) much experience with QGraphicsView, another issue hit me quite hard: The QGraphicsView is able to fix the view position automati[cg]ally after a transformation has been applied. In my case, this was rather counter-productive as obviously my transformation and the QGraphicsView seemed to "pull" in opposite directions.

Hence, I've learnt my lesson of the day: QGrapicsView::setTransformationAnchor(QGraphicsView::NoAnchor) is necessary to switch off this (in my case not-intended) auto-centering.

The other detail I find worth to notice is QGraphicsView::mapToScene() which can be used to conveniently convert widget coordinates (e.g. mouse coordinates) to scene space.

这篇关于使用Qt的缩放功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆