Qt無邊框視窗實現拖動和改變大小(修改)
先前發的程式碼有問題,主要是當視窗達到最大和最小尺寸的時候視窗改變大小的實際效果不符合邏輯,現在修改以後沒問題了。
下面我主要分享一下我的思考過程:
當我們改變一個視窗的大小時,如果視窗的寬度(高度)已經最小(最大),那麼很顯然只能放大(減小),同時拖動4個角應該可以同時改變寬度和高度,或者只改變寬度或高度(根據滑鼠的位置和視窗的大小)。改變尺寸時的實際效果大概就是像上面描述的那樣。
首先,滑鼠移動到邊緣的時候我們應當可以檢測的到,同時改變滑鼠的樣式。這個思路就是計算滑鼠的位置 和視窗上下左右的距離。你可以設定一個margin、或者設定4個margin,分別判斷 滑鼠是否落入你的邊緣區域。在實際實驗中我發現一定要給邊緣空出距離,如果你的視窗全都被其它部件遮住了,那麼在預設的情況下你的視窗是接收不到滑鼠移動事件的(除非你給靠近邊緣的部件開啟滑鼠追蹤,顯然很麻煩,而且開滑鼠追蹤是要消耗資源的)。
然後,我們將邊緣進行分類,分為 上下左右 還有4個角,4個角看成是上下左右的組合,用列舉表示成這樣。
enum {nodir,
top = 0x01,
bottom = 0x02,
left = 0x04,
right = 0x08,
topLeft = 0x01 | 0x04,
topRight = 0x01 | 0x08,
bottomLeft = 0x02 | 0x04,
bottomRight = 0x02 | 0x08} resizeDir; //更改尺寸的方向
這樣分類的好處是我們可以檢查是否包含水平分量、是否包含縱向分量,然後可以對寬度和高度進行獨立的處理,就像這樣:
if(resizeDir & top){ //檢測更改尺寸方向中包含的上下左右分量
if(height() == minimumHeight()){
ptop = min(event->globalY(),ptop);
}
else if(height() == maximumHeight()){
ptop = max(event->globalY(),ptop);
}
else {
ptop = event->globalY();
}
}
else if(resizeDir & bottom){
if(height() == minimumHeight()){
pbottom = max(event->globalY(),ptop);
}
else if(height() == maximumHeight()){
pbottom = min(event->globalY(),ptop);
}
else{
pbottom = event->globalY();
}
}
if(resizeDir & left){ //檢測左右分量
if(width() == minimumWidth()){
pleft = min(event->globalX(),pleft);
}
else if(width() == maximumWidth()){
pleft = max(event->globalX(),pleft);
}
else{
pleft = event->globalX();
}
}
else if(resizeDir & right){
if(width() == minimumWidth()){
pright = max(event->globalX(),pright);
}
else if(width() == maximumWidth()){
pright = min(event->globalX(),pright);
}
else{
pright = event->globalX();
}
}
處理完之後就是得到 視窗的矩形座標,程式碼原理很簡單:反正就是包含哪個能改變的分量就改變它。
再說說視窗拖動的實現:
視窗拖動的過程是這樣的: 滑鼠左鍵按下-> 滑鼠移動(拖動)
我們操作的時候就這2個步驟,實際程式設計的時候要再細化一下,也就是在中間插一些步驟。首先我們想一下,視窗拖動大小是不變的,那麼也就是隻要確定左上角的座標就行了。左上角的座標可以利用 滑鼠按住的位置和左上角的相對位置不變 來進行確定。 所以當左鍵按下的時候我們要記錄一下 按下的位置和左上角的相對位置。移動以後我們利用滑鼠移動的位置和相對位置來獲得調整後的視窗位置,也就是 程式設計了4個過程:
滑鼠左鍵按下-> 記錄左上角和滑鼠按下的相對位置->滑鼠移動->利用滑鼠移動後的位置和記錄的相對位置計算 視窗位置
下面貼上 實現拖動和 改變視窗大小的完整程式碼:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
QPoint dragPosition; //滑鼠拖動的位置
int edgeMargin; //滑鼠檢測的邊緣距離
enum {nodir,
top = 0x01,
bottom = 0x02,
left = 0x04,
right = 0x08,
topLeft = 0x01 | 0x04,
topRight = 0x01 | 0x08,
bottomLeft = 0x02 | 0x04,
bottomRight = 0x02 | 0x08} resizeDir; //更改尺寸的方向
private:
void testEdge(); //檢測滑鼠是否接近視窗邊緣
protected:
void mousePressEvent(QMouseEvent*event);
void mouseMoveEvent(QMouseEvent*event);
void mouseReleaseEvent(QMouseEvent*event);
};
#define min(a,b) ((a)<(b)? (a) :(b))
#define max(a,b) ((a)>(b)? (a) :(b))
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
edgeMargin = 4; //設定檢測邊緣為4
resizeDir = nodir; //初始化檢測方向為無
setWindowFlags(Qt::FramelessWindowHint); //設定無邊框
setMouseTracking(true); //開啟滑鼠追蹤
setMinimumSize(1000,540);
}
Widget::~Widget()
{
delete ui;
}
void Widget::mousePressEvent(QMouseEvent * event)
{
event->ignore();
if (event->button() == Qt::LeftButton) //每當按下滑鼠左鍵就記錄一下位置
{
dragPosition = event->globalPos() - frameGeometry().topLeft(); //獲得滑鼠按鍵位置相對視窗左上面的位置
}
}
void Widget::testEdge()
{
int diffLeft = abs(cursor().pos().x() - frameGeometry().left()); //計算滑鼠距離視窗上下左右有多少距離
int diffRight = abs(cursor().pos().x() - frameGeometry().right());
int diffTop = abs(cursor().pos().y() - frameGeometry().top());
int diffBottom = abs(cursor().pos().y() - frameGeometry().bottom());
QCursor tempCursor; //獲得當前滑鼠樣式,注意:只能獲得當前滑鼠樣式然後再重新設定滑鼠樣式
tempCursor = cursor(); //因為獲得的不是滑鼠指標,所以不能這樣用:cursor().setXXXXX
if(diffTop < edgeMargin){ //根據 邊緣距離 分類改變尺寸的方向
if(diffLeft < edgeMargin){
resizeDir = topLeft;
tempCursor.setShape(Qt::SizeFDiagCursor);
}
else if(diffRight < edgeMargin){
resizeDir = topRight;
tempCursor.setShape(Qt::SizeBDiagCursor);
}
else{
resizeDir = top;
tempCursor.setShape(Qt::SizeVerCursor);
}
}
else if(diffBottom < edgeMargin){
if(diffLeft < edgeMargin){
resizeDir = bottomLeft;
tempCursor.setShape(Qt::SizeBDiagCursor);
}
else if(diffRight < edgeMargin){
resizeDir = bottomRight;
tempCursor.setShape(Qt::SizeFDiagCursor);
}
else{
resizeDir = bottom;
tempCursor.setShape(Qt::SizeVerCursor);
}
}
else if(diffLeft < edgeMargin){
resizeDir = left;
tempCursor.setShape(Qt::SizeHorCursor);
}
else if(diffRight < edgeMargin){
resizeDir = right;
tempCursor.setShape(Qt::SizeHorCursor);
}
else{
resizeDir = nodir;
tempCursor.setShape(Qt::ArrowCursor);
}
setCursor(tempCursor); //重新設定滑鼠,主要是改樣式
}
void Widget::mouseMoveEvent(QMouseEvent * event)
{
event->ignore();
if (event->buttons() & Qt::LeftButton){ //如果左鍵是按下的
if(resizeDir == nodir){ //如果滑鼠不是放在邊緣那麼說明這是在拖動視窗
move(event->globalPos() - dragPosition);
}
else{
int ptop,pbottom,pleft,pright; //視窗上下左右的值
ptop = frameGeometry().top();
pbottom = frameGeometry().bottom();
pleft = frameGeometry().left();
pright = frameGeometry().right();
if(resizeDir & top){ //檢測更改尺寸方向中包含的上下左右分量
if(height() == minimumHeight()){
ptop = min(event->globalY(),ptop);
}
else if(height() == maximumHeight()){
ptop = max(event->globalY(),ptop);
}
else{
ptop = event->globalY();
}
}
else if(resizeDir & bottom){
if(height() == minimumHeight()){
pbottom = max(event->globalY(),ptop);
}
else if(height() == maximumHeight()){
pbottom = min(event->globalY(),ptop);
}
else{
pbottom = event->globalY();
}
}
if(resizeDir & left){ //檢測左右分量
if(width() == minimumWidth()){
pleft = min(event->globalX(),pleft);
}
else if(width() == maximumWidth()){
pleft = max(event->globalX(),pleft);
}
else{
pleft = event->globalX();
}
}
else if(resizeDir & right){
if(width() == minimumWidth()){
pright = max(event->globalX(),pright);
}
else if(width() == maximumWidth()){
pright = min(event->globalX(),pright);
}
else{
pright = event->globalX();
}
}
setGeometry(QRect(QPoint(pleft,ptop),QPoint(pright,pbottom)));
}
}
else testEdge(); //當不拖動視窗、不改變視窗大小尺寸的時候 檢測滑鼠邊緣
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
event->ignore();
if(resizeDir != nodir){ //還原滑鼠樣式
testEdge();
}
}