文章目录
控件是构成⼀个图形化界面的基本要素
Qt 作为⼀个成熟的 GUI 开发框架,内置了⼤量的常⽤控件,这⼀点在 Qt Designer 中就可以看到端倪,并且 Qt 也提供了 “自定义控件” 的能力,可以让程序猿在现有控件不能满足需求的时候,对现有控件做出扩展,或者手搓出新的控件。
1. Qwidget核心属性
在 Qt 中,使用 QWidget 类表示 “控件”,像按钮、视图,输入框,滚动条等具体的控件类,都是继承自QWidget
可以说,QWidget 中就包含了 Qt 整个控件体系中,通用的部分。
在 Qt Designer 中,随便拖⼀个控件过来,选中该控件,即可在右下方看到 QWidget 中的属性
这些属性既可以通过 QtDesigner 会直接修改,也可以通过代码的方式修改
下面我们就来认识一下常用的属性:
1.1 enable
API | 说明 |
---|---|
isEnabled() | 获取到控件的可⽤状态 |
setEnabled | 设置控件是否可使⽤, true 表⽰可⽤,false 表⽰禁⽤ |
所谓 “禁用” 指的是该控件不能接收任何用户的输⼊事件,并且外观上往往是灰色的;如果⼀个 widget 被禁用,则该 widget 的⼦元素也被禁用
在 Qt Designer 中创建按钮的时候,可以设置按钮的初始状态是 “可用” 还是 “禁用”
1.2 geometry
位置和尺寸,其实是四个属性的统称:
- x 横坐标
- y 纵坐标
- width 宽度
- height ⾼度
但是实际开发中,我们并不会直接使⽤这⼏个属性,⽽是通过⼀系列封装的⽅法来获取/修改
API | 说明 |
---|---|
geometry() | 获取到控件的位置和尺寸,返回结果是⼀个 QRect ,包含了 x, y, width, height。 其中 x, y 是左上⻆的坐标. |
setGeometry(QRect) | 设置控件的位置和尺⼨,可以直接设置⼀个 QRect |
setGeometry(int x, int y, int width, int height) | 设置控件的位置和尺⼨,分四个属性单独设置. |
下面我们来使用一下:
下面我们实现:只要点击不想的按钮,按钮就跑了的效果
window frame 的影响
如果 widget 作为⼀个窗⼝ (带有标题栏,最⼩化, 最⼤化, 关闭按钮),那么在计算尺⼨和坐标的时候就有两种算法:包含 window frame 和 不包含 window frame.
- 其中 x(), y(), frameGeometry(), pos(), move() 都是按照包含 window frame 的⽅式来计算的.
- 其中 geometry(), width(), height(), rect(), size() 则是按照不包含 window frame 的⽅式来计算的.
下面我们写个代码感受 geometry 和 frameGeometry 的区别:
- 在构造方法中,Widget 刚刚创建出来,还没有加⼊到对象树中,此时也就不具备 Window frame.
- 在按钮的 slot 函数中,由于⽤⼾点击的时候,对象树已经构造好了,此时 Widget 已经具备了 Window frame,因此在位置和尺⼨上均出现了差异
1.3 windowTitle
API | 说明 |
---|---|
windowTitle() | 获取到控件的窗⼝标题 |
setWindowTitle(const QString& title) | 设置控件的窗⼝标题 |
注意! 上述设置操作针对不同的 widget 可能会有不同的⾏为。如果是顶层 widget (独立窗口),这个操作才会有效;如果是子 widget,这个操作无任何效果
1.4 windowIcon
API | 说明 |
---|---|
windowIcon() | 获取到控件的窗⼝图标. 返回 QIcon 对象. |
setWindowIcon(const QIcon& icon) | 设置控件的窗⼝图标 |
注意!仅针对顶层 widget 有效.
注意: Windows 下路径的分隔符可以使⽤
/
也可以使⽤\
. 但是如果在 字符串 中使⽤\
, 需要写作转义字符的形式\\
. 因此我们还是更推荐使⽤/
实际开发中, 我们⼀般不会在代码中通过绝对路径引⼊图⽚. 因为我们⽆法保证程序发布后, ⽤⼾的电脑上也有同样的路径.
如果使⽤相对路径, 则需要确保代码中的相对路径写法和图⽚实际所在的路径匹配 (⽐如代码中写作 "./image/watermelon.jpg"
, 就需要在当前⼯作⽬录中创建 image ⽬录, 并把 watermelon.jpg 放进去).
Qt 使⽤ qrc 机制帮我们解决了文件的路径问题,更⽅便的来管理项⽬依赖的静态资源
- qrc 文件是⼀种XML格式的资源配置⽂件,它用XML记录硬盘上的⽂件和对应的随意指定的资源名称,应⽤程序通过资源名称来访问这些资源.
- 在Qt开发中,可以通过将资源⽂件添加到项⽬中来⽅便地访问和管理这些资源,这些资源⽂件可以位于qrc⽂件所在⽬录的同级或其⼦⽬录下.
- 在构建程序的过程中,Qt 会把资源⽂件的⼆进制数据转成 cpp 代码,编译到 exe 中,从⽽使依赖的资源变得 “路径⽆关”.
注意上述路径的访问规则.
- 使用
:
作为开头, 表⽰从 qrc 中读取资源. /
是上⾯配置的前缀- rose.jpg 是资源的名称
需要确保代码中编写的路径和添加到 qrc 中资源的路径匹配,否则资源⽆法被访问 (同时也不会有报错提⽰).
我们可以进⼊到项⽬的构建目录,可以看到,目录中多了⼀个 qrc_resource.cpp ⽂件,直接打开这个⽂件,可以看到类似如下代码:
上述代码其实就是通过 unsigned char 数组,把 rose.jpg 中的每个字节都记录下来,这些代码会被编译到 exe 中,后续⽆论 exe 被复制到哪个⽬录下, 都确保能够访问到该图⽚资源.
上述 qrc 这⼀套资源管理⽅案, 优点和缺点都很明显.
- 优点:确保了图片、字体、声⾳等资源能够真正做到 “⽬录⽆关”,⽆论如何都不会出现资源丢失的情况.
- 缺点:不适合管理体积大的资源,如果资源⽐较大 (⽐如是⼏个 MB 的⽂件), 或者资源特别多,⽣成的最终的 exe 体积就会⽐较⼤,程序运⾏消耗的内存也会增⼤, 程序编译的时间也会显著增加.
1.5 windowOpacity
API | 说明 |
---|---|
windowOpacity() | 获取到控件的不透明数值. 返回 float, 取值为 0.0 --1.0 其中 0.0 表⽰全透明, 1.0 表⽰完全不透明. |
setWindowOpacity(float n) | 设置控件的不透明数值 |
同时控制台中也可以看到 opacity 数值的变化,C++ 中 float 类型遵守 IEEE 754 标准,因此在进⾏运算的时候会有⼀定的精度误差,因此 1 -0.1 的数值并非是 0.9
1.6 cursor
API | 说明 |
---|---|
cursor() | 获取到当前 widget 的 cursor 属性,返回 QCursor 对象,当⿏标悬停在该 widget 上时,就会显⽰出对应的形状. |
setCursor(const QCursor& cursor) | 设置该 widget 光标的形状. 仅在⿏标停留在该 widget 上时⽣效 |
QGuiApplication::setOverrideCursor(const QCursor& cursor) | 设置全局光标的形状. 对整个程序中的所有 widget 都会⽣效, 覆盖上⾯的 setCursor 设置的内容 |
- 在 Qt Designer 中设置按钮的光标
- 通过代码设置按钮的光标
Qt ⾃带的光标形状有限. 我们也可以⾃⼰找个图⽚, 做成⿏标的光标.
1.7 font
属性 | 说明 |
---|---|
family | 字体家族. ⽐如 “楷体”, “宋体”, “微软雅⿊” 等. |
pointSize | 字体⼤⼩ |
weight | 字体粗细. 以数值⽅式表⽰粗细程度取值范围为 [0, 99], 数值越⼤, 越粗. |
bold | 是否加粗. 设置为 true, 相当于 weight 为 75. 设置为 false 相当于 weight 为 50 |
italic | 是否倾斜 |
underline | 是否带有下划线 |
strikeOut | 是否带有删除线 |
使用QT designer
使用代码
1.8 toolTip
API | 说明 |
---|---|
setToolTip() | 设置 toolTip. ⿏标悬停在该 widget 上时会有提⽰说明. |
setToolTipDuring() | 设置 toolTip 提⽰的时间. 单位 ms. 时间到后 toolTip ⾃动消失. |
toolTip 只是给⽤⼾看的,在代码中⼀般不需要获取到 toolTip.
1.9 focusPolicy
设置控件获取到焦点的策略。
比如某个控件能否用鼠标选中或者能否通过 tab 键选中
所谓 “焦点”, 指的就是能选中这个元素,接下来的操作 (比如键盘操作),就都是针对该焦点元素进⾏的了,这个对于输⼊框、单选框、复选框等控件非常有用的
API | 说明 |
---|---|
focusPolicy() | 获取该 widget 的 focusPolicy,返回 Qt::FocusPolicy |
setFocusPolicy(Qt::FocusPolicy policy) | 设置 widget 的 focusPolicy |
Qt::FocusPolicy 是⼀个枚举类型,取值如下
Qt::NoFocus
:控件不会接收键盘焦点Qt::TabFocus
:控件可以通过Tab键接收焦点Qt::ClickFocus
:控件在⿏标点击时接收焦点Qt::StrongFocus
:控件可以通过Tab键和⿏标点击接收焦点 (默认值)Qt::WheelFocus
: 类似于 Qt::StrongFocus , 同时控件也通过⿏标滚轮获取到焦点 (新增的选项, ⼀般很少使⽤).
1.10 styleSheet
通过 CSS 设置 widget 的样式
CSS 中可以设置的样式属性非常多,基于这些属性 Qt 只能⽀持其中⼀部分,称为 QSS
使用代码设置样式:
2. 按钮类控件
2.1 Push Button
使⽤ QPushButton
表示⼀个按钮,这也是当前我们最熟悉的⼀个控件了; QPushButton 继承自QAbstractButton
,这个类是⼀个抽象类,是其他按钮的⽗类 。
QAbstractButton 继承自QWidget,所以前面我们介绍的QWidget的各种属性、函数的使用方法,在这里也都是有效的。
在QAbstractButton 中,和 QPushButton 相关性较大的属性如下:
属性 | 说明 |
---|---|
text | 按钮中的⽂本 |
icon | 按钮中的图标 |
iconSize | 按钮中图标的尺⼨ |
shortCut | 按钮对应的快捷键 |
autoRepeat | 按钮是否会重复触发. 当⿏标左键按住不放时, 如果设为 true, 则会持续产⽣⿏标点击事件; 如果设为 false, 则必须释放⿏标, 再次按下⿏标时才能产⽣点击事件. ( “连发” 效果) |
autoRepeatDelay | 重复触发的延时时间. 按住按钮多久之后, 开始重复触发 |
autoRepeatInterval | 重复触发的周期 |
下面我就就来实现一个方向键按钮的效果
调整图标大小
设置快捷键与重复触发
2.2 Radio Button
QRadioButton 是单选按钮,可以让我们在多个选项中选择⼀个
作为 QAbstractButton 和 QWidget 的子类,上⾯介绍的属性和用法,对于 QRadioButton 同样适用
QAbstractButton 中和 QRadioButton 关系较大的属性
属性 | 说明 |
---|---|
checkable | 是否能选中 |
checked | 是否已经被选中. checkable 是 checked 的前提条件. |
autoExclusive | 是否排他,选中⼀个按钮之后是否会取消其他按钮的选中,对于 QRadioButton 来说默认就是排他 |
- 在界⾯上创建⼀个 label 和 3 个 单选按钮,实现选择性别
- 设置默认值 与 不可选
- click、press、release、toggled 的区别
QRadioButton提供了四个槽函数
- clicked 表示⼀次 “点击” (是⼀次⿏标按下+⿏标释放触发的)
- pressed 表示鼠标 “按下” (是⿏标按下触发的)
- released 表示鼠标 “释放” (是⿏标释放触发的)
- toggled 表示按钮状态切换 (是 checked 属性改变时触发的)
总的来说,toggled 是最适合 QRadioButton 的
- 分组
此时直接运⾏程序,可以看到, 这六个 QRadioButton 之间都是排他的;我们希望每⼀组内部来控制排他,但是组和组之间不能排他.
引入 QButtonGroup 进行分组
2.3 Check Box
QCheckBox 表示复选按钮,可以允许选中多个
和 QCheckBox 最相关的属性也是 checkable 和 checked,都是继承自QAbstractButton
3. 显示类控件
3.1 Label
QLabel 可以用来显示文本和图片,核心属性如下:
属性 | 说明 |
---|---|
text | QLabel 中的⽂本 |
textFormat | ⽂本的格式。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 就会被选中. |
- 文本格式
- 显示图片
虽然 QPushButton 也可以通过设置图标的⽅式设置图⽚, 但是并⾮是⼀个好的选择. 更多的时候还是希望通过 QLabel 来作为⼀个更单纯的显⽰图⽚的⽅式.
使图片自动拉伸
此时,如果拖动窗口大小,可以看到图片并不会随着窗口大小的改变而同步变化.
为了解决这个问题,可以在 Widget 中重写 resizeEvent 函数
注意:
- 此处的 resizeEvent 函数我们没有手动调用, 但是能在窗口大小变化时被自动调用。
- 这个过程就是依赖 C++中的多态来实现的,Qt 框架内部管理着 QWidget 对象表⽰咱们的窗⼝,在窗口大小发⽣改变时, Qt 就会⾃动调⽤ resizeEvent 函数。 但是由于实际上这个表⽰窗⼝的并⾮是 QWidget,⽽是 QWidget 的⼦类,也就是咱们自己写的 Widget。
- 此时虽然是通过⽗类调⽤函数,但是实际上执⾏的是⼦类的函数(也就是我们重写后的 resizeEvent )。 此处属于是多态机制的⼀种经典⽤法,通过上述过程,就可以把⾃定义的代码,插⼊到框架内部执⾏,相当于 “注册回调函数” .
- 文本对齐
QFrame
是 QLabel 的⽗类. 其中 frameShape 属性⽤来设置边框性质.
- QFrame::Box :矩形边框
- QFrame::Panel :带有可点击区域的⾯板边框
- QFrame::WinPanel :Windows⻛格的边框
- QFrame::HLine :⽔平线边框
- QFrame::VLine :垂直线边框
- QFrame::StyledPanel :带有可点击区域的⾯板边框,但样式取决于窗⼝主题
下面是枚举类型Alignment的部分值,具体内容请查看文档
- 设置伙伴
此处把 label 中的⽂本设置为 “快捷键 &A
” 这样的形式,其中 & 后⾯跟着的字符,就是快捷键.
可以通过 alt + A 的方式来触发该快捷键.
但是注意,这里的快捷键和 QPushButton 的不同。需要搭配 ALT 键 和 单个字母的方式才能触发
3.2 LCD Number
QLCDNumer 是⼀个专门用来显示数字的控件,类似于 “老式计算器” 的效果
属性 | 说明 |
---|---|
intValue | QLCDNumber 显⽰的数字值(int) |
value | QLCDNumber 显⽰的数字值(double)。 和 intValue 是联动的,例如给 value 设为 1.5, intValue 的值就是 2. 另外, 设置 value 和 intValue 的⽅法名字为 display,⽽不是 setValue 或者 setIntValue . |
digitCount | 显⽰几位数字 |
mode | 数字显⽰形式. QLCDNumber::Dec :⼗进制模式,显⽰常规的⼗进制数字。 QLCDNumber::Hex :⼗六进制模式,以⼗六进制格式显⽰数字。 QLCDNumber::Bin :⼆进制模式,以⼆进制格式显⽰数字。 QLCDNumber::Oct :⼋进制模式,以⼋进制格式显⽰数字。 只有⼗进制的时候才能显⽰⼩数点后的内容 |
segmentStyle | 设置显⽰⻛格。QLCDNumber::Flat :平⾯的显⽰⻛格,数字呈现在⼀个平坦的表⾯上。 QLCDNumber::Outline :轮廓显⽰⻛格,数字具有清晰的轮廓和阴影效果。 QLCDNumber::Filled :填充显⽰⻛格,数字被填充颜⾊并与背景区分开 |
smallDecimalPoint | 如果为true,则在两个数字位置之间绘制小数点。否则,它将占据自己的数字位置,即在数字位置上绘制。默认为false |
下面,我们使用LCDNumber,实现一个倒计时
上述代码如果直接在 Widget 构造函数中,通过⼀个循环 + sleep 的方式是否可以呢?
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
int value = ui->lcdNumber->intValue();
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
if (value <= 0) {
break;
}
ui->lcdNumber->display(value - 1);
}
}
循环会使 Widget 的构造函数⽆法执⾏完毕, 此时界⾯是不能正确构造和显⽰的
上述代码如果是在 Widget 构造函数中,另起⼀个线程,在新线程中完成循环 + sleep 是否可以呢?
- 这个代码同样是不⾏的, Qt 中规定,任何对于 GUI 上内容的操作,必须在主线程中完成,像 Widget 构造函数, 以及 connect 连接的 slot 函数,都是在主线程中调⽤的
- 当我们⾃⼰的线程中尝试对界⾯元素进⾏修改时,Qt 程序往往会直接崩溃
- 这样的约定主要是因为 GUI 中的状态往往是牵⼀发动全⾝的, 修改⼀个地⽅, 就需要同步的对其他内容进⾏调整;
- 由于多线程执⾏的顺序⽆法保障,因此Qt 从根本上禁⽌了其他线程修改 GUI 状态,避免后续的⼀系列问题
后续如果我们也有类似的需要 “周期性修改界⾯状态” 的需求,也需要优先考虑使⽤定时器
其它属性
3.3 ProgressBar
使用 QProgressBar 表示⼀个进度条
属性 | 说明 |
---|---|
minimum | 进度条最⼩值 |
maximum | 进度条最⼤值 |
value | 进度条当前值 |
alignment | ⽂本在进度条中的对⻬⽅式.Qt::AlignLeft : 左对⻬ Qt::AlignRight : 右对⻬ Qt::AlignCenter : 居中对⻬ Qt::AlignJustify : 两端对⻬ |
textVisible | 设进度条的数字是否可⻅. |
orientation | 进度条的⽅向是⽔平还是垂直 |
invertAppearance | 是否是朝反⽅向增⻓进度 |
textDirection | ⽂本的朝向 |
format | 展⽰的数字格式. %p :表⽰进度的百分⽐(0-100) %v :表⽰进度的数值(0-100) %m :表⽰剩余时间(以毫秒为单位) %t :表⽰总时间(以毫秒为单位) |
在实际开发中,进度条的取值,往往是根据当前任务的实际进度来进⾏设置的。
不要忘了,QProgressBar 同样也是 QWidget 的⼦类,因此我们可以使⽤
styleSheet
通过样式来修改进度条的颜色
3.4 Calendar Widget
QCalendarWidget 表⽰⼀个 “日历”
属性 | 说明 |
---|---|
selectDate | 当前选中的⽇期 |
minimumDate | 最小日期 |
maximumDate | 最大日期 |
firstDayOfWeek | 每周的第⼀天(也就是⽇历的第⼀列) 是周⼏. |
gridVisible | 是否显⽰表格的边框 |
selectionMode | 是否允许选择⽇期 |
navigationBarVisible | 日历上⽅标题是否显⽰ |
horizontalHeaderFormat | 日历上⽅标题显⽰的⽇期格式 |
verticalHeaderFormat | 日历第⼀列显⽰的内容格式 |
dateEditEnabled | 是否允许⽇期被编辑 |
重要信号
属性 | 说明 |
---|---|
selectionChanged(const QDate&) | 当选中的⽇期发⽣改变时发出 |
activated(const QDate&) | 当双击⼀个有效的⽇期或者按下回⻋键时发出,形参是⼀个QDate类型,保存了选中的⽇期 |
currentPageChanged(int, int) | 当年份⽉份改变时发出,形参表⽰改变后的新年份和⽉份 |
如需了解其它属性,请查阅官方文档