00. 目录


文章目录


00. 目录01. 概述02. 开发环境03. 限定窗口大小04. 手动计算调整控件分布05. 计算文本显示宽度06. 附录


01. 概述

本次内容主要讲解手动计算来调整控件分布,以及限定窗口最大尺寸和最小尺寸。 虽然使用 Qt 布局器非常方便,不需要我们自己去计算控件实时的位置和大小, 但是我们还是应该学习如何根据窗口大小,自己编写代码计算各个控件的位置和大小, 这是一项传统的技术活,多学点基本知识总是好的,因为布局器也不是万能的。

手动计算和调整控件分布这项传统技术有缺点,就是没有通用性,控件多一个、少一个都得重新计算。 但也有优点,就是程序员对界面调整的掌控有最大的自由度和灵活度。我们本节第一个例子是限定窗口的最大尺寸和最小尺寸。第二个例子是根据窗口大小和控件的类型、数 目,手动计算调整控件的分布。 第三个例子根据程序默认字体和不同文本长度计算按钮控件实时的宽度,其他控件根据文本内容调整大小的原理也是类似的。

02. 开发环境

Windows系统​:Windows10

Qt版本​:Qt5.15或者Qt6

03. 限定窗口大小

窗体里用到的控件几乎都是以 QWidget 为基类,QWidget 关于控件或窗口大小的设置有如下几个函数:

设置窗口占用矩形的函数:

void setGeometry(int x, int y, int w, int h)
void setGeometry(const QRect &)
x 和 y 是控件距离父窗口左上角的坐标,w 是宽度,h 是高度。QRect 也是以类似的 4 个参数构造。

如果不改变窗口大小,只是移动窗口左上角坐标,那么使用如下函数:

void move(int x, int y)
void move(const QPoint &)

如果不移动窗口左上角坐标,只改变窗口的尺寸大小,那么使用如下函数:

void resize(int w, int h)
void resize(const QSize &)
w 是宽度,h 是高度。QSize 也是以宽度、高度为参数构造。

当窗体尺寸变化时,不会发送信号,而是调用内部保护类型的虚函数 resizeEvent()。因为一方面信号和槽有开销,这对实时绘图不利,另一方面窗体变化应该由类对象内部处理,而不是交给外部对象处理,所以窗口大小变化时,调用的 是内部保护类型虚函数:

void QWidget::resizeEvent(QResizeEvent * event)    //virtual protected
注意一般不要在 resizeEvent() 函数内部调用 setGeometry() 或者 resize() 改变窗口尺寸,那样容易导致resizeEvent() 函数循环触发,进入死循环。

限定窗口的尺寸范围有两个属性 minimumSize (最小尺寸,该属性又可细分为 minimumWidth、minimumHeight)和 maximumSize (最大尺寸,该属性又可细分为 maximumWidth、maximumHeight),无论使用代码调用设置函数还是用 Qt 设计师直接设置属性都可以限定窗口尺寸范围:

void setMinimumSize(const QSize &)        //最小尺寸
void setMinimumSize(int minw, int minh) //最小尺寸
void setMaximumSize(const QSize &) //最大尺寸
void setMaximumSize(int maxw, int maxh) //最大尺寸

如果同时将窗口的最大尺寸和最小尺寸设置为一样大,那么窗口就是固定尺寸的,不能拉伸或缩小。

设置固定大小的窗口,可以同时设置上面的最小尺寸和最大尺寸,或者调用一个比较方便的函数:

void setFixedSize(const QSize & s)
void setFixedSize(int w, int h)

如果只希望单独设置固定宽度或者固定高度,则可以使用如下函数:

void setFixedWidth(int w)    //单独设置固定宽度
void setFixedHeight(int h) //单独设置固定高度

窗口的尺寸和坐标设置函数就介绍这些,控件的尺寸和坐标设置函数是一样的。

创建项目,配置好项目后,打开 widget.ui 界面文件,进入 QtCreator 设计模式:

