Qt的信號/槽機制給我們編程帶來了很大的便利。在多數的情況下,我們只需要一個信號對應一個槽就可以了。但有時候我們也會遇到多個信號對應一個槽的情況。比如說做一個計算器的小程序,界面上有10個按鈕分別對應0 - 9這幾個數字。按下這幾個按鈕處理的邏輯是類似的,所以我們完全沒有必要為每一個按鈕都寫一個槽來對應按鈕按下的信號。但是假如我們把10個按鈕的按下信號都連接到一個槽函數中,那麼槽函數又要怎麼區分此時按下的是哪個按鈕呢?要知道按鈕的按下信號是不帶參數的。
這時候QSignalMapper就能派生用場了。它可以將對象的信號轉發一個槽函數中。那麼具體該怎麼使用呢?舉個例子吧:
首先用QtCreator創建一個基於QWidget的程序,使用UI設計師在界面上拖6個按鈕。
接下來我們將使用QSignalMapper將這6個按鈕的信號都連接到一個槽函數中。
創建QSignalMapper對象,並將它的mapped信號連接到一個槽函數中,這個槽就是我們用來處理6個按鈕的地方。
mSignalMapper = new QSignalMapper(this); connect(mSignalMapper,SIGNAL(mapped(QWidget*)),this,SLOT(slotSignalMap(QWidget*)));
mapped信號有4個不同參數的版本,根據需求自行選擇。
2. 將QSignalMapper與按鈕關聯起來,使用QSignalMapper的setMapping函數。
mSignalMapper->setMapping(ui->pushButton,ui->pushButton); mSignalMapper->setMapping(ui->pushButton_2,ui->pushButton_2); mSignalMapper->setMapping(ui->pushButton_3,ui->pushButton_3); mSignalMapper->setMapping(ui->pushButton_4,ui->pushButton_4); mSignalMapper->setMapping(ui->pushButton_5,ui->pushButton_5); mSignalMapper->setMapping(ui->pushButton_6,ui->pushButton_6);
setMapping有4個重載函數,參數類型與mapped信號的參數一一對應。
3. 將按鈕的按下信號與QSignalMapper關聯起來。前面做了那麼多不就是為了信號連在一起嗎。
connect(ui->pushButton,SIGNAL(pressed()),mSignalMapper,SLOT(map())); connect(ui->pushButton_2,SIGNAL(pressed()),mSignalMapper,SLOT(map())); connect(ui->pushButton_3,SIGNAL(pressed()),mSignalMapper,SLOT(map())); connect(ui->pushButton_4,SIGNAL(pressed()),mSignalMapper,SLOT(map())); connect(ui->pushButton_5,SIGNAL(pressed()),mSignalMapper,SLOT(map())); connect(ui->pushButton_6,SIGNAL(pressed()),mSignalMapper,SLOT(map()));
這一步將各個按鈕的信號都連接到QSignalMapper的map()槽函數中。然後發射到第1步的slotSignalMap槽中。
4.在slotSignalMap處理
void Widget::slotSignalMap(QWidget* w){QPushButton* button = qobject_cast<QPushButton*>(w); if(NULL != button){ qDebug()<<"click:" + button->text(); }}
由於我們在關聯按鈕和QSignalMapper時(setMapping)將按鈕對象傳入,在mapped信號觸發時傳給槽函數slotSignal。我們只需要把這個參數QWiget轉換為QPushButton對象就自然知道此時按下的是哪個按鈕了。效果如圖:
使用QSignalMapper可以幫我們解決信號轉發的問題,不過感覺還是有點局限性,我們可以看到信號最終是通過QSignalMapper的mapped()轉發出去的,如果我的QPushButton的兩個不同的信號都用QSignalMapper轉發會怎樣呢?
connect(ui->pushButton,SIGNAL(clicked(bool)),mSignalMapper,SLOT(map())); //clicked(信號) connect(ui->pushButton,SIGNAL(pressed()),mSignalMapper,SLOT(map())); //pressed(信號)
我們將第一個按鈕的clicked和pressed兩個信號都通過mSignalMapper轉發出去,點擊按鈕列印出了兩個"click:1"。
說明兩個信號都在同一個槽函數中處理了,這肯定不是我們想要的。我們既想要不同對象的同一個信號在同一個槽函數中處理,又不希望一個對象的不同信號在同一個槽函數中處理。怎麼辦?
看來還需要sender()函數來幫忙了。通過在槽函數中調用sender()來獲取信號的發送者。將上面的例子改一改,先定義一個slotClicked槽函數
void Widget::slotClicked(bool bcheck){QPushButton* button = qobject_cast<QPushButton*>(sender()); //將sender()轉為QPushButton if(NULL != button){ qDebug()<<"fearlazy click:"<<button->text()<<bcheck; }}
在槽函數中使用sender()獲取此時槽函數對應的信號發送者,返回類型是QObject。 這樣我們可以直接將不同對象的信號連接到同一個槽上,還能分清楚是誰觸發的。
connect(ui->pushButton,SIGNAL(clicked(bool)),this,SLOT(slotClicked(bool))); connect(ui->pushButton_2,SIGNAL(clicked(bool)),this,SLOT(slotClicked(bool))); connect(ui->pushButton_3,SIGNAL(clicked(bool)),this,SLOT(slotClicked(bool))); connect(ui->pushButton_4,SIGNAL(clicked(bool)),this,SLOT(slotClicked(bool))); connect(ui->pushButton_5,SIGNAL(clicked(bool)),this,SLOT(slotClicked(bool))); connect(ui->pushButton_6,SIGNAL(clicked(bool)),this,SLOT(slotClicked(bool)));
看看運行效果:
最後我們再添加一個slotPressed()槽函數,並讓第一個按鈕的pressed()信號連接該槽函數。
void Widget::slotPressed(){QPushButton* button = qobject_cast<QPushButton*>(sender()); if(NULL != button){ qDebug()<<"fearlazy pressed:"<<button->text(); //列印出presse了誰 }}connect(ui->pushButton,SIGNAL(pressed()),this,SLOT(slotPressed()));//在Widget的構造函數中連接
點擊第一個按鈕看看效果如何?
可以看到pressed和clicked信號都觸發了,而且兩個信號在不同的槽函數中處理。
測試環境:Qt5.9.4