上一篇博文中,我们从高空中俯瞰了Qt5事件编织的壮阔进程。无论是最普通的鼠标点击,还是最先进的多点触控手势,抑或是跨越线程的事件传递,Qt5都为开发者们提供了一整套完备的事件处理方案和强大工具链,只等着你来施展渔阳鼎力,尽情驰骋拳打脚踢。


现在,就让我们放眼脚下,深入研究其中最为普通而又至关重要的鼠标和键盘事件吧! 毕竟,它们就如同GUI应用程序的肢体神经,承载着人机交互的一切。那么,Qt5是如何帮助我们精准捕捉和优雅响应这些来自用户的交互指令呢?让我们拨开重重迷雾,一一揭晓!


一、鼠标键盘事件


1、指尖下的强力舵手 - 鼠标事件的操纵杆

鼠标可以说是GUI程序中最常见和最主要的交互方式了。Qt5提供了多个类来帮助我们精准无误地处理各种鼠标事件,如QMouseEvent、QHoverEvent、QTabletEvent等。而QWidget及其派生类中的鼠标事件处理函数,就是驾驶这一切的高级操纵杆:

void MyWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        // 处理鼠标左键按下
        ...
    }
}

void MyWidget::mouseMoveEvent(QMouseEvent *event)
{
    // 处理鼠标移动
    ...
}

void MyWidget::mouseReleaseEvent(QMouseEvent *event) 
{
    if (event->button() == Qt::RightButton) {
        // 处理鼠标右键释放
        ...
    }
}

无论用户何时点击鼠标按钮、移动鼠标或停留在窗口区域,这些事件首先会分别进入对应的事件处理函数。我们只需要重新实现这些函数,根据QMouseEvent提供的坐标、按钮、修饰键等信息,编写自己的业务逻辑,就可以完全掌控鼠标交互了。


别忘了mousePressEvent和mouseReleaseEvent还有一个双击形式的兄弟 - mouseDblClickEvent,用于处理鼠标双击。另外,QHoverEvent也是判断鼠标悬停的重要线索。


2、神剑勾魂 指尖乾坤 - 键盘敲击的分秒必争


键盘输入是另一种重要的GUI交互方式,尤其在输入文本信息或快捷键操作时。而Qt5通过QKeyEvent和QShortcutEvent等类,也为开发者提供了驾驭键盘指令的利器:

void MyWidget::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
        // 处理回车键被按下
        ...
    } else if (event->matches(QKeySequence::Copy)) {
        // 处理复制快捷键被按下
        ...
    }
}

void MyWidget::keyReleaseEvent(QKeyEvent *event)
{
    // 处理按键释放
    ...
}

void MyWidget::customShortcut()
{
    QShortcut *shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_X), this);
    connect(shortcut, &QShortcut::activated, this, &MyWidget::handleCustomShortcut);
}

通过重写keyPressEvent和keyReleaseEvent方法,我们就能拦截并响应用户的每一次按键动作。QKeyEvent提供了被按下的按键信息,我们可以方便地进行判断和处理。

而对于常用的快捷键组合,如复制粘贴等,Qt5还为我们提供了QKeySequence的高级工具,使用它可以非常方便地检查按键是否匹配快捷键。

当然,有时候我们也需要自定义快捷键,这时就可以利用QShortcut来创建全局或上下文快捷键。只需将想要的键序列和处理函数连接起来,就可以完成快捷键操作了。


3、挥剑斩浪 壹气呵成 - 鼠标键盘并重的艺术馆


在现代的GUI应用中,鼠标和键盘输入往往是并重出击的。比如在文本编辑器中,我们可能先是使用鼠标进行区域选择,再通过键盘输入命令执行操作。再比如在绘图软件里,我们需要结合鼠标与键盘输入,来完成多种交互动作。

但是鼠标和键盘事件最终都需要在同一个事件处理中完成,这就要求我们设计出更加复杂的交互逻辑。幸运的是,Qt5为我们提供了多个辅助工具和技术:

// 使用按键修饰键
if (event->modifiers() & Qt::ShiftModifier) {
    // Shift键被按下时执行的逻辑
}

// 鼠标光标设置
if (someCondition) {
    setCursor(Qt::PointingHandCursor);
} else {
    setCursor(Qt::ArrowCursor);
}

// 焦点策略
setFocusPolicy(Qt::ClickFocus); // 单击时自动获取键盘焦点

首先是修饰键,像Shift、Ctrl、Alt等常用键被按下时,会体现在鼠标或键盘事件的modifiers()中。利用这一点,我们就能轻松地对组合键做出响应。

其次,Qt还提供了丰富的鼠标光标形状,可以根据不同状态和场景,方便地切换鼠标光标的样式,为用户提供更多视觉线索。

