ZetCode

Qt4 中的事件和信号

最后修改于 2023 年 10 月 18 日

在本 Qt4 C++ 编程教程中,我们将讨论事件和信号。

事件是任何 GUI 程序的重要组成部分。所有 GUI 应用程序都是事件驱动的。应用程序对其生命周期中生成的不同事件类型做出反应。事件主要由应用程序的用户生成。但它们也可以通过其他方式生成,例如 Internet 连接、窗口管理器或计时器。在事件模型中,有三个参与者

事件源 是状态发生变化的对象。它生成事件。事件对象 (Event) 封装了事件源中的状态变化。事件目标 是希望被通知的对象。事件源对象将处理事件的任务委托给事件目标。

当我们调用应用程序的 exec 方法时,应用程序进入主循环。主循环获取事件并将其发送给对象。 Qt 具有独特的信号和槽机制。此信号和槽机制是 C++ 编程语言的扩展。

信号和槽用于对象之间的通信。当发生特定事件时,会发出一个 信号 是一个普通的 C++ 方法;当连接到它的信号被发出时,它会被调用。

点击

第一个例子展示了一个非常简单的事件处理示例。我们有一个推按钮。通过点击推按钮,我们终止应用程序。

click.h
#pragma once

#include <QWidget>

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

这是头文件。

click.cpp
#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

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

connect 方法将信号连接到槽。当我们点击“退出”按钮时,会生成 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();
}

这是主文件。

Click
图:点击

按键

在下面的示例中,我们对按键做出反应。

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();
   } 
}

如果我们按下 Escape 键,应用程序将终止。

void KeyPress::keyPressEvent(QKeyEvent *e) {

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

在 Qt4 中处理事件的一种方法是重新实现事件处理程序。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 对象来确定 xy 值。

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();
}

这是主文件。

QMoveEvent
图:QMoveEvent

断开信号

可以断开信号与槽的连接。下一个例子展示了我们如何做到这一点。

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++ 关键字,它是 Qt4 扩展。这些扩展由预处理器处理,在代码编译之前。当我们在类中使用信号和槽时,我们必须在类定义的开头提供一个 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, 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()));
  }
}

在我们的例子中,我们有一个按钮和一个复选框。复选框连接和断开来自按钮的点击信号的槽。这个例子必须从命令行执行。

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()));
  }
}

onCheck 槽中,我们连接或断开 onClick 槽与“点击”按钮的连接。

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();
}

这是主文件。

Timer
图:计时器

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