关于布局管理器,它是Qt提供的自动安排子部件位置的东西,布局管理可以为我们节省很多对位置的调节工作。
基本的布局管理器有四种:Horizontal, Vertical, Grid, 和Form 布局管理器。这些在前面的例子都能看到是如何使用的。Qt的帮助文档里也有一个专门讲他们的例子:Basic Layouts。 QHBoxLayout, QVBoxLayout, QGridLayout,和QFormLayout 都继承自QLayout。Layouts通过调用addWidget或者addRow将窗口部件加入进去,QWidget及其子类都有一个setLayout的成员函数,可以将我们的布局管理器设置进去,这样就完成了布局。
当widgets被加入到layout,它是如下工作的:
1. 所有的widgets将根据他们的QWidget::sizePolicy()和QWidget::sizeHint()被初始化一定数量的空间。
2. 如果一个widget有stretch factors(值大于0的)要设置,那么就会为他们分配这些空间。
3. 如果其他widgets都不在需要空间了,那么就会把剩余的空间都给那些stretch factors为0的widget。那些设置了QSizePolicy::Expanding的优先分配。
4. 那些设置了最小大小却还没有被分配到他们的最小值的widgets会被分配空间直到他们的最小值。
5. 那些设置了最大大小却分配空间多于了这个最大值的widgets会被分配空间直到他们的最大值。
当我们做自己的窗体部件类时,可能会需要和它的 layout 属性交流,如果这个部件有 Qt 的布局管理器中的一个,它就已经管理好了。如果这个部件没有任何子部件,或者使用的手动布局,你一通过下面的方法改变部件的行为:
1. 重新实现QWidget::sizeHint(),它返回一个设置的窗体大小。
2. 重新实现 QWidget::minimumSizeHint() ,它返回窗体最小的大小。3. 调用QWidget::setSizePolicy(),指定需求的空间策略。
对于以上的那些改变,调用 QWidget::updateGeometry() ,它将会导致布局的重新计算,并更新窗体部件。连续调用多次这个函数,也只会导致一次布局重新计算。 如果你的窗体部件的高依赖他实际的宽(例如 label 的自动断句),在窗体部件的 setSizePolicy() 里设置 setHeightForWidth(true) ,再重新实现 QWidget::heightForWidth() 。 即使你实现了 QWidget::heightForWidth() ,提供一个合理的 sizeHint() 仍然是个好主意。
怎么写一个自定义的布局管理器?
一个供选择的方案是子类化QLayout,后面的两个例子:Border Layout,Flow Layout就为你展示了做法。
这里我们提供一个具体的例子:CardLayout类是受java布局管理器里同名类的灵感。
写自己的布局类,必须定义如下的内容:
1. 一个数据结构,存储处理要被布局管理的项,每个项都是 QLayoutItem ,在下面的例子里,我们将使用 QList 。2. addItem(),怎么增加一个项到布局管理器。
3. setGeometry() ,怎么去实现布局管理器。4. sizeHint(),布局管理器的首选大小。
5. itemAt() ,怎么去遍历布局管理器。6. takeAt(),怎么去从布局管理器中移除项。
在大多数情况你还需要实现 minimumSize() 。我们也可以参看QLayout(或其他子类)的源码来寻找灵感~
#ifndef CARD_H
#define CARD_H
#include <QtGui>
#include <QList>
class CardLayout : public QLayout
{
public:
CardLayout(QWidget *parent, int dist): QLayout(parent, 0, dist) {}
CardLayout(QLayout *parent, int dist): QLayout(parent, dist) {}
CardLayout(int dist): QLayout(dist) {}
~CardLayout();
void addItem(QLayoutItem *item);
QSize sizeHint() const;
QSize minimumSize() const;
QLayoutItem *count() const;
QLayoutItem *itemAt(int) const;
QLayoutItem *takeAt(int);
void setGeometry(const QRect &rect);
private:
QList<QLayoutItem*> list;
};
#endif
首先,我我们定义count()返回项的数量。
QLayoutItem *CardLayout::count() const
{
// QList::size() returns the number of QLayoutItems in the list
return list.size();
}
接下来定义两个函数,遍历布局管理器。itemAt(idx),返回第idx项,takeAt(idx)删除idx项
QLayoutItem *CardLayout::itemAt(int idx) const
{
// QList::value() performs index checking, and returns 0 if we are
// outside the valid range
return list.value(idx);
}
QLayoutItem *CardLayout::takeAt(int idx)
{
// QList::take does not do index checking
return idx >= 0 && idx < list.size() ? list.takeAt(idx) : 0;
}
addItem()为布局管理器用默认放置策略添加项,必须被实现,它使用QLayout::add()被QLayout的构造器使布局管理器作为父类。如果你的布局管理器需要使用其他策略,那不许提供其他的存取函数,例如使用行列号作为参数重载addItem(),addWidget(),addLayout()
void CardLayout::addItem(QLayoutItem *item)
{
list.append(item);
}
析构时,将项都遍历删除掉。
CardLayout::~CardLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}
setGeometry()函数正式执行布局管理器。使用spacing()作为项之间的间距。
void CardLayout::setGeometry(const QRect &r)
{
QLayout::setGeometry(r);
if (list.size() == 0)
return;
int w = r.width() - (list.count() - 1) * spacing();
int h = r.height() - (list.count() - 1) * spacing();
int i = 0;
while (i < list.size()) {
QLayoutItem *o = list.at(i);
QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
o->setGeometry(geom);
++i;
}
}
sizeHint()和minimumSize() 的实现很类似,
QSize CardLayout::sizeHint() const
{
QSize s(0,0);
int n = list.count();
if (n > 0)
s = QSize(100,70); //start with a nice default size
int i = 0;
while (i < n) {
QLayoutItem *o = list.at(i);
s = s.expandedTo(o->sizeHint());
++i;
}
return s + n*QSize(spacing(), spacing());
}
QSize CardLayout::minimumSize() const
{
QSize s(0,0);
int n = list.count();
int i = 0;
while (i < n) {
QLayoutItem *o = list.at(i);
s = s.expandedTo(o->minimumSize());
++i;
}
return s + n*QSize(spacing(), spacing());
}