初次接触 Qt 时,可能会对一段常见的代码感到费解:
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {
// set central widget
centralWidget = new QWidget(this);
centralWidget->setObjectName("centralWidget");
this->setCentralWidget(centralWidget);
// set layout of central widget
verticalLayout = new QVBoxLayout(centralWidget);
verticalLayout->setObjectName("verticalLayout");
verticalLayout->addWidget(...);
}
直觉上,我们会觉得直接把 verticalLayout 设为 MainWindow 的 centralWidget 会更简洁直观:
// 错误代码示范
this->setCentralWidget(verticalLayout);
因为 new 一个新的 QWidget 却只为让它去“包裹”一个“布局”1看上去是有些“绕”的,但在 Qt 里,这是必须的。这涉及到 Qt 中的 Widget 类型和它们与 Layout 之间的关系。
从布局的角度看,Qt 有两种类型的 widget:一类是普通的 widget,它们可以设置 layout;另一类则是“框架型”容器,它们的布局是预设好且不可修改的,典型的例子就是 QMainWindow,它的布局固定框架结构是:
- QMainWindow
- menu bar
- tool bar
- dock widget
- status bar
- central widget
所以我们是不能直接给它 setLayout 的。下表详细地对比了两类 widget:
| 普通容器(自由容器) | 可以设置 layout | QWidget、QFrame、QDialog |
| 框架容器(结构型容器) | 布局是预设好的固定结构,不可以设置 layout | QMainWindow、QScrollArea |
此外,还需要明确的一点是:Layout 并不是一种容器型的 Widget,QLayout 和 QWidget 是两个独立演化的类族,彼此之间不存在任何继承关系,Layout 的本质是“管理几何关系的算法”。
最后,Layout 是通过“挂靠”到(“依附”于)一个 QWidget 而存在和发挥作用的,不存在一个包含了若干子控件但却不隶属于任何 Widget 的 Layout。
基于以上三点解释,我们重新回看一下上面的问题:
- 既然 QMainWindow 的 setCentralWidget 方法需要的参数类型是 QWidget* widget,则设置一个 Layout 显然是不合法的
- 理论上 QMainWindow 也可以添加一个 setCentralLayout 方法,但这个 API 很像是一个“毛刺”,与整体的 API 设计显得极不协调,因为:
- 所有 QWidget 都是通过 setLayout 设置布局的,这个 API 势必“干预”到了基子控件的内部实现,破坏了子控件的“封装”
- 承接上一点,既然 Layout 必须“挂靠”到一个 QWidget,可以想象,setCentralLayout 方法的实现也还是得先 new 一个 widget 出来再 setLayout,如果封装起来,用户就没有机会控制或选择 new 一个什么 widget 出来了。
总之,在这个问题上,可以看出 Qt 的设计理念是:
一个 widget 要么可以设置自己(整体且唯一)的布局,然后在布局里再去嵌套其他的子 widget 和子 layout,要么就是内部结构固定,不可设置布局,也不可“干预”其“组成部分”的布局,“组成部分”的布局应留给开发者设置,但布局又不可以独立存在,它必须依附于一个 Widget,故而才出现了“new 一个新的 QWidget 却只为让它去包裹一个布局1”的情况,应该说这种情形是在 Qt 工整而严谨的 API 设计下衍生出的一种正常表现,习惯了也就不会觉得怪异了。
这个 QWidget 也并非真得“一无是处”,在布局之下添加 widget 时,parent 都是它,但这是后话了 ↩︎ ↩︎
网硕互联帮助中心





评论前必须登录!
注册