使用Qt的缩放功能 [英] Zoom functionality using 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屋!