目录
1.Label
代码示例: 显示不同格式的文本
代码示例: 显示图片
代码示例: 文本对齐, 自动换行, 缩进, 边距
代码示例: 设置伙伴
2.LCD Number
代码示例: 倒计时
3.ProgressBar
代码示例: 设置进度条按时间增长
代码示例: 创建一个红色的进度条
4.Calendar Widget
代码示例: 获取选中的日期
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 就会被选中. |
代码示例: 显示不同格式的文本
🌴1) 在界面上创建三个 QLabel
尺寸放大一些. objectName 分别为 label, label_2, label_3
🌴2) 修改 widget.cpp, 设置三个 label 的属性
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 把第一个 label 设置成显示纯文本
ui->label->setTextFormat(Qt::PlainText);
ui->label->setText("这是一段纯文本");
// 把第二个 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 文本");
}
🌴3) 运行程序, 观察效果
- 在富文本中, <b> 标签表示文本加粗。而将 <b> 标签加到纯文本中,只是被当成了单纯的文本,没有进行任何的渲染操作。
- 在markdown中 ## 表示二级标题。而在纯文本中,## 只是被单纯的当成了 “文本”。
代码示例: 显示图片
虽然 QPushButton 也可以通过设置图标的方式设置图片, 但是并非是⼀个好的选择. 更多的时候还是希望通过 QLabel 来作为一个更单纯的显示图片的方式.
🌵1) 在界面上创建一个 QLabel, objectName 为 label
🌵2) 创建 resource.qrc 文件, 并把图片导入到 qrc 中.
🌵3) 修改 widget.cpp, 给 QLabel 设置图片
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 先把 QLbel 设置成和窗口一样大,并且把这个 QLabel 左上角设置到窗口的左上角这里
// 让整个 QLabel 铺满整个窗口
QRect windowRect = this->geometry();
ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());
QPixmap pixmap(":/meng.png");
ui->label->setPixmap(pixmap);
}
执行程序, 观察效果:
这个图片本身的尺寸是 200* 200, 并没有把 QLabel 填充满.
🌵4) 修改代码, 设置 scaledContents 属性
// 启动自动拉伸,此时图片就能够填充满整个窗口了
ui->label->setScaledContents(true);
再次运行, 观察效果, 可以看到图片已经被拉伸, 可以把窗口填满了.
🌵5) 此时, 如果拖动窗口大小, 可以看到图片并不会随着窗口大小的改变而同步变化.
为了解决这个问题, 可以在 Widget 中重写 resizeEvent 函数.
// 重写 resizeEvent,这个函数会在窗口大小发生改变时被自动调用
// 此处的形参 event 是非常有用的,这里就包含了触发这个 resize 事件这一时刻,窗口的尺寸的数值。
void Widget::resizeEvent(QResizeEvent *event)
{
// 可以直接通过 this->width() 和 this->height() 设置 label 新的尺⼨, 也可以通过event 参数拿到新的尺⼨.
// ui->label->setGeometry(0, 0, this->width(), this->height());
ui->label->setGeometry(0, 0, event->size().width(), event->size().height());
qDebug() << event->size();
}
执行程序, 此时改变窗口大小, 图片也会随之变化.
与此同时, 在控制台里也能够看到尺寸变化的过程.
Qt 中,表示用户的操作,有两类概念,一个是信号,另一个是事件。当用户拖拽修改窗口大小的时候,就会触发 resize 事件(resizeEvent)。像 resize 这样的事件,是连续变化的,把窗口尺寸从 A 拖到 B 这个过程中,会触发一系列的 resizeEvent。此时就可以借助 resizeEvent 来完成上述的功能。
可以让 Widget 窗口类,重写父类(QWidget)的 resizeEvent 虚函数。 在鼠标拖动窗口尺寸的过程中,这个函数就会被反复调用执行,每次触发一个 resizeEvent 事件都会调用一次对应的虚函数。由于此处进行了函数重写,调用父类的虚函数就会实际调用到子类的对应的函数(多态)。
在实际编程中,指定回调函数其实有很多种写法:
- 设置函数指针
- 设置仿函数(函数对象)
- 设置 lambda
- 通过重写父类虚函数(框架中拿着父类的指针调用这个函数,如果你创键了子类重写了这个函数,此时在多态机制下,实际执行的就是子类的函数了)
- Qt 的信号槽
注意:
- 此处的 resizeEvent 函数我们没有手动调用, 但是能在窗口大小变化时被自动调用.
- 这个过程就是依赖 C++ 中的多态来实现的. Qt 框架内部管理着 QWidget 对象表示咱们的窗口. 在窗口大小发生改变时, Qt 就会自动调用 resizeEvent 函数.
- 但是由于实际上这个表示窗口的并非是 QWidget, 而是 QWidget 的子类, 也就是咱们自己写的 Widget. 此时虽然是通过父类调用函数, 但是实际上执行的是子类的函数(也就是我们重写后的 resizeEvent ).
- 此处属于是 多态 机制的一种经典用法. 通过上述过程, 就可以把自定义的代码, 插入到框架内部执行. 相当于 "注册回调函数" .
代码示例: 文本对齐, 自动换行, 缩进, 边距
🍒1) 创建四个 label, objectName 分别是 label ~ label_4
并且在 QFrame 中设置 frameShape 为 Box (设置边框之后看起来会更清晰一些)
QFrame 是 QLabel 的父类. 其中 frameShape 属性用来设置边框性质.
- QFrame::Box :矩形边框
- QFrame::Panel :带有可点击区域的面板边框
- QFrame::WinPanel :Windows风格的边框
- QFrame::HLine :水平线边框
- QFrame::VLine :垂直线边框
- QFrame::StyledPanel :带有可点击区域的面板边框,但样式取决于窗口主题
🍒 2) 编写 widget.cpp, 给这四个 label 设置属性
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 在构造函数中,给这几个 label 设置不同的属性
// 设置文字居中对齐
ui->label->setText("雄关漫道真如铁,而今迈步从头越。");
ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
// 设置自动换行
ui->label_2->setText("见说道、天涯芳草无归路。怨春不语。算只有殷勤,画檐蛛网,尽日惹风絮。长门事,准拟佳期又误。蛾眉曾有人妒。千金纵买相如赋,脉脉此情谁诉。君莫舞。君不见、玉环飞燕皆尘土。闲愁最苦。休去倚危栏,斜阳正在,烟柳断肠处。");
ui->label_2->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_2->setWordWrap(true);
//设置首行缩进
ui->label_3->setText("予怀怆然,感慨今昔,因自度此曲。千岩老人以为有《黍离》之悲也。淮左名都,竹西佳处,解鞍少驻初程。过春风十里,尽荠麦青青。自胡马窥江去后,废池乔木,犹厌言兵。渐黄昏,清角吹寒,都在空城。");
ui->label_3->setIndent(50);
ui->label_3->setAlignment(Qt::AlignTop | Qt::AlignLeft);
// 设置边距
ui->label_4->setText("千古江山,英雄无觅,孙仲谋处。舞榭歌台,风流总被,雨打风吹去。斜阳草树,寻常巷陌,人道寄奴曾住。想当年,金戈铁马,气吞万里如虎。元嘉草草,封狼居胥,赢得仓皇北顾。四十三年,望中犹记,烽火扬州路。");
ui->label_4->setMargin(30);
ui->label_4->setAlignment(Qt::AlignTop | Qt::AlignLeft);
}
🍒 3) 运行程序, 可以看到如下效果
- 第一个 label 垂直水平居中
- 第二个 label 设置了 wordWrap, 能够自动换行
- 第三个 label 设置了 Indent, 左侧和上方和边框有间距. 右侧则没有.
- 第四个 label 设置了 margin, 四个方向均有间距。
代码示例: 设置伙伴
🍒1) 创建两个 label 和 两个 radioButton.
objectName 分别问 label , label_2 , radioButton , radioButton_2
- 此处把 label 中的文本设置为 "快捷键 &A" 这样的形式.
- 其中 & 后面跟着的字符, 就是快捷键.
- 可以通过 alt + A 的方式来触发该快捷键.
- 但是注意, 这里的快捷键和 QPushButton 的不同. 需要搭配 alt 和 单个字母的方式才能触发.
🍒2) 编写 widget.cpp, 设置 buddy 属性
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置 label 的伙伴 widget
ui->label->setBuddy(ui->radioButton);
ui->label_2->setBuddy(ui->radioButton_2);
}
🍒3) 运行程序, 可以看到, 按下快捷键 alt + a 或者 alt + b, 即可选中对应的选项.
2.LCD Number
QLCDNumer 是一个专门用来显示数字的控件. 类似于 "老式计算器" 的效果.
核心属性:
属性 | 说明 |
intValue | QLCDNumber 显⽰的数字值(int). |
value | QLCDNumber 显⽰的数字值(double). 和 intValue 是联动的. 例如给 value 设为 1.5, intValue 的值就是 2. 另外, 设置 value 和 intValue 的⽅法名字为 display , ⽽不是 setValue 或者 setIntValue . |
digitCount | 显⽰⼏位数字. |
mode | 数字显⽰形式. 1. QLCDNumber::Dec :⼗进制模式,显⽰常规的⼗进制数字。 2. QLCDNumber::Hex :⼗六进制模式,以⼗六进制格式显⽰数字。 3. QLCDNumber::Bin :⼆进制模式,以⼆进制格式显⽰数字。 4. QLCDNumber::Oct :⼋进制模式,以⼋进制格式显⽰数字。 只有⼗进制的时候才能显⽰⼩数点后的内容. |
segmentStyle | 设置显⽰⻛格. 1. QLCDNumber::Flat :平⾯的显⽰⻛格,数字呈现在⼀个平坦的表⾯上。 2. QLCDNumber::Outline :轮廓显⽰⻛格,数字具有清晰的轮廓和阴影效果。 3. QLCDNumber::Filled :填充显⽰⻛格,数字被填充颜⾊并与背景区分开。 |
smallDecimalPoint | 设置⽐较⼩的 ⼩数点. |
代码示例: 倒计时
🍁1) 在界面上创建⼀个 QLCDNumber , 初始值设为 10.
objectName 为 lcdNumber
🍁2) 修改 widget.h 代码, 创建一个 QTimer 成员, 和一个 updateTime 函数
void updateTime();
QTimer* timer;
🍁3) 修改 widget.cpp, 在构造函数中初始化 QTimer
- QTimer 表示定时器. 通过 start 方法启动定时器之后, 就会每隔一定周期, 触发一次QTimer::timeout 信号.
- 使用 connect 把 QTimer::timeout 信号和 Widget::updateTime 连接起来, 意味着每次触发 QTimer::timeout 都会执行 Widget::updateTime,修改 LCDNumber 中的数字。
#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 信号,现在把 timeout 信号和 updateTime 连接起来
// 此时意味着每次触发 timeout 信号都会伴随 updataTime 函数的执行
connect(timer, &QTimer::timeout, this, &Widget::updateTime);
// 启动 QTimer,并且规定每隔 1000ms 触发一次 timeout 信号
timer->start(1000);
}
🍁4) 修改 widget.cpp, 实现 updateTime
- 通过 intValue 获取到 QLCDNumber 内部的数值.
- 如果 value 的值归 0 了, 就停止 QTimer . 接下来 QTimer 也就不会触发 timeout 信号了.
void Widget::updateTime()
{
int value = ui->lcdNumber->intValue();
if(value <= 0){
// 如果时间到,就停止计时器
timer->stop();
return;
}
ui->lcdNumber->display(value - 1);
}
🍁5) 执行程序, 可以看到每隔一秒钟, 显示的数字就减少 1.
倒计时
针对上述代码, 存在两个问题:
1) 上述代码如果直接在 Widget 构造函数中, 通过一个循环 + sleep 的方式是否可以呢?
代码形如:
#include <thread>
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);
}
}
显然, 这个代码是不行的. 循环会使 Widget 的构造函数无法执行完毕, 此时界面是不能正确构造和显示的.
2) 上述代码如果是在 Widget 构造函数中, 另起一个线程, 在新线程中完成 循环 + sleep 是否可以呢?
代码形如:
#include <thread>
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;
}
ui->lcdNumber->display(--value);
}
});
}
这个代码同样是不行的. Qt 中规定, 任何对于 GUI 上内容的操作, 必须在 主线程 中完成. 像 Widget 构造函数, 以及 connect 连接的 slot 函数, 都是在主线程中调用的. 而我们自己创建的线程则不是.
当我们自己的线程中尝试对界面元素进行修改时, Qt 程序往往会直接崩溃.
- 这样的约定主要是因为 GUI 中的状态往往是牵一发动全身的, 修改一个地方, 就需要同步的对其他内容进行调整.
- 比如调整了某个元素的尺寸, 就可能影响到内部的文字位置, 或者其他元素的位置. 这里一连串的修改, 都是需要按照一定的顺序来完成的.
- 由于多线程执行的顺序无法保障, 因此 Qt 从根本上禁止了其他线程修改 GUI 状态, 避免后续的一系列问题。
综上所述, 使用定时器, 是实现上述功能的最合理方案.
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 :表⽰总时间(以毫秒为单位) |
代码示例: 设置进度条按时间增长
🌵1) 在界面上创建进度条, objectName 为 progressBar
其中最小值设为 0, 最大值设为 100. 当前值设为 0.
🌵2) 修改 widget.h, 创建 QTimer 和 handle 函数.
QTimer* timer;
void handle ();
🌵3) 修改 widget.cpp, 初始化 QTimer
- 此处设置 100ms 触发一次 timeout 信号. 也就是一秒钟触发 10 次.
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);
}
🌵4) 修改 widget.cpp, 实现 handle
void Widget::handle()
{
// 获取到进度条的当前数值
int value = ui->progressBar->value();
if(value >= 100){
timer->stop();
return;
}
ui->progressBar->setValue(value + 1);
}
🌵5) 运行程序, 可以看到进度条中的进度在快速增长.
green 进度条
- 在实际开发中, 进度条的取值, 往往是根据当前任务的实际进度来进行设置的.
- 比如需要读取一个很大的文件, 就可以获取文件的总的大小, 和当前读取完毕的大小, 来设置进度条的比例.
- 由于上面我们介绍了 Qt 禁止在其他线程修改界面, 因此进度条的更新往往也是需要搭配定时器来完成的.
- 通过定时器周期触发信号, 主线程调用对应的 slot 函数. 再在 slot 函数中对当前的任务进度进行计算, 并更新进度条的界面效果.
代码示例: 创建一个红色的进度条
上述的进度条是使用绿色表示的, 但是考虑到有些人可能不喜欢绿色, 因此我们基于上面的程序改成一个红色的进度条.
不要忘了,QProgressBar 同样也是 QWidget 的子类, 因此我们可以使用 styleSheet 通过样式来修改进度条的颜色.
🌴1) 在 Qt Designer 右侧的属性编辑器中, 找到 QWidget 的 styleSheet 属性. 编辑如下内容:
- 其中的 chunk 是选中进度条中的每个 "块" . 使用 QProgressBar::text 则可以选中文本.
同时把 QProcessBar 的 alignment 属性设置为垂直水平居中.
此处如果不设置 alignment , 进度条中的数字会跑到左上角. 这个怀疑是 Qt 本身的 bug, 暂时只能先使用 alignment 来手动调整下.
🌴2) 执行程序, 可以看到如下效果. 我们就得到了一个红色的进度条.
red 进度条
4.Calendar Widget
QCalendarWidget 表示一个 "日历" , 形如:
核心属性:
属性 | 说明 |
selectDate | 当前选中的⽇期 |
minimumDate | 最⼩⽇期 |
maximumDate | 最⼤⽇期 |
firstDayOfWeek | 每周的第⼀天(也就是⽇历的第⼀列) 是周⼏. |
gridVisible | 是否显⽰表格的边框 |
selectionMode | 是否允许选择⽇期 |
navigationBarVisible | ⽇历上⽅标题是否显⽰ |
horizontalHeaderFormat | ⽇历上⽅标题显⽰的⽇期格式 |
verticalHeaderFormat | ⽇历第⼀列显⽰的内容格式 |
dateEditEnabled | 是否允许⽇期被编辑 |
重要信号:
信号 | 说明 |
selectionChanged(const QDate&) | 当选中的⽇期发⽣改变时发出 |
activated(const QDate&) | 当双击⼀个有效的⽇期或者按下回⻋键时发出,形参是⼀个QDate类型,保存了选中的⽇期 |
currentPageChanged(int, int) | 当年份⽉份改变时发出,形参表⽰改变后的新年份和⽉份 |
代码示例: 获取选中的日期
🌻1) 在界面上创建一个 QCalendarWidget 和 一个 label
objectName 为 calendarWidget , label
🌻2) 给 QCalendarWidget 添加 slot 函数
void Widget::on_calendarWidget_selectionChanged()
{
QDate date = ui->calendarWidget->selectedDate();
ui->label->setText(date.toString());
}
🌻3) 执行程序, 可以看到当选择不同的日期时, label 中的内容就会随之改变.