Qt——控件

目录

概念

QWidget核心属性

enabled

geometry

WindowFrame的影响

windowTitle

windowIcon

qrc的使用

windowOpacity

cursor

font

toolTip

focusPolicy

​编辑

styleSheet

按钮类控件

PushButton

RadioButton

CheckBox

显示类控件

Label

textFormat

pixmap

scaledContents

alignment

wordWrap

indent

margin

buddy

[LCD Number](#LCD Number)

ProgressBar

[Calendar Widget](#Calendar Widget)

输入类控件

[Line Edit](#Line Edit)

[Text Edit](#Text Edit)

[Combo Box](#Combo Box)

SpinBox

[Date Edit & Time Edit](#Date Edit & Time Edit)

Dial

Slider

多元素控件

[List Widget](#List Widget)

[Table Widget](#Table Widget)

[Tree Widget](#Tree Widget)

容器类控件

[Group Box](#Group Box)

​编辑

[Tab Widget](#Tab Widget)

布局管理器

垂直布局

水平布局

网格布局

表单布局

Spacer


概念

Widget 是 Qt 中的核心概念,英⽂翻译为 "控件" ,控件是构成⼀个图形化界⾯的基本要素,像按钮、单⾏输⼊框、多⾏输⼊框、滚动条、下拉框等,都可以称为控件。

Qt 作为⼀个成熟的 GUI 开发框架,内置了大量的常用控件。这⼀点在 Qt Designer 中就可以看见。并且 Qt 也提供了 "⾃定义控件"的能⼒, 可以让程序员在现有控件不能满⾜需求的时候,对现有控件做出扩展,或者手搓出新的控件。

原来的控件数量少,甚至没有,只能使用一些基本的控件,但是随着时代的发展,新的 GUI 开发体系越来越丰富,提供的控件数量和质量越来越好。

Qt 的控件虽然很多,但是从美观上并不是最好的,从之前写过的代码中使用的控件就可以看出,那些都是默认的样子,但是Qt 之所以还在被使用就是因为它还在进化,它也提供了一些优化手段,可以让空间变得更好看。

图形化开发还是要注重颜值的。


QWidget核心属性

打开Qt Designer右侧就可以看到QWidget中的属性,毕竟生成的Widget也是继承了QWidget,我们可以看几个常用的。

enabled

API 说明
isEnabled() 获取控件的可用状态。
setEnabed() 设置控件是否可用,true表示可用,false表示禁用。

所谓"禁用"指的是该控件不能接收任何用户的输入事件, 并且外观上往往是灰⾊的。

如果⼀个 widget 被禁⽤,则该 widget 的子元素也被禁用。

如果一开始就禁用Widget,那么按钮这个子元素也被禁用了。

现在我们就可以通过更改Qt Designer界面中pushButton的objectName改一下,通过连接信号和槽就可以实现点击按钮切换按钮可用性的功能。

geometry

**位置和尺⼨**其实是四个属性的统称:

  • x:横坐标
  • y:纵坐标
  • width:宽度
  • height:⾼度
API 说明
geometry() 获取到控件的位置和尺寸。返回结果是⼀个QRect,包含了x,y,width,height。
setGeometry(QRect) setGeometry(int x, int y, int width, int height) 设置控件的位置和尺寸,可以直接设置⼀个QRect,也可以分四个属性单独设置。

其中QRect就是"矩形",Qt中也封装了几何,QPoint表示一个点,他们两个属于小对象,通常就会按照值的方式传递参数,因为对象占的内存比较小,拷贝的消耗比较小。

现在我们往窗口中添加5个按钮,我们想要实现每次点击下方按钮,上方按钮就会向对应方向移动。

  1. 第一步肯定先要获取target按钮的geometry。这个类型对应的就是x,y,width,heigh,单位就是像素。
  2. 重新设置坐标,并把新的QRect设置到target中。
  3. 完成其他3个方向即可。

运行时会发现,通过点击按钮确实调整了坐标,这个坐标调整的就是这个控件左上角的坐标,但是高度和宽度也会有一定的变化,并没有实现我们想要的功能,所以还要再调整高度和宽度,或者说让宽度和高度不变。

cpp 复制代码
void Widget::on_pushButton_up_clicked()
{
    // 获取 target 的geometry
    QRect rect = ui->pushButton_target->geometry();

    // 使用qDebug就可以打印Qt中的内置类型
    qDebug() << rect;

//    rect.setY(rect.y() - 5);
//    rect.setHeight(rect.height() - 5); // 可以跟着调整对应的高和宽
//    ui->pushButton_target->setGeometry(rect);

    ui->pushButton_target->setGeometry(rect.x(), rect.y() - 5, rect.width(), rect.height()); // 只调整坐标,其他不变

当我们在刷视频或者在网上冲浪的时候一定见过表白程序,那么我们现在就可以设计一个。

欣然同意那就皆大欢喜,也就不做过多处理,我们关闭窗口即可,但是我们又不想让对方拒绝,所以就要设置拒绝的按钮,这就可以通过生成随机数的方式更新按钮的位置,就是让对方怎么也点不到。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    srand((unsigned)time(nullptr));
}

void Widget::on_pushButton_agree_clicked()
{
    this->close();
}

void Widget::on_pushButton_refuse_clicked()
{
    // 点击了这个按钮就挪走
    // 随机移动

    QRect rect = ui->pushButton_refuse->geometry();

    // 获取窗口的geometry 宽高
    int width = this->geometry().width();
    int heigh = this->geometry().height();

    // 生成按钮的位置
    int x = rand() % (width - rect.width());
    int y = rand() % (heigh - rect.height());

    // 移动按钮
    ui->pushButton_refuse->move(x, y);
}

运行后就会看到一个一点击就会"逃跑"的拒绝按钮,但是我们现在的这个点击是一上一下,那还有没有触发更快的信号呢?那肯定是有的,比如还有一个信号是pressed,clicked是一上一下,这个信号只有一下。

cpp 复制代码
void Widget::on_pushButton_refuse_pressed()
{
    // 点击了这个按钮就挪走
    // 随机移动

    QRect rect = ui->pushButton_refuse->geometry();

    // 获取窗口的geometry 宽高
    int width = this->geometry().width();
    int heigh = this->geometry().height();

    // 生成按钮的位置
    int x = rand() % (width - rect.width());
    int y = rand() % (heigh - rect.height());

    // 移动按钮
    ui->pushButton_refuse->move(x, y);
}

代码没有变,改一下触发的信号和槽就可以了。

WindowFrame的影响

当我们创建出一个Widget时,windows会自动帮我们生成上方的一栏,下面还会生成一个非常窄的栏,就是下面红线部分,这就是Window Frame窗口框架,这是操作系统自带的,也可以通过设置一些选项让这些部分消失,就好像打游戏时设置的窗口化或全屏一样。

如果在Widget中有一个按钮,那么它的父元素就是Widget,那么坐标是相对于Widget的原点还是Window Frame的原点呢?

在 Qt 中关于位置尺寸,提供了很多的API。

  • 有些API的位置信息是以Widget左上角为原点的
  • 有些API的位置信息是以Window Frame左上角为原点的

上面说过的geometry()setGeometry() 都是以Widget为左上角为原点的;Qt 中还有其他接口是以Window Frame为原点的,比如frameGemoetry()setFrameGeometry ()

接下来我们就来看看这两个的坐标位置是怎样的。

但是打印出来我们看到的结果并不是我们想要的,原因在于这段代码是在调用构造函数中,此时正在创建这个Widget对象,这个对象还没有被加入到Window Frame中。

那让他已经加入到Window Frame中的时候再打印就可以了,那我们就创建一个按钮,当点击按钮触发信号执行槽函数时再打印,这时候Widget一定创建好了,在这时打印就可以了。

所以在我们使用的时候一定要确定想要使用的是哪个坐标。

windowTitle

API 说明
windowTitle() 获取到控件的窗口标题。
setWindowTitle(const QString& title) 设置控件的窗口标题。

**【注意】:**当前windowTitle属性是从属于QWidget的,QWidget是一个广泛的概念,只有对顶层Widget操作才会生效,如果是子Widget是无效的。

对顶层Widget设置标题是有效的,在pushButton中设置标题是无效的,但是代码并没有报错,也没有效果,所以一定要注意。

windowIcon

这个属性表示窗口的图标。

API 说明
windowIcon() 获取到控件的窗口图标,返回 QIcon 对象
setWindowIcon(const QIcon& icon) 设置控件的窗口图标。

windowIcon和windowTitle这两个属性的API都是针对顶层窗口使用的。

之前都是推荐在堆上创建对象,主要是为了确保当前的控件的声明周期是足够的,然后要通过对象树释放对象,但是QIcon不一样。

  • QIcon是一个自身比较小的对象,创建出后要设置到QWidget中。
  • QIcon对象释不释放并不影响图标的显示,QPushButton如果提前释放了,那么在窗口中就不会显示。
  • QIcon不支持对象树,无法给他指定父对象,所以在栈上申请就可以了,程序结束就释放。

现在就可以把图标设置成任意图片了,这里使用的是绝对路径的方式,但是最好还是不要使用绝对路径,因为用户的电脑上和你的电脑上的文件路径可能是不一致的,所以更推荐使用相对路径。

如果还有问题也不要担心,可以使用qrc机制,它可以解决上述问题,也可以解决用户目录中丢失了某张图片的问题。

  • 给 Qt 项目引入一个额外的xml文件,后缀名使用.qrc表示。
  • 给xml中导入要使用的图片资源。
  • Qt 在编译项目的时候,就会根据qrc中描述的图片信息找到图片内容,并提取出图片的二进制数据。
  • 把这些数据转成C++代码,最终编译到可执行程序中,也就是.exe文件中。

数据已经在可执行程序中了,所以不怕图片文件丢失了。但是这种做法也有缺点,就是无法导入太大的文件,比如好几个GB的视频文件,转换成C++代码都是一个耗时的操作。

qrc的使用

  1. 创建一个qrc文件。
  2. 先创建一个"前缀"(Prefix),这个前缀可以理解为虚拟目录,这个目录是Qt抽象出来的,因为qrc机制就是把图片的二进制数据转换成C++代码,就类似于一个char数组,所以为了方便Qt代码可以访问到这个图片,所以就抽象出了虚拟目录。然后把前缀的名字改成"/"就可以了。
  3. 点击Add Files导入图片,找到这种图片就可以了,但是还是会报警告,一定要确保导入的图片必须在.qrc文件的同级目录或者同级目录的子目录中,所以要把图片拷贝到项目目录中。
  4. 添加成功后就可以使用这个虚拟目录了。当代码中需要访问qrc管理的文件时,就需要在路径上有":"前缀,并且创建前缀时使用的是什么名字,后面就要跟前缀 + 文件名。

现在就可以使用qrc机制处理资源了,在项目目录中有个cpp文件。

打开文件就会看到这个图片的二进制数据,编译的时候就会把这个cpp文件编译进可执行程序,程序运行时图片的数据就被加载到了内存中。

windowOpacity

这个属性表示窗口的透明度。

API 说明
windowOpacity() 获取到控件的不透明数值。返回 float,取值为 0.0 ~1.0 其中 0.0 表示全透明,1.0 表示完全不透明。
setWindowOpacity(float n) 设置不透明数值。

现在我们就来设置一下透明度。


这里对于浮点数的加减在C语言阶段也讲解过了,如果有疑问可以翻一下博客。
其实这里的if判断也是可以不写的,因为opacity取值范围就是在 0.0 ~ 1.0 之间,即使超过数值也不会有变化,这层判断就是保险一下。

cursor

设置鼠标光标的样式。

API 说明
cursor() 获取到当前 widget 的 cursor 属性,返回 QCursor 对象。当鼠标悬停在该 widget 上时,就会显示出对应的形状。
setCursor(const QCursor& cursor) 设置该 widget 光标的形状。仅在鼠标停留在该 widget 上时生效。
QGuiApplication::setOverrideCursor(co nst QCursor& cursor) 设置全局光标的形状。对整个程序中的所有 widget 都会生效。覆盖上面的 setCursor 设置的内容。

前两个API都是Widget级别的,在同一个窗口中,不同的控件可以设置成不同的光标。

我们现在就可以给一个按钮设置cursor,让鼠标放在这个控件上显示指向光标。

运行就可以了,下面我们再看看代码怎么实现的。

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QCursor>

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

    QCursor cursor(Qt::WaitCursor); // 创建一个cursor对象,WaitCursor就是等待光标
    ui->pushButton->setCursor(cursor); // 设置cursor
}

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

那这些光标都是在哪设置的呢,我们转到定义就可以看到。

cpp 复制代码
enum CursorShape {
    ArrowCursor,
    UpArrowCursor,
    CrossCursor,
    WaitCursor,
    IBeamCursor,
    SizeVerCursor,
    SizeHorCursor,
    SizeBDiagCursor,
    SizeFDiagCursor,
    SizeAllCursor,
    BlankCursor,
    SplitVCursor,
    SplitHCursor,
    PointingHandCursor,
    ForbiddenCursor,
    WhatsThisCursor,
    BusyCursor,
    OpenHandCursor,
    ClosedHandCursor,
    DragCopyCursor,
    DragMoveCursor,
    DragLinkCursor,
    LastCursor = DragLinkCursor,
    BitmapCursor = 24,
    CustomCursor = 25
};

Qt也是支持我们通过自定义图片来设置光标的,步骤还是准备图片,把图片添加到qrc文件中,再使用就可以了。

我们还需要使用的一个类就是QPixmap,这个类可以帮我们访问到这个图片,后面也会讲解的。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

//    QCursor cursor(Qt::WaitCursor);
//    ui->pushButton->setCursor(cursor);

    // 访问到图片文件
    QPixmap pixmap(":/cat.jpg");
    // 构造光标
    // 我们所用的光标都是以左上角那个点为热点,就是负责点击点
    // 可以设置热点的位置
    QCursor cursor(pixmap, 10, 10);
    // 把光标设置进去
    ui->pushButton->setCursor(cursor);
}

如果觉得图片的大小不合适也可以这样设置一下再添加到光标。

cpp 复制代码
pixmap = pixmap.scaled(width, heigh);

font

API 说明
font() 获取当前 widget 的字体信息。返回 QFont 对象。
setFont(const QFont& font) 设置当前 widget 的字体信息。

关于QFont的属性。

属性 说明
family 字体家族。比如 "楷体","宋体"等。
pointSize 字体大小。
weight 字体粗细。以数值方式表示程度取值范围为 [0,99],数值越大,字体越 粗。
bold 是否加粗。设置为 true, 相当于 weight 为 75。设置为 false 相当于 weight 为 50。
italic 是否倾斜
underline 是否带有下划线
strikeOut 是否带有删除线

我们可以在界面中放一个Label控件,就可以设置font属性。

我们也可以通过代码的方式实现这些功能。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QLabel* label = new QLabel(this);
    label->setText("这是一段文本");
    label->move(100, 200);

    // 创建字体对象
    QFont font;
    font.setFamily("黑体");
    // 设置字体大小
    font.setPixelSize(30);
    // 设置加粗
    font.setBold(true);
    // 设置倾斜
    font.setItalic(true);
    // 这是下划线
    font.setUnderline(true);
    // 设置删除线
    font.setStrikeOut(true);

    // 设置label的font
    label->setFont(font);
}

toolTip

把鼠标悬停到这个控件上就会弹出一个提示。

API 说明
setToolTip() 设置 toolTip。鼠标悬停在该 widget 上时会有提示说明。
setToolTipDuring() 这是toolTip提示的时间,单位ms,时间到后toolTip消失。

简单实现一下,在窗口中添加两个按钮,说明两个按钮的作用。

这是代码的方式实现的,图形化界面也是可以设置toolTip的。

focusPolicy

这个属性是设置控件获取的"焦点"的,比如鼠标选中输入框,键盘输入就可以显示到框中,如果鼠标点击框以外的位置,那么键盘输入的内容就不会显示到框中。

API 说明
focusPolicy() 获取该 widget 的focusPolicy,返回Qt::FocusPolicy。
setFocusPolicy(Qt::FocusPolicy policy) 设置 widget 的FocusPolicy。

一般获取控件焦点的方式有两种,一是鼠标点击,二是键盘的Tab。

Qt::FocusPolicy是一个枚举类型,取值如下:

  • **Qt::NoFocus:**控件不会接收键盘的焦点,像Label这样的控件就不想让你获取焦点。
  • **Qt::TabFocus:**控件可以通过Tab键接收焦点。
  • **Qt::ClickFocus:**控件可以通过鼠标点击接收焦点。
  • **Qt::StrongFocus:**控件可以通过上面两种方式接收焦点(默认就是它)
  • **Qt::WheelFocus:**类似与上一个,同时控件可以通过鼠标滚轮获取焦点,一般用的很少。

我们先创建一个Line Edit,这个控件默认就是StrongFocus的,所以可以通过鼠标点击和Tab键或Shift+Tab键可以切换。

我们把第一个空间的focusPolicy换成NoFocus,运行后就发现,第一个框不能选中了。

styleSheet

我们都知道写一个网页需要用到CSS样式,那么Qt作为一个GUI图形化界面开发,实际上与网页制作有很多相似之处。

Qt基于CSS中的一些属性,也支持了一部分,叫做QSS(Qt Style Sheet)。

上面两种方式都是以编辑,第一种点击右侧的'...'的控件就可以进入编辑界面。

点击"OK"后就会看到文本已经被按照写入的样式修改了。

我们也可以通过代码的方式修改样式,比如我们可以通过按钮调整界面的颜色和字体。

其实在我们写的时候很少使用颜色英文的方式表示颜色,在计算机中使用的是RGB的方式来表示颜色,对应的就是Red、Green和Blue,这是计算机中的三原色,按照三种颜色进行不同比例的混合就可以构成不同的颜色。

屏幕上的像素就是一个RGB的小灯泡,计算机中用一个字节8个比特位来表示RGB的每一个,取值就是0~255,十六进制就是0x00~0xFF,所以表示一个颜色就可以表示为:

  • RGB(255, 0, 255),表示红色是满的,绿色没有,蓝色也是满的。
  • #FF00FF,两个字母代表一个字节。

为什么要说这个呢,是因为我们把日间模式设置的背景颜色弄错了,Qt原本的默认颜色并不是白色,所以还要修改一下,那具体是什么颜色呢,我们可以使用QQ或微信的截图就可以知道这块的颜色是什么。


按钮类控件

下面我们讲述的这些控件都继承自QAbstractButton,这是一个抽象类,而它又继承了QWidget。

PushButton

我们先看看QAbstractButton中的属性:

属性 说明
text 按钮中的文本。
icon 按钮中的图标。
iconSize 按钮中图标的尺寸。
shortCut 按钮对应的快捷键。
autoRepeat 按钮是否会重复触发。 当⿏标左键按住不放时,如果设为 true,则会持续产生鼠标点击事件; 如果设为 false,则必须释放⿏标,再次按下鼠标时才能产生点击事件。
autoRepeatDelay 重复触发的延时时间。按住按钮多久之后,开始重复触发。
autoRepeatInterval 重复触发的周期。

关于PushButton这个控件我们之前也说过很多次了,具体的操作我们也知道了,所以我们看些其他的。

其实我们也可以给这个按钮加上图标,属性中也有icon属性,图片还是使用qrc文件。

但是这个图标太小了,所以还要设置一下iconSize。


还有一个属性就是快捷键 ,我们写过一个通过按界面中的按钮让target按钮动起来的功能,这次我们就可以实现一个通过按键盘上的按键实现这样的功能。

我们可以给每个按钮都添加一个图标。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置图标按钮
    ui->pushButton_target->setIcon(QIcon(":/image/search.png"));
    ui->pushButton_target->setIconSize(QSize(70, 70));

    ui->pushButton_up->setIcon(QIcon(":/image/up.png"));
    ui->pushButton_up->setIconSize(QSize(40, 40));

    ui->pushButton_down->setIcon(QIcon(":/image/down.png"));
    ui->pushButton_down->setIconSize(QSize(40, 40));

    ui->pushButton_left->setIcon(QIcon(":/image/left.png"));
    ui->pushButton_left->setIconSize(QSize(40, 40));

    ui->pushButton_right->setIcon(QIcon(":/image/right.png"));
    ui->pushButton_right->setIconSize(QSize(40, 40));
}

void Widget::on_pushButton_up_clicked()
{
    // 获取target对象
    QRect rect = ui->pushButton_target->geometry();
    // 设置新位置
    ui->pushButton_target->setGeometry(rect.x(), rect.y() - 5, rect.width(), rect.height());
}

void Widget::on_pushButton_down_clicked()
{
    // 获取target对象
    QRect rect = ui->pushButton_target->geometry();
    // 设置新位置
    ui->pushButton_target->setGeometry(rect.x(), rect.y() + 5, rect.width(), rect.height());
}

void Widget::on_pushButton_left_clicked()
{
    // 获取target对象
    QRect rect = ui->pushButton_target->geometry();
    // 设置新位置
    ui->pushButton_target->setGeometry(rect.x() - 5, rect.y(), rect.width(), rect.height());
}

void Widget::on_pushButton_right_clicked()
{
    // 获取target对象
    QRect rect = ui->pushButton_target->geometry();
    // 设置新位置
    ui->pushButton_target->setGeometry(rect.x() + 5, rect.y(), rect.width(), rect.height());
}

接下来就是设置快捷键,但是按下的快捷键可能不是一个,也许是组合键,使用QKeySequence对象,Key就是按键,Sequence就是序列。

cpp 复制代码
// 设置快捷键
ui->pushButton_up->setShortcut(QKeySequence("w"));
ui->pushButton_down->setShortcut(QKeySequence("s"));
ui->pushButton_left->setShortcut(QKeySequence("a"));
ui->pushButton_right->setShortcut(QKeySequence("d"));

// 通过按键的枚举设置按键快捷键
ui->pushButton_up->setShortcut(QKeySequence(Qt::Key_Up));
ui->pushButton_down->setShortcut(QKeySequence(Qt::Key_Down));
ui->pushButton_left->setShortcut(QKeySequence(Qt::Key_Left));
ui->pushButton_right->setShortcut(QKeySequence(Qt::Key_Right));

// 也可以设置组合键
ui->pushButton_up->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Up));
ui->pushButton_down->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Down));
ui->pushButton_left->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Left));
ui->pushButton_right->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Right));