我们右击主窗体下面空白区域,右键菜单里选择 “大小限定” ,然后看到 6 个子菜单项,解释一下:

【Qt】Qt手动布局_Qt布局

① “设定最小宽度”,就是将现在看到的窗体宽度设置为该窗体的最小宽度。

② “设定最小高度”,就是将现在看到的窗体高度设置为该窗体的最小高度。

③ “设定最小大小”,就是将现在看到的窗体尺寸设置为该窗体的最小尺寸。

④ “设定最大宽度”,就是将现在看到的窗体宽度设置为该窗体的最大宽度。

⑤ “设定最大高度”,就是将现在看到的窗体高度设置为该窗体的最大高度。

⑥ “设定最大大小”,就是将现在看到的窗体尺寸设置为该窗体的最大尺寸。

这里是右击主窗体空白区的,设置的就是主窗体的大小限定。如果右击某一个控件,那么就是设置该控件的大小限定,过程类似。

【注意】


拉伸窗口到自己想要的尺寸,然后设置最大大小、设置最大宽度、设置最大高度、设置最小宽度、设置最小高度和设置最小大小。


04. 手动计算调整控件分布

在不使用 Qt 布局器的情况下,我们可以手动规划各个控件的位置和尺寸设置,当然,这个手动计算的过程要根据不同界面进行不同的规划。手动计算调整控件分布的缺点就是没有什么通用性,换 个程序界面或者多一个控件、少一个控件都需要改写 cpp 文件中的源代码。接下来我们设计一个能跟随窗口大小变化而自动调整 各个控件分布和尺寸的程序。

新建QMainWindows项目,然后打开UI文件,设计如下:

【Qt】Qt手动布局_手动布局_02

● 首先是主界面窗体的大概情况设定:

主窗体第一行是非常大的标签控件 labelShow ,占据其他控件剩下的区域。

第二行是四个按压按钮,目前的尺寸都是 75*23 ,四个按钮的文字能全部显示出来,在窗口变大时,不需要拉伸,但是需要均匀分布在同一水平线上,四个按钮之间的间隔相同。

我们从这四个按钮计算主界面最小宽度,比如按钮间隔最小为 10,与两边框间距为 10,那么最小宽度就是

10 * 5 + 75 * 4 == 350 ,我们将主窗体最小高度设置为与最小宽度一样,比较省事,就是最小尺寸 350 * 350 ,最大尺寸不限。

主窗体第三行的控件就是一个水平滑动条 horizontalSlider ,我们希望它距离主窗体底部 10 ,宽度与第二行右边三个按钮占据宽度一样,水平滑动条高度是固定为原本的 21 。

● 现在我们来详细规划并计算各个控件的分布和尺寸:

这个主窗体的规划思路是这样的,控件之间以及控件与四个边界都是有 10 像素的间隙,然后水平方向因为第二行控件最多,并且第三行的水平滑动条依赖第二行右边三 个按钮,我们从这第二行开始规划。

当窗体拉伸后的宽度的 W,高度为 H ,按钮尺寸固定为 75*23 ,

第一个按钮的左上角起点坐标:

水平 x1 = 10,距离左边界为 10,这个是固定的。

垂直 y1 = H - 10 - 21 - 10 - 23 ,其中第一个 10 是水平滑动条距离底部的垂直空隙,21 是水平滑动条的高度,

第二个 10 是水平滑动条与按钮的垂直间隙,23 是按钮自己的高度。

四个按钮的在同一水平线上,也就是 y1 == y2 == y3 == y4。

第四个按钮也是距离右边界 10 个像素空隙,那么可以容易得出:

x4 = W - 10 - 75 ,75 是按钮自己的宽度。

四个按钮之间有三个大间隙,计算这个三个间隙总大小:

nTriGap = W - 10 - 10 - 75​4 ,两个 10 是两个边界间隙,75​4 是按钮自己占的宽度,那么单个的大间隙就为:

nGap = nTriGap / 3 。

