Qt开发总结(16)——布局Layout

总结了部分常用了基本控件之后,本篇笔记对Qt的布局管理进行总结。Qt自带一系列简单而强大的布局管理工具,以自动在窗体中排布控件。这极大的方便了开发人员管理GUI控件,达到几乎强迫症级别的优美排布,尤其是在适应不同分辨率界面的时候。

所有的QWidget子类都可以用layout管理他们的控件,用QWidget::setLayout()函数既可以为这个widget设置layout。如果一个Layout被这样用于一个widget,那么它将具备以下功能:

1.放置(设置位置)子窗体

2.适应于windows的合理默认大小

3.适应于windows的合理最小窗口

4.大小变化控制

5.当内容发生变化时的自动更新,包括:

文字大小,文本或其他子窗体的内容;

显示或隐藏一个子窗体

重新移动子窗体

Layout类

Qt的layout类非常简单实用。一方面可以作为源码写在具体的代码里,另一方面也可以在用QDesigner可视化设计时应用他们。这些类有:

类名

描述

QGraphicsAnchor

Represents an anchor between two items in a QGraphicsAnchorLayout

QGraphicsAnchorLayout

Layout where one can anchor widgets together in Graphics View

QBoxLayout

Lines up child widgets horizontally or vertically

QHBoxLayout

Lines up widgets horizontally

QVBoxLayout

Lines up widgets vertically

QFormLayout

Manages forms of input widgets and their associated labels

QGridLayout

Lays out widgets in a grid

QLayout

The base class of geometry managers

QLayoutItem

Abstract item that a QLayout manipulates

QSpacerItem

Blank space in a layout

QWidgetItem

Layout item that represents a widget

QSizePolicy

Layout attribute describing horizontal and vertical resizing policy

QStackedLayout

Stack of widgets where only one widget is visible at a time

QButtonGroup

Container to organize groups of button widgets

QGroupBox

Group box frame with a title

QStackedWidget

Stack of widgets where only one widget is visible at a time

基本布局

最简单的布局方式是水平,垂直,栅格和表单布局工具:QHBoxLayout, QVBoxLayout, QGridLayout, 和 QFormLayout.这些类都继承于QLayout基类。稍微复杂点的,可能需要上述几个布局的混合使用。

QHBoxLayout把子控件水平排布,类似于:

QVBoxLayout把子控件垂直排布,类似于:

QGridLayout把子控件以二维栅格的形式排布,类似于:

Qt开发总结(16)——布局Layout_第1张图片

QFormLayout把子控件以两列表单形式排布,类似于:

Qt开发总结(16)——布局Layout_第2张图片

代码

水平或垂直布局控制:

      

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

 

栅格布局控制:

    

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

上面的代码中按钮被分成了两列,button3的addWidget方法有5个参数,1表示开始行,0表示开始列,1表示持续行数,2表示持续列数。所以,button3布满了第二行。

表单布局控制:

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

 

 

上述代码可以看到,QFormLayout为每行添加两个控件,通常是把一个label和一个LineEdit放在一行,并且LineEdit会成为该label的buddy。上述是将button和LineEdit放在一行中。

注意,在new一个Layout对象时,不需要为它指定父对象,它会自动在创建布局时认领父对象。另外,这些子控件终究也不是Layout的子对象,他们的父对象依然还是上层widget。我们也可以用addLayout()函数来嵌套Layout,内部的Layout则会变成被插入的Layout的子对象。

当为一个window执行setLayout()时,layout将按照以下步骤工作:

1.所有的子控件会通过调用QWidget::sizePolicy() 和QWidget::sizeHint()函数初始化一些列空间出来。

2.如果存在有伸缩的控件,这时则重新分配空间以适应他们的拉伸系数。

3.如果存在控件的伸缩系数被置为0,在没有其他控件干涉的前提下,它会获取更多的空间,这种size policy被称作Expanding。

4.如果存在控件的空间比其设置的最小尺寸(通过接口函数设置的最小值或是初始化的最小值)还小,那么此时为他们分配这个最小值。(控件如果设置了伸缩系数则不是必须有尺寸最小值)。

5.如果存在控件的空间比其设置的最大尺寸还要大,则此时重新为它分配所需的最大尺寸空间。(同样,控件如果设置了伸缩系数则不是必须有尺寸最大值)。

伸缩系数

通常来说,控件没有伸缩系数,他们是被QWidget::sizePolicy()或者最小尺寸值更大的值限制,从而分配其在layout中的空间。而伸缩系数是用来设置控件到控件之间的空间的大小的。如果三个水平排布的控件通过QHBoxLayout控制,默认排布如下:

如果我们为每个控件都应用了伸缩系数,那么他们就会按系数比例分布,当然永远不会小于他们的最小尺寸:

自定义控件和布局

如果你设计了一个自定义控件,应该也要实现它的layout属性。如果自定义控件使用了某种Qt的Layout,自然不必多说。如果自定义控件没有子控件,后者用了手动的布局,可以通过以下方法实现一些可视化效果:

1.重写QWidget::sizeHint()函数以返回默认的初始化尺寸;

2.重写QWidget::minimumSizeHint()函数以返回默认的最小值尺寸;

3.调用QWidget::setSizePolicy() 函数以设置控件所需空间。

4.调用QWidget::updateGeometry()当默认尺寸要求、最小尺寸要求或空间需求发生变化时。

5.重写QWidget::heightForWidth()函数以实现默认高度决定于控件宽度值。

也可以自定义布局。

可以像上述方法自定义控件,然后重写QWidget::resizeEvent()来计算所需的空间进而调用setGeometry()函数进行子控件位置尺寸更改。另一个更好的方法是通过继承QLayout编写自己的布局管理类。编写派生类时,需要实现以下方法:

1.一个保存items的数据结构,每一个item都是QLayoutItem对象,通过用QList来实现。

2.addItem(), 如何向layout中添加item.

3.setGeometry(), 如何实现item.

4.sizeHint(),默认尺寸.

5.itemAt(),如何遍历布局item.

6.takeAt(), 如何从Layout中删除某个item.

7.大多数情况下,也需要实现minimumSize().

一个例子:

 

  //card.h
  #ifndef CARD_H
  #define CARD_H

  #include 
  #include 

  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 list;
  };
  #endif
  //card.cpp
  //#include "card.h"
  int CardLayout::count() const
  {
      // QList::size() returns the number of QLayoutItems in the list
      return list.size();
  }
  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;
  }
  void CardLayout::addItem(QLayoutItem *item)
  {
      list.append(item);
  }
  CardLayout::~CardLayout()
  {
       QLayoutItem *item;
       while ((item = takeAt(0)))
           delete item;
  }
  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;
      }
  }
  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());
  }

 

存在的问题

1.在label控件中使用富文本时,会导致父窗体布局出现问题。

2.如果父窗体设置了QLayout::FreeResize mode,它将不会适应更小窗口中的内容布局。

3.如果一个窗体要被用于QDockWidget或者QScrollArea,请在QDockWidget::setWidget() and QScrollArea::setWidget()之前设置他们自己的布局,不然这个窗体将不会出现。

其他布局实例

按照惯例,最后上几个例子:https://download.csdn.net/download/bjtuwayne/12027192

1.基本布局

Qt开发总结(16)——布局Layout_第3张图片

2.边框布局

Qt开发总结(16)——布局Layout_第4张图片

3.动态布局

Qt开发总结(16)——布局Layout_第5张图片

4.流动布局

Qt开发总结(16)——布局Layout_第6张图片

你可能感兴趣的:(Qt,Qt5,Layout)