Qt布局系统提供了一种简单而强大的方式,可以自动在窗口组件中排列子窗口组件,以确保它们充分利用可用空间。
Qt包含了一组布局管理类,用于描述窗口组件在应用程序用户界面中的布局方式。当可用空间发生变化时,这些布局会自动定位和调整窗口组件的大小,确保它们的排列一致,并且用户界面作为一个整体保持可用。
所有QWidget子类都可以使用布局来管理它们的子组件。函数QWidget::setLayout()为部件应用布局。当以这种方式在部件上设置布局时,它将负责以下任务:
Qt的布局类是为手写的c++代码设计的,为了简单起见,可以用像素来指定测量值,所以它们很容易理解和使用。使用Qt Designer创建的表单生成的代码也使用了layout类。在尝试设计表单时,Qt Designer非常有用, 因为它避免了用户界面开发中通常涉及的编译、链接和运行循环。
QBoxLayout |
水平或垂直排列子部件 |
QButtonGroup |
容器来组织按钮小部件组 |
QFormLayout |
管理输入小部件的表单及其相关标签 |
QGraphicsAnchor |
表示QGraphicsAnchorLayout中两个项目之间的锚点 |
QGraphicsAnchorLayout |
可以在图形视图中将小部件固定在一起的布局 |
QGridLayout |
在网格中布局小部件 |
QGroupBox |
带标题的组框框架 |
QHBoxLayout |
水平排列小部件 |
QLayout |
几何图形管理器的基类 |
QLayoutItem |
QLayout操作的抽象项 |
QSizePolicy |
描述水平和垂直调整大小策略的布局属性 |
QSpacerItem |
布局中的空白空间 |
QStackedLayout |
一次只能看到一个小部件的小部件堆栈 |
QStackedWidget |
一次只能看到一个小部件的小部件堆栈 |
QVBoxLayout |
垂直排列部件 |
QWidgetItem |
表示小部件的布局项 |
使用布局时,在构造子部件时不需要传递父组件。布局将自动重新设置小部件的父部件(使用QWidget::setParent()),使它们成为安装了布局的小部件的子部件。
注意:布局中的部件是安装布局的部件的子部件,而不是布局本身的子部件。部件只能有其他部件作为父部件,而不能有布局。
你可以在布局上使用addLayout()嵌套布局;然后,内部布局成为它插入的布局的子布局。
向布局中添加部件时,布局过程如下所示:
widget通常在创建时没有设置任何拉伸因子。当它们在布局中被布局时,小部件根据它们的QWidget::sizePolicy()或它们的最小大小提示(以较大的为准)获得一份空间份额。拉伸因子用于改变部件之间的空间比例。
如果我们使用没有设置拉伸因子的QHBoxLayout布局三个窗口组件,我们将得到如下布局:
如果我们对每个小部件应用拉伸因子,它们将按比例布局(但绝不会小于它们的最小尺寸提示),例如:
在创建自己的窗口组件类时,还应该告知它的布局属性。如果组件使用了Qt的布局,这一点已经解决了。如果部件没有任何子部件,或者使用手动布局,则可以使用以下任何或所有机制更改部件的行为:
每当大小提示、最小大小提示或大小策略发生变化时,调用QWidget::updateGeometry()。这将导致布局重新计算。对QWidget::updateGeometry()的多次连续调用只会导致一次布局重新计算。
如果小部件的首选高度取决于它的实际宽度(例如,具有自动断字功能的标签),则在小部件的大小策略中设置height-for-width标志,并重新实现QWidget::heightForWidth()。
即使您实现了QWidget::heightForWidth(),提供一个合理的sizeHint()仍然是一个好主意。
有关实现这些函数的进一步指导,请参阅Qt季度文章Trading Height For Width。
在标签小部件中使用富文本可能会给其父小部件的布局带来一些问题。当标签被换行时,Qt的布局管理器处理富文本的方式会导致问题。
在某些情况下,parent布局被设置为QLayout::FreeResize模式,这意味着它将不能适应其内容的布局以适应小尺寸的窗口,甚至阻止用户使窗口太小而无法使用。这可以通过对有问题的部件进行子类化,并实现适当的sizeHint()和minimumSizeHint()函数来解决。
在某些情况下,当向部件添加布局时,它是相关的。当你设置QDockWidget或QScrollArea
的widget时(使用QDockWidget::setWidget()和QScrollArea::setWidget()), widget上必须已经设置了布局。否则,部件将不可见。
如果你正在制作一个独一无二的特殊布局,你也可以像上面描述的那样制作一个自定义部件。重新实现QWidget::resizeEvent()来计算所需的大小分布,并在每个子节点上调用setGeometry()。
当布局需要重新计算时,widget将获得一个类型为QEvent::LayoutRequest的事件。重新实现QWidget::event()来处理QEvent::LayoutRequest事件。
手动布局的另一种选择是通过继承QLayout来编写自己的布局管理器。Border布局和Flow布局的例子展示了如何做到这一点。
这里我们详细介绍一个例子。CardLayout类的灵感来自于同名的Java布局管理器。它将项目(窗口组件或嵌套布局)置于彼此之上,每个项目通过QLayout::spacing()进行偏移。
要编写自己的布局类,必须定义以下内容:
大多数情况下,还需要实现minimumSize()。
#ifndef CARD_H
#define CARD_H
#include
#include
class CardLayout : public QLayout
{
public:
CardLayout(int spacing): QLayout()
{ setSpacing(spacing); }
CardLayout(int spacing, QWidget *parent): QLayout(parent)
{ setSpacing(spacing); }
~CardLayout();
void addItem(QLayoutItem *item) override;
QSize sizeHint() const override;
QSize minimumSize() const override;
int count() const override;
QLayoutItem *itemAt(int) const override;
QLayoutItem *takeAt(int) override;
void setGeometry(const QRect &rect) override;
private:
QVector m_items;
};
#endif
首先定义count()来获取列表中的项数。
int CardLayout::count() const
{
// QVector::size() returns the number of QLayoutItems in m_items
return m_items.size();
}
然后定义两个遍历布局的函数:itemAt()和takeAt()。布局系统内部使用这些函数来处理部件的删除。应用程序程序员也可以使用它们。
itemAt()返回指定索引处的元素takeAt()删除给定索引处的元素,并返回它。在这种情况下,我们使用列表索引作为布局索引。在其他数据结构更复杂的情况下,我们可能需要花费更多的精力来定义元素的线性顺序。
QLayoutItem *CardLayout::itemAt(int idx) const
{
// QVector::value() performs index checking, and returns nullptr if we are
// outside the valid range
return m_items.value(idx);
}
QLayoutItem *CardLayout::takeAt(int idx)
{
// QVector::take does not do index checking
return idx >= 0 && idx < m_items.size() ? m_items.takeAt(idx) : 0;
}
addItem()实现了布局项的默认放置策略。必须实现该函数。它由QLayout::add()使用,由QLayout构造函数使用,该构造函数接受一个布局作为父布局。如果您的布局有需要参数的高级放置选项,则必须提供额外的访问函数,例如QGridLayout::addItem()、QGridLayout::addWidget()和QGridLayout::addLayout()的跨行和跨列重载。
void CardLayout::addItem(QLayoutItem *item)
{
m_items.append(item);
}
布局承担了添加项目的责任。由于QLayoutItem不继承QObject,我们必须手动删除这些项。在析构函数中,使用takeAt()从列表中移除每一项,然后将其删除。
CardLayout::~CardLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}
setGeometry()函数实际执行布局。作为参数提供的矩形不包括margin()。如果相关,使用spacing()作为项目之间的距离。
void CardLayout::setGeometry(const QRect &r)
{
QLayout::setGeometry(r);
if (m_items.size() == 0)
return;
int w = r.width() - (m_items.count() - 1) * spacing();
int h = r.height() - (m_items.count() - 1) * spacing();
int i = 0;
while (i < m_items.size()) {
QLayoutItem *o = m_items.at(i);
QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
o->setGeometry(geom);
++i;
}
}
sizeHint()和minimumSize()在实现上通常非常相似。两个函数返回的大小应该包括spacing(),但不包括margin()。
QSize CardLayout::sizeHint() const
{
QSize s(0, 0);
int n = m_items.count();
if (n > 0)
s = QSize(100, 70); //start with a nice default size
int i = 0;
while (i < n) {
QLayoutItem *o = m_items.at(i);
s = s.expandedTo(o->sizeHint());
++i;
}
return s + n * QSize(spacing(), spacing());
}
QSize CardLayout::minimumSize() const
{
QSize s(0, 0);
int n = m_items.count();
int i = 0;
while (i < n) {
QLayoutItem *o = m_items.at(i);
s = s.expandedTo(o->minimumSize());
++i;
}
return s + n * QSize(spacing(), spacing());
}
许多Qt Widgets示例已经使用了布局,但是,存在一些示例来展示各种布局
Address Book Tutorial |
介绍GUI编程,展示如何组合一个简单但功能齐全的应用程序。 |
Border Layout Example |
演示如何沿边框排列子部件。 |
Calculator Example |
该示例展示了如何使用信号和槽来实现计算器小部件的功能,以及如何使用QGridLayout在网格中放置子小部件。 |
Calendar Widget Example |
CalendarWidget示例展示了QCalendarWidget的用法。 |
Echo Plugin Example |
这个例子展示了如何创建一个Qt插件。 |
Flow Layout Example |
展示如何为不同的窗口大小排列小部件 |
Image Composition Example |
展示了QPainter中的合成模式是如何工作的。 |
Menus Example |
菜单示例演示了如何在主窗口应用程序中使用菜单。 |
Simple Tree Model Example |
简单树模型示例展示了如何在Qt的标准视图类中使用分层模型。 |
Sub-Attaq |
这个例子展示了Qt结合动画框架和状态机框架来创建游戏的能力。 |
Layout Management | Qt Widgets 5.15.17