排列窗体上的控件(Laying Out Widgets on a Form)
中英文对照:
form(窗体),layout(布局或者排列,意思是进行窗体上控件的排列的过程,如大小位置等)
form(窗体),layout(布局或者排列,意思是进行窗体上控件的排列的过程,如大小位置等)
absolute positioning(绝对位置定位),manual layout(手工布局), layout managers(布局管理器)
Qt中有三种方式对窗体上的控件进行布局管理:绝对位置定位(absolute positioning),手工布局(manual layout),布局管理器(layout managers)。我们使用图6.1中的对话框为例对这三种方式分别进行说明。
Figure 6.1. The Find File dialog
绝对位置定位的方法是最原始的排列控件的方法。这个方法是在程序中调用控件的函数设定它的位置和相对窗体它的大小。下面是用着个方法实现的FindFileDialog的构造函数。
FindFileDialog::FindFileDialog(QWidget *parent)
: QDialog(parent)
{
...
namedLabel->setGeometry(9, 9, 50, 25);
namedLineEdit->setGeometry(65, 9, 200, 25);
lookInLabel->setGeometry(9, 40, 50, 25);
lookInLineEdit->setGeometry(65, 40, 200, 25);
subfoldersCheckBox->setGeometry(9, 71, 256, 23);
tableWidget->setGeometry(9, 100, 256, 100);
messageLabel->setGeometry(9, 206, 256, 25);
findButton->setGeometry(271, 9, 85, 32);
stopButton->setGeometry(271, 47, 85, 32);
closeButton->setGeometry(271, 84, 85, 32);
helpButton->setGeometry(271, 199, 85, 32);
setWindowTitle(tr("Find Files or Folders"));
setFixedSize(365, 240);
}
这种方法缺点很多:
1. 用户不能改变窗体的大小
2. 如果改变字体或者翻译到另一种语言,控件上的文本可能不能完全显示
3. 在一些样式下,控件的尺寸会不合适
另一种方法为手工布局。给出控件的绝对位置,但是他们的尺寸根据窗口的大小确定,可以通过重写窗体的resizeEvent()实现对子控件的大小设置:
FindFileDialog::FindFileDialog(QWidget *parent)
: QDialog(parent)
{
...
setMinimumSize(265, 190);
resize(365, 240);
}
void FindFileDialog::resizeEvent(QResizeEvent * /* event */)
{
int extraWidth = width() - minimumWidth();
int extraHeight = height() - minimumHeight();
namedLabel->setGeometry(9, 9, 50, 25);
namedLineEdit->setGeometry(65, 9, 100 + extraWidth, 25);
lookInLabel->setGeometry(9, 40, 50, 25);
lookInLineEdit->setGeometry(65, 40, 100 + extraWidth, 25);
subfoldersCheckBox->setGeometry(9, 71, 156 + extraWidth, 23);
tableWidget->setGeometry(9, 100, 156 + extraWidth,
50 + extraHeight);
messageLabel->setGeometry(9, 156 + extraHeight, 156 + extraWidth,
25);
findButton->setGeometry(171 + extraWidth, 9, 85, 32);
stopButton->setGeometry(171 + extraWidth, 47, 85, 32);
closeButton->setGeometry(171 + extraWidth, 84, 85, 32);
helpButton->setGeometry(171 + extraWidth, 149 + extraHeight, 85,
32);
}
在FindFileDialog构造函数中,设置窗体的最小尺寸为265×190,初始大小为365×240。在resizeEvent()中,变量extraWidth和extraHeight为控件相对最小尺寸的差值,根据差值计算子控件的大小,这个在改变窗体大小时控件能够跟着改变其大小。
Figure 6.2. Resizing a resizable dialog
绝对位置定位和手工布局管理都是需要更多的代码,也需要更多的常量参与计算。这样编写代码非常令人讨厌,如果设计改变了,所有的值都要重新计算一遍。虽然手工布局能改变空间大小,但是有时仍然会无法显示全部文字,为了避免这个错误,可以考虑控件的sizeHint,但是这样的代码会更加复杂了。
管理窗体上控件最简单的方法就是使用Qt的布局管理类。这些类能够给出所有类型控件的默认值,能够根据控件的字体,样式,内容得到不同的控件的sizeHint。布局管理类能够得到控件的最大,最小尺寸,在字体,内容或者窗口改变时自动调整布局。
QHBoxLayout,QVBoxLayout,QGridLayout是三个最重要的布局管理器,这些类从QLayout继承,QLayout提供布局最基本的框架。这三个类可以在代码中使用,也可以在Qt Designer中使用,下面是FindFileDialog使用布局管理器的代码
FindFileDialog::FindFileDialog(QWidget *parent)
: QDialog(parent)
{
...
QGridLayout *leftLayout = new QGridLayout;
leftLayout->addWidget(namedLabel, 0, 0);
leftLayout->addWidget(namedLineEdit, 0, 1);
leftLayout->addWidget(lookInLabel, 1, 0);
leftLayout->addWidget(lookInLineEdit, 1, 1);
leftLayout->addWidget(subfoldersCheckBox, 2, 0, 1, 2);
leftLayout->addWidget(tableWidget, 3, 0, 1, 2);
leftLayout->addWidget(messageLabel, 4, 0, 1, 2);
QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->addWidget(findButton);
rightLayout->addWidget(stopButton);
rightLayout->addWidget(closeButton);
rightLayout->addStretch();
rightLayout->addWidget(helpButton);
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addLayout(leftLayout);
mainLayout->addLayout(rightLayout);
setLayout(mainLayout);
setWindowTitle(tr("Find Files or Folders"));
}
代码中用到了QHBoxLayout,QGridLayout和QVBoxLayout。窗体的左边的子控件由QGridLayout负责,右边的子控件由QVBoxLayout负责。这两个布局由QHBoxLayout进行控制。对话框四周的边缘大小和控件之间的间隔设置为当前空间样式的缺省值,函数QLayout::setMargin()和QLayout::setSpacing()能够对这两个值进行修改。
这个对话框也可以使用Qt Designer实现,首先把所有的子控件放置在近似适当的位置,选择需要布局管理器一同管理的控件,点击Form|Layout Horizontally,Form|Layout Vertically或者Form|Layout in a Grid。在第二章我们这样创建了Spreadsheet程序的Go-to-Cell对话框和Sort对话框。
Figure 6.3. The Find File dialog's layout
QHBoxLayout 和QVBoxLayout的使用很简单,QGridLayout有点复杂。QGridLayout工作的基础是一个二维的单元格。左上角的QLabel在布局中的位置为(0,0),旁边的QLineEdit位置为(0,1)。QCheckBox占用了(2,0)和(2,1)两个列的空间,下面的QTreeWidget和QLabel也是如此。QGridLayout::addWidget()语法如下:
layout->addWidget(widget, row, column, rowSpan, columnSpan);
参数widget为插入到这个布局的子控件,(row,column)为控件占据的左上角单元格位置,rowSpan是控件占据的行数,colunmSpan是控件占据的列的个数。rowSpan和colunmSpan默认值为1。
函数addStretch()使布局管理器在指定的位置留出一块空间。上面的代码中,布局管理器在Close按钮和Help按钮之间留出一个额外的空隙。在Qt Designer中,我们可以加入一个spacer实现这一功能,在Qt Designer中,spacer表现为蓝色的弹簧式折线。
使用布局管理类还能获得其他多的功能。如果把一个控件加到一个布局中,或者从布局中删除一个控件,布局管理器会自动适应变化,调整控件大小。调用子控件的hide()或者show()函数时,布局管理器同样也会自动进行调整。如果子控件的sizeHint改变了,布局管理器就会根据控件新的sizeHint进行调整。根据所有子控件的最小尺寸和sizeHint,布局管理器还会计算出整个窗体最小尺寸。
在上例中,我们只是把控件放到布局中,使用spacer(stretches)填满余下的空间。有时,光是这些还是不够的,我们还可以改变控件的sizePolicy,或者sizeHint,使窗体的布局更加符合我们的需要。
一个控件的sizePolicy说明控件在布局管理中的缩放方式。Qt提供的控件都有一个合理的缺省sizePolicy,但是这个缺省值有时不能适合所有的布局,开发人员经常需要改变窗体上的某些控件的sizePolicy。一个QSizePolicy的所有变量对水平方向和垂直方向都适用。下面列举了一些最长用的值:
1. Fixed:控件不能放大或者缩小,控件的大小就是它的sizeHint。
2. Minimum:控件的sizeHint为控件的最小尺寸。控件不能小于这个sizeHint,但是可以放大。
3. Maximum:控件的sizeHint为控件的最大尺寸,控件不能放大,但是可以缩小到它的最小的允许尺寸。
4. Preferred:控件的sizeHint是它的sizeHint,但是可以放大或者缩小
5. Expandint:控件可以自行增大或者缩小
图6.4以文本为“Some Text”的QLabel显示了这些不同的sizePolicy的含义,
Figure 6.4. The meaning of the different size policies
在图中,Preferred和Expanding的表现是一样的,二者的区别何在那?如果一个窗体中既有Preferred控件也有Expanding控件,在改变大小时,由Expanding控件填满其余的控件,而Preferred控件不变,认为它的sizeHint。
还有两个sizePolicy值为MinimumExpanding和Ignored。MinimumExpanding在老的Qt版本中有时会用到,但是现在已经不用了。替代的方法时使用Expanding值和重写合适的minimumSizeHint()函数。Ignored和Expanding很像,只是它忽略控件的sizeHint和最小的sizeHint。
除了水平和垂直方向的值,QSizePolicy还包含了一个水平和垂直方向的放缩倍数(stretch factor)。当窗体放大时,这两个值决定不同控件放大的程度。例如,如果QTreeWidget和QTextEdit上下排列,如果我们希望QTextEdit高度为QTreeWidget的两倍,就可以设置QTextEdit的垂直放缩倍数为2,QTreeWidget的垂直放缩倍数为1。
控件的最小尺寸,最大尺寸和固定尺寸也是影响布局的因素。布局管理器排列控件时会考虑这些限制。如果这些还不够,可以创建新类重写sizeHint()。