还是更推荐使用枚举类型,因为单词拼写错误可以识别,这就要比使用字符串好很多。

运行后就可以实现我们想要的功能,不仅鼠标可以点击,键盘也可以使用,但是它们两个的区别就是键盘的按键自带连发功能 ,要想让鼠标点击也实现连发,就要设置这种属性,使用的方法就是setAutoRepeat(true)

cpp 复制代码
// 连发功能
ui->pushButton_up->setAutoRepeat(true);
ui->pushButton_down->setAutoRepeat(true);
ui->pushButton_left->setAutoRepeat(true);
ui->pushButton_right->setAutoRepeat(true);

RadioButton

QRadioButton是单选按钮。可以在多个选项中选择一个,它也符合上述按钮的继承体系,下面看看这个按钮特有的属性。

属性 说明
checkable 是否能选中
checked 是否已经被选中,checkable是checked的前提。
autoExclusive 是否排他。 选中一个按钮后是否会取消其他按钮的选中,QRadioButton 默认就是排他的。

我们可以看到一开始的选项是没有的,我们可以给选项设置一个默认值。

我们还可以禁用某个选项。

虽然我们禁用了这个选项,只是让这个选项不能被选中,但是文本还是可以相应的,因为触发了clicked信号。

也可以直接设置控件的Enabled属性。

