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
尺寸放大一些。obiectName 分别为 label,label 2,label 3
(2) 修改 widget.cpp,设置三个 label 的属性
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 ⽂本");
}
(3) 运行程序,观察效果
代码示例:显示图片
虽然 QPushButton
也可以通过设置图标的方式设置图片,但是并非是一个好的选择。更多的时候还是希望通过 QLabel
来作为一个更单纯的显示图片的方式。
(1) 在界面上创建一个 QLabel,objectName
为 label
(2) 创建 resource.qrc 文件,并把图片导入到 qrc中
(3) 修改 widget.cpp,给 QLabel 设置图片
// 设置 label ⼤⼩和窗⼝⼀样⼤
ui->label->setGeometry(0, 0, 800, 600);
QPixmap pixmap(":/huaji.png");
ui->label->setPixmap(pixmap);
执行程序,观察效果
这个图片本身的尺寸是 480 * 480, 并没有把 QLabel 填充满.
(4) 修改代码,设置 scaledContents 属性
// 设置内容伸缩
ui->label->setScaledContents(true);
再次运行,观察效果,可以看到图片已经被拉伸,可以把窗口填满了
(5) 此时如果拖动窗口大小可以看到图片并不会随着窗口大小的改变而同步变化.
为了解决这个问题,可以在 Widget 中重写 resizeEvent 函数
// 重写 widget的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();
}
执行程序,此时改变窗口大小,图片也会随之变化.
于此同时,在控制台里也能够看到尺寸变化的过程
此处的 resizeEvent 函数我们没有手动调用,但是能在窗口大小变化时被自动调用。
这个过程就是依赖 C++ 中的多态来实现的。Qt 框架内部管理着 QWidget 对象表示咱们的窗口。在窗口大小发生改变时,Qt 就会自动调用 resizeEvent 函数。
但是由于实际上这个表示窗口的并非是 QWidget,而是 QWidget 的子类。也就是咱们自己写的 Widget。此时虽然是通过父类调用函数,但是实际上执行的是子类的函数(也就是我们重写后的 resizeEvent ).
此处属于是多态机制的一种经典用法,通过上述过程,就可以把自定义的代码,插入到框架内部执行。相当于“注册回调函数”;
代码示例:文本对齐,自动换行,缩进,边距
(1) 创建四个 label,objectName 分别是 label 到 label_4
并且在 QFrame 中设置 frameShape 为 Box (设置边框之后看起来会更清晰一些)
QFrame
是QLabel
的父类。其中frameshape
属性用来设置边框性质
- OFrame::Box:矩形边框
- QFrame::Panel:带有可点击区域的面板边框
- QFrame::WinPanel :Windows风格的边框
- QFrame::HLine:水平线边框
- QFrame::VLine:垂直线边框
- OFrame::StyledPanel :带有可点击区域的面板边框,但样式取决于窗口主题
(2) 编写 widget.cpp, 给这四个 label 设置属性.
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("这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本这是⼀个很⻓的⽂本");
}
代码示例:设置伙伴
(1) 创建两个 label 和 两个 radioButton.
objectName
分别为 label
,label_2
,radioButton
,radioButton_2
此处把 label 中的文本设置为“快捷键 &A”这样的形式
其中 & 后面跟着的字符,就是快捷键
可以通过 alt + A的方式来触发该快捷键
但是注意,这里的快捷键和 QPushButton 的不同,需要搭配 alt 和 单个字母的方式才能触发
(2) 编写 widget.cpp,设置 buddy 属性
当然这里也可以使用 Qt Designer 直接设置.
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 即可选中对应的选项
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::Pct:八进制模式,以八进制格式显示数字 只有十进制的时候才能显示小数点后的内容. |
segmentStyle | 设置显示风格 QLCDNumber::Flat:平面的显示风格,数字呈现在一个平坦的表面上。 QLCDNumber::Outline: 轮廓显示风格,数字具有清晰的轮廓和阴影效果。 QLCDNumber::Filled:填充显示风格,数字被填充颜色并与背景区分。 |
smallDecimalPoint | 设置比较小的小数点. |
代码示例:倒计时
(1) 在界面上创建一个 QLCDNumber
,初始值设为 10。 objectName
为 lcdNumber
(2) 修改 widget.h 代码,创建一个 QTimer
成员,和一个 updateTime
函数
QTimer* timer;
void updateTime();
(3) 修改 widget.cpp,在构造函数中初始化 QTimer
QTimer
表示定时器通过 start
方法启动定时器之后,就会每隔一定周期,触发一次 QTimer::timeout
信号
使用 connect
把 QTimer::timeout
信号和 Widget::updateTime
连接起来,意味着每次触发 QTimer::timeout
都会执行 Widget::updateTime
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建 QTimer 实例
timer = new QTimer(this);
// 连接信号槽. QTimer 会每隔⼀定的时间触发⼀个 timeout 信号. 现在把 timeout 信号和 updateTime 连接起来.
// 此时意味着每次触发 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);
}
(5) 执行程序,可以看到每隔一秒钟,显示的数字就减少 1.
针对上述代码,存在两个问题
(1) 上述代码如果直接在 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 的构造函数无法执行完毕,此时界面是不能正确构造和显示
(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 程序往往会直接崩溃。
这样的约定主要是因为 GUI 中的状态往往是牵一发动全身的,修改一个地方,就需要同步的对其他内容进行调整。
比如调整了某个元素的尺寸,就可能影响到内部的文字位置,或者其他元素的位置。这里一连串的修改,都是需要按照一定的顺序来完成的。
由于多线程执行的顺序无法保障,因此 Qt 从根本上禁止了其他线程修改 GUI 状态避免后续的一系列问题。
ProgressBar
使用 QProgressBar
表示一个进度条
核心属性
属性 | 说明 |
---|---|
minimum | 进度条最小值 |
maximum | 进度条最大值 |
value | 进度条当前值 |
alignment | 文本在进度条中的对齐方式 Qt::AlignLeft:左对齐 Qt::AlignRight:右对齐 Qt::AlignCenter:居中对齐 Qt::AlignJustify:两端对齐 |
textVisible | 进度条的数字是否可见 |
orientation | 进度条的方向是水平还是垂直 |
invertApperance | 是否是朝反方向增长进度 |
textDirection | 文本的朝向 |
format | 展示的数字格式 %p:表示进度的百分比 (0-100) %v:表示进度的数值 (0-100) %m:表示剩余时间(以毫秒为单位) %t:表示总时间(以毫秒为单位) |
代码示例: 设置进度条按时间增长
(1) 在界面上创建进度条,objectName
为 progressBar
其中最小值设为 0,最大值设为 100。当前值设为0
(2) 修改 widget.h, 创建 QTimer 和 updateProgressBar 函数
QTimer* timer;
void updateProgressBar ();
(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::updateProgressBar);
timer->start(100);
}
(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 函数中对当前的任务进度进行计算,并更新进度条的界面效果。
代码示例:创建一个红色的进度条
不要忘了,QProgressBar
同样也是 QWidget
的子类,因此我们可以使用 styleSheet
通过样式来修改进度条的颜色。
(1) 在界面上创建一个进度条
(2) 在 Qt Designer 右侧的属性编辑器中,找到 QWidget 的 stylesheet
属性
编辑如下内容:
其中的 chunk
是选中进度条中的每个“块”,使用 QProgressBar::text
则可以选中文本
QProgressBar::chunk {background-color: #FF0000;}
同时把 QProcessBar
的 alignment
属性设置为垂直水平居中
此处如果不设置 alignment,进度条中的数字会跑到左上角。这个怀疑是 Qt 本身的 bug,暂时只能先使用 alignment 来手动调整下。
(3) 执行程序,可以看到如下效果。我们就得到了一个红色的进度条
通过上述方式,也可以修改文字的颜色,字体大小等样式
Calendar Widget
QCalendarWidget
表示一个“日历",形如
核心属性
属性 | 说明 |
---|---|
selectDate | 当前选中的日期 |
minimumDate | 最小日期 |
maximumDate | 最大日期 |
firstDayOfWeek | 每周的第一天(也就是日历的第一列)是周几 |
gridVisible | 是否显示表格的边框 |
selectionMode | 是否允许选择日期 |
navigationBarVisibleri | 日历上方标题是否显示 |
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();
qDebug() << date;
ui->label->setText(date.toString());
}
(3) 执行程序,可以看到当选择不同的日期时,label 中的内容就会随之改变