美文网首页
布局管理

布局管理

作者: 勤劳的悄悄 | 来源:发表于2017-09-08 14:48 被阅读195次

    总结

    布局的计算过程

    • 如果设置了最小尺寸(或者最小尺寸提示)、最大尺寸,则组件获取的空间不能超过这些限制
    • 如果没有设置尺寸限制,则组件会尽可能多的占据空间。如果有多个组件,则空间分配以拉伸因子为标准
    • 如果没有设置拉升因子,则以组件的尺寸策略 QWidget :: sizePolicy() 和尺寸提示 QWidget :: sizeHint() 为依据,在有多余空间的情况下,尽可能的占据多的空间。这也是组件的默认尺寸

    组件中对布局有影响的函数

    一个组件要想影响布局管理,需要使用以下任何或全部机制:

    • 重新实现 QWidget :: sizeHint() 以返回小部件的首选大小。
    • 重新实现 QWidget :: minimumSizeHint() 以返回小部件可以具有的最小尺寸。
    • 调用 QWidget :: setSizePolicy() 来指定窗口小部件的空间要求。

    在上面的任何一个操作之后,都应调用 QWidget :: updateGeometry() ,这将导致布局重新计算。即使多次连续调用 QWidget :: updateGeometry() 也只会进行一次布局重新计算。

    如果 Widget 的高度依赖于其宽度(例如,具有自动断字的标签),请在窗口小部件的大小策略中设置 height-for-width 标志,并重新实现 QWidget :: heightForWidth() 函数。

    即使实现 QWidget :: heightForWidth() ,提供一个合理的 sizeHint()仍然是一个好主意。

    自定义布局管理器

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

    • 一个用于存储被管理组件的数据结构,其中项是 QLayoutItem 类型 ,一般使用列表。
    • addItem(),如何添加项目到布局。
    • setGeometry(),如何执行布局。
    • sizeHint(),布局的首选大小。
    • itemAt(),如何迭代布局。
    • takeAt(),如何从布局中删除项目。
    • 在大多数情况下,您还将实现 minimumSize()

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

    简介

    Qt 包括一组布局管理类,用于描述在应用程序的用户界面中如何布局 Widget。 当可用的空间发生变化时,这些布局会自动定位和调整 Widget 的大小,确保它们一致地排列,并且整个用户界面保持可用。

    所有 QWidget 的子类都可以使用布局来管理他们的子元素, QWidget :: setLayout() 函数将一个 Layout 应用于 Widget, 当以这种方式在 Widget 上设置了一个 Layout 时,Layout 负责以下任务:

    • 子元素的定位。
    • 合理的窗口默尺寸。
    • 合理的的窗口最小尺寸。
    • 调整大小处理
    • 内容变化时自动更新:
      • 字体大小,文本或子元素的其他内容。
      • 隐藏或显示一个 Widget
      • 删除子元素

    Qt 的布局类

    省略......

    水平、竖直、网格以及表格布局

    为 Widget 设置布局的最简单方法是使用内置的布局管理器:QHBoxLayoutQVBoxLayoutQGridLayoutQFormLayout ,这些类继承自 QLayoutQLayout 又来自 QObject (而不是 QWidget ), 他们负责一组 Widgets 的几何管理。 要创建更复杂的布局,可以将布局管理器嵌套在一起。

    • QHBoxLayout 从左到右(或从右到左)横向排列出 Widget。

      img
    • QVBoxLayout 从上到下在垂直排列 Widget

      img
    • QGridLayout 在二维网格中显示 Widget,一个 小部件可以占用多个单元格

      img
    • QFormLayout 在以两列描述性的“标签-字段样式” 样式布局 Widget

      img

    用代码布局 Widget

    以下代码用一个 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 的使用方式是相同的。 QGridLayout 的代码有一些不同,因为我们需要指定 Widget 的行和列位置:

    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);
    layout->addWidget(button4, 2, 0);
    layout->addWidget(button5, 2, 1);
    
    // 设置布局
    window->setLayout(layout);
    
    window->show();
    

    第三个 QPushButton 跨越两列,需要为 QGridLayout :: addWidget() 指定第四和第五个参数。

    QFormLayout 在每一行上添加两个 Widget ,通常是 QLabelQLineEdit ,用来创建表单。 在同一行添加 QLabelQLineEdit 会将 QLineEdit 设置为 QLabel 的伙伴控件。 以下代码将使用 QFormLayout 在三行上放置 QPushButtons 和相应的 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();
    

    使用布局的一些提示

    使用布局时,创建 Widget 不需要传递父对象,布局管理器会自动重新设置 Widget 的父对象(使用 QWidget :: setParent()),让它们称为设置了布局管理器的那个 Widget 的子元素。

    注意:布局中的管理的 Widget 是设置布局的 Widget 的子节点,而不是布局本身的子节点, Widget 只能将其他 Widget 作为父类而不是布局。

    可以在布局上使用 addLayout() 嵌套布局,内部布局外部布局的子元素

    将 Widget 添加到布局中

    当您将 Widget 添加到布局中时,过程如下:

    1. 所有的 Widget 最初将根据其 QWidget :: sizePolicy()QWidget :: sizeHint() 分配一定的空间。
    2. 如果 Widget 设置了大于零的拉伸因子,则按照其拉伸因子的比例分配空间(如下所述)。
    3. 如果 Widget 的拉伸因子设置为零,则只有在其他 Widget 不需要空间的情况下才会获得更多的空间。其中,首先将空间分配给具有 Expanding 策略的小部件。
    4. 分配的空间小于其最小尺寸的 Widget(如果没有指定最小尺寸,则为最小尺寸提示)将分配其所需的最小尺寸。 (Widget 不一定非要设置最小尺寸或最小尺寸提示,在这种情况下,拉伸因子是其决定因素。)
    5. 分配的空间大于其最大尺寸的 Widget 都将分配其所需的最大尺寸。 (小部件不一定非要设置最大尺寸,在这种情况下,拉伸因子是其决定因素。)

    拉伸因子

    Widget 创建的时候一般没有设置拉伸因子。 当它们被放到布局管理器中时,Widget 将根据其 QWidget :: sizePolicy() 或其最小大小提示给予一定的空间份额,以较大者为准。 拉伸因子用于改变彼此占据空间的比例

    如果将三个没有设置拉伸因子的 Widget 放到 QHBoxLayout 布局中,我们将得到如下布局:

    img

    如果我们对每个 Widget 设置拉伸因子,它们将按比例(但不得小于其最小大小提示)布局。

    img

    自定义组件的布局

    当您创建自己的 Widget 类时,还应该处理其布局属性。如果 Widget 使用 Qt 的布局之一,这已经被处理了。如果 Widget 没有任何子部件,也没有使用手动布局,则可以使用以下任何或全部机制更改窗口小部件的行为:

    • 重新实现 QWidget :: sizeHint() 以返回小部件的首选大小。
    • 重新实现 QWidget :: minimumSizeHint() 以返回小部件可以具有的最小尺寸。
    • 调用 QWidget :: setSizePolicy() 来指定窗口小部件的空间要求。

    每当大小提示、最小大小提示或大小策略更改时,都应调用 QWidget :: updateGeometry() ,这将导致布局重新计算。即使多次连续调用 QWidget :: updateGeometry() 也只会进行一次布局重新计算。

    如果您的小部件的首选高度依赖于其实际宽度(例如,具有自动断字的标签),请在窗口小部件的大小策略中设置 height-for-width 标志,并重新实现 QWidget :: heightForWidth() 函数。

    即使实现 QWidget :: heightForWidth() ,提供一个合理的 sizeHint()仍然是一个好主意。

    有关实施这些功能的进一步指导,请参阅Qt季刊文章 《宽度的交易高度》。

    布局可能出现问题

    在 Label 组件中使用富文本可能会在其父窗口的布局中引入一些问题,当 Label 设置了 “word wrapped” 时,布局管理器会发生问题。

    在某些情况下,布局被设置为 QLayout :: FreeResize 模式,这意味着它不会调整其内容的布局以适合较小尺寸的窗口,甚至会阻止用户将窗口变小。 这个问题可以通过对有问题的小部件进行子类化,并实现适当的 sizeHint()minimumSizeHint() 函数来克服。

    在某些情况下,Widget 必须有布局才能被使用。 当您为 QDockWidgetQScrollArea (使用 QDockWidget :: setWidget()QScrollArea :: setWidget() )设置 Widget 时,必须已在 Widget 上设置布局。 如果没有,该 Widget 将不可见。

    手动布局

    如果您正在制作独一无二的特殊布局,可以按照上述方式制作自定义 Widget 。 在 QWidget :: resizeEvent() 中计算所需的空间,并调用每个子元素上调用 setGeometry()

    当需要重新计算布局时,Widget 将接收到一个 QEvent :: LayoutRequest 的事件对象。 可以在 QWidget :: event() 中处理 QEvent :: LayoutRequest 事件。

    如何编写自定义布局管理器

    手动布局的替代方法是通过对 QLayout 进行子类化来编写自己的布局管理器。 边框布局和流程布局示例显示了如何做到这一点。

    我们在这里详细介绍一个例子。 CardLayout 类收到来自同名的 Java 布局管理器的启发。 它将 Widget 放在彼此之上,每个 Item 偏移 QLayout :: spacing() 距离

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

    • 用于存储被布局处理的组件的数据结构。 每个组件都是一个QLayoutItem , 在这个例子中我们将使用一个 QList
    • addItem(),如何添加项目到布局。
    • setGeometry(),如何执行布局。
    • sizeHint(),布局的首选大小。
    • itemAt(),如何迭代布局。
    • takeAt(),如何从布局中删除项目。
    • 在大多数情况下,您还将实现 minimumSize()
    #ifndef CARD_H
    #define CARD_H
    
    #include <QtWidgets>
    #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;
        int count() const;
        QLayoutItem *itemAt(int) const;
        QLayoutItem *takeAt(int);
        void setGeometry(const QRect &rect);
    
    private:
        QList<QLayoutItem*> list;
    };
    
    #endif
    

    实现文件 card.cpp

    //#include "card.h"
    

    首先得定义一个 count() 函数来获得列表中的项数

    int CardLayout::count() const
    {
        // QList::size() returns the number of QLayoutItems in the list
        return list.size();
    }
    

    然后重写 itemAt()takeAt() 这两个函数,这些函数在布局系统内部用于处理 Widget 的删除,当然,程序员也可以直接使用它们。

    itemAt() 返回给定索引处的 Item 。 takeAt() 删除给定索引处的 Item ,并返回它。 这里,我们使用列表索引作为布局索引。 在其他情况下,我们有一个更复杂的数据结构,我们可能需要花费更多的精力为 Item 定义线性顺序。

    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() 为布局 Item 实现了默认放置策略,这个函数必须被实现,他会被 QLayout :: add() 使用。 如果您的布局具有需要参数的高级放置选项,则必须提供额外的访问函数,例如还来间距 QGridLayout :: addItem()QGridLayout :: addWidget()QGridLayout :: addLayout() 的重载

    void CardLayout::addItem(QLayoutItem *item)
    {
      list.append(item);
    }
    

    注意:QLayoutItem 不是继承自 QObject 因此,必须手动销毁

     CardLayout::~CardLayout()
      {
           QLayoutItem *item;
           while ((item = takeAt(0)))
               delete item;
      }
    
    

    setGeometry() 实际计算各个组件的位置和尺寸,如果需要,计算的时候可以将 spacing() 考虑进去,但是不考虑 margin()

    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() 的实现非常类似,他们都可以考虑 spacing() 但是不考虑 margin()

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

    其他注意事项

    • 此自定义布局不处理宽度的高度。
    • 我们忽略了 QLayoutItem :: isEmpty(), 这意味着布局会将隐藏的小部件视为可见的。
    • 对于复杂的布局,通过缓存计算值可以大大提高速度。 在这种情况下,实现 QLayoutItem :: invalidate() 来标记缓存的数据是脏的。
    • 调用 QLayoutItem :: sizeHint() 等可能是昂贵的。 因此,如果您稍后在同一功能中再次需要,您应该将该值存储在局部变量中。
    • 你不应该在同一个函数的同一个项目上调用 QLayoutItem :: setGeometry() 两次。 如果该项目具有多个子窗口小部件,则此调用可能非常昂贵,因为布局管理器必须每次执行完整的布局。 而是计算几何体,然后进行设置。 (这不仅适用于布局,如果您实现自己的resizeEvent() ,则应该这样做)。

    相关文章

      网友评论

          本文标题:布局管理

          本文链接:https://www.haomeiwen.com/subject/hfrvjxtx.html