#(11):布局管理器
所謂 GUI 界面,歸根結底,就是一堆組件的疊加。我們創建一個窗口,把按鈕放上面,把圖標放上面,這樣就成了一個界面。在放置時,組件的位置尤其重要。我們必須要指定組件放在哪里,以便窗口能夠按照我們需要的方式進行渲染。這就涉及到組件定位的機制。Qt 提供了兩種組件定位機制:絕對定位和布局定位。
顧名思義,絕對定位就是一種最原始的定位方法:給出這個組件的坐標和長寬值。這樣,Qt 就知道該把組件放在哪里以及如何設置組件的大小。但是這樣做帶來的一個問題是,如果用戶改變了窗口大小,比如點擊最大化按鈕或者使用鼠標拖動窗口邊緣,采用絕對定位的組件是不會有任何響應的。這也很自然,因為你并沒有告訴 Qt,在窗口變化時,組件是否要更新自己以及如何更新。如果你需要讓組件自動更新——這是很常見的需求,比如在最大化時,Word 總會把稿紙區放大,把工具欄拉長——就要自己編寫相應的函數來響應這些變化。或者,還有更簡單的方法:禁止用戶改變窗口大小。但這總不是長遠之計。
針對這種變化的需求,Qt 提供了另外的一種機制——布局——來解決這個問題。你只要把組件放入某一種布局,布局由專門的布局管理器進行管理。當需要調整大小或者位置的時候,Qt 使用對應的布局管理器進行調整。下面來看一個例子:
~~~
// !!! Qt 5
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("Enter your age");
QSpinBox *spinBox = new QSpinBox(&window);
QSlider *slider = new QSlider(Qt::Horizontal, &window);
spinBox->setRange(0, 130);
slider->setRange(0, 130);
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);
window.setLayout(layout);
window.show();
return app.exec();
}
~~~
這段例子還是有些東西值得解釋的。我們可以先來看看運行結果:
[](http://files.devbean.net/images/2012/09/layout-demo.png)
當我們拖動窗口時,可以看到組件自動有了變化:
[](http://files.devbean.net/images/2012/09/layout-demo-2.png)
我們在這段代碼中引入了兩個新的組件:`QSpinBox`和`QSlider`。`QSpinBox`就是只能輸入數字的輸入框,并且帶有上下箭頭的步進按鈕。`QSlider`則是帶有滑塊的滑竿。我們可以從上面的截圖中清楚地辨別出這兩個組件。當我們創建了這兩個組件的實例之后,我們使用`setRange()`函數設置其范圍。既然我們的窗口標題是“Enter your age(輸入你的年齡)”,那么把 range(范圍)設置為 0 到 130 應該足夠了。
有趣的部分在下面的`connect()`函數。我們已經清楚`connect()`函數的使用,因此我們寫出
~~~
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`的確有兩個信號:
* void valueChanged(int)
* void valueChanged(const QString &)
當我們使用`&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 軸方向堆疊,可以形成向導那種一頁一頁的效果。
當然,我們也可以使用 Qt 4 來編譯上面的代碼,不過,正如大家應該想到的一樣,我們必須把`connect()`函數修改一下:
~~~
// !!! Qt 4
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("Enter your age");
QSpinBox *spinBox = new QSpinBox(&window);
QSlider *slider = new QSlider(Qt::Horizontal, &window);
spinBox->setRange(0, 130);
slider->setRange(0, 130);
QObject::connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
slider, SLOT(setValue(int)));
spinBox->setValue(35);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(spinBox);
layout->addWidget(slider);
window.setLayout(layout);
window.show();
return app.exec();
}
~~~
這里我們強調一下,上面的代碼在 Qt 5 中同樣可以編譯通過。不過,我們減少了使用函數指針指定信號的步驟。也就是說,在 Qt 5 中,如果你想使用 overloaded 的 signal,有兩種方式可供選擇:
1. 使用 Qt 4 的`SIGNAL`和`SLOT`宏,因為這兩個宏已經指定了參數信息,所以不存在這個問題;
2. 使用函數指針顯式指定使用哪一個信號。
有時候,使用 Qt 4 的語法更簡潔。但是需要注意的是,Qt 4 的語法是沒有編譯期錯誤檢查的。這也是同 Qt 5 的信號槽新語法不同之處之一。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)