3. Qt 核心

Qt是一个C++应用程序框架。它拥有完备的C++图形库和集成了一系列代码模块, 支持C++,Python,QML,Javascript等多种语言,对Qt的学习实际上就是对Qt库运行机制和函数调用的理解。

经过前面的例子,我们应该对Qt有了一个初步的印象,这一章将深入了解一些Qt的底层机制。 Qt底层包含一套基本的机制,主要来自 Qt Core 。Qt 通过拓展 C++ 来提供更高级别的UI和应用程序开发组件。 Qt 对 C++ 进行了如下拓展:

  • 提供一种非常强大的无缝对象通信机制,称为信号和槽

  • 提供可查询和可设计的对象属性

  • 跨库边界工作的动态强制转换

  • 强大的事件和事件过滤器

  • 上下文字符串翻译以实现国际化

  • 复杂的间隔驱动计时器,使许多任务可以优雅地集成到事件驱动的GUI中

  • 分层且可查询的对象树,以自然方式组织对象所有权

  • 受保护的指针(QPointer),在销毁引用的对象时将其自动设置为0,这与普通的C ++指针不同,在对象被销毁时,C ++指针变为悬挂的指针

  • 支持自定义类型创建。

其中对象通信机制(signals and slots)和动态属性系统(dynamic property system)依赖元对象系统。元对象系统是C++的扩展,使该系统更适合于真正的组件GUI编程。

当然对于初学者,这些概念理解起来可能会比较吃力,建议先往后学, 等到时机成熟再返回来看看概念,届时,茅塞顿开,迷雾顿解。

3.1. 对象模型

标准的 C++ 对象模型 为对象范例提供了非常有效的运行时支持。 但是它的静态性质在某些领域不是很灵活,图形用户界面编程是一个既需要运行时效率又需要高度灵活性的领域。 Qt通过将 C++ 的速度与 Qt的对象模型 相结合以达到上面的目的。

下面的这些类是构成Qt对象模型的基础,使用标准C++技术实现的:

类名

作用

QMetaClassInfo

有关课程的其他信息

QMetaEnum

有关枚举器的元数据

QMetaMethod

有关成员函数的元数据

QMetaObject

包含有关Qt对象的元信息

QMetaProperty

有关属性的元数据

QMetaType

管理元对象系统中的命名类型

QObject

所有Qt对象的基类

QObjectCleanupHandler

观察多个QObject的生命周期

QPointer

提供指向QObject的受保护指针的模板类

QSignalBlocker

QObject :: blockSignals() 周围的异常安全包装器

QSignalMapper

捆绑可识别发件人的信号

QVariant

充当最常见Qt数据类型的并集

QObject是Qt对象模型的核心,也是所有Qt对象的基类。

3.1.1. QObject

QObject 即为一个基础类,拥有一系列成员变量和操作接口,我们通过 C++ 来继承和派生来使用这个类。 QObject配合Qt属性系统提供对象间通信,通过特定的函数接口来处理、过滤事件;QObject本身也提供基本的计时器支持。

QObjects将自己组织在 对象树 中。当您使用另一个对象作为父对象创建QObject时, 该对象会自动将其自身添加到父对象的children() 列表中。父对象拥有该对象的所有权;也就是说,它将在其析构函数中自动删除其子对象。

QObject的构建/销毁顺序

当在堆上创建QObject时(即,用new创建),可以以任何顺序构造,也可以从对象树中以任何顺序删除。

删除对象树中的任何QObject时,如果对象具有父对象,则析构函数会自动从其父对象中删除该对象。 如果对象有子对象,则析构函数会自动删除每个子对象。

QObject具有线程亲和性

当QObject接收到队列中的信号或发布的事件时,该对象的槽函数或事件处理程序将在该对象所在的线程中运行。 线程之外将无法接收排队的信号或已发布的事件。

默认情况下,QObject位于创建它的线程中。可以使用thread() 查询对象的线程亲和力,并使用moveToThread()来更改所在线程。 除此以外,所有QObject必须与其父代驻留在同一线程中。

QObject的成员变量不会自动成为其子对象,必须通过将指针传递给子对象的构造函数或通过调用 setParent() 来指定其父对象

3.2. 元对象系统