最后,焦点策略也是整合鼠标和键盘事件的重要一环。我们可以控制键盘焦点在窗口间的移动规则,使之与鼠标交互相呼应,为用户提供一致的体验。


二、触控手势


1、天地鼓动 掌跆指挥 - 触控手势交互的新时代

随着智能手机、平板电脑的普及,触控和手势这种全新的交互方式越来越受到重视。传统的鼠标和键盘在移动设备上已经无法满足用户需求,因此Qt5也及时跟上了这股潮流,为开发者提供了处理触控和手势事件的完备支持。

bool MyWidget::event(QEvent *event)
{
    if (event->type() == QEvent::Gesture) {
        // 处理手势事件
        QGestureEvent *gestureEvent = static_cast<QGestureEvent *>(event);
        qDebug() << "Gesture event detected with" << gestureEvent->gestures().size() << "gestures";

        for (QGesture *gesture : gestureEvent->gestures()) {
            if (QPinchGesture *pinch = qobject_cast<QPinchGesture *>(gesture)) {
                // 捕捉到pinch手势
                qreal scaleFactor = pinch->totalScaleFactor();
                if (pinch->state() == Qt::GestureStarted) {
                    lastScaleFactor = scaleFactor;
                } else if (pinch->state() == Qt::GestureUpdated) {
                    // 更新缩放比例
                    updateScaleFactor(scaleFactor / lastScaleFactor);
                    lastScaleFactor = scaleFactor;
                }
            }
        }
        return true;
    } else if (event->type() == QEvent::TouchBegin) {
        // 处理触摸按下事件
        ...
    }
    return QWidget::event(event);
}

这段代码展示了如何在event()函数中捕获手势事件和触摸事件。QGestureEvent包含了一个或多个QGesture对象,用于描述发生的手势类型,如QPinchGesture代表了两指缩放手势。

开发者可以遍历QGestureEvent中的所有手势对象,识别出自己感兴趣的手势类型,并根据手势的状态和属性值编写响应逻辑。

除了手势事件,还有专门的QTouchEvent类用于获取原始的多点触摸信息,如按下、移动、释放等。结合这两种事件类型,就能实现丰富的触控手势交互体验了。


三、云穷野极 水到渠成 - 纵横多线程也能游刃有余


在现代计算机系统中,单线程的计算能力已经无法满足日益复杂的需求。而Qt5本身就是一个多线程友好的框架,它对线程间事件的传递有着深度的支持和优化。

// 工作线程
QThread workerThread;
QObject *myObject = new MyObject;
myObject->moveToThread(&workerThread);
workerThread.start();

// 发送自定义事件到工作线程
QMetaObject::invokeMethod(myObject, "handleEvent", Q_ARG(QString, "Hello from main thread!"));

class MyObject : public QObject
{
    Q_OBJECT
public slots:
    void handleEvent(const QString &message)
    {
        qDebug() << "Received event from main thread:" << message;
    }
};

// GUI线程 
void setupGUI()
{
    QLabel *label = new QLabel;
    label->show();
    
    // 从工作线程更新UI
    QMetaObject::invokeMethod(label, "setText", Q_ARG(QString, "New text"), Qt::QueuedConnection);
}

这个示例展示了如何在Qt应用中横跨多线程。首先我们创建了一个工作线程,并让MyObject对象在该线程中运行。然后通过QMetaObject::invokeMethod方法,我们就能安全地将事件对象或信号发送到目标线程中。


值得注意的是,invokeMethod有多种连接类型可选,Qt::QueuedConnection可以将目标方法调用安全地加入事件队列,确保跨线程调用的安全性。

在上例中,我们还展示了如何利用invokeMethod从非GUI线程更新UI。只需以Qt::QueuedConnection的方式调用UI组件的方法,就能避免跨线程问题。

通过Qt提供的这些工具,我们就能轻松驾驭多线程开发中的种种复杂情况,毫无后顾之忧。当然,多线程开发本身就是一个贯穿始终的重要主题,我们后续还会专门就此进行更深入的探讨。


四、海阔凭鱼跃 天高任鸟飞 - 你终将驾驭住这对强将


通过本篇博文,我们系统地学习了Qt5下如何高效地处理鼠标和键盘事件。从最基本的事件处理函数入手,到组合键、光标和焦点策略的高级特性,Qt5都为开发者们提供了一整套强大而易用的武器库。


我们今天的旅程仅仅揭开了事件处理的一角。但放眼Qt的广阔天地,却有太多更高境界等着我们去开拓和征服。勇敢前行吧,未来属于勇猛者! 下一期博文,我将为您开启全新的征程,让我们共同攀登事件编程的巅峰!


03-27 15:31