使用 Qt 的缩放功能
当前实现,向视图中心缩放,因此当我们缩放时,左上角的项目或当前鼠标指针不可见.
我想要基于当前鼠标指针的缩放功能,以便当前鼠标指针上的项目向视图中心缩放.
Zoom base on 视区中心代码
void csGuiView::wheelEvent(QWheelEvent *event){if ((event->modifiers()&Qt::ControlModifier) == Qt::ControlModifier&&事件->angleDelta().x() == 0){QPoint pos = event->pos();QPointF posf = this->mapToScene(pos);双角 = 事件->angleDelta().y();双倍比例因子;如果(角度>0){缩放系数 = 1 + ( 角度/360 * 0.1);}else if (angle <0){缩放系数 = 1 - ( -angle/360 * 0.1);} 别的{缩放因子 = 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()- 这个->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;双 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(事件);}事件->接受();}
解决方案 缩放总是以鼠标指针为中心 -ndash;鼠标指针的位置只需要成为缩放的原点即可.
听起来很简单,但我在准备演示时有点费劲.(我的线性代数不是很好,抱歉.)不过,我终于让它运行起来了.
我的示例代码testQWidget-Zoom.cc
:
#include <向量>#include //用于演示缩放的小部件类类画布:公共 QWidget {//类型:私人的:结构地理{QRectF 矩形;QColor颜色;地理(const QRectF &rect,const QColor &color):矩形(矩形),颜色(颜色){ }};//变量:私人的:布尔 _initDone : 1;//flag: true ... 示例地理创建std::vector_场景;//要渲染的内容QMatrix_mat;//查看矩阵//方法:民众://构造函数.画布():QWidget(),_initDone(假){}//析构函数.虚拟 ~Canvas() = 默认值;//禁用:Canvas(const Canvas&) = 删除;帆布&运算符=(const Canvas&) = 删除;私人的://初始化样本地理无效初始化(){如果(_initDone)返回;_initDone = 真;//构建场景(使用 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);}受保护:虚拟无效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}虚拟无效轮事件(QWheelEvent *pQEvent)覆盖{//qDebug() <<车轮事件:"//qDebug() <<鼠标位置:"<<pQEvent->pos();//pos() ->虚拟画布bool matInvOK = false;QMatrix matInv = _mat.inverted(&matInvOK);如果(!matInvOK){qDebug() <<"视图矩阵不可逆!";返回;}QPointF posNDC= QPointF(pQEvent->pos().x(), pQEvent->pos().y()) * matInv;//qDebug() <<鼠标位置(NDC):"<<posNDC;float 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);画布.show();//运行时循环返回 app.exec();}
这三行是??真正有趣的(在Canvas::wheelEvent()
中):
_mat.translate(posNDC.x(), posNDC.y());//原点定位_mat.scale(delta, delta);//规模_mat.translate(-posNDC.x(), -posNDC.y());//点到原点
这是它的样子:
第一张图片是应用程序启动后的快照.
然后我指向红色矩形的中心并轻轻转动轮子.红色矩形按预期围绕鼠标指针增长.
<小时>1st 更新:
而且,这是直接使用屏幕坐标的更新版本(而不是将所有内容都转换为 NDC):
#include <向量>#include //用于演示缩放的小部件类类画布:公共 QWidget {//类型:私人的:结构地理{QRectF 矩形;QColor颜色;地理(const QRectF &rect,const QColor &color):矩形(矩形),颜色(颜色){ }};//变量:私人的:布尔 _initDone : 1;//flag: true ... 示例地理创建std::vector_场景;//要渲染的内容QMatrix_mat;//查看矩阵//方法:民众://构造函数.画布():QWidget(),_initDone(假){}//析构函数.虚拟 ~Canvas() = 默认值;//禁用:Canvas(const Canvas&) = 删除;帆布&运算符=(const Canvas&) = 删除;私人的://初始化样本地理无效初始化(){如果(_initDone)返回;_initDone = 真;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)));//缩放几何到屏幕坐标Q矩阵垫;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);}}受保护:虚拟无效paintEvent(QPaintEvent * pQEvent)覆盖{在里面();//使成为QPainter qPainter(this);qPainter.setMatrix(_mat);for (const Geo &geo : _scene) {qPainter.setPen(geo.color);qPainter.drawRect(geo.rect);}}虚拟无效轮事件(QWheelEvent *pQEvent)覆盖{//qDebug() <<"轮子事件:";//qDebug() <<鼠标位置:"<<pQEvent->pos();float 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);画布.show();//运行时循环返回 app.exec();}
相关的三行变化不大–鼠标坐标直接应用于变换.
顺便说一句.我改变了渲染–它现在可以像我使用的一样缩放线宽
qPainter.setMatrix(_mat);
在 Canvas::paintEvent()
中,而不是手动"转换所有点.
我指向蓝色矩形的中心并转动鼠标滚轮后的快照显示:
<小时>2nd 更新:
建议的矩阵操作在
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.
相关文章