問題描述
當前實現,向視圖中心縮放,因此當我們縮放時,左上角的項目或當前鼠標指針不可見.
我想要基于當前鼠標指針的縮放功能,以便當前鼠標指針上的項目向視圖中心縮放.
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() <<車輪事件:"http://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.
這篇關于使用 Qt 的縮放功能的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!