QT滑鼠訊息分析

家裡無礦有程式碼發表於2020-12-23

本文主要探索以下幾個知識點:
1.setMouseTracking的使用

2.widget的滑鼠訊息會上發給父視窗,其機制是怎樣的,怎麼阻止這種行為(WA_NoMousePropagation的使用)

3.WA_Hover有什麼用,為什麼有時需要這個.
4.和Win32視窗程式設計的一些區別(不熟悉Win32程式設計的自動略過)

先看看我們要測試的程式的樣子:

如上圖所示,控制元件的父子關係為:

青色對應的類為MyChildWidget
紫色對應的類為MoveableWidget
灰色對應的類為MainWindow也就是我們的主視窗.

接下來我們一步一步地,先把框架程式碼寫好在專案上點右鍵,選擇新增新檔案

在彈出的對話方塊中選C++  C++ Class   Choose...然後輸入MoveableWidget,並從QWidget繼承,如下圖:

同樣的手法,新增MyChildWidget

去到QT設計介面,拖一個Widget控制元件上去,修改下大小,並右鍵單擊此控制元件,選擇"提升為..."

提升為MoveableWidget,如下圖:

在再拖動一個Widget控制元件到MoveableWidget裡,修改下大小,顯得小一些,然後點右鍵,選擇"提升為..."

同樣的手法,提升為MyChildWidget,最終的樣子如下圖:

到此,我們的架構就搭建完成了.
給MoveableWidget塗成紫色,繼承函式paintEvent

void MoveableWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

    QPainter p(this);
    p.setPen(Qt::NoPen);
    p.setBrush(Qt::darkMagenta);
    p.drawRect(rect());
}

同樣的手法給MyChildWidget塗成青色

void MyChildWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

    QPainter p(this);
    p.setPen(Qt::NoPen);
    p.setBrush(Qt::cyan);
    p.drawRect(rect());
}

執行一下,顯示畫面如下:

A)測試setMouseTracking
我們先修改MoveableWidget
建構函式中加入setMouseTracking(true);
 

MoveableWidget::MoveableWidget(QWidget *parent) : QWidget(parent)
{
    setMouseTracking(true);
}

列印mouseMoveEvent

void MoveableWidget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug("MoveableWidget::mouseMoveEvent: x=%d, y=%d\n", event->x(), event->y());
}


注意,我並沒有在MainWindow中測試,原因見:https://netpt.net/forum.php?mod=viewthread&tid=65
編譯執行,我們可以看出
1)當滑鼠移到MoveableWidget上,會出現列印,這表明setMouseTracking起作用了.
2)當滑鼠移動到子視窗MyChildWidget時,列印停止
3)當滑鼠移動到主視窗上,列印停止


B)測試WA_Hover

在A)測試中,我們注意到了一點,當滑鼠移動到子視窗MyChildWidget上,列印停止,
如果我們想要這樣的結果: 就算移動到子Widget上,MoveableWidget依然可以收到滑鼠訊息事件.
那麼就需要WA_Hover出場了

上面是官方的說明,似乎沒有說明個啥出來,我來補充下:
設定WA_Hover後,就會收到三種訊息:
QEvent::HoverEnter  //滑鼠進入到控制元件
QEvent::HoverLeave  //滑鼠離開控制元件
QEvent::HoverMove  //滑鼠在控制元件上移動.
我們給MoveableWidget加上WA_Hover屬性,程式碼如下:

MoveableWidget::MoveableWidget(QWidget *parent) : QWidget(parent)
{
    setAttribute(Qt::WA_Hover, true);
    setMouseTracking(true);
}

然後,繼承event函式,增加一些列印:

bool MoveableWidget::event(QEvent *e)
{
    if (   e->type() == QEvent::HoverEnter 
        || e->type() == QEvent::HoverLeave
        || e->type() == QEvent::HoverMove   )
    {
        QHoverEvent* pHoverEvent = static_cast<QHoverEvent *>(e);

        const char* pType = "QEvent::HoverMove";
        if (e->type() == QEvent::HoverEnter)
        {
            pType = "QEvent::HoverEnter";
        }else if (e->type() == QEvent::HoverLeave)
        {
            pType = "QEvent::HoverLeave";
        }

        qDebug("type=%s (%d, %d)\n", pType, pHoverEvent->pos().x(), pHoverEvent->pos().y());
    }

    return QWidget::event(e);
}


編譯執行,我們可以看出
1)當滑鼠移到MoveableWidget上,Hover的訊息會先列印,然後會列印mouseMoveEvent.
2)當滑鼠移動到子視窗MyChildWidget時,只會列印Hover的訊息
3)當滑鼠移動到主視窗上,列印停止
4).字如其意,HoverEnter HoverMove HoverLeave是有規律的,一般來說先Enter,再Move,再Leave


C)讓MoveableWidget可以被拖動

這裡我們需要用到move函式,對於子Widget而言,要想拖動,必須使用父視窗的座標系,所以需要mapToParent進行座標轉換.
完整的程式碼如下,拖動原理請自行慢慢體會.