Qt的元对象系统提供了对象间通信,运行时类型信息和动态属性系统的信号和槽机制。

元对象系统由下面三个部分组成:

  • Q_OBject类作为提供元对象系统特性的基类。

  • Q_OBJECT类声明的private部分用于启用元对象特性,例如动态属性、信号和槽。

  • 元对象编译器 moc 是处理 Qt关于C++扩展 的程序,为每个QObject子类提供实现元对象特性所需的代码

在构建项目的时候,qmake创建makefile会自动调用生成moc的构建规则。 然后预处理阶段 moc工具 会读取 C++ 头文件,如果找到一个或多个包含 Q_OBJECT宏 的类声明,则moc会将头文件中的这些拓展转换成标准C++兼容的形式, 然后再由GNU编译套件中的C++编译器进行编译链接。

除了信号与槽机制,元对象系统还提供如下功能。

  • QObject :: metaObject() 返回该类的关联元对象。

  • QMetaObject :: className() 在运行时以字符串形式返回类名称,而无需通过C ++编译器提供本机运行时类型信息(RTTI)支持。

  • QObject :: inherits() 函数返回对象是否是继承QObject继承树中指定类的类的实例。

  • QObject :: tr() 和QObject :: trUtf8() 转换字符串以进行国际化。

  • QObject :: setProperty() 和QObject :: property() 通过名称动态设置和获取属性。

  • QMetaObject :: newInstance() 构造该类的新实例。

QObject类可以使用qobject_cast() 执行动态强制转换。qobject_cast() 函数类似于标准C++中的动态dynamic_cast(), 其优点是不需要RTTI支持,并且它可以跨动态库运行。 它尝试将其参数强制转换为尖括号中指定的指针类型,如果对象的类型正确(在运行时确定),则返回非零指针;如果对象的类型不兼容,则返回nullptr。

典型的使用如下面的代码:

embed_qt_develop_tutorial_code/Control_1/mainwindow.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
QList<QPushButton*> btnlist = ui->stackedWidget->findChildren<QPushButton*>();
foreach(auto btn, btnlist)
{
    connect(btn,SIGNAL(clicked()),this,SLOT(pushbutton_clicked()));
}

void MainWindow::pushbutton_clicked()
{
    QPushButton* btn= qobject_cast<QPushButton*>(sender());
    if(btn->objectName()=="btn_2")
    {
        if(btn->isChecked())
            ui->statusBar->showMessage(QString("%1按下").arg(btn->objectName()));
        else
            ui->statusBar->showMessage(QString("%1弹起").arg(btn->objectName()));

        return;
    }

    ui->statusBar->showMessage(QString("%1被点击").arg(btn->objectName()));
}

我们使用了非常多的按钮(QPushButton),通常来说一个按钮的clicked()信号对应着一个on_pushbutton_clicked()槽函数。 而上面的写法呢,就是多个按钮对应这一个槽函数,在这个槽函数中,我们通过qobject_cast()直接获得发信号的按钮, 再通过判断按钮的某些属性(比如objectName())来确定具体哪个按钮被按下了。

当然呢,QPushButton只能通过qobject_cast()获取QPushButton的子类。 qobject_cast()执行成功会得到对象的指针,获取失败则返回NULL。

3.3. 属性系统

Qt提供了一个复杂的属性系统,类似于一些编译器供应商提供的属性系统。但是,作为一个独立于编译器和平台的库, Qt不依赖于非标准的编译器特性。Qt的属性系统在任何标准的C++编译器中都能起作用。 当然属性系统是专门为元对象系统服务的。

下面就是QWidget类中的部分属性。

qwidget.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
...
Q_PROPERTY(bool modal READ isModal)
Q_PROPERTY(Qt::WindowModality windowModality READ windowModality WRITE setWindowModality)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
Q_PROPERTY(QRect geometry READ geometry WRITE setGeometry)
Q_PROPERTY(QRect frameGeometry READ frameGeometry)
Q_PROPERTY(QRect normalGeometry READ normalGeometry)
Q_PROPERTY(int x READ x)
Q_PROPERTY(int y READ y)
Q_PROPERTY(QPoint pos READ pos WRITE move DESIGNABLE false STORED false)
Q_PROPERTY(QSize frameSize READ frameSize)
Q_PROPERTY(QSize size READ size WRITE resize DESIGNABLE false STORED false)
Q_PROPERTY(int width READ width)
Q_PROPERTY(int height READ height)
Q_PROPERTY(QRect rect READ rect)
...