我们可以借助这个按钮理解一下其他的信号,我们通过转到槽可以看到其他信号。

第一个clicked信号我们经常使用,我们看看其他的。

  • clicked(bool),当按钮被点击后就会触发,并且checked为true。
  • pressed,当鼠标按下后就会触发。
  • released,当鼠标先按下,抬起后才会触发。
  • toggled(bool),这个信号不能因重复点击而触发,只有改变了选项才会触发,触发时checked为true,改变选项后为false。
    到这里我们就可以简单实现一下点餐的小功能,通过图形化界面的方式就可以做一个简单的点餐"小程序"。

但是有一个问题,就是RadioButton是排他的,所以这么多选项只能选一个,那是有点不合理的,我们想要每种类型都可以选择,所以Qt也帮我们针对单选按钮进行分组,使用的就是QButtonGroup类。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 使用QbuttonGroup 对单选按钮进行分组
    QButtonGroup* group1 = new QButtonGroup(this);
    QButtonGroup* group2 = new QButtonGroup(this);
    QButtonGroup* group3 = new QButtonGroup(this);

    // 把不同种类的按钮放到不同的组中
    group1->addButton(ui->radioButton);
    group1->addButton(ui->radioButton_2);
    group1->addButton(ui->radioButton_3);

    group2->addButton(ui->radioButton_4);
    group2->addButton(ui->radioButton_5);
    group2->addButton(ui->radioButton_6);

    group3->addButton(ui->radioButton_7);
    group3->addButton(ui->radioButton_8);
    group3->addButton(ui->radioButton_9);
}

CheckBox

QCheckBox 表示复选按钮,可以允许选中多个,并且也有 checkable 和 checked 属性。

我们也可以写一个简单的选课程序。

当我们选择课程后,就会判断这个选项是否被选择,如果被选择就添加到字符串中,最后再更新label的text。


显示类控件

Label

QLabel 可以用来显示文本和图片的。

属性 说明
text QLabel 中的文本。
textFormat 文本的格式。 * Qt::PlainText,纯文本 * Qt::RichText,富文本(支持 html 标签) * Qt::MarkdownText,markdown 格式 * Qt::AutoText,根据文本内容自动决定文本格式
pixmap 内部包含的图片。
scaledContents 设为 true 表示内容自动拉伸填充。 设为 false 表示不会自动拉伸。
alignment 对齐方式,可以设置水平和垂直方向如何对齐。
wordWrap 设为 true 内部的文本会自动换行。 设为 false 则内部文本不会自动换行。
indent 设置文本缩进,水平和垂直方向都生效。
margin 内部文本和边框之间的边距。
openExternalLinks 是否允许打开⼀个外部的链接。
buddy 给 QLabel 关联⼀个"伙伴" 了,这样点击 QLabel 时就能激活对应的伙伴

textFormat

我们先来看textFormat属性,我们通过设置label的这个属性以及对应的文本看看他们有什么不同。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 把第一个 label 设置成纯文本
    ui->label->setTextFormat(Qt::PlainText);
    ui->label->setText("# <b>这是一段纯文本</b>");

    // 把第二个 label 设置成富文本
    ui->label_2->setTextFormat(Qt::RichText);
    ui->label_2->setText("<b>这是一段富文本</b>");

    // 把第三个 label 设置成 markdown文本
    ui->label_3->setTextFormat(Qt::MarkdownText);
    ui->label_3->setText("# 这是一段markdown文本");
}

纯文本就是单纯的文本,不会有什么变化,富文本支持html标签,所以<b>就是给这个文本加粗,而markdown中 # 就代表一级标题,运行后我们看看结果。

pixmap

再来看pixmap属性,可以通过这个属性设置label中包含的图片,我们可以把label设置成与窗口一样的大小和一样的位置。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 先把 QLabel宽高设置成和窗口一样的大小,位置也和窗口一样
    QRect windowRect = this->geometry();
    ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());

    QPixmap pixmap(":/image/search.png");
    ui->label->setPixmap(pixmap);
}

scaledContents

那我们想要把这张图片铺满就要使用scaledContents属性,让图片自动拉伸,铺满整个窗口。

cpp 复制代码
// 图片自动拉伸,让图片铺满整个屏幕
ui->label->setScaledContents(true);

但是我们如果调整整个窗口的大小后,图片并没有跟着窗口的变化而变化。

这是因为代码在构造函数中的设置是一次性的,在程序运行前QLabel的尺寸就固定了,即使窗口改变也不会影响Label。

这里先简单提一下,后面会详细说明的,我们已经知道了我们可以通过点击按钮触发信号 ,这就是用户操作的其中一个概念,还有一个概念就是事件 ,比如鼠标滑动,拖拽修改窗口大小的时候就会触发resize事件(resizeEvent),我们的Widget窗口类要重写父类(QWidget)的resizeEvent虚函数。

鼠标拖拽窗口就会连续触发这个resizeEvent事件,连续触发这个事件就会反复调用这个函数,所以我们可以看到输出的QSize会连续改变。

我们就可以根据这个数值改变label的大小。

alignment

再来看下一个alignment属性。

我们在右下角的属性栏中可以找到这个QFrame,其中的frameShape默认是NoFrame的,我们把这个属性改成Box就可以看到界面中的label有了边框。

接下来我们可以设置label中的文字的对齐方式,就比如垂直方向对齐(vertical),水平方向对齐(horizontal)。

这也是枚举类型,我们可以看到定义中还有其他的对齐方式。

wordWrap

如果label的文本是一段特别长的文字,如果不换行就看不到后面的内容,这个属性就可以让文本自动换行。

indent

我们可以设置文本的缩进,单位是像素,但是要注意的是,这个缩进不是首行缩进,而是所有行都会缩进

margin

这个属性是来设置边距的,我们也可以用一段很长的文本来演示。

上面设置了缩进是20像素,这里设置的边距是40,边距是对于上下左右都要缩进的,所以剩下的空间不足以把所有文字都显示出来。

buddy

这个属性可以绑定伙伴关系。

这里的&A和&B是QLabel中可以指定的快捷键,这个快捷键要比QPushButton弱很多,而且只能通过Alt + a/b来触发快捷键,只要绑定了伙伴关系就可以通过快捷键选中对应的按钮。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置 label和 radioButton的伙伴关系
    ui->label->setBuddy(ui->radioButton);
    ui->label_2->setBuddy(ui->radioButton_2);
}

LCD Number

QLCDNumber是一个专门显示数字的控件,类似于老式计算器。

属性 说明
intValue 显示的数字值(int)
value 显示的数字值(double),设置这两个值的时候使用的是display()。
digitCount 显示几位数字
mode 数字显示形式: * QLCDNumber::Dec,十进制模式 * QLCDNumber::Hex,十六进制模式 * QLCDNumber::Bin,二进制模式 * QLCDNumber::Oct,十进制模式
segmentStyle 设置显示风格: * QLCDNumber::Flat,平面的显示风格 * QLCDNumber::Outline,轮廓显示风格 * QLCDNumber::Filled,填充显示风格
smallDecimalPoint 设置较小的小数点

