文章目录
一、Label
1.1 属性
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 ,margin 是上下左右四个方向同时有效;而 indent 最多只是两个方向有效(具体取决于 alignment ) |
openExternalLinks | 是否允许打开一个外部的链接(当 QLabel 文本内容包含 url 时涉及) |
buddy | 给 QLabel 关联一个“伙伴”,点击 QLabel 时激活对应的伙伴;例如伙伴是 QCheckBox ,点击标签可选中该复选框 |
1.2 显⽰不同格式的⽂本
1)在界⾯上创建三个QLabel
尺⼨放⼤⼀些.objectName分别为label,label_2,label_3
- 修改widget.cpp,设置三个label的属性
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->setTextFormat(Qt::RichText);
ui->label->setText("这是一段纯文本");
ui->label->setTextFormat(Qt::MarkdownText);
ui->label_2->setText("<b> 这是一段富文本 </b>");
ui->label->setTextFormat(Qt::AutoText);
ui->label_3->setText("## 这是一段 markdown 文本");
}
1.3 显⽰图⽚
虽然QPushButton也可以通过设置图标的方式设置图片,但是并非是一个好的选择.更多的时候还是希望通过QLabel 来作为一个更单纯的显示图片的方式.
在界⾯上创建⼀个
QLabel
,objectName
为label
创建
resource.qrc
⽂件,并把图⽚导⼊到qrc
中
修改widget.cpp,给QLabel设置图⽚
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置 label ⼤⼩和窗⼝⼀样⼤
ui->label->setGeometry(0, 0, 800, 800);
QPixmap pixmap(":/hachimitsu.jpg");
ui->label->setPixmap(pixmap);
}
这个图⽚本⾝的尺⼨是480*480,并没有把QLabel
填充满.
4) 修改代码,设置scaledContents
属性
//设置内容伸缩
ui->label->setScaledContents(true);
再次运⾏,观察效果,可以看到图⽚已经被拉伸,可以把窗⼝填满了,但还是不能随意跟窗口着缩放图片.
5) 此时,如果拖动窗⼝⼤⼩,可以看到图⽚并不会随着窗⼝⼤⼩的改变⽽同步变化
为了解决这个问题,可以在Widget
中重写resizeEvent
函数.
- 先声明函数
#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 resizeEvent(QResizeEvent* event);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
- 函数实现
#include "widget.h"
#include "ui_widget.h"
#include <QResizeEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->setGeometry(0, 0, 800, 800);
QPixmap pixmap(":/hachimitsu.jpg");
ui->label->setPixmap(pixmap);
//设置内容伸缩
ui->label->setScaledContents(true);
}
//重写 resizeEvent, 这个函数会在窗口大小发生时被自动调用
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();
}
Widget::~Widget()
{
delete ui;
}
效果:无论怎拖拉,窗口都会适配图片的大小
注意:
此处的resizeEvent函数我们没有手动调用,但是能在窗口大小变化时被自动调用.这个过程就是依赖C++中的多态来实现的.Qt框架内部管理着QWidget对象表示咱们的窗口.在窗口大小发生改变时,Qt就会自动调用resizeEvent函数.
但是由于实际上这个表示窗口的并非是QWidget,而是QWidget的子类,也就是咱们自己写的Widget.此时虽然是通过父类调用函数,但是实际上执行的是子类的函数(也就是我们重写后的resizeEvent).
此处属于是多态机制的一种经典用法.通过上述过程,就可以把自定义的代码,插入到框架内部执行.相当于"注册回调函数”.
1.4 ⽂本对⻬,⾃动换⾏,缩进,边距
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
:带有可点击区域的面板边框,但样式取决于窗口主题
- 编写
widget.cpp
,给这四个label设置属性.
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置文字居中对齐
ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->label->setText("垂直水平居中的文本");
//设置自动换行
ui->label_2->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_2->setWordWrap(true);
ui->label_2->setText("这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本");
//设置首行缩进
ui->label_3->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_3->setIndent(20);
ui->label_3->setText("这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本");
//设置边距
ui->label_4->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ui->label_4->setMargin(20);
ui->label_4->setText("这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本");
}
3)运行程序,可以看到如下效果
- 第一个label垂直水平居中
- 第二个label设置了wordWrap,能够自动换行
- 第三个label设置了Indent,左侧和上方和边框有间距.右侧则没有.
- 第四个label设置了margin,四个方向均有间距(图上仅体现出三个方向,下方看不出来).
1.5 设置伙伴
1)创建两个label和两个radioButton
.objectName
分别问label , label_2,radioButton , radioButton_2
此处把label中的文本设置为"快捷键&A"这样的形式.其中&后面跟着的字符,就是快捷键.
可以通过alt+A的方式来触发该快捷键.但是注意,这里的快捷键和QPushButton的不同.需要搭配alt和单个字母的方式才能触发.
- 编写widget.cpp,设置buddy属性
当然这⾥也可以使⽤QtDesigner
直接设置.
#include "widget.h"
#include "ui_widget.h"
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);
}
- 运⾏程序,可以看到,按下快捷键alt+a或者alt+b,即可选中对应的选项.
二、LCDNumber
QLCDNumer
是⼀个专⻔⽤来显⽰数字的控件.类似于"⽼式计算器"的效果.
2.1 核⼼属性
属性 | 说明 |
---|---|
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 | 设置比较小的小数点 |
2.2 倒计时实现
1)在界面上创建一个QLCDNumber
,初始值设为10
.
objectName
为lcdNumber
2)修改widget.h
代码,创建一个QTimer
成员,和一个updateTime
函数
#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();
QTimer* timer;
void updateTime();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
3)修改widget.cpp
,在构造函数中初始化QTimer
QTimer
表示定时器.通过start方法启动定时器之后,就会每隔一定周期,触发一次QTimer : :timeout
信号.- 使用connect把
QTimer ::timeout
信号和widget::updateTime
连接起来,
意味着每次触发QTimer: :timeout
都会执行widget: : updateTime
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建QTimer实例
timer = new QTimer(this);
//连接限号和槽,QTimer会每隔一段时间触发一个timeout 信号,现在把 timeout 信号和updateTimer连接起来
//此时意味着每次触发 timeout 信号都会伴随 updateTime 的函数执行
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()
{
qDebug() << "updateTime";
int value = ui->lcdNumber->intValue();
if(value <= 0)
{
//如果时间到,停止定时器
timer->stop();
return ;
}
ui->lcdNumber->display(value - 1);
}
- 执⾏程序,可以看到每隔⼀秒钟,显⽰的数字就减少1
2.2 针对上述代码,存在两个问题:
- 上述代码如果直接在Widget构造函数中,通过⼀个循环+sleep的⽅式是否可以呢?
代码形如
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
#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 - 1);
}
}
显然,这个代码是不⾏的.循环会使Widget的构造函数⽆法执⾏完毕,此时界⾯是不能正确构造和显⽰的.
- 上述代码如果是在Widget构造函数中,另起⼀个线程,在新线程中完成循环+sleep是否可以呢?
代码形如
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
#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;
}
this->ui->lcdNumber->display(value - 1);
}
});
}
这个代码同样是不行的.Qt 中规定,任何对于GUl上内容的操作,必须在主线程中完成.像Widget构造函数,以及connect连接的slot 函数,都是在主线程中调用的.而我们自己创建的线程则不是.
当我们自己的线程中尝试对界面元素进行修改时,Qt程序往往会直接崩溃.
这样的约定主要是因为GUI中的状态往往是牵一发动全身的,修改一个地方,就需要同步的对其他内容进行调整.
比如调整了某个元素的尺寸,就可能影响到内部的文字位置,或者其他元素的位置.这里一连串的修改,都是需要按照一定的顺序来完成的.
由于多线程执行的顺序无法保障,因此Qt从根本上禁止了其他线程修改GUI状态,避免后续的一系列问题.
综上所述,使⽤定时器,是实现上述功能的最合理⽅案.
后续如果我们也有类似的需要"周期性修改界⾯状态"的需求,也需要优先考虑使⽤定时器.
三、 ProgressBar
使用QProgressBar
表示一个进度条.
注意,不要把ProgessBar拼写成ProcessBar !
3.1 属性
1)在界⾯上创建进度条, objectName
为progressBar
其中最⼩值设为0,最⼤值设为100.当前值设为0.
2)修改widget.h
,创建QTimer
和updateProgressBar
函数.
#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();
QTimer * timer;
void updateProgressBar();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
- 修改widget.cpp,初始化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::updateProgressBar);
timer->start(1000);
}
4)修改widget.cpp,实现updateProgressBar
void Widget::updateProgressBar()
{
int value = ui->progressBar->value();
if(value >= 100)
{
timer->stop();
return;
}
ui->progressBar->setValue(value + 1);
}
5)运行程序,可以看到进度条中的进度在快速增长.
在实际开发中,进度条的取值,往往是根据当前任务的实际进度来进行设置的.
比如需要读取一个很大的文件,就可以获取文件的总的大小,和当前读取完毕的大小,来设置进度条的比例.
由于上面我们介绍了Qt禁止在其他线程修改界面,因此进度条的更新往往也是需要搭配定时器来完成的.
通过定时器周期触发信号,主线程调用对应的slot函数.再在slot 函数中对当前的任务进度进行计算,并更新进度条的界面效果.
3.2 创建⼀个红⾊的进度条
不要忘了,QProgressBar
同样也是Qwidget
的子类,因此我们可以使用styleSheet
通过样式来修改进度条的颜色.
- 在界⾯上创建⼀个进度条.
2)在 Qt Designer右侧的属性编辑器中,找到QWidget的styleSheet属性.编辑如下内容:
其中的 chunk是选中进度条中的每个"块".使用QProgressBar : :text则可以选中文本.
如果不设置对齐属性,进度条中的数字会显示在左上角。这可能是Qt自身的bug,目前只能通过手动设置对齐方式来解决。
好看的样式
QProgressBar {
/* 进度条背景(未完成区域) */
background-color: #444;
/* 进度条高度,可按需调整 */
height: 20px;
/* 边框样式,可选 */
border: 1px solid #666;
/* 边框圆角,可选 */
border-radius: 4px;
/* 让文本强制居中(核心!) */
text-align: center;
}
QProgressBar::chunk {
/* 已完成进度的颜色 */
background-color: rgb(255, 0, 0);
/* 进度块圆角,和外层匹配 */
border-radius: 3px;
}
四、Calendar Widget
4.1 核⼼属性
属性 | 说明 |
---|---|
selectDate | 当前选中的日期 |
minimumDate | 最小日期 |
maximumDate | 最大日期 |
firstDayOfWeek | 每周的第一天(也就是日历的第一列)是周几。 |
gridVisible | 是否显示表格的边框 |
selectionMode | 是否允许选择日期 |
navigationBarVisible | 日历上方标题是否显示 |
horizontalHeaderFormat | 日历上方标题显示的日期格式 |
verticalHeaderFormat | 日历第一列显示的内容格式 |
dateEditEnabled | 是否允许日期被编辑 |
重要信号
信号 | 说明 |
---|---|
当选中的日期发生改变时发出 | |
selectionChanged(const QDate&) |
|
activated(const QDate&) |
当双击一个有效的日期或者按下回车键时发出,形参是一个 QDate 类型,保存了选中的日期 |
currentPageChanged(int, int) |
当年份月份改变时发出,形参表示改变后的新年份和月份 |
4.2 获取选中的日期
1)在界面上创建一个Qcalendarwidget
和一个labelobjectName
为calendarwidget
, label
2)给Qcalendarwidget添加slot函数
void Widget::on_calendarWidget_selectionChanged()
{
QDate date = ui->calendarWidget->selectedDate();
qDebug() << date;
ui->label->setText(date.toString());
}
- 执⾏程序,可以看到当选择不同的⽇期时,
label
中的内容就会随之改变.