147.md 10.1 KB
Newer Older
W
wizardforcel 已提交
1 2 3 4
# Qt4 中的事件和信号

> 原文: [http://zetcode.com/gui/qt4/eventsandsignals/](http://zetcode.com/gui/qt4/eventsandsignals/)

W
wizardforcel 已提交
5
在 Qt4 C++ 编程教程的这一部分中,我们讨论事件和信号。
W
wizardforcel 已提交
6

W
wizardforcel 已提交
7
事件是任何 GUI 程序中的重要组成部分。 所有 GUI 应用都是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件主要由应用的用户生成。 但是它们也可以通过其他方式生成,例如互联网连接,窗口管理器或计时器。 在事件模型中,有三个参与者:
W
wizardforcel 已提交
8 9 10 11 12 13 14

*   事件来源
*   事件对象
*   事件目标

事件源是状态更改的对象。 它生成事件。 事件对象(事件)将状态更改封装在事件源中。 事件目标是要通知的对象。 事件源对象将处理事件的任务委托给事件目标。

W
wizardforcel 已提交
15
当我们调用应用的`exec()`方法时,应用进入主循环。 主循环获取事件并将其发送到对象。 Qt 具有独特的信号和槽机制。 该信号和槽机制是 C++ 编程语言的扩展。
W
wizardforcel 已提交
16

W
wizardforcel 已提交
17
信号和槽用于对象之间的通信。 当发生特定事件时,会发出信号。 槽是正常的 C++ 方法; 发出与其连接的信号时调用它。
W
wizardforcel 已提交
18

W
wizardforcel 已提交
19
## 点击
W
wizardforcel 已提交
20

W
wizardforcel 已提交
21
第一个示例显示了一个非常简单的事件处理示例。 我们有一个按钮。 通过单击按钮,我们终止该应用。
W
wizardforcel 已提交
22 23 24

`click.h`

W
wizardforcel 已提交
25
```cpp
W
wizardforcel 已提交
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
#pragma once

#include <QWidget>

class Click : public QWidget {

  public:
    Click(QWidget *parent = 0);
};

```

这是头文件。

`click.cpp`

W
wizardforcel 已提交
42
```cpp
W
wizardforcel 已提交
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
#include <QPushButton>
#include <QApplication>
#include <QHBoxLayout>
#include "click.h"

Click::Click(QWidget *parent)
    : QWidget(parent) {

  QHBoxLayout *hbox = new QHBoxLayout(this);
  hbox->setSpacing(5);

  QPushButton *quitBtn = new QPushButton("Quit", this);
  hbox->addWidget(quitBtn, 0, Qt::AlignLeft | Qt::AlignTop);

  connect(quitBtn, SIGNAL(clicked()), qApp, SLOT(quit()));
}

```

我们在窗口上显示一个`QPushButton`

W
wizardforcel 已提交
64
```cpp
W
wizardforcel 已提交
65 66 67 68
connect(quitBtn, SIGNAL(clicked()), qApp, SLOT(quit()));

```

W
wizardforcel 已提交
69
`connect()`方法将信号连接到槽。 当我们单击退出按钮时,会生成`clicked()`信号。 `qApp`是指向应用对象的全局指针。 它在`<QApplication>`头文件中定义。 发出点击信号时,将调用`quit()`方法。
W
wizardforcel 已提交
70 71 72

`main.cpp`

W
wizardforcel 已提交
73
```cpp
W
wizardforcel 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
#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();
}

```

这是主文件。

![Click](img/b3463a5162edfcb31828312d07e3cbba.jpg)

W
wizardforcel 已提交
96
图:点击
W
wizardforcel 已提交
97 98 99 100 101 102 103

## 按键

在以下示例中,我们对按键进行反应。

`keypress.h`

W
wizardforcel 已提交
104
```cpp
W
wizardforcel 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
#pragma once

#include <QWidget>

class KeyPress : public QWidget {

  public:
    KeyPress(QWidget *parent = 0);

  protected:
    void keyPressEvent(QKeyEvent * e);
};

```

这是`keypress.h`头文件。

`keypress.cpp`

W
wizardforcel 已提交
124
```cpp
W
wizardforcel 已提交
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
#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();
   } 
}

```

W
wizardforcel 已提交
143
如果我们按 `Escape` 键,则应用终止。
W
wizardforcel 已提交
144

W
wizardforcel 已提交
145
```cpp
W
wizardforcel 已提交
146 147 148 149 150 151 152 153 154
void KeyPress::keyPressEvent(QKeyEvent *e) {

   if (e->key() == Qt::Key_Escape) {  
       qApp->quit();
   } 
}

```

W
wizardforcel 已提交
155
在 Qt4 中使用事件的一种方法是重新实现事件处理器。 `QKeyEvent`是一个事件对象,其中包含有关发生的情况的信息。 在我们的例子中,我们使用事件对象来确定实际按下了哪个键。
W
wizardforcel 已提交
156 157 158

`main.cpp`

W
wizardforcel 已提交
159
```cpp
W
wizardforcel 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
#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();
}

```

这是主文件。

W
wizardforcel 已提交
180
## `QMoveEvent`
W
wizardforcel 已提交
181 182 183 184 185

`QMoveEvent`类包含移动事件的事件参数。 移动事件将发送到已移动的窗口小部件。

`move.h`

W
wizardforcel 已提交
186
```cpp
W
wizardforcel 已提交
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
#pragma once

#include <QMainWindow>

class Move : public QWidget {

  Q_OBJECT

  public:
    Move(QWidget *parent = 0);

  protected:
    void moveEvent(QMoveEvent *e);
};

```

这是`move.h`头文件。

`move.cpp`

W
wizardforcel 已提交
208
```cpp
W
wizardforcel 已提交
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
#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);
}

```

W
wizardforcel 已提交
228
在我们的代码编程示例中,我们对移动事件做出反应。 我们确定窗口客户区左上角的当前 x,y 坐标,并将这些值设置为窗口标题。
W
wizardforcel 已提交
229

W
wizardforcel 已提交
230
```cpp
W
wizardforcel 已提交
231 232 233 234 235 236 237
int x = e->pos().x();
int y = e->pos().y();

```

我们使用`QMoveEvent`对象来确定`x``y`值。

W
wizardforcel 已提交
238
```cpp
W
wizardforcel 已提交
239 240 241 242 243 244
QString text = QString::number(x) + "," + QString::number(y);

```

我们将整数值转换为字符串。

W
wizardforcel 已提交
245
```cpp
W
wizardforcel 已提交
246 247 248 249 250 251 252 253
setWindowTitle(text);

```

`setWindowTitle()`方法将文本设置为窗口的标题。

`main.cpp`

W
wizardforcel 已提交
254
```cpp
W
wizardforcel 已提交
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
#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();
}

```

这是主文件。

![QMoveEvent](img/9ecc75ef2dd9c1fb2367334852432602.jpg)

W
wizardforcel 已提交
277
图:`QMoveEvent`
W
wizardforcel 已提交
278 279 280

## 断开信号

W
wizardforcel 已提交
281
可以从槽断开信号。 下一个示例显示了我们如何完成此任务。
W
wizardforcel 已提交
282 283 284

`disconnect.h`

W
wizardforcel 已提交
285
```cpp
W
wizardforcel 已提交
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
#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;
};

```

W
wizardforcel 已提交
308
在头文件中,我们声明了两个槽。 `slots`不是 C++ 关键字,它是 Qt4 扩展名。 在代码编译之前,这些扩展由预处理器处理。 当我们在类中使用信号和时隙时,必须在类定义的开头提供`Q_OBJECT`宏。 否则,预处理器会抱怨。
W
wizardforcel 已提交
309 310 311

`disconnect.cpp`

W
wizardforcel 已提交
312
```cpp
W
wizardforcel 已提交
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
#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, SIGNAL(clicked()), this, SLOT(onClick()));
  connect(cb, SIGNAL(stateChanged(int)), this, SLOT(onCheck(int)));  
}

void Disconnect::onClick() {

  QTextStream out(stdout);
  out << "Button clicked" << endl;
}

void Disconnect::onCheck(int state) {

  if (state == Qt::Checked) {
    connect(clickBtn, SIGNAL(clicked()), this, SLOT(onClick()));
  } else {
    clickBtn->disconnect(SIGNAL(clicked()));
  }
}

```

W
wizardforcel 已提交
352
在我们的示例中,我们有一个按钮和一个复选框。 复选框用于将槽与单击的信号按钮断开连接。 此示例必须从命令行执行。
W
wizardforcel 已提交
353

W
wizardforcel 已提交
354
```cpp
W
wizardforcel 已提交
355 356 357 358 359
connect(clickBtn, SIGNAL(clicked()), this, SLOT(onClick()));
connect(cb, SIGNAL(stateChanged(int)), this, SLOT(onCheck(int)));  

```

W
wizardforcel 已提交
360
在这里,我们将信号连接到用户定义的槽。
W
wizardforcel 已提交
361

W
wizardforcel 已提交
362
```cpp
W
wizardforcel 已提交
363 364 365 366 367 368 369 370 371 372
void Disconnect::onClick() {

  QTextStream out(stdout);
  out << "Button clicked" << endl;
}

```

如果单击“单击”按钮,则将“单击按钮”文本发送到终端窗口。

W
wizardforcel 已提交
373
```cpp
W
wizardforcel 已提交
374 375 376 377 378 379 380 381 382 383 384
void Disconnect::onCheck(int state) {

  if (state == Qt::Checked) {
    connect(clickBtn, SIGNAL(clicked()), this, SLOT(onClick()));
  } else {
    clickBtn->disconnect(SIGNAL(clicked()));
  }
}

```

W
wizardforcel 已提交
385
`onCheck()`槽内,我们通过单击按钮连接或断开`onClick()`槽。
W
wizardforcel 已提交
386 387 388

`main.cpp`

W
wizardforcel 已提交
389
```cpp
W
wizardforcel 已提交
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
#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`

W
wizardforcel 已提交
416
```cpp
W
wizardforcel 已提交
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
#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`

W
wizardforcel 已提交
440
```cpp
W
wizardforcel 已提交
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
#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);
}

```

在我们的示例中,我们在窗口上显示当前本地时间。

W
wizardforcel 已提交
474
```cpp
W
wizardforcel 已提交
475 476 477 478 479 480
label = new QLabel("", this);

```

为了显示时间,我们使用标签小部件。

W
wizardforcel 已提交
481
```cpp
W
wizardforcel 已提交
482 483 484 485 486 487 488 489
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);

```

在这里,我们确定当前的本地时间。 我们将其设置为标签小部件。

W
wizardforcel 已提交
490
```cpp
W
wizardforcel 已提交
491 492 493 494
startTimer(1000);

```

W
wizardforcel 已提交
495
我们启动计时器。 每 1000ms 会生成一个计时器事件。
W
wizardforcel 已提交
496

W
wizardforcel 已提交
497
```cpp
W
wizardforcel 已提交
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
void Timer::timerEvent(QTimerEvent *e) {

  Q_UNUSED(e);

  QTime qtime = QTime::currentTime();
  QString stime = qtime.toString();
  label->setText(stime);
}

```

要处理计时器事件,我们必须重新实现`timerEvent()`方法。

`main.cpp`

W
wizardforcel 已提交
513
```cpp
W
wizardforcel 已提交
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
#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();
}

```

这是主文件。

![Timer](img/850cc32b12567f3ca4ed0032036e05eb.jpg)

W
wizardforcel 已提交
536
图:计时器
W
wizardforcel 已提交
537 538

本章专门介绍 Qt4 中的事件和信号。