目录

1.Label

代码示例: 显示不同格式的文本

代码示例: 显示图片

代码示例: 文本对齐, 自动换行, 缩进, 边距

代码示例: 设置伙伴

2.LCD Number

代码示例: 倒计时

3.ProgressBar

代码示例: 设置进度条按时间增长

代码示例: 创建一个红色的进度条

4.Calendar Widget

代码示例: 获取选中的日期


1.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

尺寸放大一些. objectName 分别为 label, label_2, label_3

🌴2) 修改 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>");

    // 把第三个 label 设置成显示 markdown
    ui->label_3->setTextFormat(Qt::MarkdownText);
    ui->label_3->setText("## 这是 markdown 文本");
}

🌴3) 运行程序, 观察效果

  • 在富文本中, <b> 标签表示文本加粗。而将 <b> 标签加到纯文本中,只是被当成了单纯的文本,没有进行任何的渲染操作。
  • 在markdown中 ## 表示二级标题。而在纯文本中,## 只是被单纯的当成了 “文本”。 

代码示例: 显示图片

        虽然 QPushButton 也可以通过设置图标的方式设置图片, 但是并非是⼀个好的选择. 更多的时候还是希望通过 QLabel 来作为一个更单纯的显示图片的方式.

🌵1) 在界面上创建一个 QLabel, objectName 为 label

 

🌵2) 创建 resource.qrc 文件, 并把图片导入到 qrc 中.

🌵3) 修改 widget.cpp, 给 QLabel 设置图片

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

    // 先把 QLbel 设置成和窗口一样大,并且把这个 QLabel 左上角设置到窗口的左上角这里
    // 让整个 QLabel 铺满整个窗口
    QRect windowRect = this->geometry();
    ui->label->setGeometry(0, 0, windowRect.width(), windowRect.height());

    QPixmap pixmap(":/meng.png");
    ui->label->setPixmap(pixmap);
}

执行程序, 观察效果:

这个图片本身的尺寸是 200* 200, 并没有把 QLabel 填充满.

🌵4) 修改代码, 设置 scaledContents 属性

    // 启动自动拉伸,此时图片就能够填充满整个窗口了
    ui->label->setScaledContents(true);

再次运行, 观察效果, 可以看到图片已经被拉伸, 可以把窗口填满了.

🌵5) 此时, 如果拖动窗口大小, 可以看到图片并不会随着窗口大小的改变而同步变化.

为了解决这个问题, 可以在 Widget 中重写 resizeEvent 函数.

// 重写 resizeEvent,这个函数会在窗口大小发生改变时被自动调用
// 此处的形参 event 是非常有用的,这里就包含了触发这个 resize 事件这一时刻,窗口的尺寸的数值。
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();
}

执行程序, 此时改变窗口大小, 图片也会随之变化.

与此同时, 在控制台里也能够看到尺寸变化的过程.

        Qt 中,表示用户的操作,有两类概念,一个是信号,另一个是事件。当用户拖拽修改窗口大小的时候,就会触发 resize 事件(resizeEvent)。像 resize 这样的事件,是连续变化的,把窗口尺寸从 A 拖到 B 这个过程中,会触发一系列的 resizeEvent。此时就可以借助 resizeEvent 来完成上述的功能。

        可以让 Widget 窗口类,重写父类(QWidget)的 resizeEvent 虚函数。 在鼠标拖动窗口尺寸的过程中,这个函数就会被反复调用执行,每次触发一个 resizeEvent 事件都会调用一次对应的虚函数。由于此处进行了函数重写,调用父类的虚函数就会实际调用到子类的对应的函数(多态)。

        在实际编程中,指定回调函数其实有很多种写法:

  1. 设置函数指针
  2. 设置仿函数(函数对象)
  3. 设置 lambda 
  4. 通过重写父类虚函数(框架中拿着父类的指针调用这个函数,如果你创键了子类重写了这个函数,此时在多态机制下,实际执行的就是子类的函数了)
  5. Qt 的信号槽

注意:

  1. 此处的 resizeEvent 函数我们没有手动调用, 但是能在窗口大小变化时被自动调用.
  2. 这个过程就是依赖 C++ 中的多态来实现的. Qt 框架内部管理着 QWidget 对象表示咱们的窗口. 在窗口大小发生改变时, Qt 就会自动调用 resizeEvent 函数.
  3. 但是由于实际上这个表示窗口的并非是 QWidget, 而是 QWidget 的子类, 也就是咱们自己写的 Widget. 此时虽然是通过父类调用函数, 但是实际上执行的是子类的函数(也就是我们重写后的 resizeEvent ).
  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 :带有可点击区域的面板边框,但样式取决于窗口主题

