QT鼠标消息分析

本文主要探索以下几个知识点:
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://www.cnblogs.com/xingzaicpp/p/16669049.html
编译运行,我们可以看出
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

代码见: http://q1024.com/files/qt_window-master.zip 000600目录

posted @ 2022-09-08 12:56  xingzaicpp  阅读(510)  评论(0编辑  收藏  举报