属性的类似于类数据成员,但是它具有可通过元对象系统访问的功能。

属性系统的语法如下:

属性语法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Q_PROPERTY(type name
        (READ getFunction [WRITE setFunction] |
            MEMBER memberName [(READ getFunction | WRITE setFunction)])
        [RESET resetFunction]
        [NOTIFY notifySignal]
        [REVISION int]
        [DESIGNABLE bool]
        [SCRIPTABLE bool]
        [STORED bool]
        [USER bool]
        [CONSTANT]
        [FINAL]
        [REQUIRED])

关键词含义下:

  • type表示属性类型,属性类型可以是QVariant支持的任何类型,也可以是用户定义的类型。

  • READ表示该属性为只读属性,且指定getFunction()来获取该属性的值,

  • WRITE表示该属性可写,指定setFunction()来设置该属性的值,只读属性不需要WRITE功能;

  • MEMBER表示指定成员变量与属性关联,这个属性可读可写,指定getFunction来读属性值,setFunction来设置属性值;

  • RESET可选项,指定resetFunction()来设置默认值;

  • NOTIFY可选项,指定该类中的一个现有信号notifySignal(),只要该属性的值发生更改,该信号就会发出;

  • REVISION可选项,它定义将在API的特定版本中使用的属性及其通知程序信号(通常用于QML)

  • DESIGNABLE可选项,指示该属性在GUI设计工具(例如Qt Designer)的属性编辑器中是否可见;

  • SCRIPTABLE可选项,指示该属性是单独存在还是依赖于其他值。

  • USER可选项,指示该属性是被指定为该类的面向用户还是可编辑的属性

  • CONSTANT可选项,指示该属性值是恒定的

  • FINAL可选项,指示该属性不会被派生类覆盖

  • REQUIRED可选项,指示该属性应由该类的用户设置

embed_qt_develop_tutorial_code/QtBase/Property/property.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef PROPERTY_H
#define PROPERTY_H

#include <QObject>

class Property : public QObject
{
    Q_PROPERTY(int data READ getData WRITE setData NOTIFY dataChangeed)
    Q_OBJECT
public:
    explicit Property(QObject *parent = nullptr);

    int getData();
    void setData(int data);

signals:
    void dataChangeed(int data);

public slots:

private:
    int m_data;
};

#endif // PROPERTY_H

这里有一个属性data,我们可以使用getData()来读取deta的值,可以使用setData()来设置data的值, 当data的值发生改变的时候,会产生dataChangeed()信号。

很多时候,我们并不知道我们使用的类有那些属性,以及如何去操作这些属性, Qt提供了QMetaObject、QMetaProperties来方便我们使用,比如我们可以使用QMetaProperty.name()来获取属性名称

我们在调用属性值的时候,有两种方式:setData() 直接设置data值,setProperty() 通过属性名设置data值。

embed_qt_develop_tutorial_code/QtBase/Property/main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "property.h"
#include <QApplication>

#include <QMetaProperty>

#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Property *p =new Property();

    p->setObjectName("property");

    const QMetaObject *metaobject = p->metaObject();

    for (int i = 0; i < metaobject->propertyCount(); ++i) {
        QMetaProperty metaproperty = metaobject->property(i);
        const char *name = metaproperty.name();
        QVariant value = p->property(name);

        qDebug()<<"name:"<<name<<"value:"<<value;
    }

    p->setData(10);
    qDebug()<<p->getData();
    qDebug()<<p->property("data");
    p->setProperty("data", 20);
    qDebug()<<p->getData();
    qDebug()<<p->property("data");

    p->setProperty("dynamic", "mydynamic");
    qDebug()<<p->property("dynamic");

    Q_CLASSINFO("Version", "3.0.0");
    qDebug()<<p->property("Version");

    return a.exec();
}

动态属性

QObject :: setProperty()也可以在运行时用于向类的实例添加新属性。 如果我们设置的属性已经存在且类型相同,则设置的值将会赋予给存在的属性,并返回true。 如果这时的属性存在但是属性的类型不兼容,则不更改属性,并返回false。

