QT高級編程技巧(一)-- 編寫高效的signal & slot通信代碼


關於QT的線程通信,我們都會想到signal & slot機制。先回顧下利用signal & slot機制實現控件消息處理的方法。

控件消息處理

假設我們的主界面上有一個使用ui->btn指向的QPushButton對象,要實現該對象的clicked消息處理,可以在主界面對象MainWindow上添加一個slot方法onBtnClicked,並在其構造函數中使用connect方法與ui->btn的clicked消息進行綁定,如下:

MainWindow::MainWindow(QWidget* parent) :
  QMainWindow(parent), ui(new ui::MainWindow)
{
    ui->setupUi(this);
    
    // 方法一,推薦使用
    connect(ui->btn, &QPushButton::clicked,
       this, &MainWindow::onBtnClicked); // 方法二 //connect(ui->btn, SIGNAL(clicked),
       this, SLOT(onBtnClicked)); … }

void MainWindow::onBtnClicked(void)
{
  doSomething();
}

事實上這種方法很繁雜。單純為了處理一個按鈕的消息我們就要新建一個slot方法,當界面控件多了的時候,會使得代碼臃腫不堪(即使你可以設計成多個控件消息共享一個槽函數)。

在新版的支持C++11的QT中,我們可以使用Lambda函數優雅地解決這個問題。如下:

MainWindow::MainWindow(QWidget* parent) :
    QMainWindow(parent), ui(new ui::MainWindow)
{
    setupUi(this);

    // 使用lambda函數實現slot方法
    connect(ui->btn, &QPushButton::clicked, [&](){
        doSomething();
    });
    …
}

使用Lambda函數的另一個好處是,可以借助閉包的概念加速開發。比如我曾做過一個運動控制項目,界面上有32個之多的QCheckBox控件,如下:

需要響應每個QCheckBox控件的clicked消息,改變對應的IO口電平輸出。我的做法是,對這些控件進行Tab排序,並將第一個控件命名為ui->ioChk,然后編碼如下:

typedef QCheckBox   *F_QCB;
#define F_QNEXT(w)  (w->nextInFocusChain())



MainWindow::MainWindow(QWidget* parent) :
  QMainWindow(parent), ui(new ui::MainWindow)
{
  setupUi(ui);
  
  …
  F_QCB cb = ui->ioChk;
  for (int i = 0; i < 32; ++i)
  {
    connect(cb, &QCheckBox::clicked, [=](bool s){
      setIo(i, s); // 改變第i個端口的輸出電平為s
    });
    cb = F_QNEXT(cb);
  }
  …
}

要讀懂上面的程序,你需要一些關於C++11標准的Lambda函數的知識,代碼中使用值捕獲方式捕獲i變量值形成閉包函數,這些閉包函數被綁定到不同的QCheckBox的clicked消息中。

線程間通信

很長的時間里,我都以為QT的signal & slot機制只適用於單向異步通信。事實上,它可以設計為帶返回的同步通信。

有個案例如下:工作線程在運行時需要同步提取界面上的參數信息(假設要提取界面上的QSpinBox的值),要同時保證界面的響應和工作線程的不間斷運轉。

一種可能的實現方法是,在工作線程上的某個對象定義一個request消息,綁定到MainWindow的response槽中,然后在MainWindow中定義一個answer消息,綁定到工作線程上的某個對象的onAnswer槽中。通信過程如下:

1. 工作線程需要界面參數值,發送request消息

2. 主線程接收到request消息,開始執行response槽方法

3. 在response函數中,獲取界面控件的值,發送answer消息

4. 工作線程收到answer消息,調用onAnswer槽方法,恢復之前的運行流程

這種方法條理清晰,但編碼實現過於繁瑣。我的實現方法是,在工作線程實現一個形如request(QString req, QVariant& arg)的消息,然后以QT::BlockBlockingQueuedConnection方式連接至MainWindow的response槽方法中。編碼如下:

WorkerThread聲明:

#ifndef __WORKER_THREAD_H
#define __WORKER_THREAD_H

class WorkerThread : public QThread {
public:
  …
  // @Override
  void run();

private:
  // 用於通信
  class InThreadObject;
  InThreadObject *ito;
  …
}

#endif

WorkerThread實現:

class WorkerThread::InThreadObject : public QObject {
  Q_OBJECT

signals:
  void request(QString req, QVariant& arg);
}

void WorkerThread::run()
{
  ito = new InThreadObject;
  …
  // 注意下面的連接使用了QT::BlockingQueuedConnection選項
  connect(ito,
       &InThreadObject::request,
       pMainWindow,
       &MainWindow::response,
       QT::BlockingQueuedConnection);
  …
  // 同步獲取界面參數值
  QVariant var;
  ito->request("param.level", var);
  // 這里會同步等待主線程執行response函數完
  int param = var.toInt();
  ……
}

MainWindow相關實現:

void MainWindow::response(QString req, QVariant& ans)
{
  if (req == "param.level")
  {
    // 獲取控件值並存放至ans引用變量中
    ans.setValue(ui->levelSpin->value());
  }
}

本次經驗分享完畢,關於一些具體的技術細節,如QT的消息與槽機制、C++11的Lambda函數還請讀者另行學習。

 

本文鏈接:http://www.cnblogs.com/wenris/p/4447481.html

作者:wenris,聯系:<wenris AT yeah.net>

后會有期哦 O(∩_∩)O


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM