事件与信号(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++函数,当指定信号触发时这个函数会被调用。信号和槽需要事先进行连接。
鼠标点击
我们之前已经见过了在QPushButton
上应用信号与槽的机制来处理点击事件。这里不再赘述,参考按钮与交互 这个部分。
按键响应
下面的这个例子显示了按键响应的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 #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 #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
的一个虚函数 。故这里我们不直接使用到槽函数。
QMoveEvent
这个事件代表 Widget 移动的过程,同样是通过 override 父类的虚函数实现的。见下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #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 #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 #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
函数来实现,其参数形式应当和 connect 时的参数一致。例如
1 2 3 4 connect (clickBtn, &QPushButton::clicked, this , &Disconnect::onClick);disconnect (clickBtn, &QPushButton::clicked, this , &Disconnect::onClick);
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 (); }
这里的startTimer
和timerEvent
都是父类提供的函数。
这里梳理一下常用的基础控件类型。这些类都是QWidget
的子类。
QLabel
QLabel
用来展示文字和图片 ,不涉及用户操作。
QSlider
QSlider
提供了便捷的连续数字选择器。长这个样子:
QComboBox
QComboBox
可以让我们从有限的下拉选项中选择。
QSpinBox
用来处理可以递增递减的离散数字
QLineEdit
单行文字输入,这个我们之前已经接触过了。
Statusbar
状态栏,通过statusBar()
函数来获取。
其他
还有好多控件就不一一说明了,很多看名字就能知道是做什么用的:
QCheckBox
QListWidget
QProgressBar
QPixmap
QSpliter
QTableWidget
绘图
这个部分也是我需要关注的重点,如何自由地绘图
QPainter
是我们进行绘图操作的画板。绘图过程在paintEvent
函数(QWidget
的函数)中完成。
我们首先来看一个绘制直线的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #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 #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
的文件内容很简单,我们之后就不放了。
在这个例子中我们用六种不同的笔触画了六根线,如下图:
更高阶的绘图还是看原教程吧,内容太多了。