但是,如果QObject中不存在具有给定名称的属性(即,该类中未使用Q_PROPERTY()声明该属性), 则会将具有给定名称和值的新属性自动添加到QObject中,但仍返回false。 这意味着不能使用返回false来确定是否实际设置了特定的属性,除非事先知道该属性已经存在于QObject中。

请注意,动态属性是按实例添加的,它们是添加到QObject而不是QMetaObject的。 通过将属性名称和无效的QVariant值传递给QObject::setProperty(),可以从实例中删除属性。 QVariant的默认构造函数构造了无效的QVariant。

可以使用QObject::property()查询动态属性,就像在编译时使用Q_PROPERTY()声明的属性一样。

自定义属性

属性使用的自定义类型需要使用Q_DECLARE_METATYPE()宏进行注册,以便可以将其值存储在QVariant对象中。 这使得它们既适用于在类定义中使用Q_PROPERTY()宏声明的静态属性,又适用于在运行时创建的动态属性。

其他属性

我们还可以使用Q_CLASSINFO()宏向类添加附加属性,语法: 附加名称-值 , 例如:Q_CLASSINFO (“版本” ,“ 3.0.0”)

3.4. 信号与槽

在GUI编程中,通常我们更改一个部件时,都希望通知另一个部件。例如,如果用户单击“关闭”按钮,我们可能希望调用窗口的close()函数。 在Qt中,就通过信号与槽的机制来实现这种需求,当发生特定事件时会发出信号,而槽就是是响应特定信号而调用的函数。

信号和插槽用于对象之间的通信。信号与槽机制是Qt的主要功能也是与其他框架提供的功能最大不同的部分。 部分框架使用回调函数来实现对象间的通信,与回调相比,信号和插槽由于提供了更大的灵活性而稍慢一些, 尽管实际应用中的差异并不明显。通常,发出连接到某些插槽的信号的速度比使用非虚拟函数调用直接调用接收器的速度大约慢十倍。 这是定位连接对象,安全地迭代所有连接(即,检查后续接收方在发射期间是否未被销毁)以及以通用方式编组任何参数所需的开销。

在例程上一章的登录框中有利用信号与槽来实现登录操作:

embed_qt_develop_tutorial_code/Designer/dialog.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void Dialog::on_pushButton_login_clicked()
{
    if(ui->lineEdit_account->text().isEmpty())
    {
        QMessageBox::information(this,"提示","账号不能为空",QMessageBox::Yes);
        return ;
    }
    if(ui->lineEdit_passwd->text().isEmpty())
    {
        QMessageBox::information(this,"提示","密码不能为空",QMessageBox::Yes);
        return;
    }

    accept();
}

所谓信号就是一个公共访问函数,当对象的内部状态以某种可能使对象的客户端或所有者感兴趣的方式更改时,该对象就会发出信号。

发出信号时,通常会立即执行与其连接的插槽,就像正常的函数调用一样。发生这种情况时,信号和时隙机制完全独立于任何GUI事件循环。 emit一旦所有插槽都返回,将执行该语句之后的代码。使用排队连接时情况略有不同; 在这种情况下,emit关键字后面的代码将立即继续,并且稍后将执行插槽。 如果将多个插槽连接到一个信号,则在发出信号时,将按照连接的顺序依次执行插槽。

信号是由Moc自动生成的,不能在.cpp文件中实现。他们永远不能有返回类型(即use void)

所谓槽也是一个正常的C++函数,可以正常调用。它们唯一的不同就是可以将信号连接到它们。

signal001

如图所示,一个信号可以连接多个槽,多个信号也可以连接一个槽,绑定信号的槽的connect函数原型如下:

qobject.h
1
2
3
4
5
static QMetaObject::Connection connect(const QObject *sender, const char *signal,const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);

static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,const QObject *receiver, const QMetaMethod &method,Qt::ConnectionType type = Qt::AutoConnection);