然后计算第二个和第三个按钮的水平坐标:

x2 = x1 + 75 + nGap;

x3 = x4 - 75 - nGap.

这样四个按钮的坐标和尺寸就都确定了。

按钮设置好之后,其他的就好办了,第三行的水平滑动条:

xSlider = x2 ,

ySlider = H - 10 - 21 ,最后的 21 是滑动条自己的高度。

滑动条宽度是 wSlider = W - x2 - 10 ,高度固定为 hSlider = 21。

现在可以计算第一行的标签控件矩形了(其实应该是包裹标签的滚动区域矩形),标签坐标为:

xLabel = 10;

yLabel = 10;

现在要计算标签的宽度和高度,

宽度为 wLabel = W - 10 - 10;

高度为 hLabel = H - 10 - 21 - 10 - 23 - 10 - 10 。

高度计算里面第一个 10 是底部边界间隙, 21 是水平滑动条高度,

第二个 10 是水平滑动条与按钮的间隙,23 是按钮的高度,

第三个 10 是按钮与标签控件的间隙,

第四个 10 是标签距离顶部的间隙。

到这里按钮的分布情况就确定了。这个界面只有 6 个控件而已,如果控件再多些,计算就会更复杂了。

当窗口大小改变时,我们需要重载主窗体基类的事件函数:

void QWidget::resizeEvent(QResizeEvent * event)
参数 event 是 QResizeEvent 类型,除了构造函数,这个 QResizeEvent 只有两个自己的公有函数:
const QSize & QResizeEvent::oldSize() const
oldSize() 用于获取窗体的旧尺寸,就是变化前的尺寸。
const QSize & QResizeEvent::size() const
size() 用于获取窗体当前的新尺寸,就是变化后的尺寸。 当然,我们都是根据新尺寸调整里面各个控件的分布和尺寸。

【小技巧】重写基类虚函数的方法

我们需要重载基类的 resizeEvent() 函数,我们可以手动在 MainWindow类的 .h 文件和 .cpp 里面,按照帮助文档里给定的函数名和参数,手动添加代码行进行重载。或者按照下面示范的操作,可以用右键菜单实现自动重载基类函数。

打开 MainWindow.h 头文件,选中类名 MainWindow,右击类名 MainWindow,然后也是在右键菜单选择 “Refactor”

【Qt】Qt手动布局_Qt手动布局_03

“Refactor” 第四个子菜单项是 “Insert Virtual Functions of Base Classes” ,就是重载基类虚函数的意思。

点击重载基类虚函数的子菜单项,进入下面插入基类虚函数对话框:

【Qt】Qt手动布局_Qt_04


该对话框最上面是树形列表,列举了所有基类可重载的虚函数,
然后有个复选框 “Hide reimplemented functions” ,是隐藏之前已经重载过的虚函数的意思。
接下来是 “Insertion options” 分组框,里面有一个组合框,用于设置插入虚函数的选项,
组合框里面有四项内容:
“Insert only declarations” ,只在类声明里面插入一个虚函数声明,不添加函数定义。
“Insert definitions inside class”,把函数定义直接放到类声明里面,函数声明直接省了,在类声明里编写新函数实体。
“Insert definitions outside class” ,把函数定义和声明都放在头文件里面,函数声明放在类声明里面,函数定义放在类声明结束之后外面的代码位置。
“Insert definitions in implementation file” ,函数声明放到头文件的类声明里面,函数的实体定义放到对应的 .cpp 文件末尾的位置。第四种是最为推荐的用法。当然,如果类的 .h 名与 .cpp 文件简短名不一样,那么可能找不到实现文件。这种情况才会用第一选项,只添加声明,回头自己在实现文件补上函数定义。

组合框下面又是一个复选框 “Add keyword ‘virtual’ to function declearation” ,这个是建议勾选的,基类是虚函数,虽然能自动继承虚函数特性,但明确加上 virtual 关键字其实更好,让代码意义更清晰,并且将来当前类的派生类也能重载该虚函数。


