其他分享
首页 > 其他分享> > 布局类(02):【纲】Layout Management [官翻]

布局类(02):【纲】Layout Management [官翻]

作者:互联网

文章目录

布局管理

Qt布局系统提供了一种简单而强大的方法,可以在小部件中自动排列子部件,以确保它们充分利用可用空间。

介绍

Qt包括一组布局管理类,用于描述小部件在应用程序用户界面中的布局方式。当小部件可用的空间量发生变化时,这些布局会自动定位和调整小部件的大小,确保它们的排列一致,并且用户界面作为一个整体仍然可用。

所有QWidget子类都可以使用布局来管理它们的子类。函数的作用是:将布局应用于小部件。以这种方式在小部件上设置布局时,它负责以下任务:

Qt的布局类

Qt的布局类是为手写C++代码设计的,为了简单起见,可以用像素来指定测量,因此它们易于理解和使用。为使用Qt设计器创建的表单生成的代码也使用布局类。Qt设计器在尝试表单设计时非常有用,因为它避免了用户界面开发中通常涉及的编译、链接和运行周期。

基本说明
QBoxLayout水平或垂直排列子窗口小部件
QButtonGroup用于组织按钮小部件组的容器
QFormLayout管理输入小部件及其相关标签的形式
QGraphicsAnchor表示QGraphicsAnchor布局中两个项之间的锚定
QGraphicsAnchorLayout可以在图形视图中将小部件锚定在一起的布局
QGridLayout在网格中布局小部件
QGroupBox带标题的分组框框架
QHBoxLayout水平排列小部件
QLayout几何管理器的基类
QLayoutItemQLayout操作的抽象项
QSizePolicy描述水平和垂直调整大小策略的布局属性
QSpacerItem版面中的空白
QStackedLayout一次只能看到一个小部件的小部件堆栈
QStackedWidget一次只能看到一个小部件的小部件堆栈
QVBoxLayout垂直排列小部件
QWidgetItem表示小部件的布局项

Horizontal, Vertical, Grid, and Form 布局

为小部件提供良好布局的最简单方法是使用内置的布局管理器:QHBoxLayout、QVBoxLayout、QGridLayout和QFormLayout。这些类继承自QLayout,而QLayout又派生自QObject(不是QWidget)。它们负责一组小部件的几何管理。要创建更复杂的布局,可以将布局管理器相互嵌套

在代码中布局小部件

下面的代码创建了一个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()在布局上嵌套布局;然后内部布局将成为其插入的布局的子级。

向布局添加小部件

将小部件添加到布局时,布局过程如下所示:

  1. 所有小部件最初将根据它们的QWidget::sizePolicy() 和QWidget::sizeHint() 分配一定量的空间。
  2. 如果任何小部件都设置了拉伸因子,并且值大于零,那么它们将按照拉伸因子的比例分配空间(解释如下)。
  3. 如果任何小部件的拉伸因子设置为零,那么只有在没有其他小部件需要空间的情况下,它们才会获得更多的空间。其中,空间首先分配给具有扩展大小策略的小部件。
  4. 分配的空间小于其最小大小的任何小部件(如果未指定最小大小,则为最小大小提示)都将分配其所需的最小大小。(小部件不必有最小大小或最小大小提示,在这种情况下,拉伸因子是它们的决定因素。)
  5. 任何分配的空间大于其最大大小的小部件都会分配其所需的最大大小空间。(小部件不必具有最大大小,在这种情况下,拉伸因子是它们的决定因素。)

拉伸因子 Stretch Factors

小部件通常创建时不设置任何拉伸因子。当它们被布置在一个布局中时,根据它们的QWidget::sizePolicy() 或它们的最小大小提示(以较大者为准),小部件被分配一部分空间。拉伸因子用于改变小部件之间的空间比例。

如果我们使用QHBoxLayout布局了三个小部件,并且没有设置拉伸因子,那么我们将得到如下布局:

Three widgets in a row

如果我们对每个小部件应用拉伸因子,它们将按比例排列(但决不能小于它们的最小尺寸提示),例如。

Three widgets with different stretch factors in a row

布局中的自定义小部件

当您创建自己的小部件类时,还应该传递其布局属性。如果小部件使用Qt的一个布局,这已经得到了处理。如果小部件没有任何子小部件,或者使用手动布局,则可以使用以下任何或所有机制更改小部件的行为:

每当大小提示、最小大小提示或大小策略更改时,调用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() 偏移。

要编写自己的布局类,必须定义以下内容:

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());
} 

进一步说明

布局示例

许多Qt小部件示例已经使用了布局,但是有几个示例可以展示各种布局。

Example说明
Address Book TutorialGUI编程简介,展示如何组合一个简单但功能齐全的应用程序。
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结合动画框架和状态机框架来创建游戏的能力。

标签:02,Management,layout,QWidget,布局,部件,官翻,QPushButton,new
来源: https://blog.csdn.net/hitzsf/article/details/113493345