還記得第一章的Hello Qt程序嗎,不知道你有沒有用QPushButton來代替QLabel來重新實現這個程序,因爲這一章需要用到這個
在正式討論信號與槽之前,我們先簡單的討論一個問題,在界面中,有很多結束/關閉的選項,點擊一下程序就結束了,在Qt中,也有類似的情況,比如我點擊一個按鈕,整個程序就結束了,放在代碼的角度來說,有一個類,當我執行他的一個成員函數(點擊了這個按鈕),另一個類(整個窗體)就要執行對應的某個成員函數,那如何把這兩個類的成員函數鏈接在一起呢,確保執行了一個函數後另一個函數也會被執行?爲了解決類似的問題,Qt提供了信號與槽的概念用於來實現這類功能,當然熟悉UNIX/Linux的朋友也瞭解信號這個概念,但需要說明的是Qt的信號和UNIX/Linux的信號沒有任何關係
以QPushButton爲例,當點擊他的時候就會出發clicked()信號,這個信號可以用來和對應的槽來連接,當沒有與槽連接時,這個信號沒有任何作用。碰巧的是QPushButton自身也有一個槽close(),這個槽的作用就是關閉自身,所以我們可以做一個按鈕,點擊他就會自己關閉,而不是點就右上角的"X"
#include<QApplication>
#include<QPushButton>
int main(int argc , char** argv)
{
QApplication app(argc,argv);
QPushButton* quit_pushbutton = new QPushButton("Quit");
quit_pushbutton->show();
QObject::connect(quit_pushbutton,SIGNAL(clicked()),quit_pushbutton,SLOT(close()));
return app.exec();
}
這段代碼和第一章Hello Qt代碼相比多了一行,就是信號與槽的鏈接,鏈接使用類QObject的靜態函數connect()來完成,這個函數看起來有點複雜,但其參數其實很簡單
connect(信號對象,SIGNAL(信號函數),槽對象,SLOT(槽函數))
這樣我們就完成了一個信號和槽的連接,當點擊按鈕是按鈕發出clicked()信號,接受這個信號的槽(函數),也就是close()也被觸發(執行),於是這個按鈕就被關閉了。
這裏需要說明的是類QObject這個類,這個類是Qt中一個很基礎的類,在Qt中所有擁有信號和槽的對象都繼承子該類,換言之,如一個類需要擁有信號或槽,則必須繼承這個QObject
然後我們來看一個稍顯負責的例子
這個程序用於顯示年齡,在一個對話框裏安裝了2個窗體,一個“顯示塊”,一個“劃杆”,當然他們確切的名字分別是QSpinBox和QSlider,在這個程序中,拖動劃塊,“顯示塊”對於的數值就會改變,同樣,修改“顯示塊”的值也會是的“劃杆”的值改變。這裏就用到了信號與槽的連接,當QSpinBox數值改變,出發一個信號,這個信號與QSlider的槽連接,這個槽作用就是改變自身的值。同樣的QSlider的值改變是也會出發一個信號,這個信號同樣的和QSpinBox的槽連接。當然這裏我們還需要面對一個問題,那就是信號與槽直接除連接外,還需要傳輸數據。
#include<QApplication>
#include<QDialog>
#include<QSpinBox>
#include<QSlider>
#include<QHBoxLayout>
int main(int argc , char** argv)
{
QApplication app(argc,argv);
QDialog* top_dialog = new QDialog;
QSpinBox* box_spinbox = new QSpinBox;
QSlider* box_slider = new QSlider(Qt::Horizontal); //註釋1
box_spinbox->setRange(0,130); //註釋2
box_slider->setRange(0,130);
QObject::connect(box_spinbox,SIGNAL(valueChanged(int)),box_slider,SLOT(setValue(int))); //註釋3
QObject::connect(box_slider,SIGNAL(valueChanged(int)),box_spinbox,SLOT(setValue(int)));
box_spinbox->setValue(30); //註釋4
QHBoxLayout* top_layout = new QHBoxLayout;
top_layout->addWidget(box_spinbox);
top_layout->addWidget(box_slider);
top_dialog->setLayout(top_layout);
top_dialog->setWindowTitle("Show Your Age");
top_dialog->show();
return app.exec();
}
註釋1 這裏生產了我們需要的3個窗體,分別是QDialog,QSpinBox和QSlider,這裏需要說明的是QSlider的構造函數有個默認的參數,是一個枚舉量,取值分別爲Qt::Horzental和Qt::Vertical,其默認值爲Qt::Vertical,有興趣的可以使用默認值在編譯下這個程序
註釋2 這裏設置2個窗體顯示數值的範圍,暫且假設人最大的年齡不會超過130歲
註釋3 這裏是整個程序的核心代碼,QSpinBox和QSlider都有一個valueChanged(int)的信號,當他們的值改變的時候會發射,他們也都有一個槽setValue(int),用於改變自身的值,這裏也說明了,信號如何把數據傳遞給槽的,所以不同窗體(類)直接的數據傳輸也可以用信號與槽來實現,這種方式在後面的例子中將會不斷的用到。
註釋4 這裏給定一個初始值,由於已經連接了信號與槽,所以設定一個窗體的值,另一個窗體的值也會改變,不需要而外設置,另外你也可以把這行代碼移動到註釋3上面看看效果
關於信號與槽
1 同一個信號可以和多個槽連接,一個槽也可以與多個信號鏈接。同時信號也可以與信號連接,類似與
connect(對象A,SIGNAL(clicked),對象B,SIGNAL(valueChanged(int))
這種情況通常用於多個信號需要同時發射
2 對於通過信號與槽傳遞參數,信號函數和槽函數的參數必須一致,如果有這樣的連接
connect(對象A,SIGNAL(valueChange(std::string,int),對象B,SLOT(valueChanged(int,std::string))
那槽 將無法接受到任何數據,如果槽函數的參數有默認值將會使用默認值,如果沒有就會出錯,而對於下面這樣的連接
connect(對象A,SIGNAL(valueChange(std::string,int),對象B,SLOT(valueChanged(std::string))
不會出現錯誤,但信號中int數據將無法傳遞到槽函數中
3 信號與槽其實就是類的成員函數,所不同的是信號函數一般沒有代碼,而槽函數有函數定義來實現具體的功能 ,當然這樣只是表明現象,其實信號函數之所以沒有代碼,是Qt已經做了一些處理,而並非真正的沒有代碼