所謂 GUI 界面,歸根結底,就是一堆組件的疊加。咱們建立一個窗口,把按鈕放上面,把圖標放上面,這樣就成了一個界面。在放置時,組件的位置尤爲重要。咱們必需要指定組件放在哪裏,以便窗口可以按照咱們須要的方式進行渲染。這就涉及到組件定位的機制。Qt 提供了兩種組件定位機制:絕對定位和佈局定位。ide
顧名思義,絕對定位就是一種最原始的定位方法:給出這個組件的座標和長寬值。這樣,Qt 就知道該把組件放在哪裏以及如何設置組件的大小。可是這樣作帶來的一個問題是,若是用戶改變了窗口大小,好比點擊最大化按鈕或者使用鼠標拖動窗口邊緣,採用絕對定位的組件是不會有任何響應的。這也很天然,由於你並無告訴 Qt,在窗口變化時,組件是否要更新本身以及如何更新。若是你須要讓組件自動更新——這是很常見的需求,好比在最大化時,Word 總會把稿紙區放大,把工具欄拉長——就要本身編寫相應的函數來響應這些變化。或者,還有更簡單的方法:禁止用戶改變窗口大小。但這總不是長遠之計。函數
針對這種變化的需求,Qt 提供了另外的一種機制——佈局——來解決這個問題。你只要把組件放入某一種佈局,佈局由專門的佈局管理器進行管理。當須要調整大小或者位置的時候,Qt 使用對應的佈局管理器進行調整。下面來看一個例子:工具
#include <QApplication> #include <QWidget> #include <QSpinBox> #include <QSlider> #include <QHBoxLayout> int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget w; w.setWindowTitle("Enter your age"); QSpinBox *spinBox = new QSpinBox(&w); QSlider *slider = new QSlider(Qt::Horizontal, &w); spinBox->setRange(0, 120); slider->setRange(0, 120); QObject::connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue); void (QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged; QObject::connect(spinBox, spinBoxSignal, slider, &QSlider::setValue); spinBox->setValue(35); QHBoxLayout *layout = new QHBoxLayout; layout->addWidget(spinBox); layout->addWidget(slider); w.setLayout(layout); w.show(); return a.exec(); }
咱們能夠先來看看運行結果:佈局
當咱們拖動窗口時,能夠看到組件自動有了變化:指針
咱們在這段代碼中引入了兩個新的組件:QSpinBox
和QSlider
。QSpinBox
就是隻能輸入數字的輸入框,而且帶有上下箭頭的步進按鈕。QSlider
則是帶有滑塊的滑竿。咱們能夠從上面的截圖中清楚地辨別出這兩個組件。當咱們建立了這兩個組件的實例以後,咱們使用setRange()
函數設置其範圍。既然咱們的窗口標題是「Enter your age(輸入你的年齡)」,那麼把 range(範圍)設置爲 0 到 130 應該足夠了。code
有趣的部分在下面的connect()
函數。咱們已經清楚connect()
函數的使用,所以咱們寫出:orm
QObject::connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue);
將 slider 的valueChanged()
信號同 spinBox 的setValue()
函數相連。這是咱們熟悉的。可是,當咱們直接寫:對象
QObject::connect(spinBox, &QSpinBox::valueChanged, slider, &QSlider::setValue);
的時候,編譯器卻會報錯:文檔
no matching function for call to 'QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))'
這是怎麼回事呢?從出錯信息能夠看出,編譯器認爲QSpinBox::valueChanged
是一個 overloaded 的函數。咱們看一下QSpinBox
的文檔發現,QSpinBox
的確有兩個信號:get
當咱們使用&QSpinBox::valueChanged
取函數指針時,編譯器不知道應該取哪個函數(記住前面咱們介紹過的,通過 moc 預處理後,signal 也是一個普通的函數。)的地址,所以報錯。解決的方法很簡單,編譯器不是不能肯定哪個函數嗎?那麼咱們就顯式指定一個函數。方法就是,咱們建立一個函數指針,這個函數指針參數指定爲 int:
void (QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged;
而後咱們將這個函數指針做爲 signal,與 QSlider 的函數鏈接:
QObject::connect(spinBox, spinBoxSignal, slider, &QSlider::setValue);
這樣便避免了編譯錯誤。
仔細觀察這兩個connect()
的做用,它們實際完成了一個雙向的數據綁定。固然,對於 Qt 本身的信號函數,咱們能夠比較放心地使用。可是,若是是咱們本身的信號,應當注意避免發生無限循環!
下面的代碼,咱們建立了一個QHBoxLayout
對象。顯然,這就是一個佈局管理器。而後將這兩個組件都添加到這個佈局管理器,而且把該佈局管理器設置爲窗口的佈局管理器。這些代碼看起來都是瓜熟蒂落的,應該很容易明白。而且,佈局管理器很聰明地作出了正確的行爲:保持QSpinBox
寬度不變,自動拉伸QSlider
的寬度。
Qt 提供了幾種佈局管理器供咱們選擇:
QHBoxLayout
:按照水平方向從左到右佈局;QVBoxLayout
:按照豎直方向從上到下佈局;QGridLayout
:在一個網格中進行佈局,相似於 HTML 的 table;QFormLayout
:按照表格佈局,每一行前面是一段文本,文本後面跟隨一個組件(一般是輸入框),相似 HTML 的 form;QStackedLayout
:層疊的佈局,容許咱們將幾個組件按照 Z 軸方向堆疊,能夠造成嚮導那種一頁一頁的效果。