有了这个控件也可以简单实现一个定时器。Qt中也封装了对应的定时器,结合了信号和槽的机制,通过QTimer这个类创建出来的对象,就会产生一个timeout信号,可以通过start方法开启定时器,并且参数中设定触发timeout信号的周期。

cpp 复制代码
class Widget : public QWidget
{
// ...
public:
    void handle();

private:
    Ui::Widget *ui;
    QTimer* timer;
};

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

    // 设置初始值
    ui->lcdNumber->display(10);

    // 创建一个QTimer实例
    timer = new QTimer(this);

    // 把QTimer的timeout信号和槽函数进行连接
    connect(timer, &QTimer::timeout, this, &Widget::handle);

    // 启动定时器,设置周期,单位是ms
    timer->start(1000);
}

void Widget::handle()
{
    int value = ui->lcdNumber->intValue();
    if (value <= 0)
    {
        timer->stop();
        return;
    }
    ui->lcdNumber->display(value - 1);
}

通过前面的学习我们也应该知道,我们不能在构造函数中执行类似的上述操作,必须要通过槽函数,如果要在构造函数中写一个定时器,假如设定10s,那么程序就会在10s之后显示出来,这是因为构造函数调用完后才会执行main函数中的Widget的show方法 ,之后才会显示界面,所以无论之前的构造函数怎么修改,都不会看到我们想要的功能。

那结合我们之前所学,可不可以创建出来一个线程,让这个线程执行更改界面的操作了,主线程不会受到影响,继续向下执行,看似可以,但是对于GUI来说,内部包含了很多的隐藏状态,但还要保证线程安全,那么就要加锁,所以Qt中禁止了其他线程直接修改界面 ,所有的对界面的修改操作必须要在主线程中完成。

Qt中的槽函数都是主线程调用的,所以使用槽函数是没有问题的,main函数最后return a.exec(),这就会使主线程进入**"事件循环"**,每循环一次就会有固定的事情要做,比如执行槽函数。


ProgressBar

QProgressBar就表示一个进度条。

属性 说明
minimum 进度条最小值
maximum 进度条最大值
value 进度条当前值
alignment 文本的对齐方式: * Qt::AlignLeft,左对齐 * Qt::AlignRight,右对齐 * Qt::AlignCenter,居中对齐 * Qt::AlignJustify,两端对齐
textVisible 进度条数字可见性
orientation 进度条的方向,分为水平和垂直
invertAppearance 是否朝反方向增长
textDirection 文本的朝向
format 显示的数字格式: * %p:百分百格式 * %v:数值的格式 * %m:剩余时间,以毫秒为单位 * %t:总时间,以毫秒为单位

我们可以给上面的定时器增加一个进度条。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置初始值
    ui->lcdNumber->display("10");

    // 创建一个QTimer实例
    timer = new QTimer(this);

    // 把QTimer的timeout信号和槽函数进行连接
    connect(timer, &QTimer::timeout, this, &Widget::handle);

    // 启动定时器,设置周期,单位是ms
    timer->start(1000);

    // 设置进度条的初值
    ui->progressBar->setValue(0);
}

void Widget::handle()
{
    int value = ui->lcdNumber->intValue();
    if (value <= 0)
    {
        timer->stop();
        return;
    }
    ui->lcdNumber->display(value - 1);
    ui->progressBar->setValue(ui->progressBar->value() + 10);
}


我们还可以给这个进度条换一个颜色,我们可以在右下角的属性栏中找到styleSheet这一栏,我们可以更改一下颜色。

这前面的QProgressBar::chunk 叫做选择器,目的就是确认后面设置的样式是针对那个控件生效,chunk就是进度条这块,后面大括号中就是添加的样式,运行后就可以看到颜色改变了。

我们一般的进度条可不是随便用的,一般都是根据实际任务来灵活设置,如果要读取一个文件,我们可以先获取文件的总大小,每读取一部分数据就可以更新一次进度条的数值,而且设置进度条的过程中往往要搭配定时器使用。


Calendar Widget

这个控件就表示一个日历,其中的核心属性:

属性 说明
selectDate 当前选中的日期
minimumDate 最小日期
maximumDate 最大日期
firstDayOfWeek 每周的第一天,日历的第一页,是周几
gridVisible 是否显示表格的边框
selectionMode 是否允许选择日期
navigationBarVisible 日历上方标题是否显示
horizontalHeaderFormat 日历上方标题显示的日期格式
verticalHeaderFormat 日历第⼀列显示的内容格式
dateEditEnabled 是否允许日期被编辑

在这个控件中也有一些重要的信号:

信号 说明
selectionChanged(const QDate&) 当选中的日期发生改变的时候发出。
activated(const QDate&) 当双击⼀个有效的日期或者按下回车键时发出,形参是⼀个QDate类型,保存了选中的日期。
currentPageChanged(int, int) 当年份月份改变时发出,形参表示改变后的新年份和月份。

通过改变选中的日期就可以触发信号,我们可以通过槽函数获取到当前选中的日期。

我们还可以给界面中添加一个label控件,当我们选择哪个日期就更改label中的文本。


输入类控件

Line Edit

QLineEdit 是用来表示单行输入框的,可以输入文本,但是不能换行。

属性 说明
text 输⼊框中的文本
inputMask 输入内容格式约束
maxLength 最大长度
frame 是否添加边框
echoMode 显示方式: * QLineEdit::Normal,默认值,文本框会显示输入的文本。 * QLineEdit::Password,输入的字符会被隐藏。 * QLineEdit::NoEcho,文本框不会显示任何输入的字符。
cursorPosition 光标所在位置
alignment 文字对齐方式, 设置水平和垂直的对齐
dragEnabled 是否允许拖拽
readOnly 是否是只读的
placeHolderText 当输入框内容为空的时候显示什么样的提示信息
clearButtonEnabled 是否会显示出"清除按钮"

这个控件也有一些信号:

属性 说明
void cursorPositionChanged(int old, int new) 当鼠标移动时发出此信号,old为原来的位置,new为新位置。
void editingFinished() 当按返回或者回车键时,或者行编辑失去焦点时,发出此信号。
void returnPressed() 当返回或回车键按下时发出此信号。如果设置了验证器,必须要验证通过才能触发。
void selectionChanged() 当选中的文本改变时,发出此信号。
void textChanged(const QString &text) 当QLineEdit中的文本改变时,发出此信号,text是新的文本。代码对文本的修改能够触发这个信号。
void textEdited(const QString &text)) 当QLineEdit中的文本改变时,发出此信号,text是新的文本。代码对文本的修改不能触发这个信号。

现在我们就可以实现一个注册界面。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 初始化第一个输入框:用户名
    ui->lineEdit_username->setPlaceholderText("请输入用户名"); // 添加空信息的提示文本
    ui->lineEdit_username->setClearButtonEnabled(true); // 添加清楚按钮

    // 初始化第二个输入框:密码
    ui->lineEdit_password->setPlaceholderText("请输入密码"); // 添加空信息的提示文本
    ui->lineEdit_password->setClearButtonEnabled(true); // 添加清楚按钮
    ui->lineEdit_password->setEchoMode(QLineEdit::Password); // 设置为密码模式

    // 初始化第三个输入框:手机号
    ui->lineEdit_phonenum->setClearButtonEnabled(true); // 添加清楚按钮
    ui->lineEdit_phonenum->setInputMask("000-0000-0000"); // 设置输入的格式

}

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

void Widget::on_pushButton_submit_clicked()
{
    QString gender = ui->radioButton_male->isChecked() ? "男" : "女";

    qDebug() << "用户名: " << ui->lineEdit_username->text() << endl
             << "密码: " << ui->lineEdit_password->text() << endl
             << "性别: " << gender << endl
             << "手机号: " << ui->lineEdit_phonenum->text() << endl;
}


上述代码设置了inputMask属性,也就是手机号,但是这个属性的功能并不强大,我们还是使用正则表达式来完成验证的工作。

我们把PushButton的enabled属性设置为false,如果LineEdit的文本验证不通过,那么提交按钮也就不能使用。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 给单行输入框设置验证器
    QRegExp regexp("^1\\d{10}$"); // 正则表达式意义:^表示起始,1为开头;\d{10}表示重复10个数字(\\转义),$表示结尾
    // 验证器
    ui->lineEdit->setValidator(new QRegExpValidator(regexp));
}

既然我们想要验证输入框的内容是否合法,那输入框的内容发生改变,验证器就要被执行。信号中有textChanged和textEdited都可以触发。

只要LineEdit中的文本发生变化,就触发信号,转到槽函数中就要判断LineEdit验证器是否通过,如果可以就恢复按钮的可用性。

上面我们已经设置了lineEdit的验证器,直接使用就可以了。

cpp 复制代码
void Widget::on_lineEdit_textChanged(const QString &text)
{
    QString content = text;
    int pos = 0;
    if (ui->lineEdit->validator()->validate(content, pos) == QValidator::Acceptable)
        ui->pushButton->setEnabled(true); // 验证通过
    else
        ui->pushButton->setEnabled(false); // 验证不通过
}

判断验证器是否通过用到的就是其中的validate(QString&, int&)。

这个函数的返回值为QValidator::State ,参数为QString& 和 int& ,槽函数的参数为const QString&类型,所以不能直接传参,而pos这个参数类似于一个输出型参数,如果不符合规则,会拿到从哪个位置开始不符合的。

这第一个参数不是const是因为Validator也是可以自定义的,并重写validate函数,上图中可以看到QValidator::validate函数是一个纯虚函数,我们使用的是QRegExpValidator中的函数。如果想要验证后更改一下字符串,那就不能是const。

cpp 复制代码
virtual QValidator::State validate(QString& input, int& pos) const override;

enum State {
    Invalid, // 验证不通过
    Intermediate,
    Acceptable // 验证通过
};

所以判断返回值是否等于QValidator::Acceptable


还有一种场景就是输入两次密码,判断两次密码是否一致。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置输入框的echoMode
    ui->lineEdit->setEchoMode(QLineEdit::Password);
    ui->lineEdit_2->setEchoMode(QLineEdit::Password);
}

void Widget::checkPassWord()
{
    const QString& s1 = ui->lineEdit->text();
    const QString& s2 = ui->lineEdit_2->text();
    if (s1.isEmpty() && s2.isEmpty())
        ui->label_3->setText("密码为空");
    else if (s1 == s2)
        ui->label_3->setText("密码一致");
    else
        ui->label_3->setText("两次输入的密码不一致");
}