我们在最上面的树形列表里面选中基类 QWidget 的 resizeEvent() 函数,

组合框里面选择第四个 “Insert definitions in implementation file” ,

勾选插入关键字 virtual 的复选框,如下图所示:

【Qt】Qt手动布局_手动布局_05

mainwindow.h内容如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

private:
Ui::MainWindow *ui;

// QWidget interface
protected:
virtual void resizeEvent(QResizeEvent *event);
};
#endif // MAINWINDOW_H

mainwindow.cpp内容如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QScrollArea>
#include <QResizeEvent>

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);

//获取标签矩形
QRect rcLabel = ui->label->geometry();

QScrollArea *pSA = new QScrollArea(this);
//将标签填充到滚动区域中
pSA->setWidget(ui->label);
//设置关东区域占据矩形
pSA->setGeometry(rcLabel);

//设置最小尺寸 构造函数最后一句就是设置最小尺寸为 350*350
this->setMinimumSize(350, 350);
}

MainWindow::~MainWindow()
{
delete ui;
}

void MainWindow::resizeEvent(QResizeEvent *event)
{
//获取当前宽度、高度
int W = event->size().width();
int H = event->size().height();

//先计算第二行四个按钮的左上角坐标,按钮尺寸固定为 75*23
//第一个按钮
int x1 = 10; //左边距 10
int y1 = H - 10 - 21 - 10 - 23; // 10 都是间隔,21 是水平滑动条高度,23 是按钮高度
//第四个按钮
int x4 = W - 10 - 75; //10 是右边距,75 是按钮宽度
int y4 = y1; //与第一个按钮同一水平线
//计算四个按钮的三个间隙总大小
int nTriGap = W - 10 - 10 - 75 * 4;
//计算单个间隙
int nGap = nTriGap / 3 ;
//计算第二个按钮坐标
int x2 = x1 + 75 + nGap;
int y2 = y1;
//计算第三个按钮左边
int x3 = x4 - 75 - nGap;
int y3 = y1;

//设置四个按钮的矩形
ui->pushButton->setGeometry(x1, y1, 75, 23);
ui->pushButton_2->setGeometry(x2, y2, 75, 23);
ui->pushButton_3->setGeometry(x3, y3, 75, 23);
ui->pushButton_4->setGeometry(x4, y4, 75, 23);

//计算第三行水平滑动条的坐标和尺寸
int xSlider = x2;
int ySlider = H - 10 - 21;
int wSlider = W - x2 - 10;
int hSlider = 21;
//设置水平滑动条的矩形
ui->horizontalSlider->setGeometry(xSlider, ySlider, wSlider, hSlider);

//计算包裹标签的滚动区域占用的矩形
int xLabel = 10;
int yLabel = 10;
int wLabel = W - 10 - 10;
int hLabel = H - 10 - 21 - 10 - 23 - 10 - 10;
//设置包裹标签的滚动区域矩形
QScrollArea *pSA = this->findChild<QScrollArea *>(); //查找子对象
if( pSA != NULL) //如果 pSA 不为 NULL 才能设置矩形
{
pSA->setGeometry(xLabel, yLabel, wLabel, hLabel);
}
}

关于标签控件的代码,因为我们在构造函数里用滚动区域 pSA 替代了标签占用的矩形,

把标签对象塞到滚动区域内部了,现在标签对象自己的矩形我们不用管。

需要把计算出来的 xLabel 、yLabel、wLabel、hLabel 设置给主界面里的滚动区域对象。

注意,在构造函数里的 pSA 是临时指针,我们没有存储它,其实应该把滚动区域对象的指针存为成员变量的,但我们没有存。

没有存成员变量,也有不存的搞法。Qt 类库的总基类 QObject 支持实时查询当前父对象包含的子对象,就是 findChild 模板函数:

T QObject::findChild(const QString & name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
这是一个模板函数,需要把被查询的指针类型用尖括号包裹,放在函数名与小括号之间,比如:
QScrollArea *pSA = this->findChild<QScrollArea *>(); //查找子对象
QScrollArea * 就是要查找的子对象指针类型,我们这里因为主界面窗体里面只有一个 QScrollArea * 对象指针,不需要参数就能查找到。
findChild() 第一个参数 name 是对象名称,就是 setObjectName() 函数设置的名字。
第二个参数 options 是查找选项,默认的 Qt::FindChildrenRecursively 是一直递归查找所有的子对象以及子对象的子对象。
如果调用下面这句,就是仅仅查找直属的子对象,不递归查找:
QPushButton *button = parentWidget->findChild("button1", Qt::FindDirectChildOnly);
这一句就是查找按压按钮的指针,对象名为 "button1",Qt::FindDirectChildOnly 就是仅仅搜寻直属子对象,不递归。

如果查找不到指定的对象指针,那么 findChild() 函数会返回 NULL 空指针。
因此在代码里需要判断该函数的返回值是否为空再进行操作,例如:
//设置包裹标签的滚动区域矩形
QScrollArea *pSA = this->findChild<QScrollArea *>(); //查找子对象
if( pSA != NULL) //如果 pSA 不为 NULL 才能设置矩形
{
pSA->setGeometry(xLabel, yLabel, wLabel, hLabel);
}

程序执行结果

【Qt】Qt手动布局_手动布局_06

05. 计算文本显示宽度

在自己规划和计算界面控件分布的时候,可能遇上控件宽度是根据文本长度变化的情况。接下来我们根据按钮控件自身的字体设置,计算某一段按钮文本的显示宽度,根据这个文 本显示宽度来确定按钮的动态宽度。

Qt 控件的基类 QWidget 原本有 能让控件根据显示内容自动调整大小 的函数:


void QWidget::adjustSize()


我们例子没有用这个函数,而是自己手动计算并调整控件宽度的。无论是用 adjustSize() 函数还是自己手动计算,都能达到类似的效果。

相关函数

QWidget 类有获取当前字体的函数:
const QFont & QWidget::font() const
另外还有获取 QFontMetrics 对象的函数,QFontMetrics 对象专门用于根据文本字符串计算文本宽度:
QFontMetrics QWidget::fontMetrics() const
我们获取控件的 QFontMetrics 字体度量对象,然后就可以用该对象的函数来确定某一段文本的显示宽度:
int QFontMetrics::width(const QString & text, int len = -1) const
int QFontMetrics::width(QChar ch) const
第一个 width() 函数中,text 就是需要计算的文本字符串,len 是指定字符串片段的字符个数,如果 len 为默认的 -1,说明计算全部字符串的显示宽度。第二个 width() 函数负责计算单个字符的显示宽度。
QFontMetrics 通常只是计算单行字符串的宽度和高度,字体度量的单行高度是用如下函数获取:
int QFontMetrics::height() const
高度函数里没有参数,这个字体高度是最大高度,以 xbg 三个字母为例,b 上凸,g 下凹,字体高度是从最上面的水平线计算到最下面水平线,这是一个最大高度。

QFontMetrics 有一个 size() 函数可以计算多行文本的总宽度和总高度,返回 QSize 对象表示尺寸:
QSize QFontMetrics::size(int flags, const QString & text, int tabStops = 0, int * tabArray = 0) const
第一个参数 flags 可以是如下几个标志位进行位或 | 后的数值:
Qt::TextSingleLine 代表忽略换行符,把所有文本作为一行来计算。
Qt::TextExpandTabs 代表展开 '\t' 制表符,用空白区域填充制表符。
Qt::TextShowMnemonic 是指将 "&x" 替换为 x 来计算,这在伙伴快捷键里常用到。

其他标志位见 enum Qt::TextFlag 枚举常量列表。
如果不设置任何标志位,就把 flags 设置为 0 ,这时候换行符 '\n' 会起作用,自动计算多行文本占用的尺寸。
QFontMetrics::size() 函数第二个参数 text 就是要计算的文本字符串。

QFontMetrics::size() 函数第三个参数和第四个参数只在 Qt::TextExpandTabs 标志位启用的时候有作用,使用方法比较怪异:
如果第四个 tabArray 不为空数组,这个数组指定每个制表符的像素点位置,tabArray 数组最后一个数值必须是 0,作为结束标志。
如果第四个 tabArray 为空指针,那么再看第三个 tabStops ,tabStops 如果非 0 ,就是指每个制表符占据的空白区域像素宽度;
如果 tabStops 和 tabArray 都没设置,都是默认的 0,那么交给 QFontMetrics 用默认策略扩展制表符。
QFontMetrics::size() 函数最简单的用法举例:
QFontMetrics fm = pWidget->fontMetrics();
QSize szMulti = fm.size(0, "ABC\n123\nXYZ");
上面第二句就是计算三行文本的总尺寸。

创建新的项目,设计UI如下图所示

【Qt】Qt手动布局_Qt_07

程序主要功能就是在 lineEdit 里面编辑文本时,动态按钮根据该文本计算显示宽度,动态调整自己的宽度;

而固定按钮的尺寸是不变的,它会根据 lineEdit 文本内容自动计算能够显示的文本片段,如果能全部显示出来就全部显示,如果不能全部显示,它就显示前面一小段文本和 “…” ,比如文本字符串 “哈哈哈哈哈哈哈哈哈” 太长了,固定按钮就显示 “哈哈哈哈…” 。

另外这里会为控件设置工具提示信息,随后会看到设置工具提示信息的代码。

【Qt】Qt手动布局_Qt手动布局_08

现在这个计算显示宽度的例子,它的三行控件在垂直方向是均匀分布的,并且控件高度全部固定为 24 。

这样如图所示,三行控件的中轴线把主窗体分割成均匀的四块区域,根据图上规划就很容易计算各个控件的分布情况了。

第一行的单行编辑控件宽度是根据窗口大小变化的,

第二行的按钮控件是根据单行编辑器里的文本动态调整宽度。

只有第三行的按钮控件,尺寸是固定的,我们根据第三行控件可以定出窗口的最小尺寸:

最小宽度是 10+54+10+75+10 ,其中 10 都是间隙和边距,54 是标签的固定宽度,75 是第三行按钮的宽度。

最小高度是 24​3 + 10​4 ,24是每行控件高度,10 是间隙和边距。

窗口大致的规划就是这样,详细的计算放在后面代码里。

为单行编辑控件添加 textEdited(QString) 信号对应的槽函数:

打开 .h 头文件,添加重载窗口调整大小的函数 resizeEvent() ,这个操作也是按照前面添加 resizeEvent() 的过程,一样地添加就行了,即选中并右击类名,从右键菜单选择

“Refactor” --> “Insert Virtual Functions of Base Classes” ,

然后从弹出的对话框添加窗口大小调整的事件函数:

mainwindow.h内容如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

private slots:
void on_lineEdit_textEdited(const QString &arg1);

private:
Ui::MainWindow *ui;

// QWidget interface
protected:
virtual void resizeEvent(QResizeEvent *event);
};
#endif // MAINWINDOW_H

