这篇文章中我认为Qt中封装的定时器QTimer是非常有用的,也是实现倒计时关键步骤所在。另外虽然创建线程不能作为另外一种方式去实现倒计时,但确能帮助我们了解如何利用标准库去创建线程

请添加图片描述

QLabel 的相关属性

1. 设置文本对齐方式

Qt Creator 右侧属性编辑区,能够看到类之间的继承关系。QLabel继承自QFrameQFrame继承自QWidget

在这里插入图片描述

Widget的构造函数中,可以给这几个label设置不同的属性。

可以设置对齐方式

  • 有水平居中:AlignHCenter
  • 有垂直居中:AlignVCenter(文本默认是垂直居中)
  • AlignCenter:垂直居中 + 水平居中
  • 有居左对齐,居右对齐:AlignLeft,AlignRight
  • 有居上对齐,居下对齐:AlignTop,AlignBottom

在这里插入图片描述

还想了解更多的对齐方式可以按住ctrl键,鼠标点击AlignTop就能跳转到对齐方式的枚举类型

在这里插入图片描述

还可以通过按位或运算进行组合搭配。比如上面的AlignCenter对齐方式

在这里插入图片描述

2. 设置自动换行 + 缩进 + 边距

  • 设置自动换行:setWordWrap,参数是true或false
  • 设置缩进:setIndent,参数是整型,单位是像素,此处设置的缩进即使文本换行了,后续的行也会产生缩进,不仅仅是首行缩进。在前端中,CSS也能设置缩进(test-indent属性),它只是首行缩进
  • 设置边距:setMargin,参数跟缩进参数一致,这个边距是指上下左右四个方向,文本到边框距离,有时文本内容并没有完全显示,那很可能是边距过大将内容给覆盖掉了

在这里插入图片描述

3. QLabel 伙伴

双击widget.ui文件,编辑图形化界面。在构造函数中设置labelradioButton伙伴关系,比如:ui->label->setBuddy(ui->radioButton); buddy→朋友,伙伴

在这里插入图片描述

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置label和radioButton的伙伴关系
    ui->label->setBuddy(ui->radioButton);
    ui->label_2->setBuddy(ui->radioButton_2);
}

在Qt中,QLabel 写的文本是可以指定快捷键的,只不过此处快捷键的规则功能上要比QPushButton弱很多文本快捷键使用方式:是在文本中使用 跟上一个字符来表示快捷键

比如 &A → 通过键盘上的alt + a来触发这个快捷键,&B → 通过键盘上的alt + b来触发。绑定了伙伴关系之后,通过快捷键就可以选中对应的单选按钮/复选按钮

在这里插入图片描述

QLCDNumber 的相关属性

QLCDNumber是一个专门用来显示数字的控件,类似于那种老式计算机的效果

在这里插入图片描述

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 设置比较小的小数点

这里我想要提出的一点是,直观来说,设置valueintvalue的方法名字应该是setvaluesetIntvalue,而不是display。我觉得用这种命名库和函数名字的方式就能帮助我们省去很多翻阅文档查询到对应方法的额外时间。所以建议大家设计库和函数时,帮助用户便捷查询对应方法也是效率提升的一大助力

2. 一个倒计时界面程序

思路:使用 QLCDNumber 显示一个初始的数值(比如 10),每隔一秒钟,数字就-1,程序减到了0就停止。此处关键要点就是如何将每秒钟通过QLCDNumber给显示到界面上

在这里插入图片描述

2.1 定时器(QTimer)

其实思路逻辑就是循环执行当前值-1后,将这个值给显示到界面上的操作。这就是周期性的执行某个逻辑

那往往会将周期性的执行某个逻辑封装成一个单独的组件,可供我们直接去使用,将这类组件/控件称之为定时器

C++标准库中并没有提供定时器的实现,Boost里面提供了对应的功能,Qt中也封装了对应的定时器,它结合了信号槽机制,就是QTimer

通过这个QTimer类创建出来的对象,就会产生一个 timeout 这样的信号,可以通过 start方法来开启定时器,并且可以在参数中设定触发 timeout 信号的周期。结合 connect,把这个 timeout 信号绑定到需要的槽函数中,就可以执行逻辑,修改 LCDNumber 中的数字了

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "QTimer"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 先设置LCDNumber的初始值
    ui->lcdNumber->display(10);
    // 创建一个QTimer对象,并将其挂到对象树上,要包含头文件
    timer = new QTimer(this);
    // 把QTimer的timeout信号和咱们自己的槽函数进行连接
    connect(timer, &QTimer::timeout, this, &Widget::handle);
    // 启动定时器,参数时触发timeout的周期,单位是ms
    timer->start(1000);

}