void MoveableWidget::mousePressEvent(QMouseEvent *event)
{
    qDebug("MoveableWidget::mousePressEvent: x=%d, y=%d\n", event->x(), event->y());

    if (event->button() == Qt::LeftButton)
    {
        m_pointClickPos = event->pos();
        m_bCanMove = true;
    }
}
void MoveableWidget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug("MoveableWidget::mouseMoveEvent: x=%d, y=%d\n", event->x(), event->y());

    if (m_bCanMove && (event->buttons() == Qt::LeftButton))
    {
        QPoint diff = event->pos() - m_pointClickPos;
        qDebug("diff(%d, %d)\n", diff.x(), diff.y());

        QPoint ptMove = mapToParent(diff);
        qDebug("move(%d, %d)\n", ptMove.x(), ptMove.y());

        move(ptMove);
    }
}
void MoveableWidget::mouseReleaseEvent(QMouseEvent *event)
{
    qDebug("MoveableWidget::mouseMoveEvent: x=%d, y=%d\n", event->x(), event->y());
    m_bCanMove = false;
}


編譯執行,我們可以看出
1)當滑鼠點選在MoveableWidget上,按住可以實現拖動.
2)當滑鼠點選在子視窗MyChildWidget時,按住也可以實現拖動,神奇

D)證實子視窗可以把滑鼠訊息發給父親視窗
通過C)的測試,我們已經可以看出,子視窗可以發滑鼠訊息給視窗.這裡我們要關注一個屬性WA_NoMousePropagation,為此,我們在MyChildWidget加入如下的程式碼:

MyChildWidget::MyChildWidget(QWidget *parent) : QWidget(parent)
{
    bool b = testAttribute(Qt::WA_NoMousePropagation);

    qDebug("b = %d\n", b);

    setAttribute(Qt::WA_NoMousePropagation, !b);

}


列印出的資訊為:
b = 0為此,我們知道,預設情況WA_NoMousePropagation為0,表示會把滑鼠訊息發給父視窗
如果把WA_NoMousePropagation設定為1, 則不會把滑鼠訊息發給父親視窗了.

編譯執行,我們可以看出:
1)當滑鼠移動到子視窗MyChildWidget時,沒有Hover的滑鼠訊息了
2)當滑鼠點選在子視窗MyChildWidget時,也無法拖動.
這充分說明,MyChildWidget沒有把滑鼠訊息發給父親視窗MoveableWidget

E)測試MyChildWidget中加入滑鼠處理函式

去掉對Qt::WA_NoMousePropagation屬性的修改,也就是預設給父親視窗傳座標

E.1: MyChildWidget只繼承mousePressEvent

void MyChildWidget::mousePressEvent(QMouseEvent *event)
{
    qDebug("MyChildWidget::mousePressEvent: x=%d, y=%d\n", event->x(), event->y());
}

編譯執行,我們可以看出:
1)當滑鼠移動到子視窗MyChildWidget時,Hover訊息正常
2)當滑鼠點選在子視窗MyChildWidget時,也無法拖動.
於是,我們可以大膽的猜測,如果MyChildWidget處理了滑鼠訊息,那麼就不會給MoveableWidget處理了.



E.2: MyChildWidget繼承mousePressEvent/mouseMoveEvent/mouseReleaseEvent

void MyChildWidget::mousePressEvent(QMouseEvent *event)
{
    qDebug("MyChildWidget::mousePressEvent: x=%d, y=%d\n", event->x(), event->y());
}
void MyChildWidget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug("MyChildWidget::mouseMoveEvent: x=%d, y=%d\n", event->x(), event->y());
}
void MyChildWidget::mouseReleaseEvent(QMouseEvent *event)
{
    qDebug("MyChildWidget::mouseReleaseEvent: x=%d, y=%d\n", event->x(), event->y());
}


編譯執行,我們可以看出:
1)當滑鼠移動到子視窗MyChildWidget時,Hover訊息正常
2)當滑鼠點選在子視窗MyChildWidget時,也無法拖動.


E.3: MyChildWidget繼承mousePressEvent/mouseMoveEvent/mouseReleaseEvent,但加上event->ignore();

void MyChildWidget::mousePressEvent(QMouseEvent *event)
{
    qDebug("MyChildWidget::mousePressEvent: x=%d, y=%d\n", event->x(), event->y());
    event->ignore();
}
void MyChildWidget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug("MyChildWidget::mouseMoveEvent: x=%d, y=%d\n", event->x(), event->y());
    event->ignore();
}
void MyChildWidget::mouseReleaseEvent(QMouseEvent *event)
{
    qDebug("MyChildWidget::mouseReleaseEvent: x=%d, y=%d\n", event->x(), event->y());
    event->ignore();
}


編譯執行,我們可以看出:
1)當滑鼠移動到子視窗MyChildWidget時,Hover訊息正常
2)當滑鼠點選在子視窗MyChildWidget時,可以拖動.

充分說明, MyChildWidget呼叫event->ignore();後,和他沒有繼承處理滑鼠訊息的行為是一樣的.


F)和Win32程式設計的區別(不熟悉Win32程式設計的請忽略)

Win32程式設計,對於一個視窗來說,您不併需要呼叫類似setMouseTracking這樣的函式,預設就會收到WM_MOUSEMOVE訊息.
Win32程式設計,如果一個視窗想要獲取視窗外的滑鼠訊息,需要在滑鼠點選時呼叫SetCapture,滑鼠抬起或者WM_CAPTURECHANGED時呼叫ReleaseCapture
Win32程式設計,當你用ALT+TAB切換任務時,通常,你的滑鼠焦點會丟失.
QT程式設計, 看起來, WIDGET已經為你呼叫了SetCapture,而且,當你用ALT+TAB切換任務時,滑鼠訊息還是發給你的.來個圖說明下


[思考]
Qt開發中觸發滑鼠懸停事件
請參考https://blog.csdn.net/chinley/article/details/95404282

程式碼見:https://gitee.com/flash008/qt_window/tree/master/000600/

相關文章