目录
一、控件概述
Widget是Qt中的核心概念,英文原义是"小部件",此处将其翻译为"控件"。控件是构成一个图形化界面的基本要素,如按钮、列表视图、树形视图、单行输入框、多行输入框、滚动条、下拉框等都可以称为"控件":
Qt作为一个成熟的GUI开发框架,内置了大量的常用控件。这一点可以在QT Designer中看出端倪:
并且Qt也提供了"自定义控件"的能力,可以在现有控件不能满足需求时,对现有控件做出扩展,或者自定义出新的控件。
原来的控件数量少,甚至没有,只能使用一些基本的控件,但是随着时代的发展,新的 GUI 开发体系越来越丰富,提供的控件数量和质量越来越好。
Qt 的控件虽然很多,但是从美观上并不是最好的,从之前写过的代码中使用的控件就可以看出,那些都是默认的样子,但是Qt 之所以还在被使用就是因为它还在进化,它也提供了一些优化手段,可以让空间变得更好看。图形化开发还是要注重颜值的。
综上: 学习QT,其中一个很重要的过程就是熟悉并掌握QT内置的常用控件;这些控件对于我们快速开发出符合需求的产品,是至关重要的。
关于控件体系的发展
控件是GUI开发中的通用概念,不仅仅局限在Qt中
第一阶段:完全没有控件.此时需要通过一些绘图 API手动的绘制出按钮或者输入框等内容,代码编写繁琐。例如文曲星的Lava平台开发:
第二阶段:只包含粗略的控件.只是提供了按钮,输入框,单选框,复选框等最常用的控件。例如html的原生控件:
第三阶段:
随着时代的发展,新的GUI开发体系越来越丰富,提供的控件的数量和质量越来越提升了,更完整的控件体系,基本可以覆盖到GUI开发中的大部分场景了。例如:早期的MFC、VB、C++ Builder、QT、Delphi,后来的Android SDK、Java FX、前端的各种UI库等等:
二、QWidget 核心属性
在Qt中使用Qwidget类表示"控件",如按钮、视图、输入框、滚动条等具体的控件类,都是继承自Qwidget。可以认为Qwidget中包含了Qt整个控件体系中的通用部分。
在Qt Designer中,随便拖一个控件过来,选中该控件,即可在右下方看到QWidget中的属性:
这些属性可以直接通过,QT Designer直接进行修改,也可以通过代码的方式直接进行修改。这些属性的具体含义在QT官方文档中都存在,我们只需要在QT官方文档中搜索QWidget类即可:
(一)QWidget的核心属性概览
在Qt中,使用QWidget类表示"控件".像按钮,视图,输入框,滚动条等具体的控件类,都是继承自QWidget。可以说,QWidget中就包含了Qt整个控件体系中,通用的部分。
在Qt Designer中,随便拖一个控件过来,选中该控件,即可在右下方看到QWidget中的属性:
这些属性既可以通过QtDesigner会直接修改,也可以通过代码的方式修改。这些属性的具体含义,在Qt Assistant中均有详细介绍:
属性 | 作用 |
---|---|
enabled | 设置控件是否可使用. true 表示可用,false 表示禁用. |
geometry | 位置和尺寸.包含x, y, width, height四个部分.其中坐标是以父元素为参考进行设置的 |
windowTitle | 设置widget标题 |
windowlcon | 设置widget图标 |
windowOpacity | 设置widget透明度 |
cursor | 鼠标悬停时显示的图标形状.是普通箭头,还是沙漏,还是十字等形状.在Qt Designer界面中可以清楚看到可选项 |
font | 字体相关属性.涉及到字体家族,字体大小,粗体,斜体,下划线等等样式 |
toolTip | 鼠标悬停在widget.上会在状态栏中显示的提示信息 |
toolTipDuring | toolTip显示的持续时间 |
statusTip | Widget状态发生改变时显示的提示信息(比如按钮被按下等) |
whatsThis | 允许使用CSS来设置widget中的样式 |
styleSheet | 允许使用CSS来设置widget中的样式.Qt中支持的样式非常丰富,对于前端开发人员上手是非常友好的. |
focusPolicy | focusPolicy该widget如何获取到焦点. ●Qt::NoFocus: 控件不参与焦点管理,即无法通过键盘或鼠标获取焦点●Qt::TabFocus:控件可以通过Tab键获得焦点●Qt::ClickFocus: 控件可以通过鼠标点击获得焦点●Qt::StrongFocus: 控件可以通过键盘和鼠标获得焦点●Qt::WheelFocus: 控件可以通过鼠标滚轮获得焦点(在某些平台或样式中可能不可用) |
contextMenuPolicy | .上下文菜单的显示策略.●Qt::DefaultContextMenu: 默认的上下文菜单策略,用户可以通过鼠标右键或键盘快捷键触发上下文菜单●Qt::NoContextMenu: 禁用上下文菜单,即使用户点击鼠标右键也不会显示菜单●Qt::PreventContextMenu: 防止控件显示上下文菜单,即使用户点击鼠标右键也不会显示菜单●Qt::ActionsContextMenu:将上下文菜单替换为控件的“动作”菜单,用户可以通过鼠标右键或键盘快捷键触发这个菜单●Qt::CustomContextMenu:使用自定义的上下文菜单,用户可以通过鼠标右键或键盘快捷键触发这个菜单 |
locale | 设置语言和国家地区 |
acceptDrops | 该部件是否接受拖放操作。如果设置为true,那么该部件就可以接收来自其他部件的拖放操作。当一一个部件被拖放到该部件_上时,该部件会接收到相应的拖放事件(如dropEvent) 。如果设置为false,那么该部件将不会接收任何拖放操作 |
minimumSize | 控件的最小尺寸.包含最小宽度和最小高度 |
maximumSize | 控件的最大尺寸.包含最大宽度和最大高度 |
sizePolicy | 尺寸策略.设置控件在布局管理器中的缩放方式. |
windowModality | 指定窗口是否具有"模态"行为 |
sizelncrement | 拖动窗口大小时的增量单位 |
baseSize | 窗口的基础大小,用来搭配sizelncrement调整组件尺寸是计算组件应该调整到的合适的值 |
palette | 调色板.可以设置widget的颜色风格 |
mouseTracking | 是否要跟踪鼠标移动事件.如果设为true,表示需要跟踪,则鼠标划过的时候该widget就能持续收到鼠标移动事件.如果设为false,表示不需要跟踪,则鼠标划过的时候widget不会收到鼠标移动事件,只能收到鼠标按下或者释放的事件 |
tabletTracking | 是否跟踪触摸屏的移动事件.类似于mouseTracking. Qt 5.9中引入的新属性 |
layoutDirection | 布局方向. .●Qt::LeftToRight: 文本从左到右排列,也是默认值。●Qt::RightToLeft:文本从右到左排列。●Qt::GlobalAtomics:部件的布局方向由全局原子性决定(其实就是根据应用程序中的其他widget布局方向确定的) |
autoFillBackground | 是否 自动填充背景颜色 |
windowFilePath | 能够把widget和一个本地文件路径关联起来 |
accessibleName | 设置widget的可访问名称.这个名称可以被辅助技术(像屏幕阅读器)获取到.设置widget的可访问名称.这个名称可以被辅助技术(像屏幕阅读器)获取到.这个属性用于实现无障碍程序的场景中(也就是给盲人写的程序).其实盲人也是可以使用电脑和手机的.甚至盲人还能成为程序猿.参见https://www.bilibili.com/video/BV1954y1d7z9 |
accessibleDescription | 设置widget的详细描述,作用同accessibleName |
inputMethodHints | 针对输入框有效,用来提示用户当前能输入的合法数据的格式.比如只能输入数字,只能输入日期等 |
下面介绍一些常用的属性,没有介绍到的可以自行查阅文档。
1. enabled
API | 说明 |
---|---|
isEnabled() | 获取到控件的可用状态 |
setEnabled(bool) | 设置控件是否可用,true表示可用,false表示禁用 |
说明:
- "禁用"是指该控件不能接收任何用户的输入事件,并且外观往往是灰色的。
- 若一个widget被禁用,则该widget的子元素也被禁用。
代码示例:在窗口上设计两个按钮控件,一个控件为A,一个控件为B,当我们点击控件B的时候,就能切换控件A的禁用状态和开启状态:
图形化界面的方式:
widget.h
运行程序,效果如下:
C++代码的实现方式:
当然了,我们也可以直接在QT Designer中的右下角直接进行勾选,即可。钩上表示这个控件被启用,否则表示被禁用:
2. geometry
geometry 意思是几何。这里表示位置和尺寸,其实是四个属性的统称
- x:横坐标
- y:纵坐标
- width:宽度
- height:⾼度
对于 Qt 的坐标系,不要忘记是一个 “左手坐标系”,其中坐标系的原点是当前父元素的左上角。
实际开发中,我们并不会直接使用这几个属性,而是通过一系列封装的方法来获取/修改:
API | 说明 |
---|---|
geometry | 获取到当前控件的的位置和尺寸信息,返回的是一个QRect,包含了x,y、width、height,其中x,y是控件左上角的坐标 |
setGeometry(QRect) | 设置一下当前控件的位置和尺寸 |
setGeometry(x,y,width,height) | 设置控件的位置和尺寸,可以直接设置一个Qrect,也可以四个属性单独设置 |
其中QRect就是“矩形”,Qt中也封装了几何,QPoint表示一个点,他们两个属于小对象,通常就会按照值的方式传递参数,因为对象占的内存比较小,拷贝的消耗比较小。
代码示例:通过点击按钮控制另外一个按钮的大小。
在界面上有5个控件,其中一个为目标控件,另外四个分别为:up、down、left、right控件
修改按钮尺寸相当于是拖着按钮左上角调整按钮尺寸。上述代码中我们是直接设置的 QRect 中的 x ,y 。实际上 QRect 内部是存储了左上和右下两个点的坐标,再通过这两个点的坐标差值计算长宽。单纯修改左上坐标就会引起整个矩形的长宽发生改变。
class Q_CORE_EXPORT QRect
{
public:
Q_DECL_CONSTEXPR QRect() noexcept : x1(0), y1(0), x2(-1), y2(-1) {}
Q_DECL_CONSTEXPR QRect(const QPoint &topleft, const QPoint &bottomright) noexcept;
Q_DECL_CONSTEXPR QRect(const QPoint &topleft, const QSize &size) noexcept;
Q_DECL_CONSTEXPR QRect(int left, int top, int width, int height) noexcept;
Q_DECL_CONSTEXPR inline bool isNull() const noexcept;
Q_DECL_CONSTEXPR inline bool isEmpty() const noexcept;
Q_DECL_CONSTEXPR inline bool isValid() const noexcept;
Q_DECL_CONSTEXPR inline int left() const noexcept;
Q_DECL_CONSTEXPR inline int top() const noexcept;
Q_DECL_CONSTEXPR inline int right() const noexcept;
Q_DECL_CONSTEXPR inline int bottom() const noexcept;
Q_REQUIRED_RESULT QRect normalized() const noexcept;
Q_DECL_CONSTEXPR inline int x() const noexcept;
Q_DECL_CONSTEXPR inline int y() const noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setLeft(int pos) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setTop(int pos) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setRight(int pos) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setBottom(int pos) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setX(int x) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setY(int y) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setTopLeft(const QPoint &p) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setBottomRight(const QPoint &p) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setTopRight(const QPoint &p) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setBottomLeft(const QPoint &p) noexcept;
Q_DECL_CONSTEXPR inline QPoint topLeft() const noexcept;
Q_DECL_CONSTEXPR inline QPoint bottomRight() const noexcept;
Q_DECL_CONSTEXPR inline QPoint topRight() const noexcept;
Q_DECL_CONSTEXPR inline QPoint bottomLeft() const noexcept;
Q_DECL_CONSTEXPR inline QPoint center() const noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveLeft(int pos) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveTop(int pos) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveRight(int pos) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveBottom(int pos) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveTopLeft(const QPoint &p) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveBottomRight(const QPoint &p) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveTopRight(const QPoint &p) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveBottomLeft(const QPoint &p) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveCenter(const QPoint &p) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void translate(int dx, int dy) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void translate(const QPoint &p) noexcept;
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR inline QRect translated(int dx, int dy) const noexcept;
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR inline QRect translated(const QPoint &p) const noexcept;
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR inline QRect transposed() const noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveTo(int x, int t) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void moveTo(const QPoint &p) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setRect(int x, int y, int w, int h) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void getRect(int *x, int *y, int *w, int *h) const;
Q_DECL_RELAXED_CONSTEXPR inline void setCoords(int x1, int y1, int x2, int y2) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void getCoords(int *x1, int *y1, int *x2, int *y2) const;
Q_DECL_RELAXED_CONSTEXPR inline void adjust(int x1, int y1, int x2, int y2) noexcept;
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR inline QRect adjusted(int x1, int y1, int x2, int y2) const noexcept;
Q_DECL_CONSTEXPR inline QSize size() const noexcept;
Q_DECL_CONSTEXPR inline int width() const noexcept;
Q_DECL_CONSTEXPR inline int height() const noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setWidth(int w) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setHeight(int h) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline void setSize(const QSize &s) noexcept;
QRect operator|(const QRect &r) const noexcept;
QRect operator&(const QRect &r) const noexcept;
inline QRect& operator|=(const QRect &r) noexcept;
inline QRect& operator&=(const QRect &r) noexcept;
bool contains(const QRect &r, bool proper = false) const noexcept;
bool contains(const QPoint &p, bool proper=false) const noexcept;
inline bool contains(int x, int y) const noexcept;
inline bool contains(int x, int y, bool proper) const noexcept;
Q_REQUIRED_RESULT inline QRect united(const QRect &other) const noexcept;
Q_REQUIRED_RESULT inline QRect intersected(const QRect &other) const noexcept;
bool intersects(const QRect &r) const noexcept;
Q_DECL_CONSTEXPR inline QRect marginsAdded(const QMargins &margins) const noexcept;
Q_DECL_CONSTEXPR inline QRect marginsRemoved(const QMargins &margins) const noexcept;
Q_DECL_RELAXED_CONSTEXPR inline QRect &operator+=(const QMargins &margins) noexcept;
Q_DECL_RELAXED_CONSTEXPR inline QRect &operator-=(const QMargins &margins) noexcept;
#if QT_DEPRECATED_SINCE(5, 0)
Q_REQUIRED_RESULT QT_DEPRECATED QRect unite(const QRect &r) const noexcept { return united(r); }
Q_REQUIRED_RESULT QT_DEPRECATED QRect intersect(const QRect &r) const noexcept { return intersected(r); }
#endif
friend Q_DECL_CONSTEXPR inline bool operator==(const QRect &, const QRect &) noexcept;
friend Q_DECL_CONSTEXPR inline bool operator!=(const QRect &, const QRect &) noexcept;
#if defined(Q_OS_DARWIN) || defined(Q_QDOC)
Q_REQUIRED_RESULT CGRect toCGRect() const noexcept;
#endif
private:
int x1;
int y1;
int x2;
int y2;
};
代码示例 2 : 控制按钮让另外一个按钮移动。
如果想实现点击方向按钮实现平移target按钮呢?在原有代码基础上修改一下即可:
此时就可以通过下面四个按钮让上面的按钮移动:
代码示例 3: 当我们在刷视频或者在网上冲浪的时候一定见过表白程序。
欣然同意那就皆大欢喜,也就不做过多处理,我们关闭窗口即可,但是我们又不想让对方拒绝,所以就要设置拒绝的按钮,这就可以通过生成随机数的方式更新按钮的位置,就是让对方怎么也点不到。
运行后就会看到一个一点击就会“逃跑”的拒绝按钮:
但是我们现在的这个点击是一下一上,那还有没有触发更快的信号呢?那肯定是有的,clicked是一下一上,比如还有一个信号是pressed,这个信号只有一下:
当然,我们还可以做的更绝一点,当用户将鼠标放到拒绝按钮上(并没有点击),就移动我们的拒绝按钮:但是对应的代码也会更复杂,需要⾃定义类继承⾃ QPushButton, 重写 mouseMoveEvent ⽅法,此处暂时不展开。
3. WindowFrame的影响
当我们创建出一个Widget时,windows会自动帮我们生成上方的一栏,下面还会生成一个非常窄的栏,就是下面红线部分,这就是Window Frame窗口框架,这是操作系统自带的,也可以通过设置一些选项让这些部分消失,就好像打游戏时设置的窗口化或全屏一样。
如果widget作为一个窗口(带有标题栏,最小化,最大化,关闭按钮),那么在计算尺寸和坐标的 时候就有两种算法:
- 包含window frame。其中x(),y(),frameGeometry(),pos(),move()都是按照包含window frame的方式来计算的。
- 不包含window frame。其中geometry(), width(), height(),rect(),size()则是按照不包含window frame的方式来计算的。
当然,如果一个不是作为窗口的widget,上述两类方式得到的结果是一致的。
API | 说明 |
---|---|
x() | 获取横坐标,计算时包含 window frame |
y() | 获取纵坐标,计算时包含 window frame |
pos() | 返回 QPoint 对象, ⾥⾯包含 x(), y(), setX(), setY() 等⽅法.计算时包含 window frame |
frameSize() | 返回 QSize 对象, ⾥⾯包含 width(), height(), setWidth(), setHeight() 等⽅法.计算时包含 window frame |
frameGeometry() | 返回 QRect 对象. QRect 相当于 QPoint 和 QSize 的结合体. 可以获取 x, y,width, size.计算时包含 window frame 对象 |
width() | 获取宽度计算时不包含 window frame |
height() | 获取⾼度,计算时不包含 window frame |
size() | 返回 QSize 对象, ⾥⾯包含 width(), height(), setWidth(), setHeight() 等⽅法.计算时不包含 window frame |
rect() | 返回 QRect 对象. QRect 相当于 QPoint 和 QSize 的结合体. 可以获取并设置 x,y, width, size.计算时不包含 window frame 对象 |
geometry() | 返回 QRect 对象. QRect 相当于 QPoint 和 QSize 的结合体. 可以获取并设置 x,y, width, size.计算时不包含 window frame 对象 |
setGeometry() | 直接设置窗⼝的位置和尺⼨. 可以设置 x, y, width, height, 或者 QRect 对象.计算时不包含 window frame 对象 |
认真观察上⾯的表格, 可以看到, 其实这⾥的 API 有 frameGeometry 和 geometry 两个就⾜够
完成所有的需求了。
为什么要提供这么多功能重复的 API 呢?
这个就涉及到 Qt API 的设计理念了:尽量符合⼈的直觉。
举个栗⼦,Qt 的 QVector,尾插元素操作,有以下⽅法:
• push_back
• append
• +=
• <<
上述⽅法的效果都是等价的,即使不翻阅⽂档,单纯的凭借直觉就能把代码写对。
代码示例 :对比 geometry 和 frame geometry 的区别
注意,如果直接在构造函数中使用geimetry和frame geometry,那么得到的窗口大小是没有区别的,因为此时 Widget 对象正在构造,还没有被加入到 window frame 中:
我们设置一个按钮,然后点击按钮才触发槽函数,把这两个 API放在槽函数里面。可以看到窗口大小的区别:
在构造方法中,widget刚刚创建出来,还没有加入到对象树中。此时也就不具备Window frame;在按钮的slot函数中,由于用户点击时,对象树已经构造好了,此时widget已经具备了Window frame,因此在位置和尺寸上均出现了差异:
4. windowTitle
API | 说明 |
---|---|
windowTitle() | 获取到控件的窗口标题 |
setWindowTitle(const Qstring& title) | 设置控件的窗口标题 |
【注意】:当前windowTitle属性是从属于QWidget的,QWidget是一个广泛的概念,只有对顶层Widget(独立窗口)操作才会生效,如果是子Widget是无效的。
对顶层Widget设置标题是有效的,在pushButton中设置标题是无效的,但是代码并没有报错,也没有效果,所以一定要注意。
5. window Icon
这个属性表示窗口的图标。
API | 说明 |
---|---|
windowIcon() | 获取到控件的窗口标题,返回QIcon对象 |
setWindowIcon(const QIcon& icon) | 设置控件的窗口标题 |
windowIcon和windowTitle这两个属性的API都是针对顶层窗口使用的,仅针对顶层widget有效。
代码示例 1 :设置窗口图标(icon 可以去阿里巴巴矢量图这个网站去下载:iconfont-阿里巴巴矢量图标库)
在指定目录下放一张图片:
之前都是推荐在堆上创建对象,主要是为了确保当前的控件的声明周期是足够的,然后要通过对象树释放对象,但是QIcon不一样。
- QIcon是一个自身比较小的对象,创建出后要设置到QWidget中。
- QIcon对象释不释放并不影响图标的显示,QPushButton如果提前释放了,那么在窗口中就不会显示。
- QIcon不支持对象树,无法给他指定父对象,所以在栈上申请就可以了,程序结束就释放。
#include "widget.h"
#include "ui_widget.h"
#include <QIcon>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建图标对象
QIcon icon("d:/C++/QT/fire.png");
// 设置图标
this->setWindowIcon(icon);
}
Widget::~Widget()
{
delete ui;
}
现在就可以把图标设置成任意图片了,这里使用的是绝对路径的方式。实际开发中,最好还是不要使用绝对路径,因为无法保证程序发布后,用户的电脑上也有同样的路径.,所以更推荐使用相对路径。
如果使用相对路径,则需要确保代码中的相对路径写法和图片实际所在的路径匹配(比如代码
中写作"./image/fire.png",就需要在当前工作目录中创建image目录,并把fire.png放进去)。
但是对于QT程序来说,当前工作目录是会变化的,比如通过QT Creator运行的程序,当前工作目录就是项目的创建目录;而直接双击exe运行,工作目录则是exe所在目录;(注意:QT中exe文件和源文件是不在通过一个目录下的!)
注意,上述构建⽬录,是随时可删除的。⽐如点击菜单栏中的 “构建” -> “清理项⽬” ,就会把这个⽬录中的内容清空掉.。
因此如果我们把图片文件放到构建目录中,可能在不小心删除后就丢失了。我们还是希望能够把图片和源代码放到一起,并且使我们的程序无论拷贝到任何位置中都能正确使用图片。
Qt使用qrc机制帮我们自动完成了上述工作,更方便的来管理项目依赖的静态资源。
- qrc ⽂件是⼀种XML格式的资源配置⽂件,它⽤XML记录硬盘上的⽂件和对应的随意指定的资源名称,应⽤程序通过资源名称来访问这些资源。
- 在Qt开发中,可以通过将资源⽂件添加到项⽬中来⽅便地访问和管理这些资源,这些资源⽂件可以位于qrc⽂件所在⽬录的同级或其⼦⽬录下。
- 在构建程序的过程中,Qt 会把资源⽂件的⼆进制数据转成 cpp 代码,编译到 exe 中,从⽽使依赖的资源变得 “路径⽆关”。
- 这种资源管理机制并非 Qt 独有,很多开发框架都有类似的机制、例如 Android 的 Resources和 AssetManager 也是类似的效果。
简而言之,qrc机制大概工作流程就是:
- 先用一个xml格式的文件来描述和获取我们的图标信息;
- 在QT编译的时候会根据这个xml文件,编译成对应的C++代码,之后程序如果想要访问图标资源只需要访问这一段C++代码即可。这样的话,就不怕用户把图标“误删”的操作了。
代码示例 2 :通过 qrc 管理图片作为图标
1. 右键项目,创建一个Qt Resource File (qrc文件),文件名随意起(不要带中文),此处叫做
resource.qrc 。
2. 先创建一个“前缀”(Prefix),这个前缀可以理解为虚拟目录,这个目录是Qt抽象出来的,因为qrc机制就是把图片的二进制数据转换成C++代码,就类似于一个char数组,所以为了方便Qt代码可以访问到这个图片,所以就抽象出了虚拟目录。此处设置成 / 即可:
3. 点击Add Files导入图片,找到这种图片就可以了,但是还是会报警告,一定要确保导入的图片必须在.qrc文件的同级目录或者同级目录的子目录中,所以要把图片拷贝到项目目录中:
4. 添加完毕后,可以在 资源编辑器 中查看添加好的文件:
5. 添加成功后就可以使用这个虚拟目录了。当代码中需要访问qrc管理的文件时,就需要在路径上有“:”前缀,并且创建前缀时使用的是什么名字,后面就要跟前缀 + 资源文件名:
现在就可以使用qrc机制处理资源了,可以看到在项目目录中有个cpp文件:
打开文件就会看到这个图片的二进制数据,编译的时候就会把这个cpp文件编译进可执行程序,程序运行时图片的数据就被加载到了内存中:
上述代码其实就是通过unsigned char数组,把 txt.png 中的每个字节都记录下来。这些代码会被编译到exe中,后续无论exe被复制到哪个目录下,都确保能够访问到该图片资源。
上述qrc这一套资源管理方案,优点和缺点都很明显:
- 优点:确保了图片,字体,声音等资源能够真正做到"目录无关",无论如何都不会出现资源丢失的情况。
- 缺点:不适合管理体积大的资源。如果资源比较大(比如是几个MB的文件),或者资源特别多,生成的最终的exe体积就会比较大,程序运行消耗的内存也会增大,程序编译的时间也会显著增加。
6. windowOpacity
这个属性表示窗口的透明度:
API | 说明 |
---|---|
windowOpacity() | 获取控件的不透明数值。返回float,取值为0.0 ~ 1.0 ,其中0.0表示全透明,1.0表示完全不透明. |
setWindowOpacity(float n) | 设置控件的不透明度 |
代码示例:调整窗口透明度
在界面上拖放两个按钮,分别用来增加不透明度和减少不透明度,objectName分别为pushButton_add 和 pushButton_sub :
当用户点击Add按钮过后可以增加窗口的透明度;当用户点击Sub按钮过后可以降低窗口的透明度。
void Widget::on_pushButton_add_clicked()
{
float opacity = this->windowOpacity();
if(opacity >= 1.0)
{
return;
}
qDebug() << opacity;
opacity += 0.1;
this->setWindowOpacity(opacity);
}
void Widget::on_pushButton_sub_clicked()
{
float opacity = this->windowOpacity();
if(opacity <= 0.0)
{
return;
}
qDebug() << opacity;
opacity -= 0.1;
this->setWindowOpacity(opacity);
}
执行程序,可以看到,点击了 Sub 之后,窗口就变透明了,同时控制台中也可以看到opacity数值的变化。
这里对于浮点数的加减在C语言阶段也讲解过了,如果有疑问可以翻一下博客。
其实这里的 if判断 也是可以不写的,因为opacity取值范围就是在 0.0 ~ 1.0 之间,即使超过数值也不会有变化,这层判断就是保险一下。
注意:该属性与windowTitle、windowIcon属性一样只针对顶层窗口起效,对于普通控件不生效。
7. cursor
设置鼠标光标的样式:
API | 说明 |
---|---|
cursor() | 获取到当前控件的cursor属性,返回QCursor对象;当鼠标悬停在该控件上时,就会显示出对应的形状 |
setCursor(const QCursor&) | 设置该控件上的光标的形状,仅在鼠标停留在该控件上时生效 |
QGUIApplication::setOverrideCursor(const QCursor&) | 设置全局光标的形状,对整个程序中的控件都生效,会覆盖setCursor设置的光标 |
代码示例 1 :通过 Qt Designer 中设置按钮的光标
创建一个按钮,然后选中,可以直接在右下角的属性设置光标:
代码示例 2 : 通过代码设置按钮的光标
#include "widget.h"
#include "ui_widget.h"
#include <QIcon>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建一个”等待"光标的对象
QCursor cursor(Qt::WaitCursor);
// 设置光标
ui->pushButton->setCursor(cursor);
}
Widget::~Widget()
{
delete ui;
}
系统内置的光标如下,Ctrl+左键点击Qt: :WaitCursor|跳转到源码即可看到.:
实际上Qt内置了很多宏来定义光标的形状,我们只需要利用这些宏来构造QCursor对象,然后在调用setCursor来设置控件的形状即可。
代码示例 3 : 自定义鼠标光标
Qt 自带的鼠标光标也有限,但是允许我们自定义光标。以下面这个图片为例子:
加入该图片至 qrc 文件中前缀为 / 下:
我们还需要使用的一个类就是QPixmap,这个类可以帮我们访问到这个图片,后面也会讲解的:
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个位图对象,加载自定义光标图片
QPixmap pixmap(":/leaf_cursor.png");
//scaled 函数可以设置图片尺寸
pixmap = pixmap.scaled(64,64);
//创建 QCursor对象,并指定“热点”为(2,2)坐标位置
//所谓“热点” 就是鼠标点击时生效的位置
QCursor cursor(pixmap,2,2);
//设置光标
this->setCursor(cursor);
}
Widget::~Widget()
{
delete ui;
}
8. font
表示字体的格式:
API 说明 font() 获取到当前控件的字体信息,返回QFont对象 setFont(const QFont&) 设置当前控件的字体信息
关于QFont的字段:
字段 | 说明 |
---|---|
family | 字体家族,eg:“楷体”、“宋体”、“微软雅黑”等 |
weight | 字体粗细,以数值方式表示粗细程度,取值范围是[0,99],数值越大,越粗 |
bold | 是否加粗,设置为true,相当于weight为75;设置为false,相当于weight设置为50 |
italic | 是否倾斜 |
underline | 是否自带下划线 |
strikeOut | 是否带删除线 |
代码示例 1 : 在 Qt Designer 中设置字体属性
在界面上创建一个 label,在右侧的属性编辑区,设置该 label 的 font 的相关属性,可以实时看到文字的变化:
代码示例 2 :在代码中设置字体属性
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.setPointSize(20);//设置字体大小
font.setBold(true);//设置字体加粗
font.setItalic(true);//设置字体倾斜
font.setUnderline(true);//设置字体下划线
font.setStrikeOut(true);//设置字体删除线
//设置字体对象到 label 上
label->setFont(font);
}
9. toolTip
把鼠标悬停到这个控件上就会弹出一个提示。
API | 说明 |
---|---|
setToolTip() | 设置toolTip,鼠标悬停在控件上时又提示说明 |
setToolTipDuring() | 设置toolTip的提示时间,单位ms,时间到过后,toolTip会自动消失 |
也有toolTip接口来获取toolTip,但是一般这个都是给用户看的,代码中一般不回获取。
代码示例:设置 toolTip,说明两个按钮的作用
在界面上拖放两个按钮:objectName设置为pushButton_yes 和pushButton_no
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->pushButton_yes->setToolTip("这是一个yes按钮");
ui->pushButton_yes->setToolTipDuration(3000);// 提示3秒
ui->pushButton_no->setToolTip("这是一个no按钮");
ui->pushButton_no->setToolTipDuration(3000);// 提示3秒
}
也可以通过 ui 界面右边的属性直接设置 toolTip,把鼠标光标移到按钮上,即可看到提示:
10. focusPolicy
这个属性是设置控件获取的“焦点”的,比如鼠标选中输入框,键盘输入就可以显示到框中,如果鼠标点击框以外的位置,那么键盘输入的内容就不会显示到框中。
焦点:
所谓 “焦点”,指的就是能选中这个元素。接下来的操作 (⽐如键盘操作),就都是针对该焦点元素进⾏的了,这个对输⼊框,单选框,复选框等控件⾮常有⽤的:当我光标选中Bing的搜索框的时候,我就可以向搜索框中进行写入,而当我光标点击其它地方,那么这时候,无论我输入什么东西,都不会被Bing搜索框接收:
API | 说明 |
---|---|
focusPolicy() | 获取当前控件的focusPolicy,返回Qt::FocusPolicy |
setFocusPolicy(Qt::FocusPolicy policy) | 设置当前控件的focusPolicy |
一般获取控件焦点的方式有两种,一是鼠标点击,二是键盘的Tab。Qt::FocusPolicy是一个枚举类型,取值如下:
- Qt::NoFocus:控件不会接收键盘的焦点,像Label这样的控件就不想让你获取焦点。
- Qt::TabFocus:控件可以通过Tab键接收焦点。
- Qt::ClickFocus:控件可以通过鼠标点击接收焦点。
- Qt::StrongFocus:控件可以通过上面两种方式接收焦点(默认就是它)
- Qt::WheelFocus:类似与上一个,同时控件可以通过鼠标滚轮获取焦点,一般用的很少。
代码示例 :通过 Qt Designer 设置focusPolicy
在界面上创建四个单行输入框(Line Edit),通过右下角的属性界面可直接设置每个输入框获取到焦点的策略。这个控件默认就是StrongFocus的,所以可以通过鼠标点击和Tab键或Shift+Tab键可以切换。:
我们把第一个空间的focusPolicy换成NoFocus,运行后就发现,第一个框不能选中了:
11. styleSheet
我们都知道写一个网页需要用到CSS样式,那么Qt作为一个GUI图形化界面开发,实际上与网页制作有很多相似之处。
CSS (Cascading Style Sheets层叠样式表)本身属于网页前端技术。主要就是用来描述界面的样式。所谓"样式",包括不限于大小,位置,颜色,间距,字体,背景,边框等。我们平时看到的丰富多彩的网页,就都会用到大量的CSS。
Qt虽然是做GUI开发,但实际上和网页前端有很多异曲同工之处.因此Qt也引入了对于CSS 的支持,叫做QSS(Qt Style Sheet)。具体的支持情况可以参考Qt文档中"Qt Style Sheets Reference"章节。
代码示例 1 : 设置文本样式
编辑右侧的styleSheet属性或者右键文本:
设置样式:
编辑完成样式之后,可以看到在Qt Designer中能够实时预览出效果:
此处的语法格式同CSS,使用键值对的方式设置样式。其中键和值之间使用 : 分割,键值对之间使用 ; 分割。另外,Qt Designer 只能对样式的基本格式进行校验,不能检测出哪些样式不被Qt支持,比如 text- align: center 这样的文本居中操作,就无法支持。
代码示例 2 : 实现切换夜间模式
在界面上放3个控件,一个多行输入框(Text Edit),一个日间模式按钮,一个夜间模式按钮(objectName分别为pushButton light和pushButton dark);
当我们点击日间就将整个界面切换到日间,当我么点击夜间按钮,就会将整个界面切换到夜间;
- 日间:文字为黑色,背景为白色
- 夜间:文字为白色,背景为黑色
void Widget::on_pushButton_light_clicked()
{
//设置窗口的样式
this->setStyleSheet("background-color: white;");
//设置输入框的样式
ui->textEdit->setStyleSheet("background-color:white;color:black");
//设置按钮的样式
ui->pushButton_light->setStyleSheet("color:black");
ui->pushButton_dark->setStyleSheet("color:black");
}
void Widget::on_pushButton_dark_clicked()
{
//设置窗口的样式
this->setStyleSheet("background-color: black;");
//设置输入框的样式
ui->textEdit->setStyleSheet("background-color:black;color:white");
//设置按钮的样式
ui->pushButton_light->setStyleSheet("color:white");
ui->pushButton_dark->setStyleSheet("color:white");
}
运行程序,查看效果:
嗯,不错的,运行结果是符合预期的,但是有一点小瑕疵就是,白天模式和原始模式是不匹配的,按理来说,原始模式应该就是白天模式,白天模式就是原始模式,但是白天模式更白,原始模式似乎偏黄一点,这是为什么?主要是因为,刚开始的时候我们还没有点击任何按钮,因此我们的夜间模式或黑夜模式都不会神效,因此界面就是初始化颜色,当我们点击黑夜或者白天按钮过后整个界面也就会发生变化!为此,我们只需要将白天模式的背景颜色调成和原始状态一样就好了。
关于计算机中的颜色表示
计算机中使用"像素"表示屏幕上的一一个基本单位(也就是一个 发亮的光点),每个光点都使用三个字节表示颜色,分别是R (red), G (green), B (blue) - -个字节表示(取值范围是0-255,或者0x00-0xFF)。混合三种不同颜色的数值比例,就能搭配出千千万万的颜色出来:
- rgb(255, 0, 0)或者#FF0000 或者#F00 表示纯红色
- rgb(0, 255, 0)或者#00FF00 或者#0FO 表示纯绿色
- rgb(0, 0, 255)或者#0000FF或者#00F表示纯蓝色
- rgb(255, 255, 255)或者#FFFFFF或者#FFF 表示纯白色
- rgb(0, 0, 0)或者#000000 或者#000表示纯黑色
当然,上述规则只是针对一般的程序而言是这么设定的,实际的显示器,可能有8bit色深或者
10bit色深等,实际情况会更加复杂。
获取计算机中 RGB 的小技巧。在 QQ截图中,内置了一个获取颜色的RGB方式:
将日间模式的颜色改为 rgb(240, 240, 240) 即可:
void Widget::on_pushButton_light_clicked()
{
//设置窗口的样式
this->setStyleSheet("background-color: rgb(240,240,240);");
//设置输入框的样式
ui->textEdit->setStyleSheet("background-color:white;color:black");
//设置按钮的样式
ui->pushButton_light->setStyleSheet("color:black");
ui->pushButton_dark->setStyleSheet("color:black");
}
三、按钮类控件
(一)PushButton
Qt中使用QPushButton表示一个按钮。QPushButton继承自QAbstractButton,该类是一个抽象类,是其他按钮的父类:
在Qt Designer中也能够看到这里的继承关系
QAbstractButton中,和QPushButton相关性较大的属性
属性 | 说明 |
---|---|
text | 按钮文本 |
icon | 按钮图标 |
iconSize | 按钮图标大小 |
shortCut | 按钮对应的快捷键 |
autoRepeat | 按钮是否会重复触发;如果设置为true,鼠标左键一直按着,则会产生持续的鼠标点击事件;如果设置为false,鼠标左键一直按着,则不会触发持续的鼠标点击事件,只有当鼠标松开,再次按下才会触发一次鼠标点击事件 |
autoRepeatDelay | 重复触发的延时时间,按住按钮多久过后,开始重复触发 |
autoRepeatInterval | 重复触发的周期 |
- QAbstractButton作为QWidget的子类,当然也继承了QWidget的属性,上面介绍的属性是QAbstractButton的单独属性,那么前面介绍的QWidget核心属性对应QAbstractButton同样适用。
- Qt的API设计风格非常清晰,上面列出的属性都是可以获取和设置的;eg:获取文本:text();设置文本:setText(“你好”)。
事实上,QPushButton的核心功能都是QAbstractButton提供的,自身提供的属性都较为简单,其中default和autoDefault影响的是按下enter时自动点击哪个按钮的行为,flat把按钮设置为扁平的样式。暂时不做过多关注。
代码示例 1 : 带有图标的按钮
创建resource.qrc 文件,并导入图片:
在界面上创建一个按钮:
编写代码,如果我们觉得,图标的大小太小了,那么我们可以通过setIconSize(const QSize&)接口来设置图标的大小:
#include "widget.h"
#include "ui_widget.h"
#include <Qicon>
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建图标
QIcon icon(":/pig.png");
// 设置图标
ui->pushButton->setIcon(icon);
// 设置图标大小
ui->pushButton->setIconSize(QSize(50, 50));
}
Widget::~Widget()
{
delete ui;
}
运行程序,查看结果:
代码示例2:带有快捷键的按钮
在界面中拖五个按钮:五个按钮的objectName分别为pushButton_target,pushButton_up,
pushButton_down, pushButton_left,pushButton_right。
创建 resource.qrc ,并导入五个图片:
- 可以通过图形化界面创建对应的图像,并且通过图形化界面的方式来为猪头按钮设置图标,箭头就不另外设置:
- 通过纯代码创建对应的图像设置按钮图标,并添加快捷键。按下的快捷键可能不是一个,也许是组合键,使用QKeySequence对象,Key就是按键,Sequence就是序列:
// 设置快捷键 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_W); ui->pushButton_down->setShortcut(QKeySequence(Qt::Key_S)); ui->pushButton_left->setShortcut(QKeySequence(Qt::Key_A)); ui->pushButton_right->setShortcut(QKeySequence(Qt::Key_D)); // 也可以设置组合键 ui->pushButton_up->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_W)); ui->pushButton_down->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S)); ui->pushButton_left->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_A)); ui->pushButton_right->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
这里还是更推荐使用枚举类型,因为单词拼写错误可以识别,这就要比使用字符串好很多:
#include "widget.h"
#include "ui_widget.h"
#include <QIcon>
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置猪头图标
ui->pushButton_target->setIcon(QIcon(":/image/pig.png"));
ui->pushButton_target->setIconSize(QSize(80,80));
// 设置箭头
ui->pushButton_up->setIcon(QIcon(":/image/up.png"));
ui->pushButton_down->setIcon(QIcon(":/image/down.png"));
ui->pushButton_left->setIcon(QIcon(":/image/left.png"));
ui->pushButton_right->setIcon(QIcon(":/image/right.png"));
// 设置快捷键
ui->pushButton_up->setShortcut(QKeySequence(Qt::Key_W));
ui->pushButton_down->setShortcut(QKeySequence(Qt::Key_S));
ui->pushButton_left->setShortcut(QKeySequence(Qt::Key_A));
ui->pushButton_right->setShortcut(QKeySequence(Qt::Key_D));
//按键快捷键默认可以重复触发,鼠标不行,以下代码设置鼠标重复触发
ui->pushButton_up->setAutoRepeat(true);
ui->pushButton_down->setAutoRepeat(true);
ui->pushButton_left->setAutoRepeat(true);
ui->pushButton_right->setAutoRepeat(true);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_up_clicked()
{
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()
{
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()
{
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()
{
QRect rect = ui->pushButton_target->geometry();
ui->pushButton_target->setGeometry(rect.x() + 5,rect.y(),rect.width(),rect.height());
}
运行后就可以实现我们猪头的移动,不仅鼠标可以点击,键盘也可以使用,但是它们两个的区别就是键盘的按键自带连发功能:
(二)RadioButton
QRadioButton是单选按钮,可以让我们在多个选项中选择一个,作为QAbstractButton和QWidget的子类,上面介绍的属性和用法,对于QRadioButton同样适用。
QAbstractButton中和QRadioButton关系较大的属性:
属性 | 说明 |
---|---|
checkable | 是否能被选中 |
checked | 是否已经被选中,checkable时checked的前提条件 |
autoExclusive | 是否排它 选中一个按钮之后是否会取消其他按钮的选中 对于QRadioButton而言,默认就是排它的 |
代码示例 1 :选择性别
在界面上创建一-个label,和3个单选按钮
设置的文本如下图:3个单选按钮的objectName分别为radioButton_male,radioButton_female,radioButton_other:
编写对应的槽函数:
void Widget::on_radioButton_male_clicked()
{
ui->label->setText("您选择的性别为:男");
}
void Widget::on_radioButton_female_clicked()
{
ui->label->setText("您选择的性别为:女");
}
void Widget::on_radioButton_other_clicked()
{
ui->label->setText("您选择的性别为:其他");
}
运行结果如下,点击按钮选择性别:
当前的代码中程序启动的时候,什么也没有被选中,我们可以让程序的默认选项为男,因此我们的代码可以按照如下更改,在构造函数内设置默认选项:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->radioButton_male->setChecked(true);
ui->label->setText("您选择的性别为:男");
}
还可以禁用某个选项,这里主要有两种方式来实现:
①调用 setEnabled() 直接禁用“其它”控件:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->radioButton_male->setChecked(true);
ui->label->setText("您选择的性别为:男");
ui->radioButton_other->setEnabled(false);
}
②调用 setCheckable() 让这个控件本身无法被选中,这里的选中是说无法被打勾,但是可以被点击:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->radioButton_male->setChecked(true);
ui->label->setText("您选择的性别为:男");
// ui->radioButton_other->setEnabled(false);
ui->radioButton_other->setCheckable(false);
}
其他按钮无法被选中,但是可以触发点击信号,因此文本被替换成了“其他” :
代码示例 2 : clicked,press,release,toggled 的区别
- clicked():表示一次 “点击”,即鼠标左键按下+松开就会触发该信号。
- clicked(bool):点击,即鼠标左键按下+松开会触发该信号,在触发该信号时,会将该控件是否被选中的状态也一并发送出去,对于QPushButton按钮来说没用,对于QRadioButton来说有用。
- pressed():表示鼠标“按下”,即鼠标左键按下,就会触发该信号.
- released():表示鼠标 “释放”,即鼠标左键松开,就会触发该信号。
- toggled(bool):表示按钮状态切换,就会发送该信号。
接着,我们来分别演示一下这几个信号的用法和区别:
在界面上创建四个单选按钮:objectName 分别为 radioButton_1,radioButton_2,radioButton_3,radioButton_4
给1创建clicked槽函数,给2创建pressed槽函数,给3创建released槽函数,给4创建toggled槽函数:
void Widget::on_radioButton_1_clicked(bool checked)
{
qDebug() << "clicked: " << checked;
}
void Widget::on_radioButton_2_pressed()
{
qDebug() << "pressed";
}
void Widget::on_radioButton_3_released()
{
qDebug() << "released";
}
void Widget::on_radioButton_4_toggled(bool checked)
{
// checked 状态发生改变,就会触发者信号
if(checked) qDebug() << "toggled true";
else qDebug() << "toggled false";
}
运行程序,可以看到:
- clicked 是一次鼠标按下+鼠标释放触发的
- pressed 是鼠标按下触发的
- released 是鼠标释放触发的
- toggled 是checked 属性改变时触发的
代码示例 3 :单选框分组
在界面上创建6个单选框,用来模拟麦当劳点餐界面,objectName分别为radioButton至radioButton_6
此时直接运行程序,可以看到,这六个QRadioButton之间都是排他的。
这里我们主要有两种解决方案:
①关闭RadioButton的排他性:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->radioButton_1->setAutoExclusive(false);
ui->radioButton_2->setAutoExclusive(false);
ui->radioButton_3->setAutoExclusive(false);
ui->radioButton_4->setAutoExclusive(false);
ui->radioButton_5->setAutoExclusive(false);
ui->radioButton_6->setAutoExclusive(false);
}
这样确实能解决问题,但是也会引发一个新问题,就是,虽然我接触了排他性了,但是对于同一组中的商品我也能同时选中了,比如:
这是不合理的,我们希望每一组内部来控制排他,而不同组之间不存在排他性!但是组和组之间不能排他。
②采用QButtonGroup分组排他:
我们使用QButtonGroup将同一组的商品放在一起,然后进行组内排他:
#include <QButtonGroup>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QButtonGroup *bg1 = new QButtonGroup(this);
QButtonGroup *bg2 = new QButtonGroup(this);
QButtonGroup *bg3 = new QButtonGroup(this);
bg1->addButton(ui->radioButton_1);
bg1->addButton(ui->radioButton_2);
bg2->addButton(ui->radioButton_3);
bg2->addButton(ui->radioButton_4);
bg3->addButton(ui->radioButton_5);
bg3->addButton(ui->radioButton_6);
}
同时也不存在,同一组内也可以同时选择的情况:
(三)CheckBox
QCheckBox表示复选按钮,可以允许选中多个。和QCheckBox最相关的属性是checkable和checked,都是继承自QAbstractButton
代码示例 : 获取复选按钮的取值
在界面上创建三个复选按钮和一个普通按钮,objectName分别为checkBox_learn、checkBox_billiard、checkBox_lgame以及pushButton
给pushButton添加slot函数
void Widget::on_pushButton_clicked()
{
QString result = "今日安排:";
if(ui->checkBox_learn->isChecked())
result += ui->checkBox_learn->text() + " ";
if(ui->checkBox_billiard->isChecked())
result += ui->checkBox_billiard->text() + " ";
if(ui->checkBox_game->isChecked())
result += ui->checkBox_game->text();
ui->label->setText(result);
}
运行结果如下:
四、显示类控件
(一)Label
QLabel 可以用来显示文本和图片的。
属性 | 说明 |
---|---|
text | QLabel中的文本内容 |
textFormat | QLabel中文本内容的格式:Qt:: PlainText 纯文本; Qt::RichText 富文本(支持html标签); Qt::MarkdownText markdown格式;Qt::AutoText 根据文本内容,自动觉得文本格式; |
pixmap | QLabel中的图片 |
scaledContents | 设为 true 表⽰内容⾃动拉伸填充 QLabel,设为 false 则不会⾃动拉伸 |
alignment | 对⻬⽅式.可以设置⽔平和垂直⽅向如何对⻬ |
wordWrap | 设为 true 内部的⽂本会⾃动换⾏.设为 false 则内部⽂本不会⾃动换⾏ |
indent | 设置⽂本缩进. ⽔平和垂直⽅向都⽣效 |
margin | 内部⽂本和边框之间的边距.不同于于 indent, 但是是上下左右四个⽅向都同时有效.⽽ indent 最多只是两个⽅向有效(具体哪两个⽅向有效取决于 alignment ) |
openExternalLinks | 是否允许打开⼀个外部的链接.(当 QLabel ⽂本内容包含 url 的时候涉及到) |
buddy | 给 QLabel 关联⼀个 “伙伴” , 这样点击 QLabel 时就能激活对应的伙伴.例如伙伴如果是⼀个 QCheckBox, 那么该 QCheckBox 就会被选中 |
1. textFormat
代码示例 1 :显示不同格式的文本
在界面上创建三个QLabel,尺寸放大一些,objectName分别为label,abel_2,label_3
第一个QLabel显示普通文本,第二个文本框显示html文本,第三个文本框显示markdown文本:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->setTextFormat(Qt::PlainText);
ui->label->setText("这是一段纯文本");
ui->label_2->setTextFormat(Qt::RichText);
ui->label_2->setText("<b>这是一段富文本</b>"); // 加粗
ui->label_3->setTextFormat(Qt::MarkdownText);
ui->label_3->setText("# 这是一段 markdown 文本"); // 一级标题
}
运行结果如下:
2. pixmap
代码示例 2 :显示图片
虽然QPushButton|也可以通过设置图标的方式设置图片,但是并非是一个好的选择,更多的时候
还是希望通过QLabel来作为一个更单纯的显示图片的方式。
- pixmap属性:可以通过这个属性设置label中包含的图片,我们可以把label设置成与窗口一样的大小和一样的位置。
在界面上创建一个 QLabe,objectName为label
创建 resource.qrc 文件,并把图片导入到 qrc 中
#include <QLabel>
#include <QPixmap>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置 label 大小和窗口一样大
QRect windowRect = this->geometry();
ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());
QPixmap pixmap(":/panda.png");
ui->label->setPixmap(pixmap);
}
执行程序,观察效果:
3. scaledContents
因为图片本身尺寸的问题,并没有把窗口填满
修改代码,设置 setScaledContents 属性:
// 开启图片相对于label的自动拉伸
ui->label->setScaledContents(true);
再次运行,观察效果,可以看到窗口已经被图片填满了:
但是这有一个问题,就是随着我们拉动我们的widget窗口的大小,我们发现,图片还是保持原有大小,并没有随着窗口的改变而铺满全场:
这是为什么?
因为上面的设置是在widget构造函数里面设置的,是一次性的,当程序跑起来过后,图片会铺满初始屏幕,但是之后窗口大小在发生改变的话,那么图片还是会保持原样不回变化;显然,这样的结果是不合理的,我们现在希望,图片能够随着窗口的大小的改变而发生改变。
为了解决这个问题,我们可以在Widget中重写resizeEvent()函数,这个函数会在窗口大小发生变化时自动调用:
#include "widget.h"
#include "ui_widget.h"
#include <QLabel>
#include <QPixmap>
#include <QResizeEvent>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置 label 大小和窗口一样大
QRect windowRect = this->geometry();
ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());
QPixmap pixmap(":/panda.png");
ui->label->setPixmap(pixmap);
// 开启图片相对于label的自动拉伸
ui->label->setScaledContents(true);
}
Widget::~Widget()
{
delete ui;
}
void Widget::resizeEvent(QResizeEvent *event)
{
ui->label->setGeometry(0, 0, event->size().width(), event->size().height());
qDebug() << event->size();
}
执行程序,此时改变窗口大小,图片也会随之变化:
在控制台查看窗口大小的改变过程:
上面的resizeEvent函数我们没有手动调用,但是能够在窗口大小发生变化时被自动调用,这个过程就是依赖于C++中的多态来实现的。Qt框架内部管理着QWidget对象表示咱们的窗口。在这个窗口发生改变的时候,Qt就会自动调用resizeEvent函数;但是由于实际上这个表示窗口的并非是QWidget,而是Widget。此时虽然是通过父类调用函数,,但是实际上执行的是子类的函数,也就是我们重写过后的resizeEvent函数。
这里先简单提一下,后面会详细说明的,我们已经知道了我们可以通过点击按钮触发信号,这就是用户操作的其中一个概念,还有一个概念就是事件,比如鼠标滑动、上述拖拽修改窗口大小等的时候就会触发resize事件(resizeEvent),我们的Widget窗口类要重写父类(QWidget)的resizeEvent虚函数。
鼠标拖拽窗口就会连续触发这个resizeEvent事件,连续触发这个事件就会反复调用这个函数,所以我们可以看到输出的QSize会连续改变。
4. alignment
代码示例 3 :文本对齐
创建四个label,objectName分别是 label 到 label_4。在右下角的属性栏中可以找到QFrame,其中的frameShape默认是NoFrame的,我们把这个属性改成Box就可以看到界面中的label有了边框(设置边框之后看起来会更清晰一些):
QFrame|是QLabel的父类,其中 frameShape 属性用来设置边框性质
- QFrame::Box:矩形边框
- QFrame::Panel:带有可点击区域的面板边框
- QFrame::WinPanel:Windows风格的边框
- QFrame::HLine:水平线边框
- QFrame::VLine:垂直线边框
- QFrame:: StyledPanel:带有可点击区域的面板边框,但样式取决于窗口主题
接下来我们可以设置label中的文字的对齐方式,就比如居中对齐(垂直方向对齐(vertical)+水平方向对齐(horizontal)):
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置水平对齐和垂直对齐
ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
// 设置右上对齐
ui->label_2->setAlignment(Qt::AlignRight | Qt::AlignTop);
}
运行效果如下:
QLabel中的原始对齐方式,垂直对齐+靠左对齐。
这也是枚举类型,我们可以看到定义中还有其他的对齐方式:
5. wordWrap
如果label的文本是一段特别长的文字,如果不换行就看不到后面的内容,这个属性就可以让文本自动换行:
// 设置自动换行
ui->label_3->setText("这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,");
ui->label_3->setWordWrap(true);
运行效果如下:
6. indent
设置文本的缩进,单位是像素,但是要注意的是,这个缩进不是首行缩进,而是所有行都会缩进:
// 设置自动换行
ui->label_3->setText("这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,");
ui->label_3->setWordWrap(true);
// 设置缩进
ui->label_3->setIndent(20);
运行效果如下:
7. margin
这个属性是来设置边距的,我们也可以用一段很长的文本来演示:
ui->label_4->setText("这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,这是一段很长的文本,");
// 将QLabel的边框描绘出来
ui->label_4->setFrameShape(QLabel::Shape::Panel);
// 置顶对齐+靠左对齐
ui->label_4->setAlignment(Qt::AlignTop | Qt::AlignLeft);
// 自动换行
ui->label_4->setWordWrap(true);
// 设置边距为30
ui->label_4->setMargin(30);
注意: 这是有效区域,如果有效区域太小,那么就无法显示所有文本:
8. buddy
这个属性可以绑定伙伴关系。创建两个label和两个radioButton,objectName分别问label, label_2,radioButton,radioButton_2
此处把label中的文本设置为"快捷键&A"这样的形式,其中&后面跟着的字符,就是快捷键。
可以通过 alt + A 的方式来触发该快捷键。但是注意,这里的快捷键和QPushButton的不同,需要搭配 alt 和单个字母的方式才能触发:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->setBuddy(ui->radioButton);
ui->label_2->setBuddy(ui->radioButton_2);
}
运行程序,只要绑定了伙伴关系就可以通过快捷键选中对应的按钮,按下快捷键 alt+ A 或者 alt+ B,即可选中对应的选项:
(二)LCD Number
QLCDNumber是一个专门显示数字的控件,类似于老式计算器:
核心属性如下:
属性 | 说明 |
---|---|
intValue | QLCDNumber显示的数字(int) |
value | QLCDNumber显示的数字(double) 和intValue是联动的(如:给value设置为1.5,intValue的值就是2) |
digCount | 显示几位数字 |
mode | 数字显示形式:
只有十进制时才能显示小数点后面的数字 |
segmentStyle | 设置显示的风格:
|
smallDecimalPoint | 设置比较小的小数点 |
代码示例:倒计时
在界面上创建一个QLCDNumber,初始值设为10,objectName为lcdNumber。创建一个 QTimer 成员,和一个 handle() 槽函数。
Qt中也封装了对应的定时器,结合了信号和槽的机制,通过QTimer这个类创建出来的对象,就会产生一个timeout信号,可以通过start方法开启定时器,并且参数中设定触发timeout信号的周期,这个周期可以我们自己设置:
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handle();
private:
Ui::Widget *ui;
QTimer *timer;
};
#endif // WIDGET_H
// widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QLabel>
#include <QPixmap>
#include <QResizeEvent>
#include <QDebug>
#include <QTimer>
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);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handle()
{
int value = ui->lcdNumber->intValue();
if(value <= 0)
{
timer->stop();
return;
}
ui->lcdNumber->display(value-1);
}
运行效果如下:
1.上述代码如果直接在Widget构造函数中,通过一个循环+ sleep 的方式是否可以呢?
#include <thread>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
while(true)
{
int value = ui->lcdNumber->intValue();
std::this_thread::sleep_for(std::chrono::seconds(1));
if(value <= 0) break;
ui->lcdNumber->display(value-1);
}
}
最终呈现出来的结果就是,只有0,没有出现像上述一样的倒计时显示:
通过前面的学习我们也应该知道,我们不能在构造函数中执行类似的上述操作,必须要通过槽函数,如果要在构造函数中写一个定时器,假如设定10s,那么程序就会在10s之后显示出来,这是因为构造函数调用完后才会执行main函数中的Widget的show方法,之后才会显示界面,所以无论之前的构造函数怎么修改,都不会看到我们想要的功能。
2.上述代码如果是在Widget构造函数中,另起一个线程,在新线程中完成 循环+ sleep是否可以呢?
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
std::thread t([this](){
int value = this->ui->lcdNumber->intValue();
while(true)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
if (value <= 0) break;
this->ui->lcdNumber->display(value-1);
}
});
}
可以看到,程序直接出现了错误,因为Qt中规定,任何对于GUI上内容的操作,必须在主线程中完成,像Widget构造函数,以及connect连接的slot函数,都是在主线程中调用的。而在我们自己创建的线程则不能,所以Qt中禁止了其他线程直接修改界面,当我们自己的线程中尝试对界面元素进行修改时,Qt程序往往会直接崩溃。
这样的约定主要是因为GUI中的状态往往是牵一发动全身的,修改一个地方,就需要同步的对其他内容进行调整。比如调整了某个元素的尺寸,就可能影响到内部的文字位置,或者其他元素的位置。这里一连串的修改,都是需要按照一定的顺序来完成的。由于多线程执行的顺序无法保障,因此Qt从根本上禁止了其他线程修改GUI状态,避免后续的一系列问题。
综上所述,使用定时器,是实现上述功能的最合理方案
后续如果我们也有类似的需要"周期性修改界面状态"的需求,也需要优先考虑使用定时器。
(三)ProgressBar
QProgressBar表示一个进度条。
核⼼属性如下:
属性 | 说明 |
---|---|
minimum | 进度条最⼩值 |
maximum | 进度条最⼤值 |
value | 进度条当前值 |
alignment | ⽂本在进度条中的对⻬⽅式 |
textVisible | 进度条的数字是否可⻅ |
orientation | 进度条的⽅向是⽔平还是垂直 |
invertAppearance | 是否是朝反⽅向增⻓进度 |
textDirection | ⽂本的朝向 |
format | 展⽰的数字格式 |
代码示例 : 设置进度条按时间增长
在界面上创建进度条,objectName为progressBar,初始值设为10:
初始化QTimer,此处设置100ms触发一次timeout信号,也就是一秒钟触发10次:
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &Widget::handle);
timer->start(100); // 100毫秒
}
Widget::~Widget()
{
delete ui;
}
void Widget::handle()
{
int value = ui->progressBar->value();
if(value >= 100)
{
timer->stop();
return;
}
ui->progressBar->setValue(value + 1);
}
运行效果如:
在实际开发中,进度条的取值,往往是根据当前任务的实际进度来进行设置的。比如需要读取一个很大的文件,就可以获取文件的总的大小,和当前读取完毕的大小,来设置进度条的比例。由于上面我们介绍了Qt禁止在其他线程修改界面,因此进度条的更新往往也是需要搭配定时器来完成的。
通过定时器周期触发信号,主线程调用对应的slot函数,再在slot函数中对当前的任务进度进
行计算,并更新进度条的界面效果。
代码示例 : 创建一个红色的进度条
可以通过前文我们学习的QSS来进行修改,其中的 chunk 是选中进度条中的每个"块",使用QProgressBar::text则可以选中文本.:
QProgressBar::chunk {background-color: rgb(255, 0, 0)}
前面的QProgressBar::chunk叫做选择器,目的就是确认后面设置的样式是针对那个控件生效,chunk就是进度条这块,后面大括号中就是添加的样式,运行后就可以看到颜色改变了:
此处如果不设置alignment,进度条中的数字会跑到左上角,这个怀疑是Qt本身的bug,暂时只能
先使用alignment来手动调整下:
我们一般的进度条可不是随便用的,一般都是根据实际任务来灵活设置,如果要读取一个文件,我们可以先获取文件的总大小,每读取一部分数据就可以更新一次进度条的数值,而且设置进度条的过程中往往要搭配定时器使用。
(四)Calendar Widget
QCalendarWidget表示一个“日历",形如:
其中的核心属性:
属性 | 说明 |
---|---|
selectDate | 当前选中的⽇期 |
minimumDate | 最⼩⽇期 |
maximumDate | 最⼤⽇期 |
firstDayOfWeek | 每周的第⼀天(也就是⽇历的第⼀列) 是周⼏ |
gridVisible | 是否显⽰表格的边框 |
selectionMode | 是否允许选择⽇期 |
navigationBarVisible | ⽇历上⽅标题是否显⽰ |
horizontalHeaderFormat | ⽇历上⽅标题显⽰的⽇期格式 |
verticalHeaderFormat | ⽇历第⼀列显⽰的内容格式 |
dateEditEnabled | 是否允许⽇期被编辑 |
在这个控件中也有一些重要的信号:
信号 | 说明 |
---|---|
selectionChanged(const QDate&) | 当选中的⽇期发⽣改变时发出 |
activated(const QDate&) | 当双击⼀个有效的⽇期或者按下回⻋键时发出,形参是⼀个QDate类型,保存了选中的⽇期 |
currentPageChanged(int, int) | 当年份⽉份改变时发出,形参表⽰改变后的新年份和⽉份 |
代码示例:获取选中的日期
在界面上创建一个QCalendarWidget和一个label,objectName为calendarWidget,label
给QCalendarWidget添加slot函数
void Widget::on_calendarWidget_selectionChanged()
{
QDate date = ui->calendarWidget->selectedDate();
qDebug() << date;
ui->label->setText(date.toString());
}
执行程序,可以看到当选择不同的日期时,label中的内容就会随之改变:
五、输入类控件
(一)Line Edit
QLineEdit 用来表示单行输入框,可以输入一段文本,但是不能换行:
属性 | 说明 |
---|---|
text | 输入框中的文本 |
inputMask | 输入内容格式约束 |
maxLength | 最大长度 |
frame | 是否添加边框 |
echoMode | 显示方式:
|
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是新的⽂本。代码对⽂本的修改不能触发这个信号 |
代码示例 1 :实现一个注册界面,录入个人信息,并且将用户信息打印在控制台
在界面上创建三个输入框和两个单选按钮,一个普通按钮,三个输入框的objectName为lineEdit_name,lineEdit_password,lineEdit_phone;两个单选按钮的objectName为radioButton_male,radioButton_female;按钮的objectName为pushButton
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 初始化第一个输入框
ui->lineEdit_name->setPlaceholderText("请输入姓名");
ui->lineEdit_name->setClearButtonEnabled(true);
// 初始化第二个输入框
ui->lineEdit_password->setPlaceholderText("请输入密码");
ui->lineEdit_password->setClearButtonEnabled(true);
ui->lineEdit_password->setEchoMode(QLineEdit::Password);
// 初始化第三个输入框
ui->lineEdit_phone->setPlaceholderText("请输入电话号码");
ui->lineEdit_phone->setClearButtonEnabled(true);
// 验证手机号码必须是 11 位数字,并且按照 "xxx-xxxx-xxxx" 格式输入
ui->lineEdit_phone->setInputMask("000-0000-0000");
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
QString gender = ui->radioButton_male->isChecked() ? "男" : "女";
qDebug() << "姓名: " << ui->lineEdit_name->text()
<< "密码: " << ui->lineEdit_password->text()
<< "性别: " << gender
<< "电话: " << ui->lineEdit_phone->text();
}
执行程序,可以看到,随着用户输入内容之后,点击确定按钮,就能打印到输入的信息:
上述代码设置了inputMask属性,也就是手机号,inputMask只能进行简单的输入格式校验。实际开发中,基于正则表达式的方式是更核心的方法。
代码示例 2 :使用正则表达式验证输入框的数据
此处要求在输入框中输入一个合法的电话号码(1 开头,11位,全都是数字),如果验证不通过,则确定按钮无法点击。
关于正则表达式:正则表达式是一种在计算机中常用的,使用特殊字符描述一一个字符串的特征的机制。在进行字符串匹配时非常有用。
正则表达式的语法还比较复杂,–般都是随用随查,不需要背下来。
正则表达式参考文档
正则表达式在线工具
继续使用上个例子的界面:
- 使用 QRegularExpression() 创建一个正则对象 “^1//d{10}$” 表示“以1开头,后面跟上任意10个十进制数字”。
- 使用 QRegularExpressionValidator 创建一个验证器对象,Qt 中内置了四个主要的验证器对象:
QRegularExpressionValidator,在匹配性能上做出了一定的优化。
把PushButton的enabled属性设置为false,如果LineEdit的文本验证不通过,那么提交按钮也就不能使用:
#include <QRegExpValidator>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置按钮默认是禁用状态
ui->pushButton->setEnabled(false);
// 初始化第一个输入框
ui->lineEdit_name->setPlaceholderText("请输入姓名");
ui->lineEdit_name->setClearButtonEnabled(true);
// 初始化第二个输入框
ui->lineEdit_password->setPlaceholderText("请输入密码");
ui->lineEdit_password->setClearButtonEnabled(true);
ui->lineEdit_password->setEchoMode(QLineEdit::Password);
// 初始化第三个输入框
ui->lineEdit_phone->setPlaceholderText("请输入电话号码");
ui->lineEdit_phone->setClearButtonEnabled(true);
// 验证手机号码必须是 11 位数字,并且按照 "xxx-xxxx-xxxx" 格式输入
// ui->lineEdit_phone->setInputMask("000-0000-0000");
// 给 lineEdit_phone 注册一个 validator
ui->lineEdit_phone->setValidator(new QRegularExpressionValidator(QRegularExpression("^1\\d{10}$")));
}
既然我们想要验证输入框的内容是否合法,那输入框的内容发生改变,验证器就要被执行。信号中有textChanged和textEdited都可以触发。只要LineEdit中的文本发生变化,就触发信号,转到槽函数中就要判断LineEdit验证器是否通过,如果可以就恢复按钮的可用性。给lineEdit 添加 textEdited 信号的 slot 函数
- void Widget::on_lineEdit_phone_textEdited(const QString &arg1) 的参数是当前输入框的内容。
- 通过 ui->lineEdit_phone->validator() 获取到内置的验证器。
void Widget::on_lineEdit_phone_textEdited(const QString &arg1)
{
QString content = arg1;
int pos = 0;
if(ui->lineEdit_phone->validator()->validate(content, pos) == QValidator::Acceptable)
{
// 验证通过,设置按钮位可用状态
ui->pushButton->setEnabled(true);
}
else
{
// 验证不通过,按钮位禁用状态
ui->pushButton->setEnabled(false);
}
}
判断验证器是否通过用到的就是其中的validate(QString&, int&):
- 第一个参数填写的是要验证的字符串,由于参数要求是 QString& ,而不是 const String&,需要把这个变量复制一下。
- 第二个参数是一个 int& ,是输出型参数,当验证的字符串不匹配时,返回这个字符串的长度。
- 返回值是一个枚举类型。QValidator::Acceptable 表示验证通过,QValidator::Invalid 表示不通过。
这第一个参数不是const是因为Validator也是可以自定义的,并重写validate函数,上图中可以看到QValidator::validate函数是一个纯虚函数,我们使用的是QRegExpValidator中的函数。如果想要验证后更改一下字符串,那就不能是const。
virtual QValidator::State validate(QString& input, int& pos) const override;
enum State {
Invalid, // 验证不通过
Intermediate,
Acceptable // 验证通过
};
所以判断返回值是否等于QValidator::Acceptable:
代码示例 3 :验证两次输入的密码是否一致
在界面上创建一个 label 和两个输入框
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QRegExpValidator>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->lineEdit->setEchoMode(QLineEdit::Password);
ui->lineEdit_2->setEchoMode(QLineEdit::Password);
ui->label->setText("密码为空");
}
Widget::~Widget()
{
delete ui;
}
void Widget::compare()
{
const QString &s1 = ui->lineEdit->text();
const QString &s2 = ui->lineEdit_2->text();
if(s1.isEmpty() && s2.isEmpty())
{
ui->label->setText("密码为空");
}
else if(s1 == s2)
{
ui->label->setText("再次输入的密码相同");
}
else
{
ui->label->setText("再次输入的密码不同");
}
}
void Widget::on_lineEdit_textEdited(const QString &arg1)
{
(void) arg1; //因为没有用到这个参数,编译器会报警告,这样写可以骗过编译器。
compare();
}
void Widget::on_lineEdit_2_textEdited(const QString &arg2)
{
(void) arg2;
compare();
}
运行程序,观察效果。当两次密码输入相同时,就会提示密码相同:
代码示例 4 :切换显示密码
当我们输入密码的时候,旁边都会有一个按钮让我们选择是否显示密码,现在我们也可以实现一下。
创建一个输入框和一个复选按钮
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->lineEdit->setEchoMode(QLineEdit::Password);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_checkBox_toggled(bool checked)
{
if(checked)
ui->lineEdit->setEchoMode(QLineEdit::Normal);
else
ui->lineEdit->setEchoMode(QLineEdit::Password);
}
执行程序,可以看到,切换复选框的状态,就可以控制输入框显示密码:
(二)Text Edit
QTextEidt 表示多行输入框,也是一个富文本 & markdown 编辑器,并且能在内容超出编辑范围时自动提供滚动条。
属性 | 说明 |
---|---|
markdown | 输⼊框内持有的内容. ⽀持 markdown 格式. 能够⾃动的对markdown ⽂本进⾏渲染成 html |
html | 输⼊框内持有的内容. 可以⽀持⼤部分 html 标签. 包括 img 和 table 等. |
placeHolderText | 输⼊框为空时提⽰的内容. |
readOnly | 是否是只读的 |
undoRedoEnable | 是否开启 undo / redo 功能.按下 ctrl + z 触发 undo;按下 ctrl + y 触发 redo |
autoFormating | 开启⾃动格式化 |
tabstopWidth | 按下缩进占多少空间 |
overwriteMode | 是否开启覆盖写模式 |
acceptRichText | 是否接收富⽂本内容 |
verticalScrollBarPolicy | 垂直⽅向滚动条的出现策略
|
horizontalScrollBarPolicy | ⽔平⽅向滚动条的出现策略
|
核心信号
信号 | 说明 |
---|---|
textChanged() | ⽂本内容改变时触发 |
selectionChanged() | 选中范围改变时触发 |
cursorPositionChanged() | 光标移动时触发 |
undoAvailable(bool) | 可以进⾏ undo 操作时触发 |
redoAvailable(bool) | 可以进⾏ redo 操作时触发 |
copyAvaiable(bool) | ⽂本被选中/取消选中时触发 |
代码示例:获取多行输入框的内容
创建一个多行输入框和一个label:
首先我们先来看一下textChanged()信号,只有textEdit中的文本发生改变,就会触发这个信号。通过toPlaiText()获取到内部的文本。类似的,QTextEdit还提供了toMarkdown和toHtml,根据需要我们调整不同的获取方式:
void Widget::on_textEdit_textChanged()
{
const QString& content = ui->textEdit->toPlainText();
ui->label->setText(content);
}
执行程序,可以看到当输入框中的内容发生变化时,label中的内容同步发生改变.
代码示例:验证输入框的各种信号
先依次输入“abcd”,光标位置和文本都在改变,光标向左移动,位置就会减一,最后选中文本,选中信号就会触发,同时光标位置也改变了。这些操作都是使用了QTextCursor中的方法。
// 文本改变就会触发信号
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” 四个字母时,undo可以触发也就是可以使用Ctrl+z,此时槽函数的参数就是true,按下组合键后,TextEdit中没有了文本,所以undo就不可用了,参数也就变成了false,而redo正好相反。当我们选中这四个字符后,copy就可以使用,参数变为true,取消选中参数变为false,这些也很好理解。
void Widget::on_textEdit_undoAvailable(bool b)
{
qDebug() << "undoAvailable: " << b;
}
void Widget::on_textEdit_redoAvailable(bool b)
{
qDebug() << "redoAvailable: " << b;
}
void Widget::on_textEdit_copyAvailable(bool b)
{
qDebug() << "copyAvailable: " << b;
}
(三)Combo Box
QComboBox 表示下拉框。核心属性:
属性 | 说明 |
---|---|
currentText | 当前选中的⽂本 |
currentIndex | 当前选中的条⽬下标。从 0 开始计算,如果当前没有条⽬被选中,值为 -1 |
editable | 是否允许修改,设为 true 时,QComboBox 的⾏为就⾮常接近 QLineEdit,也可以设置 validator |
iconSize | 下拉框图标 (⼩三⻆) 的⼤⼩ |
maxCount | 最多允许有多少个条⽬ |
核心方法:
核心信号:
信号 | 说明 |
---|---|
activated(int) | 当⽤⼾选择了⼀个选项时发出 |
activated(const QString & text) | 当⽤⼾选择了⼀个选项时发出 |
currentIndexChanged(int) | 当前选项改变时发出 |
currentIndexChanged(const QString & text) | 当前选项改变时发出 |
editTextChanged(const QString & text) | 当编辑框中的⽂本改变时发出(editable 为 true 时有效) |
代码示例 : 使用下拉框模拟点餐
在界面上创建三个下拉框和一个按钮:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->comboBox->addItem("巨无霸");
ui->comboBox->addItem("麦辣鸡腿堡");
ui->comboBox_2->addItem("薯条");
ui->comboBox_2->addItem("虾饼");
ui->comboBox_3->addItem("可乐");
ui->comboBox_3->addItem("雪碧");
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
qDebug() << "汉堡选择: " << ui->comboBox->currentText();
qDebug() << "小食选择: " << ui->comboBox_2->currentText();
qDebug() << "饮料选择: " << ui->comboBox_3->currentText();
}
选择选项,查看效果:
我们还可以直接在图形化界面中添加:
代码示例:从文件中加载下拉框的选项
很多时候下拉框的选项并非是固定的,而是通过读取文件/读取网络获取到的。
通过读取文件选择:选择一个路径里面创建一个文件,文件中写入饮料,在构造函数中打开这个文件,按行读取文件中的内容,并依次设置到ComboBox中:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <fstream>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
std::ifstream file("D:\\C++\\QT\\code\\QComboBox\\config.txt");
std::string line;
while(std::getline(file, line))
{
ui->comboBox->addItem(QString::fromStdString(line));
}
file.close();
}
Widget::~Widget()
{
delete ui;
}
运行效果如下:
(四)SpinBox
使用QSpinBox或者QDoubleSpinBox表示"微调框",它是带有按钮的输入框,可以用来输入整
数 / 浮点数,通过点击按钮来修改数值大小。
由于SpinBox 和QDoubleSpinBox用法基本相同,就只介绍SpinBox| 的使用了
核心属性:
属性 | 说明 |
---|---|
value | 存储的数值 |
singleStep | 每次调整的 “步⻓”. 按下⼀次按钮数据变化多少。 |
displayInteger | 数字的进制. 例如 displayInteger 设为 10, 则是按照 10 进制表⽰. 设为 2 则为 2进制表⽰。 |
minimum | 最⼩值 |
maximum | 最⼤值 |
suffix | 后缀 |
prefix | 前缀 |
wrapping | 是否允许换⾏ |
frame | 是否带边框 |
alignment | ⽂字对⻬⽅式. |
readOnly | 是否允许修改 |
buttonSymbol | 按钮上的图标
|
accelerated (加速的) | 按下按钮时是否为快速调整模式 |
correctionMode | 输⼊有误时如何修正
|
keyboardTrack | 是否开启键盘跟踪
|
核心信号:
信号 | 说明 |
---|---|
textChanged(QString) | 微调框的⽂本发⽣改变时会触发.参数 QString 带有 前缀 和 后缀. |
valueChanged(int) | 微调框的⽂本发⽣改变时会触发.参数 int, 表⽰当前的数值. |
代码示例 :调整麦当劳购物车的份数
在界面上创建内容:三个下拉框:objectName为comboBox 到comboBox_3;三个微调框: objectName为spinBox到spinBox_3;一个按钮:objectName 为pushButton
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 初始化下拉框
ui->comboBox->addItem("巨无霸");
ui->comboBox->addItem("麦辣鸡腿堡");
ui->comboBox_2->addItem("薯条");
ui->comboBox_2->addItem("虾饼");
ui->comboBox_3->addItem("可乐");
ui->comboBox_3->addItem("雪碧");
// 初始化微调框
ui->spinBox->setValue(1);
ui->spinBox->setRange(1, 5);
ui->spinBox_2->setValue(1);
ui->spinBox_2->setRange(1, 5);
ui->spinBox_3->setValue(1);
ui->spinBox_3->setRange(1, 5);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
qDebug() << "当前下单内容: \n"
<< ui->comboBox->currentText() << ": " << ui->spinBox->value() << "份\n"
<< ui->comboBox_2->currentText() << ": " << ui->spinBox_2->value() << "份\n"
<< ui->comboBox_3->currentText() << ": " << ui->spinBox_3->value() << "份";
}
运行效果如下:
(五)Date Edit & Time Edit
QDateEdit 和 QTimeEdit 分别作为日期和时间的微调框,还有一个是QDateTimeEdit日期时间微调框,我们主要说这个控件,其他几个的用法也是非常相似。
属性 | 说明 |
---|---|
dateTime | 时间⽇期的值. 形如 2000/1/1 0:00:00 |
date | 单纯⽇期的值. 形如 2001/1/1 |
time | 单纯时间的值. 形如 0:00:00 |
displayFormat | 时间⽇期格式.形如 yyyy/M/d H:mm;
|
minimumDateTime | 最⼩时间⽇期 |
maximumDateTime | 最⼤时间⽇期 |
timeSpec |
|
关于本地时间(LocalTime)和协调世界时(UTC):
UTC时间是一个基于原子钟的标准时间,不受地球的自转周期影响,和格林威治时间(GMT)是非常接近的,科学家会通过精密的设备来测量并维护。咱们的计算机内部使用的时间就是基于UTC时间。
本地时间则是基于不同的时区,对UTC时间做出了一些调整。比如咱们使用的北京时间,位于“东八区”,就需要在UTC时间基础上+8个小时的时差。
核心信号:
信号 | 说明 |
---|---|
dateChanged(QDate) | ⽇期改变时触发 |
timeChanged(QTime) | 时间改变时触发 |
dateTimeChanged(QDateTime) | 时间⽇期任意⼀个改变时触发 |
代码示例:实现日期计算器
在界面上创建两个QDateTimeEdit和一个按钮,一个label
QDateTimeEdit的objectName 为dateTimeEdit_old和dateTimeEdit_new
编写计算按钮的slot函数
- 使用 daysTo() 函数可以计算两个日期的天数
- secsTo() 函数可以计算两个时间的秒数
- 通过(秒数/ 3600) 换算成小时数,再余上24得到零几个小时
- 使用QString::number把整数转成QString进行拼接
void Widget::on_pushButton_clicked()
{
// 获取到两个时间框的时间日期
QDateTime timeOld = ui->dateTimeEdit_old->dateTime();
QDateTime timeNew = ui->dateTimeEdit_new->dateTime();
// 计算日期差值
int days = timeOld.daysTo(timeNew);
int hours = (timeOld.secsTo(timeNew) / 3600) % 24;
// 设置 label 内容
ui->label->setText(QString::number(days) + "天" + QString::number(hours) + QString("个小时"));
}
运行结果如下:
结果不正确,这是因为Qt 给的计算天数的函数 daysTo() 有一点问题
查阅文档可知:
返回从该日期时间到另一日期时间的天数。天数是指从这个日期时间到另一个日期时间之间达到午夜的次数。这意味着从23:55到第二天0:05的10分钟差算作一天。
所以修改代码,用秒数来计算:
void Widget::on_pushButton_clicked()
{
// 获取到两个时间框的时间日期
QDateTime timeOld = ui->dateTimeEdit_old->dateTime();
QDateTime timeNew = ui->dateTimeEdit_new->dateTime();
// 计算日期差值
// int days = timeOld.daysTo(timeNew);
int days = (timeOld.secsTo(timeNew) / 3600) / 24;
int hours = (timeOld.secsTo(timeNew) / 3600) % 24;
// 设置 label 内容
ui->label->setText(QString::number(days) + "天" + QString::number(hours) + QString("个小时"));
}
计算结果正确:
(六)Dial
QDial 表示一个旋钮。下面是他的核心属性:
属性 | 说明 |
---|---|
value | 持有的数值 |
minimum | 最⼩值 |
maximum | 最⼤值 |
singleStep | 按下⽅向键的时候改变的步⻓ |
pageStep | 按下 pageUp / pageDown 的时候改变的步⻓ |
sliderPosition | 界⾯上旋钮显⽰的初始位置 |
tracking | 外观是否会跟踪数值变化。默认值为 true,⼀般不需要修改 |
wrapping | 是否允许循环调整。即数值如果超过最⼤值,是否允许回到最⼩值。(调整过程能否 “套圈”) |
notchesVisible | 是否显⽰刻度线 |
notchTarget | 刻度线之间的相对位置。数字越⼤,刻度线越稀疏。 |
核心信号:
信号 | 说明 |
---|---|
valueChanged(int) | 数值改变时触发 |
rangeChanged(int, int) | 范围变化时触发 |
代码示例:通过旋钮调整窗透明度
在界面上创建一个旋钮和一个label
通过旋钮就可以调整一些属性,比如我们前面说过的不透明度windowOpacity,wrapping就表示旋钮可否循环,notchesVisible表示是否显示刻度,我们可以使用valueChanged信号实现:
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置可以循环旋转
ui->dial->setWrapping(true);
//设置刻度线可见
ui->dial->setNotchesVisible(true);
//设置最大值
ui->dial->setMaximum(100);
//设置最小值
ui->dial->setMinimum(0);
//设置初始值为
ui->dial->setValue(100);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_dial_valueChanged(int value)
{
ui->label->setText(QString("当前不透明度为: ") + QString::number(value));
this->setWindowOpacity((double)value / 100);
}
运行效果如下:
(七)Slider
QSlider 表示一个滑动条,它和 QDial 这个控件都是继承自QAbstractSlider,所以用法基本相同:
属性 | 说明 |
---|---|
value | 数值 |
minimum | 最小值 |
maximum | 最大值 |
singleStep | 按下方向键时改变的步长 |
pageStep | 按下pageUp/pageDown时改变的步长 |
sliderPosition | 滑动条显示的初始位置 |
tracking | 外观是否会跟踪数值变化 默认值为true,一般不需要修改 |
orientation | 滑动条的方向是水平还是垂直 |
invertedAppearance | 是否要翻转滑动条的方向 |
tickPosition | 刻度的位置 |
tickInterval | 刻度的密集程度 |
核心信号:
信号 | 说明 |
---|---|
valueChanged(int) | 数值改变时触发 |
rangedChanged(int,int) | 范围改变时触发 |
代码示例:调整窗口大小
在界面上创建两个滑动条,分别是水平和垂直滑动条。objectName分别为 horizontalSlider 和verticalSlider
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->horizontalSlider->setMinimum(500);
ui->horizontalSlider->setMaximum(2000);
ui->horizontalSlider->setSingleStep(100);
ui->horizontalSlider->setValue(800);
ui->verticalSlider->setMinimum(500);
ui->verticalSlider->setMaximum(1500);
ui->verticalSlider->setSingleStep(100);
ui->verticalSlider->setValue(600);
// 翻转朝向,默认滑块从下往上增长,改成从下往上增长
ui->verticalSlider->setInvertedAppearance(true);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_horizontalSlider_valueChanged(int value)
{
QRect rect = this->geometry();
this->setGeometry(rect.x(), rect.y(), value, rect.height());
}
void Widget::on_verticalSlider_valueChanged(int value)
{
QRect rect = this->geometry();
this->setGeometry(rect.x(), rect.y(), rect.width(), value);
}
当滑动滑块时,窗口的大小就会发生相应的变化:
代码示例:通过自定义快捷键调整滑动条位置
设置 - 减小value,设置 = 增加value(+和=在同一个键),控制水平方向的滑动条
默认情况下滑动条可以通过方向键或者pageUp / pageDown调整大小.
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QShortcut>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->horizontalSlider->setMinimum(500);
ui->horizontalSlider->setMaximum(2000);
ui->horizontalSlider->setSingleStep(100);
ui->horizontalSlider->setValue(800);
ui->verticalSlider->setMinimum(500);
ui->verticalSlider->setMaximum(1500);
ui->verticalSlider->setSingleStep(100);
ui->verticalSlider->setValue(600);
// 翻转朝向,默认滑块从下往上增长,改成从下往上增长
ui->verticalSlider->setInvertedAppearance(true);
// 设置快捷键
QShortcut *shortCut1 = new QShortcut(this);
shortCut1->setKey(QKeySequence("-"));
connect(shortCut1, &QShortcut::activated, this, &Widget::sub);
QShortcut *shortCut2 = new QShortcut(this);
shortCut2->setKey(QKeySequence("="));
connect(shortCut2, &QShortcut::activated, this, &Widget::add);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_horizontalSlider_valueChanged(int value)
{
QRect rect = this->geometry();
this->setGeometry(rect.x(), rect.y(), value, rect.height());
}
void Widget::on_verticalSlider_valueChanged(int value)
{
QRect rect = this->geometry();
this->setGeometry(rect.x(), rect.y(), rect.width(), value);
}
void Widget::sub()
{
int value = ui->horizontalSlider->value();
ui->horizontalSlider->setValue(value - 20);
}
void Widget::add()
{
int value = ui->horizontalSlider->value();
ui->horizontalSlider->setValue(value + 20);
}
通过 “-”和“=” 快捷键可以控制水平方向的窗口大小:
六、多元素控件
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了。xxxWidget和xxxView的区别:
以QTableWidget和QTableView为例:
- QTableView 是基于 MVC 设计的控件。QTableView ⾃⾝不持有数据,使⽤ QTableView 的
时候需要⽤⼾创建⼀个 Model 对象 (⽐如 QStandardModel ),并且把 Model 和
QTableView 关联起来。后续修改 Model 中的数据就会影响 QTableView 的显⽰;修改
QTableView 的显⽰也会影响到 Model 中的数据(双向绑定)。 - QTableWidget 则是 QTableView 的⼦类,对 Model 进⾏了封装,不需要⽤⼾⼿动创建
Model 对象,直接就可以往 QTableWidget 中添加数据了。
(一)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, introw) | 在指定的位置插⼊元素 |
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) | ⿏标进⼊元素时触发 |
在上述介绍中,涉及到一个关键的类,QListWidgetItem。这个类表示 QListWidget 中的一个元素。核心方法如下:本质就是一个 “文本” + “图标” 组成的。
方法 | 说明 |
---|---|
setFont | 设置字体 |
setIcon | 设置图标 |
setHidden | 设置隐藏 |
setSizeHint | 设置尺⼨ |
setSelected | 设置是否选中 |
setText | 设置⽂本 |
setTextAlignment | 设置⽂本对⻬⽅式 |
代码示例: 在输入框中输入选项来,添加进QListWidget,点击删除按钮来删除选中的选项
在界面上创建一个 ListWidget (也可以使用ListView,然后右键点击 “变型为”,可以变型为 ListWidget),再创建一个 lineEdit 和两个按钮:
注:ListWidget是ListView的子类,功能比ListView更丰富,使用 ListWidget即可。
或者:
运行程序,可以通过输入框新增元素,选中元素,删除元素
(二)Table Widget
使用QTableWidget 表示一个表格控件,一个表格中包含若干行,每一行又包含若干列,表格中的每个单元格,是一个QTableWidgetItem对象。
QTableWidget核心方法:
方法 | 说明 |
---|---|
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, QTableWidgetItem*) | 设置指定列的表头 |
setVerticalHeaderItem(int row, QTableWidgetItem*) | 设置指定⾏的表头 |
QTableWidgetItem核心信号:
信号 | 说明 |
---|---|
cellClicked(int row, int column) | 点击单元格时触发 |
cellDoubleClicked(int row, int column) | 双击单元格时触发 |
cellEntered(int row, int column) | ⿏标进⼊单元格时触发 |
currentCellChanged(int row, int column, int previousRow, int previousColumn) | 选中不同单元格时触发 |
QTableWidgetItem核⼼⽅法:
方法 | 说明 |
---|---|
row() | 获取当前是第⼏⾏ |
column() | 获取当前是第⼏列 |
setText(const QString&) | 设置⽂本 |
setTextAlignment(int) | 设置⽂本对⻬ |
setIcon(const QIcon&) | 设置图标 |
setSelected(bool) | setSelected(bool) |
setSizeHints(const QSize&) | 设置尺⼨ |
setFont(const QFont&) | 设置字体 |
代码示例:使用QTableWidget
在界面上创建QTableWidget 和四个按钮,一个输入框
注意:QTableWidget是QTableView的子类,功能比QTableView更丰富,使用 QTableWidget即可。
#include "widget.h"
#include "ui_widget.h"
#include <QListWidget>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// ui->listWidget->addItem("C++");
// ui->listWidget->addItem("Java");
// ui->listWidget->addItem("Python");
//默认情况下运行后单元格是可允许被编辑的,如不想被编辑可以加入这行代码
//ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
// 创建三行
ui->tableWidget->insertRow(0);
ui->tableWidget->insertRow(1);
ui->tableWidget->insertRow(2);
// 创建三列
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("1001"));
ui->tableWidget->setItem(0, 1, new QTableWidgetItem("张三"));
ui->tableWidget->setItem(0, 2, new QTableWidgetItem("20"));
ui->tableWidget->setItem(1, 0, new QTableWidgetItem("1002"));
ui->tableWidget->setItem(1, 1, new QTableWidgetItem("李四"));
ui->tableWidget->setItem(1, 2, new QTableWidgetItem("19"));
ui->tableWidget->setItem(2, 0, new QTableWidgetItem("1003"));
ui->tableWidget->setItem(2, 1, new QTableWidgetItem("王五"));
ui->tableWidget->setItem(2, 2, new QTableWidgetItem("21"));
}
Widget::~Widget()
{
delete ui;
}
//void Widget::on_pushButton_insert_clicked()
//{
// // 获取当前输入框的内容
// const QString &text = ui->lineEdit->text();
// if(text.isEmpty()) return;
// ui->listWidget->addItem(text);
//}
//void Widget::on_pushButton_delete_clicked()
//{
// // 获取当前被选择元素的行数
// int row = ui->listWidget->currentRow();
// // 如果未选中 row 为 -1
// // 删除
// if(row != -1)
// {
// ui->listWidget->takeItem(row);
// }
//}
void Widget::on_pushButton_insertRow_clicked()
{
// 获取到行数
int rowCount = ui->tableWidget->rowCount();
// 插入新行
ui->tableWidget->insertRow(rowCount);
}
void Widget::on_pushButton_insertCol_clicked()
{
// 获取到列数
int colCount = ui->tableWidget->columnCount();
// 插入新列
ui->tableWidget->insertColumn(colCount);
// 设置列名
const QString &name = ui->lineEdit->text();
ui->tableWidget->setHorizontalHeaderItem(colCount, new QTableWidgetItem(name));
}
void Widget::on_pushButton_eraseRow_clicked()
{
// 获取选中的行号
int curRow = ui->tableWidget->currentRow();
// 删除对应的行
ui->tableWidget->removeRow(curRow);
}
void Widget::on_pushButton_eraseCol_clicked()
{
// 获取到选中的列号
int curCol = ui->tableWidget->currentColumn();
// 删除对应的列
ui->tableWidget->removeColumn(curCol);
}
运行程序,即可操作该表格:
(三)Tree Widget
使用QTreeWidget表示一个树形控件,里面的每个元素,都是一个QTreeWidgetItem,每个
QTreeWidgetItem可以包含多个文本和图标,每个文本 / 图标为一个列。可以给 QTreeWidget 设置顶层节点(顶层节点可以有多个),然后再给顶层节点添加子节点,从而构成树形结构。
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 名称 |
QTreeWidget 核心信号:
信号 | 说明 |
---|---|
currentItemChanged(QTreeWidgetItemcurrent, 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() | 获取该元素的⽗节点 |
代码示例:使用QTreeWidget
在界面上创建一个TreeView,右键=>变形为=> TreeWi dget,再创建一个lineEdit和三个按钮
注意:TreeWidget是TreeView的子类,功能比TreeVi ew更丰富,使用TreeWidget 即可。
编写 widget.cpp 代码,也可以直接在 ui 界面添加:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->treeWidget->setHeaderLabel("动物");
QTreeWidgetItem *item1 = new QTreeWidgetItem();
item1->setText(0, "猫");
ui->treeWidget->addTopLevelItem(item1);
QTreeWidgetItem *item2 = new QTreeWidgetItem();
item2->setText(0, "狗");
ui->treeWidget->addTopLevelItem(item2);
QTreeWidgetItem *item3 = new QTreeWidgetItem();
item3->setText(0, "鸟");
ui->treeWidget->addTopLevelItem(item3);
}
编写按钮的槽函数:
void Widget::on_pushButton_addTop_clicked()
{
// 获取输入框的内容
const QString &text = ui->lineEdit->text();
if(text.isEmpty()) return;
// 添加到顶层节点中
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText(0, text);
ui->treeWidget->addTopLevelItem(item);
}
void Widget::on_pushButton_add_clicked()
{
// 获取输入框的内容
const QString &text = ui->lineEdit->text();
if(text.isEmpty()) return;
// 获取到当前选中的j节点
QTreeWidgetItem *currentItem = ui->treeWidget->currentItem();
if(currentItem == nullptr) return;
// 构造新的 Item
QTreeWidgetItem *newItem = new QTreeWidgetItem();
newItem->setText(0, text);
// 添加到 item 到选择节点
currentItem->addChild(newItem);
// 展开父节点
currentItem->setExpanded(true);
}
void Widget::on_pushButton_delete_clicked()
{
// 获取到当前选中的节点
QTreeWidgetItem *currItem = ui->treeWidget->currentItem();
if(currItem == nullptr) return;
// 获取当前节点的父节点
QTreeWidgetItem *parent = currItem->parent();
if(parent == nullptr)
{
// 顶层节点
int index = ui->treeWidget->indexOfTopLevelItem(currItem);
ui->treeWidget->takeTopLevelItem(index);
}
else
{
// 非顶层节点
parent->removeChild(currItem);
}
}
添加到顶层元素操作:
添加到选中元素操作:
删除选中元素操作(删除鸟类元素):
七、容器类控件
(一)Group Box
QGroupBox是一个分组框,可以把其他控件放到里面作为一组,所以内部的控件的父元素就不是this了,而是GroupBox,这个控件只是在界面中控件多的时候把那些具有关联关系的控件组织到一起,这样就会更清晰,更好看。下面就是它的属性
属性 | 方法 |
---|---|
title | 分组框的标题 |
alignment | 分组框内部内容的对⻬⽅式 |
flat | 是否是 “扁平” 模式 |
checkable | 是否可选择,设为 true, 则在 title 前⽅会多出⼀个可勾选的部分. |
checked | 描述分组框的选择状态 (前提是 checkable 为 true) |
分组框只是⼀个⽤来 “美化界⾯” 这样的组件,并不涉及到⽤⼾交互和业务逻辑,属于 “锦上添花” 。
代码示例:给麦当劳案例加上分组框
在界面上创建三个分组框,并且在分组框内部创建下拉框和微调框,通过右侧的对象框也可以看出,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) | 在标签⻚关闭时触发. 参数为被关闭的选项卡编号. |
代码示例:使用标签页管理多组控件
在界面上创建一个QTabWidget,和一个按钮。按钮的objectName为pushButton,并勾选标签页的可关闭按钮的显示:
注意:QTabWidget中的每个标签页都是一个QWidget点击标签页,就可以直接切换,右键QTabWidget,可以添加标签页或者删除标签页。
编写widget.cpp,进行初始化,给标签页中放个简单的label:
- 注意新创建的label的父元素,是ui->tab和ui->tab_2。Qt中使用父子关系决定该控件"在
哪里"。
#include "widget.h"
#include "ui_widget.h"
#include <QLabel>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
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);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
// 获取当前有多少个标签页
int count = ui->tabWidget->count();
// 创建新的 widget
QWidget *w = new QWidget();
ui->tabWidget->addTab(w, QString("Tab") + QString::number(count + 1));
// 给 widget 中添加label
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);
}
运行程序,可以通过按钮添加标签页,也可删除标签页:
八、Layouts(布局管理器)
之前使用Qt在界面上创建的控件,都是通过"绝对定位"的方式来设定的(拖拽或者通过代码这种“手动”的方式),也就是每个控件所在的位置,都需要计算坐标,最终通过 setGeometry 或者move 方式摆放过去。
这种设定方式其实并不方便,尤其是界面如果内容比较多,不好计算,而且一个窗口大小往往是可以调整的,按照绝对定位的方式,并且也无法自适应窗口大小。
因此Qt引入"布局管理器" (Layout)机制,来解决上述问题。
当然,布局管理器并非Qt独有。其他的GUI开发框架,像Android、前端等也有类似的机制。
(一)垂直布局
使用 QVBoxLayout 表示垂直的布局管理器,V是vertical的缩写:
核心属性:
属性 | 说明 |
---|---|
layoutLeftMargin | 左侧边距 |
layoutRightMargin | 右侧边距 |
layoutTopMargin | 上⽅边距 |
layoutBottomMargin | 下⽅边距 |
layoutBottomMargin | 下⽅边距 |
layoutSpacing | 相邻元素之间的间距 |
注意:Layout只用于页面布局,没有提供信号。
代码示例:使用QVBoxLayout 管理多个控件
编写代码,创建布局管理器和三个按钮,并且把按钮添加到布局管理器中。使用addWidget 把控件添加到布局管理器中.:
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QVBoxLayout>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建三个按钮
QPushButton *b1 = new QPushButton("按钮1");
QPushButton *b2 = new QPushButton("按钮2");
QPushButton *b3 = new QPushButton("按钮3");
// 创建布局管理器,并且把按钮添加进去
// 指定父元素为 this
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(b1);
layout->addWidget(b2);
layout->addWidget(b3);
}
Widget::~Widget()
{
delete ui;
}
运行程序,可以看到此时界面上的按钮就存在于布局管理器中,随着窗口尺寸变化而发生改变。
此时三个按钮的尺寸和位置,都是自动计算出来的:
通过上述代码的方式,只能给这个widget设定一个布局管理器。实际上也可以通过Qt Design在一个窗口中创建多个布局管理器。
代码示例:创建两个QVBoxLayout
在界面上创建两个QVBoxLayout, 每个QVBoxLayout 各放三个按钮:
运行后就发现了问题,这个界面并没有随着窗口大小改变而改变,这是因为我们在代码中创建的Layout就是一个Layout,但是在图形化界面中操作中先创建了一个Widget,再创建一个Layout:
一个父Widget可以包含多个子Widget,那这些子Widget又可以包含多个Layout,在Layout中就可以创建控件了。
所以,如果想要界面中的控件随着窗口大小改变而改变,那就在代码中设置一个Layout,如果只是想让一些控件放到一起,就可以通过图形化界面的方式直接拖拽创建。
创建Layout和创建控件的顺序可以颠倒,可以选中想要放到布局管理器中的控件,然后点击窗口上方的这个按钮就可以了:
(二)水平布局
使用 QHBoxLayout 表示水平的布局管理器,H是horizontal的缩写。
核心属性:
属性 | 说明 |
---|---|
layoutLeftMargin | 左侧边距 |
layoutRightMargin | 右侧边距 |
layoutTopMargin | 上⽅边距 |
layoutBottomMargin | 下⽅边距 |
layoutBottomMargin | 下⽅边距 |
layoutSpacing | 相邻元素之间的间距 |
代码示例:使用 QHBoxLayout 管理控件
编写代码,创建布局管理器和三个按钮,并且把按钮添加到布局管理器中:
#include <QHBoxLayout>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建三个按钮
QPushButton *b1 = new QPushButton("按钮1");
QPushButton *b2 = new QPushButton("按钮2");
QPushButton *b3 = new QPushButton("按钮3");
QHBoxLayout *layout = new QHBoxLayout(this);
layout->addWidget(b1);
layout->addWidget(b2);
layout->addWidget(b3);
}
运行程序,可以看到此时界面上的按钮就存在于布局管理器中,随着窗口尺寸变化而发生改变。
此时三个按钮的尺寸和位置,都是自动计算出来的:
Layout里面可以再嵌套上其他的layout,从而达到更复杂的布局效果。
代码示例:嵌套的layout
在代码中创建以下内容,使用addLayout给layout中添加子layout.:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建顶层layout
QVBoxLayout *vLayout = new QVBoxLayout(this);
// 添加两个按钮进去
QPushButton *b4 = new QPushButton("按钮4");
QPushButton *b5 = new QPushButton("按钮5");
vLayout->addWidget(b4);
vLayout->addWidget(b5);
// 创建要嵌套进去的 layout
QHBoxLayout *hLayout = new QHBoxLayout(this);
// 添加两个按钮进去
QPushButton *b6 = new QPushButton("按钮6");
QPushButton *b7 = new QPushButton("按钮7");
hLayout->addWidget(b6);
hLayout->addWidget(b7);
// 嵌套
vLayout->addLayout(hLayout);
}
执行程序,观察效果:
通过这种方式就可以进行嵌套,而且按钮也可以根据窗口改变而改变。
(三)网格布局
Qt中还提供了 QGridLayout 用来实现网格布局的效果,可以达到M * N的这种网格的效果。
核心属性:
整体和QVBoxLayout 以及 QHBoxLayout 相似,但是设置spacing的时候是按照垂直水平两个方向来设置的。
属性 | 说明 |
---|---|
layoutLeftMargin | 左侧边距 |
layoutRightMargin | 右侧边距 |
layoutTopMargin | 上⽅边距 |
layoutBottomMargin | 下⽅边距 |
layoutBottomMargin | 下⽅边距 |
layoutHorizontalSpacing | 相邻元素之间⽔平⽅向的间距 |
layoutVerticalSpacing | 相邻元素之间垂直⽅向的间距 |
layoutRowStretch | ⾏⽅向的拉伸系数 |
layoutColumnStretch | 列⽅向的拉伸系数 |
代码示例:使用 QGridLayout 管理元素
代码中创建QGridLayout和4个按钮,使用 addWidget 添加控件到布局管理器中,但是添加的同时会指定两个坐标,表示放在第几行第几列:
#include <QGridLayout>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建四个按钮
QPushButton *b1 = new QPushButton("按钮1");
QPushButton *b2 = new QPushButton("按钮2");
QPushButton *b3 = new QPushButton("按钮3");
QPushButton *b4 = new QPushButton("按钮4");
// 创建网格布局管理器,并添加元素
QGridLayout *layout = new QGridLayout();
layout->addWidget(b1, 0, 0);
layout->addWidget(b2, 0, 1);
layout->addWidget(b3, 1, 0);
layout->addWidget(b4, 1, 1);
// 设置 layout 至窗口中
this->setLayout(layout);
}
执行代码,观察效果,可以看到当前的这几个按钮是按照2行2列的方式排列的:
如果调整行列坐标为下列代码:
layout->addWidget(b1, 0, 0);
layout->addWidget(b2, 0, 1);
layout->addWidget(b3, 0, 2);
layout->addWidget(b4, 0, 3);
执行代码,可以看到这几个按钮都在同一行了,相当于QHBoxLayout:
如果调整坐标为下列代码:
layout->addWidget(b1, 1, 0);
layout->addWidget(b2, 2, 0);
layout->addWidget(b3, 3, 0);
layout->addWidget(b4, 4, 0);
执行代码,可以看到这几个按钮都在同一-列了,相当于QVBoxLayout
任意调整行列,即可看到不同的效果:
layout->addWidget(b1, 0, 0);
layout->addWidget(b2, 1, 1);
layout->addWidget(b3, 2, 2);
layout->addWidget(b4, 3, 3);
编写代码形如:
layout->addWidget(b1, 0, 0);
layout->addWidget(b2, 1, 1);
layout->addWidget(b3, 2, 2);
layout->addWidget(b4, 100, 100);
此处也要注意,设置行和列的时候,如果设置的是一个很大的值,但是这个值和上一个值之间并 没有其他的元素,那么并不会在中间腾出额外的空间:
代码示例:设置QGridLayout中元素的大小比例
创建6个按钮,按照2行3列的方式排列,使用 setColumnStretch 设置每一列的拉伸系数:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建六个按钮
QPushButton *b1 = new QPushButton("按钮1");
QPushButton *b2 = new QPushButton("按钮2");
QPushButton *b3 = new QPushButton("按钮3");
QPushButton *b4 = new QPushButton("按钮4");
QPushButton *b5 = new QPushButton("按钮5");
QPushButton *b6 = new QPushButton("按钮6");
// 创建网格布局管理器,并添加元素
QGridLayout *layout = new QGridLayout();
layout->addWidget(b1, 0, 0);
layout->addWidget(b2, 0, 1);
layout->addWidget(b3, 0, 2);
layout->addWidget(b4, 1, 0);
layout->addWidget(b5, 1, 1);
layout->addWidget(b6, 1, 2);
// 设置拉伸比例
// 第0列拉伸比例设置为1
layout->setColumnStretch(0, 1);
// 第1列拉伸比例设置为0,纪委固定大小,不参与拉伸
layout->setColumnStretch(1, 0);
// 第2列拉伸比例设置为3
layout->setColumnStretch(2, 3);
// 设置 layout 至窗口中
this->setLayout(layout);
}
执行程序,可以看到每一列的宽度是不同的,并且随着鼠标拖拽窗口调整动态变化:
另外,QGridLayout也提供了 setRowStretch 设置行之间的拉伸系数,上述案例中,直接设置setRowStretch 效果不明显,因为每个按钮的高度是固定的,需要把按钮的垂直方向的sizePolicy 属性设置为 QSizePolicy::Expanding 尽可能填充满布局管理器,才能看到效果。
代码示例:设置垂直方向的拉伸系数
编写代码,创建6个按钮,按照3行2列方式排列,使用 setSizePolicy 设置按钮的尺寸策略,可选的值如下:
- QSizePolicy::Ignored:忽略控件的尺寸,不对布局产生影响。
- QSizePolicy::Mi nimum:控件的最小尺寸为固定值,布局时不会超过该值。
- QSizePolicy::Maximum:控件的最大尺寸为固定值,布局时不会小于该值。
- QSizePolicy::Preferred:控件的理想尺寸为固定值,布局时会尽量接近该值。
- QSi zePolicy::Expanding:控件的尺寸可以根据空间调整,尽可能占据更多空间。
- QSizePolicy::Shrinking:控件的尺寸可以根据空间调整,尽可能缩小以适应空间。
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建六个按钮
QPushButton *b1 = new QPushButton("按钮1");
QPushButton *b2 = new QPushButton("按钮2");
QPushButton *b3 = new QPushButton("按钮3");
QPushButton *b4 = new QPushButton("按钮4");
QPushButton *b5 = new QPushButton("按钮5");
QPushButton *b6 = new QPushButton("按钮6");
//设置按钮的 sizePolicy,此时按钮的水平方向和垂直方向都会尽量舒展开
b1->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
b2->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
b3->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
b4->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
b5->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
b6->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
// 创建网格布局管理器,并添加元素
QGridLayout *layout = new QGridLayout();
layout->addWidget(b1, 0, 0);
layout->addWidget(b2, 0, 1);
layout->addWidget(b3, 0, 2);
layout->addWidget(b4, 1, 0);
layout->addWidget(b5, 1, 1);
layout->addWidget(b6, 1, 2);
// 设置 layout 至窗口中
this->setLayout(layout);
}
执行代码,观察效果,此时的按钮垂直方向都舒展开了,并且调整窗口尺寸,按钮也会同步变化:
总的来说,使用 QGridLayout |能够代替很多QHBoxLayout 和QVBoxLayout嵌套的场景,毕
竟嵌套的代码写起来是比较麻烦的。
另外不要忘了,QGridLayout里面也能嵌套QHBoxLayout和QVBoxLayout,QHBoxLayout和QVBoxLayout里面也能嵌套QGridLayout。
灵活使用上述布局管理器,就可以实现出任意的复杂界面。
(四)表单布局
除了上述的布局管理器之外,Qt还提供了QFormLayout,属于是QGridLayout的特殊情况,专门用于实现两列表单的布局。
这种表单布局多用于让用户填写信息的场景。左侧列为提示,右侧列为输入框
代码示例:使用QFormLayout 创建表单
编写代码,创建QFormLayout,以及三个label和三个lineEdit
- 使用 addRow 方法来添加一行,每行包含两个控件:第一个控件固定是QLabel/文本,第二个控件则可以是任意控件。
- 如果把第一个参数填写为NULL,则什么都不显示。
#include <QFormLayout>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 layout
QFormLayout *layout = new QFormLayout();
this->setLayout(layout);
// 创建三个label
QLabel *label1 = new QLabel("姓名");
QLabel *label2 = new QLabel("年龄");
QLabel *label3 = new QLabel("电话");
// 创建三个 lineEdit
QLineEdit *le1 = new QLineEdit();
QLineEdit *le2 = new QLineEdit();
QLineEdit *le3 = new QLineEdit();
QPushButton *btn = new QPushButton("提交");
layout->addRow(label1, le1);
layout->addRow(label2, le2);
layout->addRow(label3, le3);
layout->addRow(nullptr, btn);
}
执行程序,可以看到如下效果:
(五)Spacer
使用布局管理器的时候,可能需要在控件之间,添加一段空白,就可以使用QSpacerItem来表示。
核心属性:
属性 | 说明 |
---|---|
width | 宽度 |
height | ⾼度 |
hData | ⽔平⽅向的 sizePolicy;QSizePolicy::Ignored : 忽略控件的尺⼨,不对布局产⽣影响。QSizePolicy::Minimum : 控件的最⼩尺⼨为固定值,布局时不会超过该值。QSizePolicy::Maximum : 控件的最⼤尺⼨为固定值,布局时不会⼩于该值。QSizePolicy::Preferred : 控件的理想尺⼨为固定值,布局时会尽量接近该值。 QSizePolicy::Expanding : 控件的尺⼨可以根据空间调整,尽可能占据更多空间。QSizePolicy::Shrinking : 控件的尺⼨可以根据空间调整,尽可能缩⼩以适应空间。 |
vData | 垂直⽅向的 sizePolicy选项同上 |
上述属性在构造函数设置即可。
代码示例:创建一组左右排列的按钮
在界面上创建一个QVBoxLayout,添加两个按钮:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QHBoxLayout* layout = new QHBoxLayout();
this->setLayout(layout);
QPushButton* b1 = new QPushButton("按钮1");
QPushButton* b2 = new QPushButton("按钮2");
layout->addWidget(b1);
layout->addWidget(b2);
}
并在两个按钮之间添加一个 QSpacerItem 来增加空白:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QHBoxLayout* layout = new QHBoxLayout();
this->setLayout(layout);
QPushButton* b1 = new QPushButton("按钮1");
QPushButton* b2 = new QPushButton("按钮2");
QSpacerItem* spacer = new QSpacerItem(200,20);
layout->addWidget(b1);
layout->addSpacerItem(spacer);
layout->addWidget(b2);
}
运行程序,查看效果,可以看到空白大了很多:
在 Qt Designer 中,也可以直接给界面上添加 spacer