Widget::~Widget()
{
    delete ui;
}

void Widget::handle()
{
    // 先要拿到LCDNumber中的数字
    int value = ui->lcdNumber->intValue();
    if (value <= 0)
    {
        // 数字已经减到0了,可以停止定时器了
        // 这里就是为啥要将timer定义成Widget类的成员变量,在槽函数中就可以直接使用timer变量
        timer->stop();
        return;
    }
    ui->lcdNumber->display(value - 1);
}

widget.h

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    // 进行槽函数的声明,鼠标点击handle后,按住alt + enter,快速定义槽函数
    void handle();
    
private:
    Ui::Widget *ui;
    // 直接定义为成员变量,方便槽函数直接使用
    QTimer* timer;
};

动图展示效果

请添加图片描述

2.2 不借助 QTimer(创建线程)→不成功

其实思路逻辑就是程序休眠(sleep)一秒,当前值-1后,将这个值给显示到界面上。这里需要借助sleep函数,sleepWindowsapi,需要包含Windows.h头文件才能使用的

但是sleep只能在VS中使用,而我们所使用的Qt用的是MMGW这样的一个gccWindows版本的编译器,它是无法直接去使用Windows.h的头文件的

所以C++11标准库中引入了sleep_for来代替sleep的休眠操作

std::this thread 起到的效果就是获取当前的线程,在当前这个进程中使用 sleep_for 去休眠(只要支持标准C++11都是可以正常使用的),std chronoC++11中表示时间,去指定休眠时间

#include "widget.h"
#include "ui_widget.h"
#include<thread>

// 这里先要去编辑widget.ui文件,拖拽一个QLCDNumber对象

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 设置初始值
    ui->lcdNumber->display(10);

    // 首先先获取到LCDNumber的值
    int value = ui->lcdNumber->intValue();
    while (true)
    {
        // 先休眠一秒,需要包含头文件thread
        std::this_thread::sleep_for(std::chrono::seconds(1));
        if (value <= 0)
        {
            break;
        }
        value -= 1;
        ui->lcdNumber->display(value);
    }
}

Widget::~Widget()
{
    delete ui;
}

但是运行程序后,你会发现等了10秒后界面才出来,关键界面一出来就只显示0。这是什么原因呢?

只有等构造函数执行完成后,才会去显示窗口。在构造函数的这10秒确实是在更新lcdNumber的值,但是你这个窗口并没有显示出来啊。下面的show语句得构造函数执行完成之后才执行显示,所以窗口最终显示0

这样的一个操作就把整个Widget的构造函数相当于给阻塞住了,阻塞住构造函数,那就会导致这个窗口无法进行显示

在这里插入图片描述

这里想去解决这个问题,可以尝试在构造函数中,另外去创建一个线程,在新的线程中,执行上述循环 + 更新操作。线程操作本身就是操作系统提供的API,比如Linux下创建线程所提供的API:pthread_createWindows下的API可以参阅MSDN windows的文档

在Qt中创建线程,C++标准库就可以通过一些条件编译的方式去兼容不同的系统

#include "widget.h"
#include "ui_widget.h"
#include<thread>


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 设置初始值
    ui->lcdNumber->display(10);

    // 创建线程,这里用到了lambda表达式
    std::thread t([this](){
        int value = ui->lcdNumber->intValue();
        while (true)
        {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            if (value <= 0)
                break;
            value -= 1;
            ui->lcdNumber->display(value);
        }
    });

}

绿色框中的意思是:程序在没有活跃的异常(exception)的情况下被终止了。换句话说,程序在执行过程中遇到了某种问题,导致它无法正常运行,但问题并不是由显式的异常抛出(throw)引起的

在这里插入图片描述

具体原因是因为

在Qt中,窗口界面有一个专门的线程去负责维护更新的(将这个线程称之为主线程→main函数所在的线程)

对于GUI来说,内部包含了很多的隐藏状态,Qt为了保证修改界面的过程中线程安全是不会受到影响的,所以Qt禁止了其他线程直接修改界面

比如在创建的线程中:ui->lcdNumber->display(value);形如这种操作,就是在修改界面。因此Qt为了确保线程安全,直接要求所有对界面的修改操作,必须在主线程中完成!

对于Qt的槽函数来说,默认情况下,槽函数都是由主线程调用的,这也是为什么在槽函数中修改界面是没有任何问题的!

对于return a.exec()语句, a.exec就会使主线程进入事件循环,exec就会一直循环下去,每执行一次循环,都会有一些固定的事情要操作(比如负责执行槽函数的执行等)

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