inline QMetaObject::Connection connect(const QObject *sender, const char *signal,const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;

函数共有五个参数,第一个是信号的发送者,第二个是信号,第三个是信号的接收者,第四个是响应的槽函数,第五个为信号与槽连接的方式,

通常信号与槽常用的两种写法:

embed_qt_develop_tutorial_code/SerialPort/mainwindows.cpp
1
2
3
4
//方式1
connect(m_serial, &QSerialPort::readyRead, this, &MainWindow::readData);
//方式2
connect(m_serial, SIGNAL(readyRead()), this, SLOT(readData()));

自定义信号

在例程中,我们用了三个窗口,分别演示主窗口和子窗口,子窗口和主窗口,以及子窗口与子窗口之间的通信。

signal002

实现了一个Form界面类,分别由主窗口创建from_1和from_2;From类实现如下:

embed_qt_develop_tutorial_code/QtBase/Signal/from.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#ifndef FORM_H
#define FORM_H

#include <QWidget>

namespace Ui {
class Form;
}

class Form : public QWidget
{
    Q_OBJECT

public:
    explicit Form(QWidget *parent = nullptr);
    ~Form();

    void setLabText(const QString str);
    void setSendStr(QString str_1,QString str_2);

signals:
    void close();
    void sendMainText(const QString str);
    void sendChildText(const QString str);

private slots:
    void showMyself();
    void hideMyself();
    void reiveLabText(const QString str);
    void reiveMainText(const QString str);
    void reiveChildText(const QString str);

    void on_btn_send_1_clicked();
    void on_btn_send_2_clicked();
    void on_btn_hide_clicked();

protected:
    void closeEvent(QCloseEvent*event);

private:
    Ui::Form *ui;
    QString sendstr_1;
    QString sendstr_2;
};

#endif // FORM_H

3.5. 事件机制

在Qt中,事件是从抽象QEvent类派生的对象,它们表示发生在应用程序内部或由于应用程序需要了解的外部活动而发生的事情。 例如QMouseEvent和QKeyEvent,来自窗口系统的鼠标事件和键盘事件;QTimerEvent,来自定时器事件;有些则来自应用程序本身。

3.5.1. 事件传递

当事件发生时,Qt通过构造适当的QEvent子类的实例来表示一个事件,并通过调用其event()函数将其传递到QObject的特定实例(或其子类之一)。 此函数不处理事件而是根据所传递事件的类型,为该特定类型的事件调用事件处理程序,并根据该事件被接受还是忽略发送响应。 事件可以由QObject子类的任何实例接收和处理。

Widget中常用的事件处理函数:

void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);
void keyPressEvent(QKeyEvent *event);
void closeEvent(QCloseEvent *event);
void resizeEvent(QResizeEvent *event);
void paintEvent(QPaintEvent *event);

每个事件都有一个关联的类型,在QEvent::Type中定义了相关类型,并且可以用作运行时类型信息的便捷来源,以快速确定构造给定事件对象的子类。

大多数事件类型都有特殊的类,尤其是QResizeEvent,QPaintEvent,QMouseEvent,QKeyEvent和QCloseEvent。 每个类都将QEvent子类化,并添加特定于事件的函数。例如,QResizeEvent添加了size()和oldSize()来使窗口能够发现其尺寸如何被更改。 一些类支持不止一种实际的事件类型。QMouseEvent支持鼠标按键按下,双击,移动和其他相关操作。

事件类型

描述

QEvent::None

0

无事件产生

QEvent::Close

19

窗口小部件已关闭(QCloseEvent)

QEvent::KeyPress

6

按键(QKeyEvent)

QEvent::MouseButtonPress

2

按下鼠标(QMouseEvent)

QEvent::Paint

12

屏幕更新是必需的(QPaintEvent)

QEvent::Timer

1

常规计时器事件(QTimerEvent)

QEvent::User

1000

用户定义的事件

QEvent::MaxUser

65535

上次用户事件ID

更多事件类型参考

许多应用程序都希望创建并发送自己的事件。您可以通过构造合适的事件对象并使用QCoreApplication :: sendEvent()和QCoreApplication :: postEvent()发送事件, 以与Qt自己的事件循环完全相同的方式发送事件。

  • sendEvent() 立即处理事件。返回时,事件过滤器和/或对象本身已经处理了该事件。对于许多事件类,有一个称为isAccepted()的函数,该函数告诉您该事件是被最后一个调用的处理程序接受还是拒绝。

  • postEvent() 将事件发布到队列中,以供以后分派。下次Qt的主事件循环运行时,它将分派所有已发布的事件,并进行一些优化。例如,如果有多个调整大小事件,它们将被压缩为一个。这同样适用于绘画事件:QWidget :: update()调用postEvent(),通过避免多次重新绘画来消除闪烁并提高速度。

  • postEvent() 也用于对象初始化期间,因为通常会在对象初始化完成后很快分派已发布的事件。在实现小部件时,重要的是要意识到事件可以在其生命周期的很早就交付,因此,在其构造函数中,一定要在有可能接收到事件之前尽早初始化成员变量。

