在 Qt5 C++入门教程的这一部分,我们将讨论事件和信号。
事件是任何图形用户界面(GUI)程序的重要组成部分。所有 GUI 应用程序都是事件驱动的。一个应用程序会对其生命周期中生成的不同类型的事件做出反应。事件主要由应用程序的用户生成。但它们也可以通过其他方式生成,例如,通过互联网连接、窗口管理器或定时器。在事件模型中,有三个参与者:
- 事件源
- 事件对象
- 事件目标
事件源是状态发生变化的对象。它会生成事件。事件对象(Event)封装了事件源中的状态变化。
事件目标是希望得到通知的对象。事件源对象将处理事件的任务委托给事件目标。
当我们调用应用程序的 exec 方法时,应用程序会进入主循环。主循环会获取事件并将它们发送给对象。Qt 有一个独特的信号和槽机制(signal and slot)。这种信号和槽机制(signal and slot)是对 C++ 编程语言的扩展。
信号和槽(signal and slot)用于对象之间的通信。当特定事件(Event)发生时,会发出信号(signal)。槽是一个普通的 C++ 方法;当连接(connect)到它的信号(signal )被发出时,槽(slot)就会被调用。
信号和槽(signal and slot)用于对象之间的通信。当特定事件发生时,会发出一个信号(signal)。槽是一种普通的 C++ 方法;当连接到它的信号被发出时,槽就会被调用
- Qt5 点击示例
第一个示例展示了一个非常简单的事件处理示例。我们有一个按钮。通过点击该按钮,我们将终止应用程序。
click.h
#pragma once
#include <QWidget>
class Click : public QWidget {
public:
Click(QWidget *parent = nullptr);
};
click.cpp
#include <QPushButton>
#include <QApplication>
#include <QHBoxLayout>
#include "click.h"
Click::Click(QWidget *parent)
: QWidget(parent) {
auto *hbox = new QHBoxLayout(this);
hbox->setSpacing(5);
auto *quitBtn = new QPushButton("Quit", this);
hbox->addWidget(quitBtn, 0, Qt::AlignLeft | Qt::AlignTop);
connect(quitBtn, &QPushButton::clicked, qApp, &QApplication::quit);
}
我们在窗口上显示一个 QPushButton 按钮。
connect(quitBtn, &QPushButton::clicked, qApp, &QApplication::quit);
connect 方法将一个信号连接到槽。当我们点击 “Quit” 按钮时,会生成 clicked 信号。qApp 是指向应用程序对象的全局指针,它在<QApplication>头文件中定义。当 clicked 信号发出时,会调用 quit 方法。
main.cpp
#include <QApplication>
#include "click.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Click window;
window.resize(250, 150);
window.setWindowTitle("Click");
window.show();
return app.exec();
}
- Qt5 按键事件
在下面的示例中,我们将对按键操作做出反应。
keypress.h
#pragma once
#include <QWidget>
class KeyPress : public QWidget {
public:
KeyPress(QWidget *parent = 0);
protected:
void keyPressEvent(QKeyEvent * e);
};
这是 keypress.h 头文件。
keypress.cpp
#include <QApplication>
#include <QKeyEvent>
#include "keypress.h"
KeyPress::KeyPress(QWidget *parent)
: QWidget(parent)
{ }
void KeyPress::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_Escape) {
qApp->quit();
}
}
当我们按下 Esc 键时,应用程序将终止。
void KeyPress::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
qApp->quit();
}
}
在 Qt5 中处理事件的一种方式是重新实现事件处理程序。QKeyEvent 是一个事件对象,它包含了发生事件的相关信息。在我们的例子中,我们使用这个事件对象来确定实际按下的是哪个键。
main.cpp
#include <QApplication>
#include "keypress.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
KeyPress window;
window.resize(250, 150);
window.setWindowTitle("Key press");
window.show();
return app.exec();
}
这是主文件。
- QMoveEvent
QMoveEvent 类包含了移动事件的事件参数。移动事件会发送给已移动的窗口部件。
move.h
#pragma once
#include <QMainWindow>
class Move : public QWidget {
Q_OBJECT
public:
Move(QWidget *parent = 0);
protected:
void moveEvent(QMoveEvent *e);
};
这是 move.h 头文件。
move.cpp
#include <QMoveEvent>
#include "move.h"
Move::Move(QWidget *parent)
: QWidget(parent)
{ }
void Move::moveEvent(QMoveEvent *e) {
int x = e->pos().x();
int y = e->pos().y();
QString text = QString::number(x) + "," + QString::number(y);
setWindowTitle(text);
}
在我们的代码编程示例中,我们对移动事件做出反应。我们确定窗口客户区左上角的当前 x、y 坐标,并将这些值设置为窗口的标题。
int x = e->pos().x();
int y = e->pos().y();
我们使用 QMoveEvent 对象来确定 x、y 值。
QString text = QString::number(x) + "," + QString::number(y);
我们将整数值转换为字符串。
setWindowTitle(text);
setWindowTitle 方法将文本设置为窗口的标题
main.cpp
#include <QApplication>
#include "move.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Move window;
window.resize(250, 150);
window.setWindowTitle("Move");
window.show();
return app.exec();
}
- 断开信号连接
可以将信号与槽断开连接。下一个示例展示了我们如何实现这一点。
disconnect.h
#pragma once
#include <QWidget>
#include <QPushButton>
class Disconnect : public QWidget {
Q_OBJECT
public:
Disconnect(QWidget *parent = 0);
private slots:
void onClick();
void onCheck(int);
private:
QPushButton *clickBtn;
};
在头文件中,我们声明了两个槽。slots 不是 C++ 关键字,它是 Qt5 的扩展。这些扩展在代码编译之前由预处理器处理。当我们在类中使用信号和槽时,必须在类定义的开头提供 Q_OBJECT 宏。否则,预处理器会报错。
disconnect.cpp
#include <QTextStream>
#include <QCheckBox>
#include <QHBoxLayout>
#include "disconnect.h"
Disconnect::Disconnect(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(5);
clickBtn = new QPushButton("Click", this);
hbox->addWidget(clickBtn, 0, Qt::AlignLeft | Qt::AlignTop);
QCheckBox *cb = new QCheckBox("Connect", this);
cb->setCheckState(Qt::Checked);
hbox->addWidget(cb, 0, Qt::AlignLeft | Qt::AlignTop);
connect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
connect(cb, &QCheckBox::stateChanged, this, &Disconnect::onCheck);
}
void Disconnect::onClick() {
QTextStream out(stdout);
out << "Button clicked" << endl;
}
void Disconnect::onCheck(int state) {
if (state == Qt::Checked) {
connect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
} else {
disconnect(clickBtn, &QPushButton::clicked, this,
&Disconnect::onClick);
}
}
在我们的示例中,我们有一个按钮和一个复选框。复选框用于连接和断开按钮的 clicked 信号与槽的连接。这个示例必须从命令行执行。
connect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
connect(cb, &QCheckBox::stateChanged, this, &Disconnect::onCheck);
在这里,我们将信号连接到我们自定义的槽。
void Disconnect::onClick() {
QTextStream out(stdout);
out << "Button clicked" << endl;
}
如果我们点击 “Click” 按钮,会将 “Button clicked” 文本发送到终端窗口。
void Disconnect::onCheck(int state) {
if (state == Qt::Checked) {
connect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
} else {
disconnect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
}
}
在 onCheck 槽中,我们根据接收到的状态,将 onClick 槽与 “Click” 按钮的信号进行连接或断开连接。
main.cpp
#include <QApplication>
#include "disconnect.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Disconnect window;
window.resize(250, 150);
window.setWindowTitle("Disconnect");
window.show();
return app.exec();
}
- 定时器
定时器用于实现单次执行或重复执行的任务。一个使用定时器的很好的例子是时钟;我们必须每秒更新显示当前时间的标签。
timer.h
#pragma once
#include <QWidget>
#include <QLabel>
class Timer : public QWidget {
public:
Timer(QWidget *parent = 0);
protected:
void timerEvent(QTimerEvent *e);
private:
QLabel *label;
};
timer.cpp
#include "timer.h"
#include <QHBoxLayout>
#include <QTime>
Timer::Timer(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(5);
label = new QLabel("", this);
hbox->addWidget(label, 0, Qt::AlignLeft | Qt::AlignTop);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
startTimer(1000);
}
void Timer::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
}
在我们的示例中,我们在窗口上显示当前的本地时间。
label = new QLabel("", this);
为了显示时间,我们使用了一个标签部件。
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
在这里,我们确定当前的本地时间,并将其设置到标签部件上。
startTimer(1000);
我们启动定时器。每隔 1000 毫秒会生成一个定时器事件。
void Timer::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
}
要处理定时器事件,我们必须重新实现 timerEvent 方法。
main.cpp
#include <QApplication>
#include "timer.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Timer window;
window.resize(250, 150);
window.setWindowTitle("Timer");
window.show();
return app.exec();
}