void Widget::on_lineEdit_textEdited(const QString &arg1)
{
    checkPassWord();
}

void Widget::on_lineEdit_2_textEdited(const QString &arg1)
{
    checkPassWord();
}

最后我们看到槽函数中的参数没有使用,所以编译器有一个警告,如何解决参考Linux中解决方式,使用**(void)arg1**,既使用了变量,有没有什么影响,还没有警告了。
当我们输入密码的时候,旁边都会有一个按钮让我们选择是否显示密码,现在我们也可以实现一下。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置lineEdit的echoMode
    ui->lineEdit->setEchoMode(QLineEdit::Password);

}

void Widget::on_checkBox_toggled(bool checked) // 这里我们使用toggled信号,因为这个信号当checkbox点击的时候是true,取消的时候是false
{
    if (checked == true)
        ui->lineEdit->setEchoMode(QLineEdit::Normal);
    else
        ui->lineEdit->setEchoMode(QLineEdit::Password);
}

Text Edit

QTextEidt 表示多行输入框,也是一个富文本 & markdown 编辑器,并且能在内容超出编辑范围时自动提供滚动条。

还有一个就是QPlainTextEdit 这个控件只能支持纯文本。

属性 说明
markdown 输入框内持有的内容。支持 markdown 格式。能够自动的对 markdown 文本进行渲染成 html 。
html 输入框内的内容可以支持大部分 html 标签。
placeHolderText 输入框为空时提示的内容
readOnly 是否只读
undoRedoEnable 是否开启undo / redo 功能 * 按下 ctrl + z 触发 undo * 按下 ctrl + y 触发 redo
autoFormating 开启自动格式化
tabstopWidth 按下缩进占多少空间
overwriteMode 是否开启覆盖写模式
acceptRichText 是否接收富文本内容
verticalScrollBarPolicy 垂直方向滚动条的出现策略 * Qt::ScrollBarAsNeeded:根据内容自动决定是否需要滚动条。这是默认值。 * Qt::ScrollBarAlwaysOff:总是关闭滚动条。 * Qt::ScrollBarAlwaysOn:总是显示滚动条。
horizontalScrollBarPolicy 水平方向滚动条的出现策略 * Qt::ScrollBarAsNeeded:根据内容自动决定是否需要滚动条。这是默认值。 * Qt::ScrollBarAlwaysOff:总是关闭滚动条。 * Qt::ScrollBarAlwaysOn:总是显示滚动条。
信号 说明
textChanged() 文本内容改变时触发
selectionChanged() 选中范围改变时触发
cursorPositionChanged() 光标移动时触发
undoAvailable(bool) 可以进行 undo 操作时触发
redoAvailable(bool) 可以进行 redo 操作时触发
copyAvaiable(bool) 文本被选中或取消选中时触发

首先我们先来看一下textChanged信号,只有textEdit中的文本发生改变,就会触发这个信号,我们可以简单实现一个同步程序。

cpp 复制代码
void Widget::on_textEdit_textChanged()
{
    const QString str = ui->textEdit->toPlainText();
    ui->label->setText(str);
}


我们再来看看其他信号是怎样触发的。

cpp 复制代码
void Widget::on_textEdit_textChanged() // 文本改变就会触发信号
{
    qDebug() << "textChanged: " << ui->textEdit->toPlainText();
}

void Widget::on_textEdit_selectionChanged() // 选中文本就会触发信号
{
    QTextCursor cursor = ui->textEdit->textCursor();
    qDebug() << "selectionChanged: " << cursor.selectedText();
}

void Widget::on_textEdit_cursorPositionChanged() // 光标改变就会触发信号
{
    QTextCursor cursor = ui->textEdit->textCursor();
    qDebug() << "cursorPositionChanged: " << cursor.position();
}

先依次输入"abcd",光标位置和文本都在改变,光标向左移动,位置就会减一,最后选中文本,选中信号就会触发,同时光标位置也改变了。

这些操作都是使用了QTextCursor中的方法。

cpp 复制代码
void Widget::on_textEdit_undoAvailable(bool b) // Ctrl+z(撤销)是否可以使用
{
    qDebug() << "undoAvailable: " << b;
}

void Widget::on_textEdit_redoAvailable(bool b) // Ctrl+y(反撤销)是否可以使用
{
    qDebug() << "redoAvailable: " << b;
}

void Widget::on_textEdit_copyAvailable(bool b) // 是否可以复制
{
    qDebug() << "copyAvailable: " << b;
}

还有三个信号,当我们输入"abcd"四个字母时,undo可以触发也就是可以使用Ctrl+z,此时槽函数的参数就是true,按下组合键后,TextEdit中没有了文本,所以undo就不可用了,参数也就变成了false,而redo正好相反。

当我们选中这四个字符后,copy就可以使用,参数变为true,取消选中参数变为false,这些也很好理解。


Combo Box

QComboBox 表示下拉框。

属性 说明
currentText 当前选中的文本
currentIndex 当前选中的条目下标,从0开始计算,如果当前没有条目被选中,值为 -1。
editable 是否允许修改。 设为true时,非常接近LineEdit,可以设置validator验证器。
iconSize 下拉框图标的大小
maxCount 最多允许有多少个条目

核心方法。

方法 说明
addItem(const QString&) 添加一个条目
currentIndex() 获取当前条目的下标,从0开始,如果没有条目被选中,值为 -1。
currentText() 获取当前条目的文本内容

核心信号。

信号 说明
activated(int) activated(const QString& text) 当用户选择一个选项时发出。 这个时候相当于用户点开下拉框,并且鼠标划过某个选项。此时还没有确认做出选择。
currentIndexChanged(int) currentIndexChanged(const QString& text) 当前选项改变时发出。 此时用户已经明确的选择了⼀个选项。 用户操作或者通过代码操作都会触发这个信号。
editTextChanged(const QString& text) 当编辑框中的文本改变时发出 (editable 为 true 时有效)

我们可以继续模拟选课。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    ui->comboBox->addItem("C/C++");
    ui->comboBox->addItem("数据结构");
    ui->comboBox->addItem("操作系统");
    ui->comboBox->addItem("Java");

    ui->comboBox_2->addItems({"Python", "JavaWeb", "大数据"});

    ui->comboBox_3->addItems({"足球", "篮球", "乒乓球"});
}

void Widget::on_pushButton_clicked()
{
    qDebug() << "选择的课程: " << ui->comboBox->currentText() << "," 
             << ui->comboBox_2->currentText() << "," 
             << ui->comboBox_3->currentText();
}

我们还可以直接在图形化界面中添加。

但是通常这些选项都不是固定的,就比如每年的选课都会有些变化,所以不应该让这些选项被写死,我们可以从网络中读取,或者从文件中读取。

我们在桌面创建一个文件,文件中写入课程,在构造函数中打开这个文件,按行读取文件中的内容,并依次设置到ComboBox中。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 读取文件,将文件中的每一行内容作为ComboBox的选项
    std::ifstream file("C:/Users/14755/Desktop/config.txt");
    if (!file.is_open())
    {
        qDebug() << "文件打开失败";
        return;
    }
    // 按行读取
    std::string line;
    while (std::getline(file, line))
    {
        // 取到的内容设置为选项
        ui->comboBox->addItem(QString::fromStdString(line)); // 把string转换成QString
    }
    file.close();
}

SpinBox

QSpinBox 和 QDoubleSpinBox 表示微调,他是带有按钮的输入框,通过按钮修改输入的数值。

属性 说明
value 存储的数值
singleStep 每次调整的"步长"。按下⼀次按钮数据变化多少。
displayInteger 数字的进制
maximum 最大值
minimum 最小值
suffix 后缀
prefix 前缀
wrapping 是否允许换行
frame 是否带边框
alignment 文字对齐方式
readOnly 是否允许修改
buttonSymbol 按钮上的图标形式: * UpDownArrows,上下箭头形式 * PlusMinus,加减号形式 * NoButtons,没有按钮
accelerated(加速) 按下按钮时是否为快速调整模式
correctionMode 输入有误时如何修正: * QAbstractSpinBox::CorrectToPreviousValue:如果数值的最小值是0,用户输入的值从 1 变成了 -1,SpinBox会恢复为上⼀个有效值,也就是 1。 * QAbstractSpinBox::CorrectToNearestValue:例子同上,此时数值变成 0。
keyboardTrack 是否开启键盘跟踪。 * 设为 true, 每次在输⼊框输入⼀个数字,都会触发⼀次 valueChanged() 和 textChanged() 信号. * 设为 false, 只有在最终按下 enter 或者输⼊框失去焦点, 才会触发valueChanged() 和 textChanged()。

核心信号。

信号 说明
textChanged(QString) 微调框的文本发生改变时会触发。参数 QString 带有前缀和后缀。
valueChanged(int) 微调框的文本发生改变时会触发。参数 int,表示当前的数值。

实现一个订书程序,点击确认下单按钮后,获取每本书的数量。

cpp 复制代码
void Widget::on_pushButton_clicked()
{
    qDebug() << "当前下单的书记有: " << endl
             << ui->label->text() << ":" << ui->spinBox->value() << "本" << endl
             << ui->label_2->text() << ":" << ui->spinBox_2->value() << "本" << endl
             << ui->label_3->text() << ":" << ui->spinBox_3->value() << "本" << endl;
}

我们还可以给给每种书加一个限制数量和初始值,数值只能在范围之内。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置范围
    ui->spinBox->setRange(1, 50);
    ui->spinBox_2->setRange(1, 50);
    ui->spinBox_3->setRange(1, 50);

    // 设置初值
    ui->spinBox->setValue(1);
    ui->spinBox_2->setValue(1);
    ui->spinBox_3->setValue(1);
}

Date Edit & Time Edit

QDateEdit 和 QTimeEdit 分别作为日期和时间的微调框,还有一个是QDateTimeEdit日期时间微调框,我们主要说这个控件,其他几个的用法也是非常相似。

属性 说明
dateTime 时间日期的值
date 单日期的值
time 单日期的值
displayFormat 时间日期格式,例如 yyyy/M/d H:mm * y 表示年份 * M 表示月份 * d 表示日期 * H 表示小时 * m 表示分钟 * s 表示秒
minimumDateTime 最小时间日期
maximumDateTime 最大时间日期
timeSpec * Qt::LocalTime :显示本地时间。 * Qt::UTC :显示协调世界时(UTC)。 * Qt::OffsetFromUTC :显示相对于UTC的偏移量(时差)

