前文补充
QRadioButton(单选按钮)
QRadioButton 是单选按钮,允许在多个选项中选择一个。作为 QAbstractButton 和 QWidget 的子类,前面介绍的属性和用法对 QRadioButton 同样适用。
QAbstractButton 中与 QRadioButton 关系较大的属性
属性 | 说明 |
---|---|
checkable | 是否能选中 |
checked | 是否已经被选中。checkable 是 checked 的前提条件。 |
autoExclusive | 是否排他。选中一个按钮后是否会取消其他按钮的选中。QRadioButton 默认排他。 |
代码示例:选择性别
-
在界面上创建一个 label 和 3 个单选按钮,文本分别为“男”“女”“其他”,objectName 分别为
radioButton_male
、radioButton_female
、radioButton_other
。 -
修改
widget.cpp
,编辑三个 QRadioButton 的 slot 函数:
void Widget::on_radioButton_male_clicked()
{
// 将界面上的内容进行更新
ui->label->setText("您选择的性别为:男");
}
void Widget::on_radioButton_female_clicked()
{
// 将界面上的内容进行更新
ui->label->setText("您选择的性别为:女");
}
void Widget::on_radioButton_other_clicked()
{
// 将界面上的内容进行更新
ui->label->setText("您选择的性别为:其他");
}
运行程序,选择不同单选按钮,label 提示文字随之变化。开始没有任何的选中
程序启动时默认不选任何选项,可修改为默认选中“男”:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//添加一个默认的选型
ui->radioButton_male->setChecked(true);//此时单选按钮就被选中了
ui->label->setText("您选择的性别为:男");
}
可禁用“其他”选项:在构造函数之后接续代码:
// 仅禁止选中但仍能触发点击 --- 文本还是会变化
ui->radioButton_other->setCheckable(false);
// 彻底禁用(无法选中也无法响应输入)
ui->radioButton_other->setEnabled(false);
// ui->radioButton_other->setDisabled(true);//这个也一样的作用
代码示例:click, press, release, toggled 的区别
-
clicked
:一次“点击”(按下+释放 = pressed + released)我们无参的使用多了,我们下面使用带参的 -
pressed
:鼠标“按下” -
released
:鼠标“释放” -
toggled
:按钮状态切换(checked 属性改变)
-
在界面上创建四个单选按钮,objectName 为
radioButton
至radioButton_4
。 -
分别创建槽函数:
void Widget::on_radioButton_clicked(bool checked)
{
// 这个 checked 参数就是表示当前 radiobutton 的选中状态
qDebug() << "clicked: " << checked;
}
void Widget::on_radioButton_2_pressed()
{
qDebug() << "pressed";
}
void Widget::on_radioButton_3_released()
{
qDebug() << "releaseed";
}
void Widget::on_radioButton_4_toggled(bool checked)
{
// checked 状态发生改变,就会触发这个信号
qDebug() << "toggled: " << checked;
}
运行程序,观察不同信号触发时机。
总结:toggled 最适合 QRadioButton!!!
代码示例:单选框分组
基于 RedioButton,实现一个简单的模拟点餐的功能!
-
在界面上创建 6 个单选框,objectName 为
radioButton
至radioButton_6
,默认是全部排他。 -
一旦页面上是需要存在“多组单选按钮”的时候,希望组合组之间不要有影响。
-
Qt 引入
QButtonGroup
这样的工具类进行分组,每组内部排他,组间互不影响:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 使用 QButtonGroup 对单选按钮进行分组
QButtonGroup* group1 = new QButtonGroup(this);
QButtonGroup* group2 = new QButtonGroup(this);
QButtonGroup* group3 = new QButtonGroup(this);
// 把对应单选按钮,放在不同的组里
group1->addButton(ui->radioButton);
group1->addButton(ui->radioButton_2);
group1->addButton(ui->radioButton_3);
group2->addButton(ui->radioButton_4);
group2->addButton(ui->radioButton_5);
group2->addButton(ui->radioButton_6);
group3->addButton(ui->radioButton_7);
group3->addButton(ui->radioButton_8);
}
再次运行程序,即可实现正确的分组排他。
QCheckBox(复选按钮)
QCheckBox 表示复选按钮,允许选中多个。与 QCheckBox 最相关的属性同样继承自 QAbstractButton:checkable
与 checked
。
tristate
为 QCheckBox 独有,可实现“三态复选框”,较冷门,暂时不讨论。
代码示例:获取复选按钮的取值
-
在界面上创建三个复选按钮和一个普通按钮,objectName 分别为
checkBox_eat
、checkBox_sleep
、checkBox_play
及pushButton
。 -
为
pushButton
添加 slot 函数:
void Widget::on_pushButton_clicked()
{
QString result = "今天你的安排是: ";
if(ui->checkBox_study->isChecked())
{
result += ui->checkBox_study->text() + " ";
}
if(ui->checkBox_game->isChecked())
{
result += ui->checkBox_game->text() + " ";
}
if(ui->checkBox_work->isChecked())
{
result += ui->checkBox_work->text() + " ";
}
ui->label->setText(result);
}
运行程序,点击确认按钮,控制台输出选中的内容。
Tool Button(QToolButton)
QToolButton 的大部分功能与 QPushButton 一致,但主要应用于工具栏、菜单等场景,此处暂不介绍。
介绍完了按钮类控件,接下来,我们开启对显示类控件的介绍!
显示类控件
QLabel(标签)
QLabel 可以用来显示文本和图片。核心属性如下:
属性 | 说明 |
---|---|
text | QLabel 中的文本 |
textFormat | 文本的格式:
|
pixmap | QLabel 内部包含的图片 --- 这是单纯为了放图片的更好的选择! |
scaledContents | 设为 true 表示内容自动拉伸填充 QLabel;设为 false 则不会自动拉伸 |
alignment | 对齐方式。可以设置水平和垂直方向如何对齐(左/右居中,上/下居中) |
wordWrap | 设为 true 内部的文本会自动换行;设为 false 则内部文本不会自动换行(文本长度超出了Label本身,是否要自动换行,QLabel 是不会给我们提供滚动条的,有的控件,QTEXTEdit --- 多行编辑框是有滚动条的) |
indent | 设置文本缩进。水平和垂直方向都生效(两个方向) |
margin | 内部文本和边框之间的边距。不同于 indent,上下左右四个方向同时有效 |
openExternalLinks | 是否允许打开一个外部的链接(当 QLabel 文本内容包含 url 时涉及到) |
buddy | 给 QLabel 关联一个“伙伴”,点击 QLabel 时就能激活对应的伙伴。例如伙伴是一个 QCheckBox,则该 QCheckBox 就会被选中 |
代码示例:显示不同格式的文本 - textFormat
-
在界面上创建三个 QLabel,尺寸放大一些,objectName 分别为
label
、label_2
、label_3
。 -
修改
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>");//<b>标签:进行文本加粗
// 把第三个label 设置成显示markdown
ui->label_3->setTextFormat(Qt::MarkdownText);
ui->label_3->setText("# 这是 markdown 文本");//# :让文本成为一级标题
}
运行程序,观察效果:
代码示例:显示图片
虽然 QPushButton 也可以通过设置图标的方式设置图片,但并非好选择。更多时候希望通过 QLabel 作为更单纯的显示图片方式。
-
在界面上创建一个 QLabel,objectName 为
label
。 -
创建
resource.qrc
文件,并把图片导入到 qrc 中。 -
修改
widget.cpp
,给 QLabel 设置图片:
先将 Qlable 设置为和窗口一样大,并且被这个 QLabel 左上角设置到窗口的左上角
让整个 QLabel 铺满整个窗口,我们可以通过窗口的 geometry 来获取:
// 先将 Qlable 设置为和窗口一样大,并且被这个 QLabel 左上角设置到窗口的左上角
// 让整个 QLabel 铺满整个窗口
QRect windowRect = this->geometry();
ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());
// ui->label->setGeometry(0, 0, 800, 600);//硬编码
QPixmap pixmap(":/dog.png");
ui->label->setPixmap(pixmap);
执行程序,观察效果。我的图片本身尺寸是 200×200,并没有把 QLabel 填充满。
修改代码,设置 scaledContents
属性:来实现自动拉伸的填充 Label
// 设置内容伸缩
ui->label->setScaledContents(true);
再次运行,图片已被拉伸填满窗口。
此时拖动窗口大小,图片不会同步变化。上面的代码是在构造函数中,进行的如上的尺寸设置,这个设置相当于是一次性的,一旦程序运行起来之后,QLabel 的尺寸就固定下来了,窗口发生改变,此时,QLabel 是不会变化的!
我们需要介绍一个知识点 --- 事件
对于之前的信号和槽,用户的操作会对应一些信号(点击按钮,触发指定信号),有些用户操作,对应的不是信号,而是事件!
所以在 Qt 中,表示用户的操作,有两类概念:一个是信号,另一个是事件!对于事件,后续会详细介绍,当前我们就来简单起个头:
就像我们鼠标拖动窗口大小的时候,就会让我们的 Qt 程序触发一个 resize 事件(resizeEvent),像这种事件是连续触发的:将窗口尺寸从 A 拖到 B 这个过程中,会连续触发一系列的 resizeEvent!之前的信号是点一下,只触发一下信号
此时接可以借助 resizeEvent 来完成上述的功能!
为解决此问题,可在 Widget 中重写父类(QWidget)的 resizeEvent
虚函数:
在实际编程中,指定回调函数其实有很多种写法:
- 设置函数指针
- 设置仿函数函数对象
- 设置 lambda 表达式
- 重写父类虚函数 --- (框架中拿着父类的指针调用这个函数,如果你创建的子类重写了这个函数,此时在多态机制下,实际执行的就是子类的函数了)
// 重写 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
)。此处属于多态机制的经典用法,通过上述过程可把自定义代码插入到框架内部执行,相当于“注册回调函数”。
代码示例:文本对齐、自动换行、缩进、边距
创建四个 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 设置属性:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置文字居中对齐
ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->label->setText("垂直and水平居中的文本");
// 设置自动换行
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("这是一个很长的文本这是一个很长的文本这是一个很长的文本这是一个很长的文本这是一个很长的文本这是一个很长的文本");
}
运行程序,效果如下:
-
第一个 label 垂直水平居中
-
第二个 label 设置了 wordWrap,能够自动换行
-
第三个 label 设置了 indent,左侧和上方与边框有间距,右侧则没有
-
第四个 label 设置了 margin,四个方向均有间距(图上仅体现三个方向,下方看不出来)
代码示例:设置伙伴
QLabel 的伙伴:就是可以将 QLabel 和单选框或者复选框这样的控件绑定成伙伴关系 --- 此时就可以通过 QLabel 来触发单选框/复选框的选择操作!
创建两个 label 和两个 radioButton,objectName 分别为 label
、label_2
、radioButton
、radioButton_2
。
此处把 label 中的文本设置为 "快捷键 &A" 这种形式,其中 &
后面的字符就是快捷键,可通过 Alt + A 触发。注意这里的快捷键和 QPushButton 不同,需要搭配 Alt 和单个字母才能触发。
编写 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);
}
运行程序,按下快捷键 Alt + A 或 Alt + B,即可选中对应选项。
Qt 中,QLbel 中写的文本,是可以指定“快捷键”的,此处的快捷键的规则功能上要比 QPushButton 弱很多,使用方式是:
在文本中使用"&"跟上一个字符来表示快捷键,比如:
"&A" --- 通过键盘上的 Alt + a 来触发这个快捷键;
"&B" --- 通过键盘上的 Alt + b 来触发这个快捷键。
绑定了伙伴关系之后,通过快捷键就可以选中对应的单选框/复选框了!
LCDNumber(数字显示)
QLCDNumber 是专门用来显示数字的控件,类似老式计算器的效果。
核心属性
属性 | 说明 |
---|---|
intValue | QLCDNumber 显示的数字值(int)。 |
value | QLCDNumber 显示的数字值(double)与 intValue 联动; 例如给 value 设为 1.5,intValue 的值就是 2; 另外,设置 value 和 intValue 的方法名为 display,而非 setValue 或 setIntValue。 |
digitCount | 显示几位数字。 |
mode | 数字显示形式:
仅十进制可显示小数点后内容 |
segmentStyle | 显示风格:
|
smallDecimalPoint | 设置较小的小数点。 |
代码示例:倒计时
界面放一个 QLCDNumber,初始值设为 10,objectName 为 lcdNumber。
// 设置初始值
ui->lcdNumber->display("10");
修改 widget.h 代码,增加成员:创建一个 QTimer 成员,和一个 updateTime 函数
QTimer* timer;
void updateTime();
widget.cpp 构造函数初始化 QTimer
- QTimer 表⽰定时器,这个类创建出来的对象,就会产生一个 timeout 这样的信号,通过 start ⽅法启动定时器之后,并且参数中设定触发 timeout 信号的周期,就会每隔⼀定周期,触发⼀次 QTimer::timeout 信号 --- C++标准库没有提供定时器的实现,但是boost库中提供了对应的功能!Qt 封装了对应的定时器(结合了信号槽机制)
- 我们就可以结合 connect,把这个 timeout 信号绑定到需要的槽函数中,就可以执行逻辑,修改 LCDNumber 中的数字了 --- 使⽤ connect 把 QTimer::timeout 信号和 Widget::updateTime 连接起来,意味着每次触发 QTimer::timeout 都会执⾏ Widget::updateTime
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置初始值
ui->lcdNumber->display("10");
// 创建一个 QTimer 实例
timer = new QTimer(this);
// 把 QTimer 的 timeout 信号和咱们自己的槽函数进行连接
connect(timer, &QTimer::timeout, this, &Widget::updateTime);
// 启动定时器,参数是触发 timeout 的周期,单位是 ms
timer->start(1000);
}
widget.cpp 实现 updateTime
- 通过 intValue 获取到 QLCDNumber 内部的数值。
- 如果 value 的值归 0 了,就停止 QTimer --- timer->stop()。接下来 QTimer 也就不会触发 timeout 信号了。
void Widget::updateTime()
{
// qDebug()<<"过了一秒";
// 先拿到 LCDNumber 中的数字
int value = ui->lcdNumber->intValue();
if(value <= 0)
{
// 数字减到0了,要停止定时器
timer->stop();//timer 设置成成员变量!!!
return;
}
ui->lcdNumber->display(value-1);
}
运行程序,每秒钟数字减 1。
针对上述代码的两个问题:
1.上述代码如果直接在 Widget 构造函数中,通过一个循环 + sleep 的方式是否可以呢?
代码形如:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
int value = ui->lcdNumber->intValue();
while(true)
{
// 先休眠 1秒 --- 获取当前线程的sleep
std::this_thread::sleep_for(std::chrono::seconds(1));
if(value <=0 )
{
break;
}
ui->lcdNumber->display(--value);
}
}
我们执行程序,一段时间之间内是没有显示出来执行的程序的,而是等了一会,但是出来的界面已经是0了!
显然,这个代码是不行的。循环会使 Widget 的构造函数无法执行完毕,此时界面是不能正确构造和显示的。而是需要将 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 状态,避免后续的一系列问题。
综上所述,使用定时器,是实现上述功能的最合理方案。后续如果我们也有类似的需要“周期性修改界面状态”的需求,也需要优先考虑使用定时器。
QProgressBar(进度条)
使用 QProgressBar 表示一个进度条。
注意:不要把 ProgessBar 拼写成 ProcessBar!
核心属性
属性 | 说明 |
---|---|
minimum | 进度条最小值 |
maximum | 进度条最大值 |
value | 进度条当前值 |
alignment | 文本在进度条中的对齐方式
|
textVisible | 进度条的数字是否可见 |
orientation | 进度条的方向是水平还是垂直 |
invertAppearance | 是否是朝反方向增长进度 |
textDirection | 文本的朝向 |
format | 展示的数字格式
|
代码示例:设置进度条按时间增长
在界面上创建进度条,objectName 为 progressBar
其中最小值设为 0,最大值设为 100当前值设为 0
修改 widget.h,创建 QTimer 和 updateProgressBar 函数
QTimer* timer;
void updateProgressBar();
修改 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); // 100ms触发一次timeout信号,即一秒钟触发10次
}
修改 widget.cpp,实现 updateProgressBar
void Widget::updateProgressBar() {
int value = ui->progressBar->value();
if (value >= 100) {
timer->stop();
return;
}
ui->progressBar->setValue(value + 1);
}
运行程序,可以看到进度条中的进度在快速增长
实际开发中,进度条的取值通常根据当前任务的实际进度设置:
例如读取大文件时,可以根据文件总大小和已读取大小设置进度条比例
Qt禁止在其他线程修改界面,因此进度条更新通常需要搭配定时器完成
-
通过定时器周期触发信号
-
主线程调用对应的slot函数
-
在slot函数中计算当前任务进度并更新界面
代码示例:创建红色进度条
上述的进度条使用表示紫色的,但是考虑到有些人可能不喜欢紫色,因此改成红色的进度条。
不要忘了,QProgressBar 同样也是 QWidget 的子类,因此可以使用 styleSheet 通过样式来修改进度条的颜色。
在界面上创建一个进度条
在Qt Designer右侧属性编辑器中,找到QWidget的styleSheet属性,编辑如下内容:
QProgressBar::chunk {
background-color: #FF0000;
}
同时把QProgressBar的alignment属性设置为垂直水平居中
注意:如果不设置alignment,进度条中的数字可能会跑到左上角,这可能是Qt的bug,暂时只能手动调整对齐方式
执行程序,可以看到红色进度条效果:
通过上述方式,也可以修改文字的颜色、字体大小等样式