🍒 2) 编写 widget.cpp, 给这四个 label 设置属性

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

    // 在构造函数中,给这几个 label 设置不同的属性

    // 设置文字居中对齐
    ui->label->setText("雄关漫道真如铁,而今迈步从头越。");
    ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);

    // 设置自动换行
    ui->label_2->setText("见说道、天涯芳草无归路。怨春不语。算只有殷勤,画檐蛛网,尽日惹风絮。长门事,准拟佳期又误。蛾眉曾有人妒。千金纵买相如赋,脉脉此情谁诉。君莫舞。君不见、玉环飞燕皆尘土。闲愁最苦。休去倚危栏,斜阳正在,烟柳断肠处。");
    ui->label_2->setAlignment(Qt::AlignTop | Qt::AlignLeft);
    ui->label_2->setWordWrap(true);

    //设置首行缩进
    ui->label_3->setText("予怀怆然,感慨今昔,因自度此曲。千岩老人以为有《黍离》之悲也。淮左名都,竹西佳处,解鞍少驻初程。过春风十里,尽荠麦青青。自胡马窥江去后,废池乔木,犹厌言兵。渐黄昏,清角吹寒,都在空城。");
    ui->label_3->setIndent(50);
    ui->label_3->setAlignment(Qt::AlignTop | Qt::AlignLeft);

    // 设置边距
    ui->label_4->setText("千古江山,英雄无觅,孙仲谋处。舞榭歌台,风流总被,雨打风吹去。斜阳草树,寻常巷陌,人道寄奴曾住。想当年,金戈铁马,气吞万里如虎。元嘉草草,封狼居胥,赢得仓皇北顾。四十三年,望中犹记,烽火扬州路。");
    ui->label_4->setMargin(30);
    ui->label_4->setAlignment(Qt::AlignTop | Qt::AlignLeft);
}

🍒 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 属性

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, 即可选中对应的选项.

2.LCD Number

QLCDNumer 是一个专门用来显示数字的控件. 类似于 "老式计算器" 的效果.

核心属性:

属性 说明
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 设置⽐较⼩的 ⼩数点.

代码示例: 倒计时

🍁1) 在界面上创建⼀个 QLCDNumber , 初始值设为 10.

objectName 为 lcdNumber

🍁2) 修改 widget.h 代码, 创建一个 QTimer 成员, 和一个 updateTime 函数

void updateTime();

QTimer* timer;

🍁3) 修改 widget.cpp, 在构造函数中初始化 QTimer

  • QTimer 表示定时器. 通过 start 方法启动定时器之后, 就会每隔一定周期, 触发一次QTimer::timeout 信号.
  • 使用 connect 把 QTimer::timeout 信号和 Widget::updateTime 连接起来, 意味着每次触发 QTimer::timeout 都会执行 Widget::updateTime,修改 LCDNumber 中的数字。
#include <QTimer>

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

    // 设置初始值
    ui->lcdNumber->display(10);

    // 创建 QTimer 实例
    timer = new QTimer(this);

    // 连接信号槽,QTimer 会每隔一定的时间触发一个 timeout 信号,现在把 timeout 信号和 updateTime 连接起来
    // 此时意味着每次触发 timeout 信号都会伴随 updataTime 函数的执行
    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()
{
    int value = ui->lcdNumber->intValue();
    if(value <= 0){
        // 如果时间到,就停止计时器
        timer->stop();
        return;
    }
    ui->lcdNumber->display(value - 1);
}

🍁5) 执行程序, 可以看到每隔一秒钟, 显示的数字就减少 1.

倒计时


针对上述代码, 存在两个问题:

1) 上述代码如果直接在 Widget 构造函数中, 通过一个循环 + sleep 的方式是否可以呢?

代码形如:

#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);
    }
}

        显然, 这个代码是不行的. 循环会使 Widget 的构造函数无法执行完毕, 此时界面是不能正确构造和显示的.

2) 上述代码如果是在 Widget 构造函数中, 另起一个线程, 在新线程中完成 循环 + sleep 是否可以呢?

代码形如:

#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;
            }
            ui->lcdNumber->display(--value);
        }
    });
}

        这个代码同样是不行的. Qt 中规定, 任何对于 GUI 上内容的操作, 必须在 主线程 中完成. 像 Widget 构造函数, 以及 connect 连接的 slot 函数, 都是在主线程中调用的. 而我们自己创建的线程则不是.

        当我们自己的线程中尝试对界面元素进行修改时, Qt 程序往往会直接崩溃.

  • 这样的约定主要是因为 GUI 中的状态往往是牵一发动全身的, 修改一个地方, 就需要同步的对其他内容进行调整.
  • 比如调整了某个元素的尺寸, 就可能影响到内部的文字位置, 或者其他元素的位置. 这里一连串的修改, 都是需要按照一定的顺序来完成的.
  • 由于多线程执行的顺序无法保障, 因此 Qt 从根本上禁止了其他线程修改 GUI 状态, 避免后续的一系列问题。

综上所述, 使用定时器, 是实现上述功能的最合理方案.

3.ProgressBar

使用 QProgressBar 表示一个进度条.

核心属性:

属性 说明
minimum 进度条最⼩值
maximum 进度条最⼤值
value 进度条当前值
alignment

⽂本在进度条中的对⻬⽅式.

• Qt::AlignLeft : 左对⻬

• Qt::AlignRight : 右对⻬

• Qt::AlignCenter : 居中对⻬

• Qt::AlignJustify : 两端对⻬

textVisible 进度条的数字是否可⻅.
orientation 进度条的⽅向是⽔平还是垂直
invertAppearance 是否是朝反⽅向增⻓进度
textDirection ⽂本的朝向.
format

展⽰的数字格式.

• %p :表⽰进度的百分⽐(0-100)

• %v :表⽰进度的数值(0-100)

• %m :表⽰剩余时间(以毫秒为单位)

• %t :表⽰总时间(以毫秒为单位)


代码示例: 设置进度条按时间增长

🌵1) 在界面上创建进度条, objectName 为 progressBar

其中最小值设为 0, 最大值设为 100. 当前值设为 0.

🌵2) 修改 widget.h, 创建 QTimer 和 handle 函数.

QTimer* timer;

void handle ();

🌵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::handle);
    // 启动定时器
    timer->start(100);
}

🌵4) 修改 widget.cpp, 实现 handle

void Widget::handle()
{
    // 获取到进度条的当前数值
    int value = ui->progressBar->value();
    if(value >= 100){
        timer->stop();
        return;
    }
    ui->progressBar->setValue(value + 1);
}

🌵5) 运行程序, 可以看到进度条中的进度在快速增长.

green 进度条

  • 在实际开发中, 进度条的取值, 往往是根据当前任务的实际进度来进行设置的.
  • 比如需要读取一个很大的文件, 就可以获取文件的总的大小, 和当前读取完毕的大小, 来设置进度条的比例.
  • 由于上面我们介绍了 Qt 禁止在其他线程修改界面, 因此进度条的更新往往也是需要搭配定时器来完成的.
  • 通过定时器周期触发信号, 主线程调用对应的 slot 函数. 再在 slot 函数中对当前的任务进度进行计算, 并更新进度条的界面效果.

代码示例: 创建一个红色的进度条

        上述的进度条是使用绿色表示的, 但是考虑到有些人可能不喜欢绿色, 因此我们基于上面的程序改成一个红色的进度条.

        不要忘了,QProgressBar 同样也是 QWidget 的子类, 因此我们可以使用 styleSheet 通过样式来修改进度条的颜色.

🌴1) 在 Qt Designer 右侧的属性编辑器中, 找到 QWidget 的 styleSheet 属性. 编辑如下内容:

  • 其中的 chunk 是选中进度条中的每个 "块" . 使用 QProgressBar::text 则可以选中文本.

同时把 QProcessBar 的 alignment 属性设置为垂直水平居中.

        此处如果不设置 alignment , 进度条中的数字会跑到左上角. 这个怀疑是 Qt 本身的 bug, 暂时只能先使用 alignment 来手动调整下.

🌴2) 执行程序, 可以看到如下效果. 我们就得到了一个红色的进度条.

red 进度条

4.Calendar Widget

QCalendarWidget 表示一个 "日历" , 形如:

核心属性:

属性 说明
selectDate 当前选中的⽇期
minimumDate 最⼩⽇期
maximumDate 最⼤⽇期
firstDayOfWeek 每周的第⼀天(也就是⽇历的第⼀列) 是周⼏.
gridVisible 是否显⽰表格的边框
selectionMode 是否允许选择⽇期
navigationBarVisible ⽇历上⽅标题是否显⽰
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();
    ui->label->setText(date.toString());
}

🌻3) 执行程序, 可以看到当选择不同的日期时, label 中的内容就会随之改变.

Logo

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

更多推荐