核心信号。

信号 说明
dateChanged(QDate) 日期改变时触发
timeChanged(QTime) 时间改变时触发
dateTimeChanged(QDateTime) 时间日期任意⼀个改变时触发

通过这个控件我们就可以实现一个时间计算器,计算两个日期和时间的差值。

cpp 复制代码
void Widget::on_pushButton_clicked()
{
    // 获取两个QDateTimeEdit中的日期和时间
    QDateTime datetimeOld = ui->dateTimeEdit->dateTime();
    QDateTime datetimeNew = ui->dateTimeEdit_2->dateTime();
    qDebug() << datetimeOld << endl << datetimeNew;

    // 计算日期的差值
    // Qt官方文档中说过这个daysTo函数会把不足一天算成两天,就是过24点的时候,所以我们还是使用秒去算天
    // int days = datetimeOld.daysTo(datetimeNew); 
    int secs = datetimeOld.secsTo(datetimeNew);

    // 把秒换算成天,整数除法会舍去不足的一天
    int days = (secs / 3600) / 24;
    // 把计算后的秒换成小时,计算天数后还有多少小时
    int hours = (secs / 3600) % 24;

    QString s = ui->label_3->text();
    s += QString::number(days) + QString("天零") +QString::number(hours) + QString("小时");
    ui->label_3->setText(s);
}

Dial

QDial 表示一个旋钮。下面是他的核心属性。

属性 说明
value 持有的数值
maximum 最大值
minimum 最小值
singleStep 按下方向键的时候改变的步长
pageStep 按下 pageUp / pageDown 的时候改变的步长
sliderPosition 界面上旋钮显示的初始位置
tracking 外观是否会跟踪数值变化,默认值为 true,⼀般不需要修改。
wrapping 是否允许循环调整,即数值如果超过最大值,是否允许回到最小值。
notchesVisible 是否显示刻度线
notchTarget 刻度线之间的相对位置。数字越大,刻度线越稀疏。

核心信号。

属性 说明
valueChanged(int) 数值改变时触发
rangeChanged(int, int) 范围变化时触发

通过旋钮就可以调整一些属性,比如我们前面说过的不透明度windowOpacity,wrapping就表示旋钮可否循环,notchesVisible表示是否显示刻度,我们可以使用valueChanged信号实现。

cpp 复制代码
void Widget::on_dial_valueChanged(int value)
{
    qDebug() << value;
    this->setWindowOpacity((double)value / 100);
}

Slider

QSlider 表示一个滑动条,它和 QDial 这个控件都是继承自QAbstractSlider,所以用法基本相同,属性我们挑几个不一样的。

属性 说明
orientation 滑动条的方向是水平还是垂直。
invertedAppearance 是否要翻转滑动条的方向。
tickPosition 刻度的位置。
tickInterval 刻度的密集程度。

信号也和QDial的信号是一样的。

我们可以通过这个滑动条调整一些窗口大小。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    ui->horizontalSlider->setMinimum(100);
    ui->horizontalSlider->setMaximum(2000);
    ui->horizontalSlider->setValue(800);
    ui->horizontalSlider->setSingleStep(50);

    ui->verticalSlider->setMinimum(100);
    ui->verticalSlider->setMaximum(1500);
    ui->verticalSlider->setValue(600);
    ui->verticalSlider->setSingleStep(50);
}

void Widget::on_horizontalSlider_valueChanged(int value)
{
    const QRect& rect = this->geometry();
    this->setGeometry(rect.x(), rect.y(), value, rect.height());
}

void Widget::on_verticalSlider_valueChanged(int value)
{
    const QRect& rect = this->geometry();
    this->setGeometry(rect.x(), rect.y(), rect.width(), value);
}

不鼠标拖拽的方式可以,我们还可以设置快捷键来控制滑动条,从而控制它来执行其他操作。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 快捷键的设置需要用到 QShortCut这个类
    QShortcut* shortcut1 = new QShortcut(this);
    shortcut1->setKey(QKeySequence("=")); // 键盘上的+

    QShortcut* shortcut2 = new QShortcut(this);
    shortcut2->setKey(QKeySequence("-")); // 键盘上的-

    // 使用信号和槽
    connect(shortcut1, &QShortcut::activated, this, &Widget::addValue);
    connect(shortcut2, &QShortcut::activated, this, &Widget::subValue);

}

void Widget::on_horizontalSlider_valueChanged(int value)
{

    ui->label->setText(QString("当前值为: ") + QString::number(value));
}

void Widget::addValue()
{
    // 获取 Slider的值
    int value = ui->horizontalSlider->value();
    if (value < ui->horizontalSlider->minimum())
        return;
    ui->horizontalSlider->setValue(value + 10);
}

void Widget::subValue()
{
    // 获取 Slider的值
    int value = ui->horizontalSlider->value();
    if (value > ui->horizontalSlider->maximum())
        return;
    ui->horizontalSlider->setValue(value - 10);
}

多元素控件

Qt 中提供的多元素控件有这几个:

  • QListWidget
  • QListView
  • QTableWidget
  • QTableView
  • QTreeWidget
  • QTreeView

这些控件都是两两一组,分为List(列表)、Table(表格)和Tree(树形) ,分别以Widget和View为后缀,View是更底层的实现,Widget是基于View封装而来的。

View是MVC结构 的一种典型实现,M(Mode)为数据,V(View)为界面或视图,C(Controller)为控制器,控制数据和视图之间的业务流程

View只是负责实现视图。不负责数据存储,也不负责数据和视图之间的交互,要使用View就要自己实现Mode和Controller,所以Widget就实现了这两个功能,可以直接使用,所以我们下面就直接介绍Widget了。


List Widget

QListWidget 就是一个纵向的列表,核心属性为:

属性 说明
currentRow 当前被选中的是第几行
count ⼀共有多少行
sortingEnabled 是否允许排序
isWrapping 是否允许换行
itemAlignment 元素的对齐方式
selectRectVisible 被选中的元素矩形是否可见
spacing 元素之间的间隔

核心方法:

方法 说明
addItem(const QString& label) addItem(QListWidgetItem* item) 向列表中添加元素。
currentItem() 返回 QListWidgetItem* 表示当前选中的元素。
setCurrentItem(QListWidgetItem* item) 设置选中哪个元素。
setCurrentRow(int row) 设置选中第几行的元素。
insertItem(const QString& label, int row) insertItem(QListWidgetItem* item, int row) 在指定的位置插⼊元素。
item(int row) 返回 QListWidgetItem* 表示第 row 行的元素。
takeItem(int row) 删除指定行的元素, 返回 QListWidgetItem* 表示是哪个元素被删除了。

】】 核心信号:
核心信号:

信号 说明
currentItemChanged(QListWidgetItem* current, QListWidgetItem* old) 选中不同元素时会触发。参数是当前选中的元素和之前选中的元素。
currentRowChanged(int) 选中不同元素时会触发,参数是当前选中元素的行数。
itemClicked(QListWidgetItem* item) 点击某个元素时触发。
itemDoubleClicked(QListWidgetItem* item) 双击某个元素时触发。
itemEntered(QListWidgetItem* item) 鼠标进入元素时触发。

依旧是选课的例子,可以向列表中增加或删除课程。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 先添加一些元素,通过代码的方式 添加项目
    ui->listWidget->addItem("C++");
    ui->listWidget->addItem("数据结构");
    ui->listWidget->addItem("操作系统");
    ui->listWidget->addItem("Java");
}

void Widget::on_pushButton_insert_clicked()
{
    // 先获取LineEdit的内容
    const QString& text = ui->lineEdit->text();
    // 添加到QListWidget中
    if (text != "")
        ui->listWidget->addItem(text);
}

void Widget::on_pushButton_erase_clicked()
{
    // 获取被选中的ListWidget中的项目
    int row = ui->listWidget->currentRow();
    if (row < 0)
        return;
    // 按行号删除元素
    ui->listWidget->takeItem(row);
}

Table Widget

QTableWidget 表示一个表格控件,一个表格包含若干行,每一行又包含若干列,每一个单元格都是 QTableWidgetItem 对象,下面就是它的核心方法。

方法 说明
item(int row, int column) 根据行数列数获取指定的 QTableWidgetItem*
setItem(int row, int column, QTableWidget*) 根据行数列数设置表格中的元素
currentItem() 返回被选中的元素 QTableWidgetItem*
currentRow() 返回被选中元素是第几行
currentColumn() 返回被选中元素是第几列
row(QTableWidgetItem* ) 获取指定 item 是第几行
column(QTableWidgetItem* ) 获取指定 item 是第几列
rowCount() 获取行数
columnCount() 获取列数
insertRow(int row) 在第 row 行处插入新行
insertColumn(int column) 在第 column 列插入新列
removeRow(int row) 删除第 row 行
removeColumn(int column) 删除第 column 列
setHorizontalHeaderItem(int column, QTableWidget*) 设置指定列的表头
setVerticalHeaderItem(int row, QTableWidget*) 设置指定行的表头

核心信号:

信号 说明
cellClicked(int row, int column) 点击单元格时触发
cellDoubleClicked(int row, int column) 双击单元格时触发
cellEntered(int row, int column) 鼠标进入单元格时触发
currentCellChanged(int row, int column, int previousRow, int previousColumn) 选中不同单元格时触发

我们可以直接右击table Widget,选择第一个选项来使用图形化界面的方式编辑表格。

还是使用代码的方式来实现这些功能。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 创建 n 行
    ui->tableWidget->insertRow(0);
    ui->tableWidget->insertRow(1);
    ui->tableWidget->insertRow(2);

    // 创建 n 列
    ui->tableWidget->insertColumn(0);
    ui->tableWidget->insertColumn(1);
    ui->tableWidget->insertColumn(2);

    // 为每一列添加表头
    ui->tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem("学号"));
    ui->tableWidget->setHorizontalHeaderItem(1, new QTableWidgetItem("姓名"));
    ui->tableWidget->setHorizontalHeaderItem(2, new QTableWidgetItem("成绩"));

    // 给表格中添加数据
    ui->tableWidget->setItem(0, 0, new QTableWidgetItem("101"));
    ui->tableWidget->setItem(0, 1, new QTableWidgetItem("张三"));
    ui->tableWidget->setItem(0, 2, new QTableWidgetItem("90"));
}

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

