文章目录
- 介绍
- Qt的布局类
- Horizontal, Vertical, Grid, and Form 布局
- 在代码中布局小部件
- 使用布局的提示
- 向布局添加小部件
- 布局中的自定义小部件
- 布局问题
- 手动布局
- 如何编写自定义布局管理器
- 进一步说明
- 布局示例
布局管理
Qt布局系统提供了一种简单而强大的方法,可以在小部件中自动排列子部件,以确保它们充分利用可用空间。
介绍
Qt包括一组布局管理类,用于描述小部件在应用程序用户界面中的布局方式。当小部件可用的空间量发生变化时,这些布局会自动定位和调整小部件的大小,确保它们的排列一致,并且用户界面作为一个整体仍然可用。
所有QWidget子类都可以使用布局来管理它们的子类。函数的作用是:将布局应用于小部件。以这种方式在小部件上设置布局时,它负责以下任务:
- 子部件的定位
- windows的合理默认大小
- 窗户的合理最小尺寸
- 调整大小处理
- 内容更改时自动更新:
- 子窗口小部件的字体大小、文本或其他内容
- 隐藏或显示子小部件
- 移除子部件
Qt的布局类
Qt的布局类是为手写C++代码设计的,为了简单起见,可以用像素来指定测量,因此它们易于理解和使用。为使用Qt设计器创建的表单生成的代码也使用布局类。Qt设计器在尝试表单设计时非常有用,因为它避免了用户界面开发中通常涉及的编译、链接和运行周期。
基本 | 说明 |
---|---|
QBoxLayout | 水平或垂直排列子窗口小部件 |
QButtonGroup | 用于组织按钮小部件组的容器 |
QFormLayout | 管理输入小部件及其相关标签的形式 |
QGraphicsAnchor | 表示QGraphicsAnchor布局中两个项之间的锚定 |
QGraphicsAnchorLayout | 可以在图形视图中将小部件锚定在一起的布局 |
QGridLayout | 在网格中布局小部件 |
QGroupBox | 带标题的分组框框架 |
QHBoxLayout | 水平排列小部件 |
QLayout | 几何管理器的基类 |
QLayoutItem | QLayout操作的抽象项 |
QSizePolicy | 描述水平和垂直调整大小策略的布局属性 |
QSpacerItem | 版面中的空白 |
QStackedLayout | 一次只能看到一个小部件的小部件堆栈 |
QStackedWidget | 一次只能看到一个小部件的小部件堆栈 |
QVBoxLayout | 垂直排列小部件 |
QWidgetItem | 表示小部件的布局项 |
Horizontal, Vertical, Grid, and Form 布局
为小部件提供良好布局的最简单方法是使用内置的布局管理器:QHBoxLayout、QVBoxLayout、QGridLayout和QFormLayout。这些类继承自QLayout,而QLayout又派生自QObject(不是QWidget)。它们负责一组小部件的几何管理。要创建更复杂的布局,可以将布局管理器相互嵌套。
- QHBoxLayout将小部件按水平行从左到右(或从右到左表示从右到左的语言)进行布局。
- QVBoxLayout在垂直列中从上到下排列小部件。
- QGridLayout在二维网格中布局小部件。小部件可以占用多个单元格。
- QFormLayout以2列描述性标签字段样式布局小部件。
在代码中布局小部件
下面的代码创建了一个QHBoxLayout,它管理五个qpushbutton的几何图形,如上面的第一个屏幕截图所示:
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
layout->addWidget(button4);
layout->addWidget(button5);
window->setLayout(layout);
window->show();
QVBoxLayout的代码是相同的,但创建布局的行(第8行)除外。QGridLayout的代码有点不同,因为我们需要指定子小部件的行和列位置:
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 1, 0, 1, 2);
// 第三个按钮跨越两列。这可以通过将2指定为QGridLayout::addWidget()的第五个参数来实现。
layout->addWidget(button4, 2, 0);
layout->addWidget(button5, 2, 1);
window->setLayout(layout);
window->show();
QFormLayout将在一行中添加两个小部件,通常是QLabel和QLineEdit来创建表单。在同一行中添加QLabel和QLineEdit将把QLineEdit设置为QLabel的伙伴。下面的代码将使用QFormLayout在三行上放置QPushbutton和相应的QLineEdit对。
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QLineEdit *lineEdit1 = new QLineEdit();
QPushButton *button2 = new QPushButton("Two");
QLineEdit *lineEdit2 = new QLineEdit();
QPushButton *button3 = new QPushButton("Three");
QLineEdit *lineEdit3 = new QLineEdit();
QFormLayout *layout = new QFormLayout;
layout->addRow(button1, lineEdit1);
layout->addRow(button2, lineEdit2);
layout->addRow(button3, lineEdit3);
window->setLayout(layout);
window->show();
使用布局的提示
当使用布局时,在构造子窗口小部件时不需要传递父窗口小部件。布局将自动重新分配小部件( 使用QWidget::setParent() ),以便它们是安装布局的小部件的子部件。
注意:布局中的小部件是安装布局的小部件的子部件,而不是布局本身的子部件。小部件只能将其他小部件作为父部件,而不是布局。
可以使用addLayout()在布局上嵌套布局;然后内部布局将成为其插入的布局的子级。
向布局添加小部件
将小部件添加到布局时,布局过程如下所示:
- 所有小部件最初将根据它们的QWidget::sizePolicy() 和QWidget::sizeHint() 分配一定量的空间。
- 如果任何小部件都设置了拉伸因子,并且值大于零,那么它们将按照拉伸因子的比例分配空间(解释如下)。
- 如果任何小部件的拉伸因子设置为零,那么只有在没有其他小部件需要空间的情况下,它们才会获得更多的空间。其中,空间首先分配给具有扩展大小策略的小部件。
- 分配的空间小于其最小大小的任何小部件(如果未指定最小大小,则为最小大小提示)都将分配其所需的最小大小。(小部件不必有最小大小或最小大小提示,在这种情况下,拉伸因子是它们的决定因素。)
- 任何分配的空间大于其最大大小的小部件都会分配其所需的最大大小空间。(小部件不必具有最大大小,在这种情况下,拉伸因子是它们的决定因素。)
拉伸因子 Stretch Factors
小部件通常创建时不设置任何拉伸因子。当它们被布置在一个布局中时,根据它们的QWidget::sizePolicy() 或它们的最小大小提示(以较大者为准),小部件被分配一部分空间。拉伸因子用于改变小部件之间的空间比例。
如果我们使用QHBoxLayout布局了三个小部件,并且没有设置拉伸因子,那么我们将得到如下布局:
如果我们对每个小部件应用拉伸因子,它们将按比例排列(但决不能小于它们的最小尺寸提示),例如。
布局中的自定义小部件
当您创建自己的小部件类时,还应该传递其布局属性。如果小部件使用Qt的一个布局,这已经得到了处理。如果小部件没有任何子小部件,或者使用手动布局,则可以使用以下任何或所有机制更改小部件的行为:
- Reimplement QWidget::sizeHint() 返回小部件的首选大小。
- Reimplement QWidget::minimumSizeHint() 返回小部件可以具有的最小大小。
- 调用QWidget::setSizePolicy() 指定小部件的空间要求。
每当大小提示、最小大小提示或大小策略更改时,调用QWidget::updateGeometry() 。这将导致重新计算布局。多次连续调用QWidget::updateGeometry() 只会导致一次布局重新计算。
如果小部件的首选高度取决于其实际宽度(例如,带有自动分词的标签),请在小部件的大小策略中设置宽度标志的高度,然后重新实现QWidget::heightForWidth() 。
即使实现了QWidget::heightForWidth() ,提供一个合理的sizeHint() 仍然是一个好主意。
有关实现这些功能的进一步指导,请参阅Qt季刊文章Trading Height For Width。
布局问题
在标签小部件中使用富文本会给其父级小部件的布局带来一些问题。当标签是word包装时,Qt的布局管理器处理富文本的方式会导致问题的出现。
在某些情况下,父布局被置于QLayout::freesize模式,这意味着它将无法调整其内容的布局以适应小窗口,甚至无法防止用户使窗口太小而无法使用。这可以通过对有问题的小部件进行子类化,并实现适当的sizeHint() 和minimumSizeHint() 函数来克服。
在某些情况下,当布局被添加到小部件时,它是相关的。设置QDockWidget或QScrolArea的小部件(使用QDockWidget::setWidget() 和QScrolArea::setWidget() )时,必须已经在小部件上设置了布局。否则,小部件将不可见。
手动布局
如果您正在制作一个独一无二的特殊布局,您还可以制作一个如上所述的定制小部件。重新实现QWidget::resizeEvent() 以计算所需的大小分布,并对每个子级调用setGeometry() 。
当需要重新计算布局时,小部件将获得QEvent::LayoutRequest类型的事件。重新实现QWidget::event() ,以处理QEvent::LayoutRequest事件。
如何编写自定义布局管理器
手动布局的替代方法是通过子类化QLayout来编写自己的布局管理器。【边框布局】和【流布局示例】演示了如何执行此操作。
这里我们给出一个详细的例子。CardLayout类的灵感来自同名的Java布局管理器。它将项目(小部件或嵌套布局)放在彼此的顶部,每个项目由QLayout::spacing() 偏移。
要编写自己的布局类,必须定义以下内容:
- 存储由布局处理的项的数据结构。每个项目都是一个QLayoutItem。在本例中,我们将使用QVector。
- addItem(),如何添加项目到布局。
- setGeometry(),如何执行布局。
- sizeHint(),布局的首选大小。
- itemAt(),如何在布局上迭代。
- takeAt(),如何从布局中移除项目。
- 在大多数情况下,还将实现minimumSize()。
The Header File (card.h)
#ifndef CARD_H
#define CARD_H
#include <QtWidgets>
#include <QVector>
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<QLayoutItem*> m_items;
};
#endif
The Implementation File (card.cpp)
#include "CardLayout.h"
/* 首先,我们定义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());
}
进一步说明
- 此自定义布局不处理宽度的高度。
- 我们忽略QLayoutItem::isEmpty() ;这意味着布局将把隐藏的小部件视为可见。
- 对于复杂的布局,缓存计算值可以大大提高速度。在这种情况下,实现QLayoutItem::invalidate() 来标记缓存的数据是脏的。
- 调用QLayoutItem::sizeHint() 等可能会很昂贵。因此,如果以后在同一个函数中再次需要该值,则应将其存储在局部变量中。
- 不应在同一函数中的同一项上调用QLayoutItem::setGeometry() 两次。如果项目有几个子小部件,那么这个调用可能会非常昂贵,因为布局管理器每次都必须完成一个完整的布局。相反,计算几何图形,然后进行设置。(这不仅适用于布局,例如,如果实现自己的resizeEvent() ,则也应该这样做。)
布局示例
许多Qt小部件示例已经使用了布局,但是有几个示例可以展示各种布局。
Example | 说明 |
---|---|
Address Book Tutorial | GUI编程简介,展示如何组合一个简单但功能齐全的应用程序。 |
Border Layout Example | 演示如何沿边框排列子小部件。 |
Calculator Example | 该示例演示了如何使用信号和插槽来实现计算器小部件的功能, 以及如何使用QGridLayout将子小部件放置在网格中。 |
Calendar Widget Example | 日历小部件示例显示了QCalendarWidget的使用。 |
Echo Plugin Example | 这个例子展示了如何创建Qt插件。 |
Flow Layout Example | 演示如何为不同的窗口大小排列小部件。 |
Image Composition Example | 显示了合成模式如何在QPainter中工作。 |
Menus Example | 菜单示例演示了如何在主窗口应用程序中使用菜单。 |
Simple Tree Model Example | 简单的树模型示例演示了如何将层次模型与Qt的标准视图类一起使用。 |
Sub-Attaq | 这个例子展示了Qt结合动画框架和状态机框架来创建游戏的能力。 |