【QT】显示类控件
目录
一、Label
二、LCD Number
三、ProgressBar
四、Calendar Widget
QT专栏:QT_uyeonashi的博客-CSDN博客
一、Label
QLabel 可以用来显示文本和图片.
核心属性如下
代码示例: 显示不同格式的文本
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);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 函数.
// 重写 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 属性用来设置边框性质.
• 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);// 设置文字居中对齐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) 创建两个 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 的伙伴 widgetui->label->setBuddy(ui->radioButton);ui->label_2->setBuddy(ui->radioButton_2);
}
3) 运行程序, 可以看到, 按下快捷键 alt + a 或者 alt + b, 即可选中对应的选项.
二、LCD Number
QLCDNumer 是一个专门用来显示数字的控件. 类似于 "老式计算器" 的效果.
核心属性
代码示例: 倒计时
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 表示一个进度条.
代码示例: 设置进度条按时间增长
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-1 color: #FF0000;}
同时把 QProcessBar 的 alignment 属性设置为垂直水平居中.
此处如果不设置 alignment , 进度条中的数字会跑到左上角. 这个怀疑是 Qt 本身的 bug, 暂时只能先使用 alignment 来手动调整下.
3) 执行程序, 可以看到如下效果. 我们就得到了一个红色的进度条
通过上述方式, 也可以修改文字的颜色, 字体大小等样式.
四、Calendar Widget
QCalendarWidget 表示一个 "日历" , 形如
核心属性
重要信号
代码示例: 获取选中的日期
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 中的内容就会随之改变.
本篇完!