void Widget::on_pushButton_eraseRow_clicked()
{
    // 先获取选中的行
    int curRow = ui->tableWidget->currentRow();
    // 删除这一行
    ui->tableWidget->removeRow(curRow);
}

void Widget::on_pushButton_insertCol_clicked()
{
    // 先获取textEdit中的新增列名
    const QString& colName = ui->lineEdit->text();
    if (colName == "")
        return;
    // 获取一共有多少列
    int colCount = ui->tableWidget->columnCount();
    // 在最后一列后添加一列
    ui->tableWidget->insertColumn(colCount);
    // 给列加上列明
    ui->tableWidget->setHorizontalHeaderItem(colCount, new QTableWidgetItem(colName));
    // 添加后清空lineEdit
    ui->lineEdit->setText("");

}

void Widget::on_pushButton_eraseCol_clicked()
{
    // 先获取选中的列
    int curCol = ui->tableWidget->currentColumn();
    // 删除这一行
    ui->tableWidget->removeColumn(curCol);
}

Tree Widget

QTreeWidget 表示一个属树形结构,里面的每一个控件都是 QTreeWidgetItem 对象,每个对象可以包含多个文本和图标,每个文本/图标为一个列。

可以给 QTreeWidget 设置顶层节点,可以有多个顶层节点,再给顶层节点添加子节点,这就是树形结构,下面是它的核心方法。

方法 说明
clear 清空所有子节点
addTopLevelItem(QTreeWidgetItem* item) 新增顶层节点
topLevelItem(int index) 获取指定下标的顶层节点
topLevelItemCount() 获取顶层节点个数
indexOfTopLevelItem(QTreeWidgetItem* item) 查询指定节点是顶层节点中的下标
takeTopLevelItem(int index) 删除指定的顶层节点,返回 QTreeWidgetItem* 表示被删除的元素
currentItem() 获取到当前选中的节点,返回 QTreeWidgetItem*
setCurrentItem(QTreeWidgetItem* item) 选中指定节点
setExpanded(bool) 展开/关闭节点
setHeaderLabel(const QString& text) 设置 TreeWidget 的 header 名称

核心信号:

信号 说明
currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* old) 切换选中元素时触发
itemClicked(QTreeWidgetItem* item, int col) 点击元素时触发
itemDoubleClicked(QTreeWidgetItem* item, int col) 双击元素时触发
itemEntered(QTreeWidgetItem* item, int col) 鼠标进入时触发
itemExpanded(QTreeWidgetItem* item) 元素被展开时触发
itemCollapsend(QTreeWidgetItem* item) 元素被折叠时触发

还要介绍一下QTreeWidgetItem,这个也是很重要的,这是它的属性:

属性 说明
text 持有的文本
textAlignment 文本对齐方式
icon 持有的图表
font 文本字体
hidden 是否隐藏
disabled 是否禁用
expand 是否展开
sizeHint 尺寸大小
selected 是否选中

这是QTreeWidgetItem的方法。

方法 说明
addChild(QTreeWidgetItem* child) 新增子节点
childCount() 子节点的个数
child(int index) 获取指定下标的子节点,返回 QTreeWidgetItem*
takeChild(int index) 删除对应下标的子节点
removeChild(QTreeWidgetItem* child) 删除对应的子节点
parent() 获取该元素的父节点

我们可以模拟实现一个磁盘目录,我们可以手动添加文件夹,也可以删除。一系列的操作都是通过界面上的QLineEdit中的内容和选择的按钮实现。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置根节点的名字
    ui->treeWidget->setHeaderLabel("此电脑");
    // 新增顶层节点
    QTreeWidgetItem* item = new QTreeWidgetItem();
    // 每个节点都可以设置多个列,这里就设置一个列了
    item->setText(0, "C:");
    // 添加到顶层
    ui->treeWidget->addTopLevelItem(item);
    // 添加子节点
    QTreeWidgetItem* childItem = new QTreeWidgetItem();
    childItem->setText(0, "Program Files");
    item->addChild(childItem);
}

void Widget::on_pushButton_insertTopLevelItem_clicked()
{
    // 获取输入框中的内容
    const QString& nodeName = ui->lineEdit->text();
    if (nodeName == "")
        return;
    // 新增节点
    QTreeWidgetItem* item = new QTreeWidgetItem();
    // 设置节点名称
    item->setText(0, nodeName);
    // 添加到顶层
    ui->treeWidget->addTopLevelItem(item);

    // 清空lineEdit
    ui->lineEdit->setText("");
}

void Widget::on_pushButton_insertItem_clicked()
{
    // 获取输入框中的内容
    const QString& nodeName = ui->lineEdit->text();
    if (nodeName == "")
        return;
    // 获取当前选中的Item
    QTreeWidgetItem* curItem = ui->treeWidget->currentItem();
    // 创建子节点
    QTreeWidgetItem* childItem = new QTreeWidgetItem();
    childItem->setText(0, nodeName);
    // 添加子节点
    curItem->addChild(childItem);

    // 清空lineEdit
    ui->lineEdit->setText("");
}

void Widget::on_pushButton_eraseItem_clicked()
{
    // 获取选中的节点
    QTreeWidgetItem* curItem = ui->treeWidget->currentItem();
    if (curItem == nullptr) // 如果没有选中就返回
        return;
    // 获取其父元素,让其父元素删除
    QTreeWidgetItem* parItem = curItem->parent();
    if (parItem == nullptr) // 说明这个元素是顶层元素
    {
        // 获取该元素的下标
        int index = ui->treeWidget->indexOfTopLevelItem(curItem);
        ui->treeWidget->takeTopLevelItem(index);
    }
    else // 说明是普通元素
        parItem->removeChild(curItem);

}

容器类控件

Group Box

QGroupBox是一个分组框,可以把其他控件放到里面作为一组,所以内部的控件的父元素就不是this了,而是GroupBox,这个控件只是在界面中控件多的时候把那些具有关联关系的控件组织到一起,这样就会更清晰,更好看。下面就是它的属性。

属性 说明
title 分组框的标题。
alignment 分组框内部内容的对齐方式。
flat 是否是"扁平"模式。
checkable 是否可选择,设为true会在title前方多出一个可勾选框。
checked 描述分组框的选择状态,但前提是checkable为true。

他也没有信号和其他方法,所以这个控件就是为了美化界面的,我们还是修改一下选课的程序。

GroupBox中分别是选修课和选课的人数,通过右侧的对象框也可以看出,GroupBox就是作为父元素的。


Tab Widget

QTabWidget 是一个带有标签页的控件,可以往里面添加一些widget,可以通过标签页来切换,这是它的属性。

属性 说明
tabPosition 标签页所在的位置。 * North,上方 * South,下方 * West,左侧 * East,右侧
currentIndex 当前选中了第几个标签页,从0开始计算。
currentTabText 当前选中的标签页的文本。
currentTabName 当前选中的标签页的名字。
currentTabIcon 当前选中的标签页的图标。
currentTabToolTip 当前选中的标签页的提示信息。
tabsCloseable 标签页是否可以关闭。
movable 标签页是否可以移动。

核心信号:

属性 说明
currentChanged(int) 在标签页发生切换时触发,参数为被点击的选项卡编号
tabBarClicked(int) 在点击选项卡的标签条的时候触发,参数为被点击的选项卡编号.
tabBarDoubleClicked(int) 在双击选项卡的标签条的时候触发,参数为被点击的选项卡编号
tabCloseRequest(int) 在标签页关闭时触发,参数为被关闭的选项卡编号

这就像是一个浏览器的界面似的,可以增加标签页,也可以删除标签页,每一个标签页都是一个QWidget。

我们可以给tabWidget勾选上一些属性。

勾选上这个属性后就可以在标签页旁边出现一个"×",每次点击这个按钮就会触发**tabCloseRequest(int)**信号,触发后就可以关闭这个标签。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 向每一个标签页中添加一个label
    QLabel* label1 = new QLabel(ui->tab); // 这里指定的父元素就是各自的标签页
    label1->setText("标签页1");
    label1->resize(100, 50);

    QLabel* label2 = new QLabel(ui->tab_2);
    label2->setText("标签页2");
    label2->resize(100, 50);
}

void Widget::on_pushButton_clicked()
{
    // 使用addTab方法创建标签页
    // 参数1 要指定一个QWidget
    // 参数2 要指定标签页的 text(标题)
    QWidget* w = new QWidget();
    // 获取这是第一个标签页
    int count = ui->tabWidget->count();
    ui->tabWidget->addTab(w, "Tab " + QString::number(count + 1));
    // 显示一个QLabel
    QLabel* label = new QLabel(w);
    label->setText(QString("标签页") + QString::number(count + 1));
    label->resize(100, 50);

    // 也可以设置新标签页被选中
    ui->tabWidget->setCurrentIndex(count);

}

void Widget::on_tabWidget_tabCloseRequested(int index)
{
    ui->tabWidget->removeTab(index);
}

布局管理器

我们之前都是使用拖拽或者通过代码这种"手动"的方式来进行页面布局的,我们要知道的是这种手动的方式并不科学,因为它比较复杂,而且不够精确,无法对窗口大小进行自适,所以就引入了布局管理器这种概念。

Qt 中提供了很多布局管理器:

  • 垂直布局
  • 水平布局
  • 网格布局
  • 表单布局

垂直布局

这个布局使用的是QVBoxLayout 表示垂直的布局管理器,V表示Vertical,它也是有属性的。

属性 说明
layoutLeftMargin 左侧边距
layoutRightMargin 右侧边距
layoutTopMargin 上方边距
layoutBottomMargin 下方边距
layoutSpacing 相邻元素之间的边距

使用布局管理器处理控件都是通过"绝对定位"地方方式来设置的,每个控件的位置就是经过计算的,所以像前面说过的setGeometry和move就失效了。简单实现一个程序,创建按钮,把按钮添加到布局管理器上。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 创建按钮,使用垂直布局管理
    QPushButton* button1 = new QPushButton("按钮1"); // 这里没有指定父元素
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");

    // 创建布局管理器
    QVBoxLayout* layout = new QVBoxLayout();
    layout->addWidget(button1); // 这里就隐含了这些按钮的父元素是layout
    layout->addWidget(button2);
    layout->addWidget(button3);

    // 把布局管理器添加到窗口中
    this->setLayout(layout); // layout的父元素就是this,所以都在对象树上,释放也就不用处理了
}