新包含的 是窗口大小调整的事件, 是字体度量类,用于计算文本显示宽度。

在构造函数里面增加了一句 setMinimumSize() 函数调用,把我们之前规划的最小尺寸设置给主窗体,没有限定最大尺寸。

接着是槽函数 on_lineEdit_textEdited() 的内容,这个槽函数代码比较多,我们分两块来讲解,第一块代码是关于 “动态按钮” 尺寸计算和设置的:

void MainWindow::on_lineEdit_textEdited(const QString &arg1)
{
//获取按钮文本字体度量对象
QFontMetrics fm = ui->pushButton->fontMetrics();
//计算按钮文本的显示宽度
int nTextWidth = fm.width(arg1);
//获取动态按钮原先的尺寸
QSize szButton1 = ui->pushButton->size();
//修改宽度 10表示文本两端留的空隙
szButton1.setWidth(nTextWidth + 10);

//重置大小
ui->pushButton->resize(szButton1);
//设置动态按钮文本
ui->pushButton->setText(arg1);
//设置动态按钮工具提示信息
ui->pushButton->setToolTip(arg1);


}

代码解析

槽函数开始处获取了按钮的字体度量对象 fm;

然后计算文本字符串 arg1 的显示宽度 nTextWidth ;

接着获取按钮原来尺寸 szButtonDynamic ;

