0 事件
用戶對界面中的控件進行操作后,控件需要進行響應,響應一般有兩種方式,一種是之前學過的槽函數(shù),另一種是事件,部分操作無法通過槽函數(shù)響應,只能通過事件,例如鼠標移動,鍵盤輸入等。
當某個事件(鼠標、鍵盤)發(fā)生的時候,相關控件就會收到這個事件,并且調用相應的事件處理函數(shù)進行相應,事件處理函數(shù)的命名都是以Event結尾的。
1 需求
在窗口內的任意位置點擊,然后窗口會有一個標簽(使用QLabel)顯示鼠標所在位置的坐標:隨著鼠標在窗口內不同位置進行點擊,標簽顯示的坐標也在變換。效果如下圖所示:
2 查看控件的事件處理函數(shù)
在幫助界面搜索QWidget,然后點擊Protected Functions
可以看到很多以“Event”結尾的函數(shù),這些函數(shù)的參數(shù)都是各個事件,并且這些函數(shù)都使用了virtual修飾,即他們都是虛函數(shù),繼承時表現(xiàn)為多態(tài)特性。
我們可以往下滑,找到鼠標事件
截圖中紅色方框框出來的幾個分別為:鼠標單擊、鼠標移動、鼠標點擊、鼠標釋放,說明這個控件可以對這四種鼠標事件進行響應,它們的參數(shù)均為鼠標事件指針(QMouseEvent *)。
3 UI設計
打開QT設計師,在Display Widgets中選擇Label,將其拖到窗口中:
給主窗口來個布局
右下角可以看到類的繼承層次結構,我們剛剛拖到窗口中的控件,類型是QLabel
在QFrame的frameShape屬性中選擇Box
窗口中會出現(xiàn)黑色的框線
4 新建一個類,繼承QLabel
設計好UI后,之所以還要新建類去繼承已有類,是因為原先的事件處理函數(shù)無法滿足需求,我們需要重寫相應的事件處理函數(shù)(這部分稍后會說)。
由于新建類的時候,父類選項沒有QLabel,所以這里新建類的時候先選擇繼承QWidget(也可以選其他),產生代碼后手動改父類
打開新建類的頭文件,將父類由QWidget改成QLabel
對應的cpp文件也要改,在構造函數(shù)的初始化列表中,將父類改過來
5 對已有對象進行類型提升
在UI設計界面的對象樹中,右擊我們剛剛在UI設計階段,拖到窗口中的空間(QLabel對象),然后點擊“提升為”
在彈出的對話框中,填寫提升的類名稱,然后點擊添加
再次填寫提升的類名稱,然后點擊提升
此時可以看到,我們剛剛拖到窗口中的控件,類型變成了MyLabel
然后Ctrl+R,看看有沒有錯。
6 重寫事件處理函數(shù)
MyLabel.h內容如下:
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QWidget>
#include <QLabel>
class MyLabel : public QLabel
{
Q_OBJECT
public:
explicit MyLabel(QWidget *parent = nullptr);
signals:
public:
void mousePressEvent(QMouseEvent *event);
};
#endif // MYLABEL_H
輸入完函數(shù)聲明后,按下alt+回車,會顯示提示消息,然后選擇“在mylabel.cpp添加定義”,就能在mylabel.cpp中自動生成函數(shù)頭,十分方便。
MyLabel.cpp內容如下:
#include "mylabel.h"
#include "QMouseEvent"
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
}
void MyLabel::mousePressEvent(QMouseEvent *event)
{
//獲取鼠標事件的一些信息
//獲取坐標
int x = event->x();
int y = event->y();
//格式化顯示坐標,并居中
QString str = QString("<h1><center>[%1, %2]</center></h1>").arg(x).arg(y);
this->setText(str);
}
這里QString str = QString("<h1><center>[%1, %2]</center></h1>").arg(x).arg(y);
是將字符串進行格式化匹配,QString中是可以使用html語法的,隨后的this->setText(str)
是輸出指定的字符串。
現(xiàn)在我們按下Ctrl+R運行,然后點擊窗口中的任意一點,然后就能看到坐標了
至此,大功告成。
我們這個項目的結構如下圖所示:
項目中的main.cpp、widget.h和widget.cpp都沒有修改,這幾個文件一般很少去動,除非要和后端進行聯(lián)動。
7 項目進一步拓展
(1)獲取鼠標按鍵
在mylabel.cpp中輸入event->button();
,然后移動光標到函數(shù)上,按兩遍F1(筆記本電腦可能和某些屏幕、聲音設置的快捷鍵沖突,這種情況下需要按住鍵盤左下角的fn鍵,然后按兩遍F1)
可以看到這個函數(shù)的返回值類型是Buttons,點擊QT::MouseButton
QT 5.14.2的幫助文檔中,button()返回值是Qt3DInput::QMouseEvent::Buttons
類型,而用這個類來定義event->button的返回值卻報錯,我不知道怎么回事,所以上面使用的是QT 5.9文檔內容。也就是說,5.14.2并不是穩(wěn)定版本,編譯器和幫助文檔有沖突,我們以后查看返回值類型的時候,用ctrl,然后鼠標點擊要查看的函數(shù),找到源碼查看返回值類型,然后再調用幫助文檔。
可以看到QT::MouseButton
枚舉類型(一般Qt后面跟兩個冒號的,都是枚舉類型),紅框框出來的為需要掌握的鍵,分別是:未按下、任意鍵、左鍵、右鍵、中鍵(滾輪)、中鍵,最后兩個含義相同。
將mylabel.cpp的代碼修改如下:
#include "mylabel.h"
#include "QMouseEvent"
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
}
void MyLabel::mousePressEvent(QMouseEvent *event)
{
//獲取鼠標事件的一些信息
//獲取坐標
int x = event->x();
int y = event->y();
//獲取鼠標按鍵
Qt::MouseButton btn = event->button();
QString strButton ="" ;
if(btn == Qt::LeftButton)
{
strButton = "LeftButton";
}
if(btn == Qt::RightButton)
{
strButton = "RightButton";
}
if(btn == Qt::MidButton)
{
strButton = "MidButton";
}
//格式化顯示坐標,并居中
QString str = QString("<h1><center>[%1, %2][%3]</center></h1>").
arg(x).arg(y).arg(strButton);
this->setText(str);
qDebug()<<"press";
}
運行結果如下:
(2)鼠標移動
在MyLabel類中新增一個函數(shù),定義如下(mylabel.h中也要聲明,這里不再贅述):
void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
//獲取坐標
int x = ev->x();
int y = ev->y();
QString str=QString("<h1><center>Move[%1, %2][%3]</center></h1>").arg(x).arg(y);
this->setText(str);
}
點擊運行后,按下鼠標,會調用mousePressEvent,移動鼠標會調用mouseMoveEvent
(3)顯示多個按鍵
對于按下按鍵后(不松開)拖動的情況,會先調用mousePressEvent,然后調用mouseMoveEvent,我們希望標簽能顯示Move和坐標的同時,也顯示按下了哪個按鈕,例如Move[300, 300][LeftButton]
,對于鼠標移動過程中,同時按下左鍵和右鍵的情況,我們也希望能夠檢測到。則可以對mouseMoveEvent做如下修改:
void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
//獲取坐標
int x = ev->x();
int y = ev->y();
//獲取鼠標按鍵
Qt::MouseButtons btns = ev->buttons(); //可以一次獲取多個按鍵
QString strButton ="" ;
if(btns & Qt::LeftButton) //按位與操作
{
strButton += "LeftButton "; //+是字符串拼接,因為有可能按了多個鍵
}
if(btns & Qt::RightButton)
{
strButton += "RightButton "; //+是字符串拼接,因為有可能按了多個鍵
}
if(btns & Qt::MidButton)
{
strButton += "MidButton ";
}
QString str=QString("<h1><center>Move[%1, %2][%3]</center></h1>").
arg(x).arg(y).arg(strButton);
this->setText(str);
}
這里需要注意的是,ev->buttons()返回一個數(shù),我們用二進制的角度來看,假如它返回的是000101,那么它是000100(MidButton)和000001(LeftButton)按位與的結果,表示同時按下了兩個按鍵。
后面的幾個if判斷條件,其實是按位與運算。因為每一種Qt::MouseButton常量,只有某一位是1,其他位都為0,因此要從ev->buttons()的返回值中將某一位抽取出來,可以使用按位與運算,例如要判斷是否按下了RightButton(000010),可以用RightButton與返回值進行按位與運算,結果為0則表示沒按下,非0則表示按下了,假設返回值為000011,000010&000011結果是000010,結果非0,因此按下了。
運行后,按下鼠標拖動如下:
在按下鼠標(左鍵)拖動的那一瞬間,是否調用了mousePressEvent?可以在mousePressEvent函數(shù)中增加一行打印功能,來查看是否調用。
我自己嘗試了,點下的那一瞬間,確實是先調用mousePressEvent,然后調用mouseMoveEvent,即點下的一瞬間先相應mousePressEvent,隨后移動鼠標時相應mouseMoveEvent,此時由于鼠標左鍵還沒松開,因此能被ev->buttons()檢測到。
對于同時按下多個按鍵進行拖動,也能正常顯示
(4)設置默認鼠標跟蹤
上面的程序,在開始的時候,沒有跟蹤鼠標,也就是下面這個樣子:
只有點擊了窗口以后,才會顯示“Move+坐標+按鍵名稱”
如果我們想在窗口打開后,鼠標移動到窗口中(未點擊)就開始跟蹤鼠標的移動,那么可以在構造函數(shù)中進行設置
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
//默認情況下,窗口不會主動跟蹤鼠標
//只有當某個鼠標按鍵按下的情況下才開始跟蹤
//如果想一開始跟蹤,就要使用以下函數(shù)
this->setMouseTracking(true);
}
8 QT事件分發(fā)機制
為什么我們點擊了鼠標,就會調用mousePressEvent,移動了鼠標就會調用mouseMoveEvent?
當某個事件(鼠標、鍵盤)發(fā)生的時候,相應的控件就會收到這個事件,并且根據(jù)事件的類型調用相應的事件處理函數(shù),這個過程都在一個名為event的事件中,這個函數(shù)可以在幫助文檔中查看,它是一個虛函數(shù),可以被子類重寫。
一般情況下,不會去重寫這個函數(shù),我們能這里為了了解事件分發(fā)機制,因此對其進行重寫。
這里僅僅以鼠標移動為例
bool MyLabel::event(QEvent *e)
{
//返回值:true表示該事件得到處理,如果是false,沒被處理,事件會繼續(xù)傳遞到父窗口
//QEvent就是所有Event類的父類,這里其實是使用了多態(tài)
//判斷event的類型
if(e->type()==QEvent::MouseMove) //e->type()返回的是鼠標事件的類型
{
this->mouseMoveEvent(static_cast<QMouseEvent *>(e)); //這句如果注釋掉,則鼠標移動將不會有反應
return true;
}
//其他類型的事件,交給父類的event函數(shù)去處理
return QLabel::event(e); //父類的event函數(shù)因為被覆蓋了,所以需要域作用符
}
如果將this->mouseMoveEvent(static_cast<QMouseEvent *>(e));
注釋掉,則當鼠標移動時將不會有反應,此時就把鼠標移動給攔截過濾了。文章來源:http://www.zghlxwxcb.cn/news/detail-543705.html
事件的機制,可以用下面這張圖來總結,事件過濾器這里沒講,這個并不重要:文章來源地址http://www.zghlxwxcb.cn/news/detail-543705.html
到了這里,關于QT的使用3:鼠標事件的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!