运行后的结果可以看到按钮被创建了,而且改变窗口的大小的时候,按钮也会跟着改变。

看到这里一定觉得布局管理器很好用,可以帮助我们管理这个界面,但是这个布局管理器在一个Widget中只能设置一个,所以就需要通过别的方式。

我们也可以在图形化界面中设置。

这样我们就创建了两个垂直布局管理器,但是刚说过布局管理器只能创建一个,那我们再运行看看。

运行后就发现了问题,这个界面并没有随着窗口大小改变而改变,这是因为我们在代码中创建的Layout就是一个Layout,但是在图形化界面中操作中先创建了一个Widget,再创建一个Layout。

一个父Widget可以包含多个子Widget,那这些子Widget又可以包含多个Layout,在Layout中就可以创建控件了。

所以,如果想要界面中的控件随着窗口大小改变而改变,那就在代码中设置一个Layout,如果只是想让一些控件放到一起,就可以通过图形化界面的方式直接拖拽创建。

创建Layout和创建控件的顺序可以颠倒,可以选中想要放到布局管理器中的控件,然后点击窗口上方的这个按钮就可以了。


水平布局

QHBoxLayout 表示水平的布局管理器,H是Horizontal,属性和垂直管理器是一样的就不过多赘述了。

使用方式也是一样的,就是把QVBoxLayout换成QHBoxLayout。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 创建按钮,使用水平布局管理
    QPushButton* button1 = new QPushButton("按钮1"); // 这里没有指定父元素
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");

    // 创建布局管理器
    QHBoxLayout* layout = new QHBoxLayout();
    layout->addWidget(button1); // 这里就隐含了这些按钮的父元素是layout
    layout->addWidget(button2);
    layout->addWidget(button3);

    // 把布局管理器添加到窗口中
    this->setLayout(layout); // layout的父元素就是this,所以都在对象树上,释放也就不用处理了
}

这些操作两个管理器相差不大,现在就可以让两个管理器嵌套起来。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 创建垂直布局管理器
    QVBoxLayout* vlayout = new QVBoxLayout();
    this->setLayout(vlayout);

    // 添加两个按钮
    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    vlayout->addWidget(button1);
    vlayout->addWidget(button2);

    // 创建水平布局管理器
    QHBoxLayout* hlayout = new QHBoxLayout();

    // 将按钮添加到水平布局管理器
    QPushButton* button3 = new QPushButton("按钮3");
    QPushButton* button4 = new QPushButton("按钮4");
    hlayout->addWidget(button3);
    hlayout->addWidget(button4);

    // 再把水平布局管理器添加到垂直布局管理器中
    vlayout->addLayout(hlayout);
}

通过这种方式就可以进行嵌套,而且按钮也可以根据窗口改变而改变。


网格布局

QGridLayout 用来实现网格布局的效果,也就是M * N的网格,下面是他不同于以上两种的属性,它没有layoutSpacing相邻元素边距这个属性,变成了另外两个属性。

属性 说明
layoutHorizontalSpacing 相邻元素之间水平方向的间距
layoutVerticalSpacing 相邻元素之间垂直方向的间距
layoutRowStretch 行方向的拉伸系数
layoutColumnStretch 列方向的拉伸系数

我们继续使用PushButton看看这个布局的效果。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");
    QPushButton* button4 = new QPushButton("按钮4");

    QGridLayout* layout = new QGridLayout();
    layout->addWidget(button1, 0, 0);
    layout->addWidget(button2, 0, 1);
    layout->addWidget(button3, 1, 0);
    layout->addWidget(button4, 1, 1);

    this->setLayout(layout);
}

稍微改一下参数就可以变成其他的网格布局。

cpp 复制代码
QGridLayout* layout = new QGridLayout();
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 1, 1);
layout->addWidget(button3, 2, 2);
layout->addWidget(button4, 3, 3);

后面这两个参数也就是坐标,如果参数的横坐标相同就表示QHBoxLayout,如果参数的纵坐标相同就表示QVBoxLayout。

既然多出了两个属性,layoutRowStretch和layoutColumnStretch,那就要看看这两个属性是怎样的,我们先来设置一下列拉伸系数。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 创建6个按钮,按照2 * 3布局排列
    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");
    QPushButton* button4 = new QPushButton("按钮4");
    QPushButton* button5 = new QPushButton("按钮5");
    QPushButton* button6 = new QPushButton("按钮6");

    // 创建布局管理器
    QGridLayout* layout = new QGridLayout;
    layout->addWidget(button1, 0, 0);
    layout->addWidget(button2, 0, 1);
    layout->addWidget(button3, 0, 2);
    layout->addWidget(button4, 1, 0);
    layout->addWidget(button5, 1, 1);
    layout->addWidget(button6, 1, 2);

    // 设置列拉伸系数,此处三列按照1 : 2 : 3的方式设置宽度
    layout->setColumnStretch(0, 1);
    layout->setColumnStretch(1, 2);
    layout->setColumnStretch(2, 3);
    // 第一个参数为第几列,第二个参数就是拉伸系数,如果拉伸系数设置为0,就代表该列不参与拉伸,为固定值


    // 设置布局管理器
    this->setLayout(layout);
}

行拉伸系数和列拉伸系数的设置方式是一样的,只不过按照上述的方式去设置列拉伸系数,恐怕实现不了我们想要的效果,那是因为SizePolicy起到的影响,这个是QWidget的属性。

cpp 复制代码
QSizePolicy::Ignored // 忽略控件的尺⼨,不对布局产⽣影响。
QSizePolicy::Minimum // 控件的最⼩尺⼨为固定值,布局时不会超过该值。
QSizePolicy::Maximum // 控件的最⼤尺⼨为固定值,布局时不会⼩于该值。
QSizePolicy::Preferred // 控件的理想尺⼨为固定值,布局时会尽量接近该值。
QSizePolicy::Expanding // 控件的尺⼨可以根据空间调整,尽可能占据更多空间。
QSizePolicy::Shrinking // 控件的尺⼨可以根据空间调整,尽可能缩⼩以适应空间。

为什么可以设置列的拉伸系数呢,那是因为按钮垂直方向也就是高没有拉伸开,而水平方向也就是宽默认是拉伸的,这也就是为什么不管我们设不设置拉伸系数,按钮的宽也是默认拉伸开的。

想要达到我们想要的效果就要设置按钮在垂直方向也可以拉伸。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 创建6个按钮,按照3 * 2布局排列
    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");
    QPushButton* button4 = new QPushButton("按钮4");
    QPushButton* button5 = new QPushButton("按钮5");
    QPushButton* button6 = new QPushButton("按钮6");

    // 让水平和垂直方向都可以拉伸(默认水平方向是可以拉伸的)
    button1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    button2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    button3->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    button4->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    button5->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    button6->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    // 创建布局管理器
    QGridLayout* layout = new QGridLayout;
    layout->addWidget(button1, 0, 0);
    layout->addWidget(button2, 0, 1);
    layout->addWidget(button3, 1, 0);
    layout->addWidget(button4, 1, 1);
    layout->addWidget(button5, 2, 0);
    layout->addWidget(button6, 2, 1);

    // 设置行拉伸系数,此处三列按照1 : 2 : 3的方式设置高度
    layout->setRowStretch(0, 1);
    layout->setRowStretch(1, 2);
    layout->setRowStretch(2, 3);

    // 设置布局管理器
    this->setLayout(layout);
}

表单布局

Qt 还提供了 QFormLayout 表单布局,这属于 QGridLayout 的特殊情况,是一个N行两列的表单布局,这种表单布局多用于让用户填写信息,第一列为提示,第二列为输入框。

现在就可以写一个信息输入的程序。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置成三行两列
    QFormLayout* layout = new QFormLayout();
    this->setLayout(layout);

    // 创建3个Label作为第一列
    QLabel* label1 = new QLabel("学号");
    QLabel* label2 = new QLabel("班级");
    QLabel* label3 = new QLabel("姓名");

    // 创建3个LineEdit作为第二列
    QLineEdit* edit1 = new QLineEdit();
    QLineEdit* edit2 = new QLineEdit();
    QLineEdit* edit3 = new QLineEdit();

    // 把上述控件添加到表单中
    layout->addRow(label1, edit1);
    layout->addRow(label2, edit2);
    layout->addRow(label3, edit3);

    // 创建提交按钮
    QPushButton* button = new QPushButton("提交");
    layout->addRow(button);
}

Spacer

使用布局管理器的时候可能需要在控件之间添加一段空白,这就可以使用QSpacerItem来表示。

属性 说明
width 宽度
height 高度
hData 水平方向的SizePolicy,具体的内容可以在网格布局中找到: * QSizePolicy::Ignored * QSizePolicy::Minimum * QSizePolicy::Maximum * QSizePolicy::Preferred * QSizePolicy::Expanding * QSizePolicy::Shrinking
vData 垂直方向的SizePolicy。

为什么要有这个控件呢,还是因为我们在布局管理器中设置控件发现,如果设置一个水平布局,两个控件的距离会很近,如果想要让两个控件之间有距离,那就需要使用Spacer。

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QHBoxLayout* layout = new QHBoxLayout();
    this->setLayout(layout);

    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");

    // 创建 spacer 使两个按钮存在空白
    QSpacerItem* spacer = new QSpacerItem(200, 20); // 指明宽高

    layout->addWidget(button1);

    // 如果想要在中间添加空白,那么就在两个中间添加
    layout->addSpacerItem(spacer);

    layout->addWidget(button2);

}

在控件栏的这个地方也可以看到有Spacers。

相关推荐
Smile灬凉城6664 分钟前
反序列化为啥可以利用加号绕过php正则匹配
开发语言·php
lsx20240616 分钟前
SQL MID()
开发语言
Dream_Snowar19 分钟前
速通Python 第四节——函数
开发语言·python·算法
西猫雷婶20 分钟前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
鸿蒙自习室21 分钟前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
言、雲28 分钟前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
汪洪墩1 小时前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
云空1 小时前
《QT 5.14.1 搭建 opencv 环境全攻略》
开发语言·qt·opencv
Anna。。1 小时前
Java入门2-idea 第五章:IO流(java.io包中)
java·开发语言·intellij-idea
我曾经是个程序员1 小时前
鸿蒙学习记录
开发语言·前端·javascript