动态按钮新尺寸不要修改高度,我们将 szButtonDynamic 的宽度设置为 nTextWidth + 10,这个 10 是为文本显示左右两端各留了 5 个像素的空白,这样按钮文字看起来不会挤到边框;

得到新尺寸 szButtonDynamic ,用 resize() 函数把这个新尺寸设置给动态按钮;

然后设置动态按钮的文本为 arg1;

设置动态按钮的工具提示信息也为 arg1。

图形界面的控件都可以设置工具提示信息 setToolTip(QString),当鼠标悬停在控件上的时候,过一阵子会自动显示一行提示信息,就是表示该控件附加提示信息的,一般用于展示控件的功能作 用等。

以下代码是关于固定按钮文本设置的:

//固定按钮尺寸为75 * 24 可显示文本的宽度为75 - 10 = 65
if (nTextWidth <= 65)
{
//文字全部可以显示
ui->pushButton_2->setText(arg1);
}
else
{
//文本不可以全部显示
QString strPart;
QString strDot = "...";
//字符串长度
int nStrLen = arg1.length();
//新的文本显示长度
int nNewTextWidth = 0;

for (int i = 0; i < nStrLen; i++)
{
strPart += arg1[i];
nNewTextWidth = fm.width(strPart + strDot);
if (nNewTextWidth >= 65)
{
break;
}
}

//为固定按钮设置文本
ui->pushButton_2->setText(strPart + strDot);
}

//设置固定按钮的工具提示信息
ui->pushButton_2->setToolTip(arg1);

因为固定按钮的尺寸是 75*24,为文本两端要留 10 像素间隙,文本能占有的宽度就是 75 - 10 == 65。

这是判断 arg1 文本显示宽度 nTextWidth 是否不超过 65,如果不超过就直接把 arg1 文本设置给固定按钮,不需要额外计算。

如果 nTextWidth 超过了 65,那么需要进行计算:

strPart 是我们最终需要从 arg1 里面截取的前面一部分字符串;

strDot 是省略号 “…”;

nStrLen 是字符串的总长度,也就是所有字符计数;

nNewTextWidth 用于计算 (strPart + strDot) 字符串总长度。

然后进入 for 循环,该循环每次为 strPart 添加一个字符,计算新的 (strPart + strDot) 总的显示宽度 nNewTextWidth ,

如果新的 nNewTextWidth 刚刚好超了可显示宽度 65,那么就退出循环。

经过该循环处理后,(strPart + strDot) 就是我们在固定按钮里需要显示的文本,

我们把 (strPart + strDot) 设置给固定按钮。

槽函数最后一句是为固定按钮设置工具提示信息 arg1 ,这样即使固定按钮自己显示不了全部的文本,它工具提示信息显示的是完整的文本 arg1 。

槽函数就是上面那些,槽函数功能主要是设置两个按钮的文本、工具提示信息,并且根据文本内容实时调整动态按钮的尺寸,如果文本过长,固定按钮只会显示文本前面的片 段和 “…”。

最后部分是 resizeEvent() 虚函数的代码,根据窗口大小调整各个控件的分布情况。我们有三行控件,下面就针对每一行的控件设置代码来讲解,首先是第一行标签和单行编辑控件的:

