問題描述
使用Qt的QGraphicsView
顯示圖像時,鼠標滾輪的動作由其自帶的wheelEvent
事件控制。滾動滾輪可以控制圖像上下移動。
現希望通過滾動滾輪實現圖像縮放,且縮放的中心爲鼠標所在位置。
解決方案
1. 自定義GraphicsView
類
HighGraphicsView
類繼承自QGraphicsView
類,重寫了wheelEvent
和mouseMoveEvent
兩個事件。其中,對mouseMoveEvent
,在原有的基礎上加入了觸發鼠標位置變化的事件,這個鼠標位置是相對於View
的(x,y)
座標。對wheelEvent
,放棄原有事件,記錄滾動間隔,發送信號。
HighGraphicsView.h
#pragma once
#include <qgraphicsview.h>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QScrollBar>
class HighGraphicsView :
public QGraphicsView
{
Q_OBJECT
public:
HighGraphicsView(QWidget *parent = nullptr);
~HighGraphicsView();
signals:
void mousePositionChanged(int x, int y);
void wheelScrollChanged(int step);
protected:
void wheelEvent(QWheelEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
// Mouse Position
int x, y;
};
HighGraphicsView.cpp
#include "HighGraphicsView.h"
HighGraphicsView::HighGraphicsView(QWidget *parent)
: QGraphicsView(parent)
{
}
HighGraphicsView::~HighGraphicsView()
{
}
void HighGraphicsView::wheelEvent(QWheelEvent *event)
{
QPoint numPixels, numDegrees;
numPixels = event->pixelDelta();
numDegrees = event->angleDelta() / 8;
int step = 0;
if (!numPixels.isNull())
{
step = numPixels.y();
}
else if (!numDegrees.isNull())
{
QPoint numSteps = numDegrees / 15;
step = numSteps.y();
}
// Enlarge: +; Shrink: -
emit wheelScrollChanged(step);
}
void HighGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
QGraphicsView::mouseMoveEvent(event);
x = event->x();
y = event->y();
emit mousePositionChanged(x, y);
}
2. 記錄鼠標位置
鼠標的位置包括三類:
- 圖像上的位置;
View
中的位置;
Scene
中的位置;
其對應的轉換關係如下所示:
其中,是View->horizontalScrollBar()->value()
的值,是View->verticalScrollBar()->value()
的值,是當前圖像方向上的縮放比例,其表達式爲:
在引用HighGraphicsView
的類中,存儲兩類鼠標位置:
// Mouse Position
int x, y; // x0,y0
int viewX, viewY; // x,y
創建槽onMousePositionChanged
,用來接收View
發出的mousePositionChanged
信號
void onMousePositionChanged(int x, int y)
{
viewX = x;
viewY = y;
QRectF rect = View->mapToScene(View->viewport()->geometry()).boundingRect();
x += int(rect.x() - 1); // Rectangle begin with (1,1)
y += int(rect.y() - 1);
x = int(double(x) * (double(Width) / double(currentWidth)));
y = int(double(y) * (double(Height) / double(currentHeight)));
this->x = x;
this->y = y;
if (x >= Width || x < 0 || y >= Height || y < 0)
{
ui.statusBar->showMessage(u8"Outside");
}
else
{
ui.statusBar->showMessage(QString().sprintf(u8"(x=%d, y=%d)", x, y));
}
}
綁定信號與槽
// Receive (x,y) coordinate
connect(View, SIGNAL(mousePositionChanged(int, int)), this, SLOT(onMousePositionChanged(int, int)));
3. 圖像縮放
要以鼠標爲中心進行圖像縮放,可以分爲兩個步驟進行:1. 縮放;2. 平移。縮放過程較爲簡單,只需要根據比例對原圖進行縮放即可。平移的目的是爲了使圖像在縮放前後鼠標所在的位置相對於圖像不變、相對於視窗(View
)不變,不變量爲、$ (x_0, y_0)$。
根據之間的關係(見第1小節),可以得到
由於、$ (x_0, y_0)h,v$的值,即View
相對於Scene
的偏移量:
通過View->horizontalScrollBar()->setValue()
和View->verticalScrollBar()->setValue()
設置偏移量,完成平移。
創建響應滾輪變化信號的槽:
void onWheelScrollChanged(int step)
{
currentHeight += currentHeight / 20 * step;
currentWidth += currentWidth / 20 * step;
ImageItem->setPixmap(QPixmap::fromImage(Image.scaled(currentWidth, currentHeight)));
Scene = new QGraphicsScene(this);
Scene->addItem(ImageItem);
View->setScene(Scene);
int horizontal, vertical;
horizontal = int(double(x * currentWidth) / double(Width) - viewX);
vertical = int(double(y * currentHeight) / double(Height) - viewY);
View->horizontalScrollBar()->setValue(horizontal);
View->verticalScrollBar()->setValue(vertical);
}
綁定信號與槽
// Receive wheel scroll signal
connect(View, SIGNAL(wheelScrollChanged(int)), this, SLOT(onWheelScrollChanged(int)));