要创建自定义类型的事件,您需要定义一个事件编号,该事件编号必须大于QEvent :: User, 并且可能需要子类化QEvent才能传递有关自定义事件的特定信息。

为了方便起见,可以使用registerEventType()函数为您的应用程序注册和保留自定义事件类型。 这样做可以避免意外重用应用程序中其他地方已经使用的自定义事件类型。

embed_qt_develop_tutorial_code/QtyBase/Event/myevent.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#ifndef MYEVENT_H
#define MYEVENT_H

#include <QObject>
#include <QEvent>

class MyEvent : public QEventh
{
public:
    MyEvent();
    ~MyEvent();

    static Type myEventype();

private:
    static Type myType;
};

#endif // MYEVENT_H
embed_qt_develop_tutorial_code/QtyBase/Event/mainwindows.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void MainWindow::handletimeout()
{
    MyEvent *mypostEvent = new MyEvent();
    //postevent
    QCoreApplication::postEvent(this, mypostEvent);

    //sendEvent
    MyEvent *sendsendEvent = new MyEvent();
    qDebug() <<QCoreApplication::sendEvent(this, sendsendEvent);
    delete sendsendEvent;
}

3.5.2. 事件处理

传递事件的通常方法是调用虚拟函数。例如,通过调用QWidget::paintEvent()来传递QPaintEvent事件。 这个虚函数负责适当地做出反应,通常通过重新绘制窗口小部件来实现。如果您没有在虚拟函数的实现中执行所有必要的工作,则可能需要调用基类的实现。

embed_qt_develop_tutorial_code/QtyBase/Event/mainwindows.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void MainWindow::mousePressEvent(QMouseEvent * event)
{
    QString mesg;

    if(event->button() == Qt::LeftButton)
        mesg=QString("鼠标左键:(%1,%2)").arg(event->x()).arg(event->y());
    else if(event->button() == Qt::RightButton)
        mesg=QString("鼠标右键:(%1,%2)").arg(event->x()).arg(event->y());
    else if(event->button() == Qt::MiddleButton)
        mesg=QString("鼠标滚轮:(%1,%2)").arg(event->x()).arg(event->y());

    ui->statusBar->showMessage(mesg);
    ui->textEdit->append(QString("[%1] %2").arg(QDateTime::currentDateTime().toString("hh:mm:ss")).arg(mesg));
}

bool MainWindow::event(QEvent *event)
{
    QString mesg=QString("事件类型:(eventype,%1)").arg(event->type());
    qDebug()<<mesg;

    if (event->type() == QEvent::KeyPress) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(event);
        if (ke->key() == Qt::Key_Tab) {
            QString mesg=QString("tab按键:(eventype,%1)").arg(event->type());
            ui->statusBar->showMessage(mesg);
            ui->textEdit->append(QString("[%1] %2").arg(QDateTime::currentDateTime().toString("hh:mm:ss")).arg(mesg));
            return true;
        }
    }

    return QWidget::event(event);
}

更多键值参考

3.5.3. 事件过滤器

有时,一个对象需要查看并可能拦截传递给另一个对象的事件。例如,对话框通常要过滤某些小部件的按键;例如,例如,修改返回键处理。

所述的QObject :: installEventFilter()函数通过设置一个使此事件过滤器,造成提名过滤器对象接收的事件为在其目标对象的QObject :: eventFilter()函数。事件过滤器可以在目标对象之前处理事件,从而使它可以根据需要检查和丢弃事件。可以使用QObject :: removeEventFilter()函数删除现有的事件过滤器。

调用过滤器对象的eventFilter()实现时,它可以接受或拒绝事件,并允许或拒绝事件的进一步处理。如果所有事件过滤器都允许进一步处理事件(每次返回false),则事件将发送到目标对象本身。如果其中一个停止处理(返回true),则目标过滤器和任何以后的事件过滤器将根本看不到该事件。

通过在QApplication或QCoreApplication对象上安装事件过滤器,还可以过滤整个应用程序的所有事件。此类全局事件过滤器在特定于对象的过滤器之前被调用。这是非常强大的功能,但是它也会减慢整个应用程序中每个事件的事件传递速度。通常应使用所讨论的其他技术来代替。

embed_qt_develop_tutorial_code/QtyBase/Event/mainwindows.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool MainWindow::event(QEvent *event)
{
    QString mesg=QString("事件类型:(eventype,%1)").arg(event->type());
    qDebug()<<mesg;

    if (event->type() == QEvent::KeyPress) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(event);
        if (ke->key() == Qt::Key_Tab) {
            QString mesg=QString("tab按键:(eventype,%1)").arg(event->type());
            ui->statusBar->showMessage(mesg);
            ui->textEdit->append(QString("[%1] %2").arg(QDateTime::currentDateTime().toString("hh:mm:ss")).arg(mesg));
            return true;
        }
    }
    if (event->type() == MyEvent::myEventype())
    {
        MyEvent *myEvent = dynamic_cast<MyEvent*>(event);
        QString mesg=QString("自定义事件");
        ui->statusBar->showMessage(mesg);
        ui->textEdit->append(QString("[%1] %2").arg(QDateTime::currentDateTime().toString("hh:mm:ss")).arg(mesg));
        return true;
    }

    return QWidget::event(event);
}

3.6. 例程说明

野火提供的Qt Demo已经开源,仓库地址在:

文档所涉及的示例代码也提供下载,仓库地址在:

本章例程在 embed_qt_develop_tutorial_code/QtBase

例程中有三个子项目,Property、Signal和Event

例程展示了Qt中的属性,信号与槽和事件。

3.6.1. 编程思路

Property

演示了自定义的属性以及动态添加属性

  • 新建一个类Property

  • 添加了一个data属性 Q_PROPERTY(int data READ getData WRITE setData NOTIFY dataChangeed)

  • 在主函数中设置并读取该属性值

Signal

演示了信号在不同窗口之间的信号关联与传递

  • 新建窗口类 Form

  • 新建主窗口1,子窗口1,子窗口2

  • 绑定信号与槽 分别实现,主窗口与子窗口1,子窗口1与主窗口,子窗口1与子窗口2之间的通信

Event

演示了Qt中常用的事件以及事件处理,自定义事件并处理。

  • 新建一个主窗口

  • 响应主窗口的常见事件

  • 新建一个自定义事件,并响应

  • 对主窗口安装事件过滤器,捕捉控件按键按下事件

3.6.2. 代码讲解

3.6.3. 编译构建

build001
  • Ubuntu 选择 Desktop Qt 5.11.3 GCC 64bit 套件,编译运行

  • LubanCat 选择 ebf_lubancat,只编译

提示

当两种构建套件进行切换时,请重新构建项目或清除之前项目。针对我们的工程还需要手动重新构建QtUI和Skin。

build002

3.6.4. 运行结果

3.6.4.1. PC实验

直接点击编译并运行程序,效果如下:

result001
result002

3.6.4.2. LubanCat实验

通过SCP或者NFS将编译好的程序拷贝到LubanCat上

NFS环境搭建 参考这里

SCP命令如下:

# scp传输文件
# scp 文件名 服务器上的某个用户@服务器ip:/文件保存路径
scp filename server_user_name@192.168.0.205:server_file_path
# 从服务器拉取文件
# scp 服务器上的某个用户@服务器ip:/服务器文件存放路径 拉取文件保存路径
scp server_user_name@192.168.0.229:server_file_path local_path

编译好的程序在 embed_qt_develop_tutorial_code/app_bin/base 目录中,通过scp命令将编译好的程序拉到LubanCat。

scp root@192.168.0.174:/home/embed_qt_develop_tutorial_code/app_bin/base/Signal /usr/local/qt-app/

在LubanCat运行程序,使用run_myapp.sh配置好环境,并执行 Signal 。

sudo /usr/local/qt-app/run_myapp.sh /usr/local/qt-app/Signal
result003