void MainWindow::resizeEvent(QResizeEvent *event)
{
//获取当前宽度、高度
int W = event->size().width();
int H = event->size().height();

//计算第一行控件分布,标签尺寸都是 54*24
//标签1
int xLabel1 = 10;
int yLabel1 = H/4 - 12;
//标签尺寸不变,移动坐标即可
ui->label->move(xLabel1, yLabel1);
//单行编辑控件
int xLineEdit = xLabel1 + 54 + 10; //54是标签宽度,10是间隙
int yLineEdit = yLabel1;
int wLineEdit = W - xLineEdit - 10; // 10 是右边距
int hLineEdit = 24;
//设置矩形
ui->lineEdit->setGeometry(xLineEdit, yLineEdit, wLineEdit, hLineEdit);


}

W 是主窗体宽度,H 是主窗口宽度。

xLabel1 是第一行标签的左边距,固定为 10;

yLabel1 = H/4 - 12,其中 H/4 是第一行控件的中轴线,12 是控件上半截的高度。

标签控件尺寸固定,只需要用 move() 函数把坐标移动到 (xLabel1, yLabel1) 就行了。

单行编辑控件的 xLineEdit = xLabel1 + 54 + 10,是标签控件右边间隔 10 的位置,54 是标签宽度;

yLineEdit 与标签控件 yLabel1 一样的垂直位置;

wLineEdit 是单行编辑控件宽度,除去单行编辑控件左边区域 xLineEdit 和右边距 10,剩下的就是自己的宽度;

单行编辑控件高度固定 hLineEdit 为 24 。

然后设置单行编辑控件占据的矩形,就完成第一行控件的分布了。

//计算第二行控件分布,标签控件 54*24
//标签2
int xLabel2 = 10;
int yLabel2 = 2*H/4 - 12;
//移动标签
ui->label_2->move(xLabel2, yLabel2);
//动态按钮
int xButtonDynamic = xLabel2 + 54 + 10;
int yButtonDynamic = yLabel2;
//移动即可,尺寸由上面槽函数 on_lineEdit_textEdited 处理
ui->pushButton->move(xButtonDynamic, yButtonDynamic);

标签控件 xLabel2 = 10,距离左边界 10 像素;

yLabel2 = 2​H/4 - 12 ,第二行中轴线的垂直坐标是 2​H/4,12 是控件上半截的高度。

标签控件尺寸是固定的,将坐标移动到 (xLabel2, yLabel2) 就行了。

动态按钮的 xButtonDynamic = xLabel2 + 54 + 10,是距离标签右边间隔 10 的位置,54 是标签的宽度。

yButtonDynamic 与标签垂直位置 yLabel2 一样。

这个动态按钮的尺寸由前面槽函数调整,这里只需要把坐标移动到 (xButtonDynamic, yButtonDynamic) 即可。

//计算第三行控件分布,标签控件 54*24
//标签3
int xLabel3 = 10;
int yLabel3 = 3*H/4 - 12;
//移动标签
ui->label_3->move(xLabel3, yLabel3);
//固定按钮
int xButtonFixed = xLabel3 + 54 + 10;
int yButtonFixed = yLabel3;
//移动即可,尺寸固定
ui->pushButton_2->move(xButtonFixed, yButtonFixed);

标签控件 xLabel3 = 10,也是距离左边界 10 像素。

yLabel3 = 3​H/4 - 12,其中 3​H/4 是第三行控件的中轴线垂直位置,12 是控件上半部分高度。

标签大小不需要调整,只需要移动到坐标 (xLabel3, yLabel3) 。

固定按钮的 xButtonFixed = xLabel3 + 54 + 10,是位于标签控件右边间隔 10 的位置,54 是标签控件宽度。

yButtonFixed 与标签的垂直坐标 yLabel3 一样。

固定按钮的尺寸不用调整,直接移动到坐标 (xButtonFixed, yButtonFixed) 就行了。

执行结果如下:

【Qt】Qt手动布局_Qt布局_09

06. 附录

6.1 Qt教程汇总

6.2 源码下载:

下载:

下载: