1 事件与信号(Events and signals)

事件系统在任何 GUI 框架中都是非常重要的,所有的 GUI 应用都是事件驱动的(Event-driven)。这些信号可以是用户产生,也可能是程序内部产生。在一个事件模型中,有如下三个重要的部分

  • event source
  • event object
  • event target

其中,event source 是指产生事件的对象,一般在产生事件的同时,其内部状态也发生了变化。event object 则是指描述事件本身的对象。event target 则是需要响应这些事件的对象。

当我们调用QApplication::exec函数时,应用进入主循环中(main loop)。主循环负责获取事件并将事件发送给合适的响应对象。Qt 实现了独特的 signal and slot 机制。这一机制是对 C++变成语言的扩展。这里,信号(signal)和槽(slot)用于对象之间的通信,其中信号在事件发生时产生。槽一般是一个正常的 C++函数,当指定信号触发时这个函数会被调用。信号和槽需要事先进行连接。

1.1 鼠标点击

我们之前已经见过了在QPushButton上应用信号与槽的机制来处理点击事件。这里不再赘述,参考按钮与交互这个部分。

1.2 按键响应

下面的这个例子显示了按键响应的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
// keypress.h
#pragma once

#include <QWidget>

class KeyPress : public QWidget {

public:
KeyPress(QWidget *parent = 0);

protected:
void keyPressEvent(QKeyEvent * e);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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();
}

注意keyPressEvent是父类QWidget的一个虚函数。故这里我们不直接使用到槽函数。

1.3 QMoveEvent

这个事件代表 Widget 移动的过程,同样是通过 override 父类的虚函数实现的。见下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// move.h
#pragma once

#include <QMainWindow>

class Move : public QWidget {

Q_OBJECT

public:
Move(QWidget *parent = 0);

protected:
void moveEvent(QMoveEvent *e);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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();
}

1.4 断开信号和槽的联系

信号和槽可以通过槽连接气力啊,也可以断开。断开通过disconnect函数来实现,其参数形式应当和 connect 时的参数一致。例如

1
2
3
4
connect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
// ...
disconnect(clickBtn, &QPushButton::clicked, this,
&Disconnect::onClick);

1.5 Timer

Timer 是周期性发出的事件(当然也可以用来做 single shot)。Timer 的典型例子就是用来实现一个时钟:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma once

#include <QWidget>
#include <QLabel>

class Timer : public QWidget {

public:
Timer(QWidget *parent = 0);

protected:
void timerEvent(QTimerEvent *e);

private:
QLabel *label;
};
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
#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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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();
}

这里的startTimertimerEvent都是父类提供的函数。

2 QWidget

这里梳理一下常用的基础控件类型。这些类都是QWidget的子类。

2.1 QLabel

QLabel用来展示文字和图片,不涉及用户操作。

2.2 QSlider

QSlider提供了便捷的连续数字选择器。长这个样子:

2.3 QComboBox

QComboBox可以让我们从有限的下拉选项中选择。

2.4 QSpinBox

用来处理可以递增递减的离散数字

2.5 QLineEdit

单行文字输入,这个我们之前已经接触过了。

2.6 Statusbar

状态栏,通过statusBar()函数来获取。

2.7 其他

还有好多控件就不一一说明了,很多看名字就能知道是做什么用的:

  • QCheckBox
  • QListWidget
  • QProgressBar
  • QPixmap
  • QSpliter
  • QTableWidget

3 绘图

这个部分也是我需要关注的重点,如何自由地绘图

QPainter是我们进行绘图操作的画板。绘图过程在paintEvent函数(QWidget的函数)中完成。

我们首先来看一个绘制直线的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// lines.h
#pragma once

#include <QWidget>

class Lines : public QWidget {

public:
Lines(QWidget *parent = 0);

protected:
void paintEvent(QPaintEvent *event);
void drawLines(QPainter *qp);
};
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
47
48
49
// lines.cpp
#include <QPainter>
#include "lines.h"

Lines::Lines(QWidget *parent)
: QWidget(parent)
{ }

void Lines::paintEvent(QPaintEvent *e) {

Q_UNUSED(e);

QPainter qp(this);
drawLines(&qp);
}

void Lines::drawLines(QPainter *qp) {

QPen pen(Qt::black, 2, Qt::SolidLine);
qp->setPen(pen);
qp->drawLine(20, 40, 250, 40);

pen.setStyle(Qt::DashLine);
qp->setPen(pen);
qp->drawLine(20, 80, 250, 80);

pen.setStyle(Qt::DashDotLine);
qp->setPen(pen);
qp->drawLine(20, 120, 250, 120);

pen.setStyle(Qt::DotLine);
qp->setPen(pen);
qp->drawLine(20, 160, 250, 160);

pen.setStyle(Qt::DashDotDotLine);
qp->setPen(pen);
qp->drawLine(20, 200, 250, 200);

QVector<qreal> dashes;
qreal space = 4;

dashes << 1 << space << 5 << space;

pen.setStyle(Qt::CustomDashLine);
pen.setDashPattern(dashes);

qp->setPen(pen);
qp->drawLine(20, 240, 250, 240);
}

main.cpp的文件内容很简单,我们之后就不放了。

在这个例子中我们用六种不同的笔触画了六根线,如下图:

更高阶的绘图还是看原教程吧,内容太多了。