C++ Qt——从入门到入土 (一)

目录

1.环境的配置

1.1 Qt的下载

1.2 在Vs中配置Qt

1.3 解决中文乱码的问题

 2.第一个窗口(搜索窗口)

2.1 构造一个Qt Widgets项目

2.2 Qt初体验——窗口基本属性设置&调整

2.2.1 构造析构函数

2.2.2 设置窗口标题和大小

2.2.3 窗口初始位置的移动

2.2.4 窗口背景颜色设置

*2.2.5 QstyleSheet详细

3.Qt常用控件

3.0 何为控件

3.1 按钮——QPushButton

3.2 信号与槽函数

3.2.1 概述

3.2.2 槽函数与连接

3.2.3 信号与发射

3.3 自定义信号的发射

3.4 文本、图片的“显示屏”——QLabel

3.4.0 概述

3.4.1 初始化与文本设置

3.4.2 点缀QLabel

3.4.3 部分文字效果变化

3.4.4 图片的判断存在、读取、加载

3.4.5 图片在QLabel上的展示

3.4.6 例子——应用QLabel为窗口添加背景

*3.4.7 关于QWidget背景图片的其他设置方法

3.4.8 进击!利用QLabel播放动图

3.4.9 以上的全部代码及图片资源连接

3.5 文本输入框——QLineEdit(从这里开始正式布局搜索窗口)

3.5.0 概述

3.5.1 初始化与默认提示文字的设置

3.5.2 实现当输入框内有文字输入时激活搜索按钮

3.5.3 获取并展示搜索内容

3.6 模式、对象的选择框——QCheckBox

3.6.0 概述

3.6.1 QCheckBox的初始化

3.6.2 QCheckBox选中状态的反馈

3.7 Qt中的“小浏览器”——QTextBrowser

3.7.0 概述

3.7.1 基于QTextBrowser自定义一个历史记录浏览框控件

3.7.2 Qt框架下读取txt文本内容

3.7.3 在窗口中添加和初始化自定义的历史浏览框

3.7.4 历史记录框的隐藏与失能


1.环境的配置

1.1 Qt的下载

Qt的下载链接如下:Index of /archive/qt/5.12/5.12.3

常见问题&解决:

我在下载时遇到了浏览器拦截,说无法安全下载此文件,解决的办法是在浏览器的设置选项中关闭智能拦截(关掉如图的选项即可)

 注意:

Qt挺大的,占用大概20G的内存,所以要注意好安装的位置避免空间不够得重新安装

安装步骤:

参考:(18条消息) QT安装具体图解_流楚丶格念的博客-CSDN博客_qt安装


1.2 在Vs中配置Qt

第一步:添加系统环境变量

首先找到桌面的我的电脑图标,右键点击选择属性,然后依次点击高级系统设置和环境变量

 找到自己安装的Qt的路径,进入后找到如下几个bin的位置并依次添加到系统环境变量的path中去:

第二步:下载Qt tools

在Vs的扩展->管理扩展中搜索Qt找到要下载的扩展工具(下载好之后需要重新打开 Vs)

 划重点!!!

下载好之后记得关闭Qt Tools的自动更新(如下图),否则整个项目都会出现奇奇怪怪的错误。

此后进行Vs的环境配置:

如图依次点击:扩展->Qt Vs Tools->Options 

再按照下图进行版本添加

 

 至此,环境已配置完成!


1.3 解决中文乱码的问题

在新建的Qt项目中(新建Qt项目详见2.1),按照下图顺序进行编码设置

 

 2.第一个窗口(搜索窗口)

2.1 构造一个Qt Widgets项目

首先,依次按照下图建立一个新的Qt项目,一般都继承自QWidget,因为这貌似是Qt所有窗口类的基类。

搞定后点击Finish就得到了一个带一个继承自QWidget类的窗口的Qt项目

2.2 Qt初体验——窗口基本属性设置&调整

2.2.1 构造析构函数

在这之前我们先干一件事——写好析构函数以避免内存外溢。

通过如上方式构造的一个窗口类中,一开始就有一个私有成员

Ui::FinddialogClass ui;

乍一看貌似没有指针成员析构与否并不影响,但是在之后添加控件时我们往往以指针的形式来定义它们,这时候挨个析构过去相当麻烦,所以Qt的这个ui变量如果写成指针的形式,然后再在析构中释放ui指针的内存即可自动释放掉该Qt类下的所有指针成员的内存。 

将上述代码修改为:

Ui::FinddialogClass *ui;

紧接着构造析构函数:

头文件中:

class Finddialog : public QWidget
{
    Q_OBJECT
public:
    Finddialog(QWidget *parent = Q_NULLPTR);
    //析构防止内存溢出
    ~Finddialog();
private:
    Ui::FinddialogClass *ui;
};

源文件中:

//析构防止内存溢出
Finddialog::~Finddialog()
{
    delete ui;
}

2.2.2 设置窗口标题和大小

接下来我们在类的初始构造函数中设置窗口标题和其大小

Finddialog::Finddialog(QWidget *parent)
    : QWidget(parent)
{

    ui->setupUi(this);

    //设置窗口标题
    setWindowTitle(tr("查询系统"));
    //重设窗口大小
    resize(1080, 720);

};

效果如下:

让我们看看函数原型来了解一下参数:

标题设置函数:setWindowTitle(const QString &);

可见其参数类型为一个QString对象,而QString类是整个Qt对字符串处理使用的类。

因此在自己直接写入字符串时最好以tr()包裹,这时一个好习惯(参考自C++ Gui Qt4编程一书)

所以显然下述程序是错误的:

#include "finddialog.h"
#include <string>

using namespace std;

string my_title = "查询系统";

Finddialog::Finddialog(QWidget *parent)
    : QDialog(parent)
{

    ui->setupUi(this);

    //设置窗口标题
    setWindowTitle(my_title);
    //重设窗口大小
    resize(1080, 720);

};

要解决这个问题就涉及到了String类与QString类之间的转换:

从String转换为QString:

利用函数QString::fromStdString(String &);

将QString转换为String:

xx.toStdString()

其中xx为一个QString对象

即上述例子应该修改为:

#include "finddialog.h"
#include <string>

using namespace std;

string my_title = "查询系统";

Finddialog::Finddialog(QWidget *parent)
    : QWidget(parent)
{
    ui->setupUi(this);

    //设置标题与大小
    setWindowTitle(QString::fromStdString(my_title));
    setFixedSize(1080, 720);
}

//析构
Finddialog::~Finddialog()
{
    delete ui;
}

大小设置函数:setFixedSize(const QSize &);

其中QSize为Qt框架下的尺寸类型,即(宽,高)两个整数

当然Qt还提供了设置宽和高(横向和纵向)的两个函数:

setFixedHeight(int h);

setFixedWidth(int w);

2.2.3 窗口初始位置的移动

窗口初始生成位置的移动依托于下面的函数

move(const QPoint &);

其中QPoint表示Qt坐标系下的一个点,这里就要说说Qt坐标系,我们假设QPoint的坐标构成为(x, y),则坐标系可如下图所示:

 可以看出QPoint的第一个坐标值也就是x越大则该点在屏幕上越靠右,y值越大则越靠下

而这里的move函数实际上是以窗口的左上角为基准,通过将左上角的点放到指定的QPoint处来实现窗口初始位置的变化。

可以在窗口初始化时添加move代码:

Finddialog::Finddialog(QWidget *parent)
    : QWidget(parent)
{
    ui->setupUi(this);

    //设置标题与大小
    setWindowTitle(QString::fromStdString(my_title));
    setFixedSize(1080, 720);
    //窗口生成位置移动
    move(500, 200);
}

效果如下:

2.2.4 窗口背景颜色设置

函数原型:

setStyleSheet(const QString &styleSheet);

这里设置背景色用到的sheet写法为:"QXx { background-color : QColor;}"

其中:QXx为要设置的控件的Qt基类,比如这个窗口继承自QLabel那就写成"QLabel { backgroung-color : QColor;}"。而QColor指Qt中表示颜色的数值

其中常用的QColor表示法有三种:

1.使用系统预置颜色

如:"QLabel { backgroung-color : green;}"、"QLabel { backgroung-color : blue;}"等

这里可以用的颜色有:redgreenblueyellowgray、black、whiteorangepinkbrown、beige(米白色)、aqua(水绿色)、tan(褐色)、silvergoldskyblue等等

以米白色为例:

Finddialog::Finddialog(QWidget *parent)
    : QWidget(parent)
{

    ui->setupUi(this);

    //设置标题与大小
    setWindowTitle(QString::fromStdString(my_title));
    setFixedSize(1080, 720);
    //窗口生成位置移动
    move(500, 200);

    //设置
    setStyleSheet("QWidget { background-color : beige;}");

}

注意:当你写的是不存在的预置颜色会默认设置成黑色

2.使用RGB三原色表示法(rgba(r, g, b))

例:"QLabel { backgroung-color : rgba(174, 221, 129);}"

效果如下:

这里推荐一篇博客:(18条消息) 好看的常用背景色RGB数值_wujinpengjiusan的博客-CSDN博客_好看的颜色rgb

3.使用十六进制表示颜色

如:"QLabel { backgroung-color : #3697ad;}"

效果如下:

使用时建议参考网站:#3c8dbc十六进制颜色代码表,图表,调色板,绘图&油漆 (encycolorpedia.cn)

*2.2.5 QstyleSheet详细

参考博客:(18条消息) 【Qt开发】StyleSheet使用总结_码农code之路-CSDN博客_stylesheet用法

在QstyleSheet中有下面这些枚举量:

background-color:Qcolor;      背景色

color:QColor;                  字体颜色

border-radius:Rpx;                    边框圆角半径(R为int型表示圆角半径)

border:2px solid green;                边框2像素,实现,绿色

font:10pt;                      字体大小10

应用到一个按钮控件则得到:(详见2.3.1)

先在.h文件中在窗口类下添加一个关闭按钮指针成员(记得引入#include <QPushButton>)

QPushButton* closeButton; 

在到.cpp的窗口类构造函数中初始化它

    // 初始化按钮
    closeButton = new QPushButton(tr("关掉我吧!")); // 分配空间
    closeButton->setParent(this); // 绑定到当前窗口
    closeButton->setFixedSize(100, 30); // 大小设置
    closeButton->move(980, 0); // 右上角
    // 样式设置
    closeButton->setStyleSheet("QPushButton { background-color : white;"\
        "color : blue;"\
        "border-radius : 2px;"\
        "border : 2px solid green;"\
        "font : 15px;}");
    // 点击时关闭
    connect(closeButton,
        &QPushButton::clicked,
        this,
        &Finddialog::close);

效果如下: 

这里特别解释一下setStyleSheet中的\符号:

因为要同时设置多个属性的时候堆在一行里代码的可读性会很差很差,所以我们常常会一个属性独占一行,而每个属性的设置都是由一串QString字符设置的,那么分行时就要注意字符串的拼接问题,这里的\符号就是用来拼接跨行的字符串的

举一个很简单的例子:

#include <iostream>

using namespace std;

int main()
{

    cout<<"我爱"\
          "C艹!"\
          "C艹,"\
          "万岁!"<<endl; 

}

 随便来个简单的编译器比如dev-c++运行一下这个小例程得到的结果将是下图:

 可以看到利用\可以使分行的字符串进行无缝衔接,这一招在Qt框架下同样管用

3.Qt常用控件

3.0 何为控件

什么是控件呢,字面理解就是可以控制的部件。它是人机交互的渠道,通过对一个控件进行点击、双击、输入等操作可以使其发出信号,而对应的信号被某些控件接受时就会执行对应的槽函数。(关于信号和槽函数详见后文)比如2.2.5中的关闭按钮例子,在我们点击关闭按钮时,这一点击信号会被这个按钮发射出来,并由我们的Finddialog窗口接受,同时执行关闭窗口的操作。

所以控件就可以简单的理解成是一个可视化的交互渠道。当然你也可以使他失能变成静默控件,即不能被点击、选中或输入,只能拿来看。


3.1 按钮——QPushButton

接下来我想通过按钮来让你快速入门控件并对信号和槽函数的机制有一个初步的印象。

为一个窗口添加一个控件的基本流程如下:

 先来看一个具体的例子吧,还是以2.2.5中的关闭按钮为例:

首先进行头文件中的声明步骤(1、2步)

 然后转到.cpp中进行按钮的初始化

 如此一来便得到了一个关闭按钮。

下面来逐步分析:

第一第二步有类基础的你应该不难理解,就是将一个窗口中的每一个控件声明成该窗口的一个成员变量。以此将控件的操作权给到该窗口。

第三步中,由于我们习惯性地将控件以指针的方式进行编写,那么第一步自然是分配空间。也即:

closeButton = new QPushButton(tr("关掉我吧!"));

这里用到了一个带参构造,将你想要呈现在按钮里的文本放入QPushButton的参数中。

当然也可以用无参构造和设置按钮文本来实现,比如:

closeButton = new QPushButton;
closeButton->setText(tr("关掉我吧!"));

这里用到了Qt控件的一个比较通用的方法:

设置控件上的文本内容:

setText(const Qstring &);

这一方法许多控件都具有。

这时候如果你已经急不可耐地运行了你就会发现一个问题——在窗口上找不到按钮。这是为什么呢?因为在Qt框架下,控件与窗口之间是子和父的关系,想要让一个控件在一个窗口上展现出来就要明确的说明该控件的“父亲”——也就是它要在哪展示。

这一关系的声明用到了一个所有控件都有的方法:

setParent(QWidget* parent);

从这个函数原型我们也可以发现父指针的类型是QWidget*,这侧面反映了QWidget是Qt窗口类中的基类这一事实。那么,调用这个方法将关闭按钮绑定到当前窗口就很简单了。因为这一步往往在当前窗口的构造中同步进行,所以括号中就是this指针(当前窗口对象的指针)

closeButton->setParent(this);

紧接着就是大小、位置、样式的设置,这里用到的方法和上面窗口设置中介绍的一样:

大小设置用:setFixedSize(const QSzie &);resize(const QSzie &);

位置的设置用:move(const QPoint &);

样式设置用:setStyleSheet(const QString &styleSheet);

即:

    closeButton->setFixedSize(100, 30); // 大小设置
    closeButton->move(980, 0); // 右上角
    // 样式设置
    closeButton->setStyleSheet("QPushButton { background-color : white;"\
                               "color : blue;"\
                               "border-radius : 2px;"\
                               "border : 2px solid green;"\
                               "font : 15px;}");

接下来,要实现点击这个按钮时让窗口关闭,这就要用到Qt中特有的信号&槽函数机制!

敲重点!!!(信号和槽函数是重中之重!!!)


3.2 信号与槽函数

3.2.1 概述

信号和槽函数的机制在我看来是一项成功的仿生学(手动狗头/dodge)。这一机制是用在控件与窗口还有系统进行交互的过程中的,用人话说就是程序中的几个部分互相请求“帮忙”的过程

想象一下你需要别人帮忙的时候是怎样的步骤:

1.发现自己有麻烦需要帮忙了

2.将这一信号通过语言或者文字传递给能给你提供所需帮助的人

3.那个人帮你解决困难

注意上面加粗的四个部分,它们就对应了信号与槽函数的四大要素——信号发送者、信号、信号接收者、槽函数

其中信号发送者,也就是“你”,往往是某一个控件,而信号接收者则既可以是一个控件也可以是窗口。

那么就只剩下信号和槽函数是我们比较陌生的了。接下来逐一介绍。

3.2.2 槽函数与连接

先说槽函数(因为Qt的许多类中已经预置了很多信号)。

假如现在“帮助者”收到了一个要关闭窗口的信号请求,那就要通过槽函数来实现。而怎样的请求应该对应怎样的帮助是需要预先就定义好的。前面说了“帮助者”可以是控件或窗口,而控件与窗口之间有明确的子-父关系,那么我们只要将槽函数定义在窗口中即可。

关于这一点,进一步理解就是帮助的“权限”或者说能力。比如关闭窗口这一动作是父亲窗口的变动,依托于它生存的儿子控件们不一定有可以操纵它的权利。或者说这个忙控件们没有能力帮助,而控件可以操纵的窗口也一定可以操作,因此将所有槽函数声明在窗口中是十分合理的。

定义的方式为在public slots:下定义

以关闭窗口为例,则有:

.h中在窗口类的public slots:下定义该槽函数

class Finddialog : public QWidget
{
    Q_OBJECT

public:
    Finddialog(QWidget *parent = Q_NULLPTR);
private:
    Ui::FinddialogClass* ui;
    QPushButton* closeButton;
public slots:
    void close_window();
};

.cpp中进行实现

// 初代关闭窗口的槽函数
void Finddialog::close_window()
{
    this->close();
}

到这里,“帮助者”已经知道自己收到关闭信号时应该干啥了,此时距离成功就差临门一脚——怎么将四个要素连接起来

在Qt框架下,连接交互四要素的工具是connect函数,而connect有两种常见的用法:

第一种:

connect(sender,                 //信号发射者,为一个指针
        &Filed::signal,        //发射的信号,形式为&加上作用域加上信号函数名
        reciver,               //信号的接收者,为一个指针
        &Filed::slot);         //对应的槽函数,形式为&加上作用域加上槽函数名

这一种写法的特点是signal与slot的参数不用写出来,十分简洁漂亮。适用于不需要传递具体信息时的情况。

第二种:

connect(sender,                        //信号发射者,为一个指针
        SIGNAL(signal(arg type)),      //发射的信号,形式为SIGNAL括号内加上带参数的信号函数
        reciver,                       //信号的接收者,为一个指针
        SLOT(slot(arg type)));         //对应的槽函数,形式为SLOT括号内加上带参数的槽函数

这里值得注意的是:信号函数和槽函数种的参数只需要保留类型!!!不用具体写出参数名!

例如有一个代表大脑的指针brain发出信号函数want_eat(QString &food);和一个嘴巴mouth对应的槽函数eat(QString &food);

则用这种连接方式应写为:

connect(brain,                        //信号发射者——大脑
        SIGNAL(want_eat(QString &)),  //发射想吃东西的信号,并且带有一个字符信息表示想吃啥
        mouth,                        //嘴巴接受到这一信号
        SLOT(eat(QString &)));        //开始恰想恰的东西

这种写法就适用于信号和槽函数之间有参数传递的情况。


而这里我们的点击信号和关闭槽函数显然不需要有参数的传递,所以写为:

// 点击时关闭
    connect(closeButton,
            &QPushButton::clicked,
            this,
            &Finddialog::close_window);

这样我们就实现了点击关闭按钮实现关闭窗口的功能 

3.2.3 信号与发射

再来说说信号,回到前面的例子,信号是求助者像帮忙者发出的信号,而这一信号只需要包含一些必要条件,比如“今晚到凤凰城边上替我打个麻将”这一求助信号就包含了时间、地点等信息,但往往求助的信号是不含很多具体信息的,比如“帮我答个到”、“帮我带个饭”、“老师捞捞”等等。但无论含有多少具体信息,它们都有一个共性——求助者除了发出求助信号外往往不用做出其他举措。所以信号函数在定义时大多是只定义而不实现的。

那么信号函数应该如何定义呢?由于它不用实现的性质,所以直接在信号发射者对应的.h文件中的类内的signals:下定义好即可。并且由于控件与窗口的父子关系与窗口的全局性,所以一般我们让自定义的信号由当前的窗口来发射,于是自定义信号函数一般都是定义在窗口类的signals:下的。

比如上述想吃东西的信号,在定义时就可以写成:

signals:
    void want_eat(QString &food);

而这一信号并不需要在.cpp的源文件中进行实现。

到这里其实还不够,前面说了,信号是需要发射的,而常用的一些信号比如鼠标点击、键盘输入等等在Qt框架下是已经预设了的,它们的发射一般不需要我们自己实现,比如上面的关闭按钮的实现中的按钮点击信号QPushButton::clicked(),在我们用鼠标点击按钮时他就会自己发射这一信号,而这一步实现其实是封装在QPushButton中了的。而在我们自定义信号时,想要发射它还需要进行实现。一般发射信号的步骤会加在某一槽函数中。比如看到了一个食物的槽函数会在现在饥饿状态时发射一个想吃这个食物的信号。如:

public slots:
    void see_food(QString &food)
    {
        if (is_hungry())
        {
            emit want_eat(food);
        }
    }

可以看到,这其中发射信号时使用的语法是emit + 信号函数。

上述就是信号的定义与发射的过程,接下来我们通过一个具体的例子来巩固一下。

假设现在有一个这样的需求——需要在窗口中添加一个按钮,点击一次它就会“逃跑”一次(即随机移动一次),同时根据点击次数刷新文本。

乍一看直接使用QPushButton::clicked()的预设信号就好,可是很快我们就应该意识到这样不行,因为我们需要获取点击的次数。这时候就需要自定义一个信号函数来表示第几次被点击了。

而点击次数计算我选择在发射点击几次的信号的槽函数中利用静态变量来计数。下面我先贴上我的实现代码然后进行分析。

finddialog.h为:

#pragma once

#include <QtWidgets/QWidget>
#include "ui_finddialog.h"
#include <QPushButton>
#include <QMessageBox>
#include <QRandomGenerator>

class Finddialog : public QWidget
{
    Q_OBJECT

public:
    Finddialog(QWidget *parent = Q_NULLPTR);
private:
    Ui::FinddialogClass* ui;
    QPushButton* closeButton;
    // 信息对话框
    QMessageBox* info_box = NULL;
    // 调皮按钮
    QPushButton* misc_button;
signals:
    // 调皮按钮点击信号
    void misc_clicked(const QString& num);
public slots:
    void shutdown_window();
    void close_window();
    // 发射点击信号
    void send_misc_clicked();
    // 调皮按钮开始调皮
    void misc_change(const QString& num_str);
};

finddialog.cpp为:

#include "finddialog.h"

Finddialog::Finddialog(QWidget *parent)
    : QWidget(parent)
{
    ui->setupUi(this);

    // 设置标题与大小
    setWindowTitle(tr("图鉴系统"));
    setFixedSize(1080, 720);
    // 窗口生成位置移动
    move(500, 200);
    // 设置
    this->setStyleSheet("QWidget { background-color : rgba(174, 221, 129);}");

    // 初始化关闭按钮
    closeButton = new QPushButton(tr("关掉我吧!")); // 分配空间
    closeButton->setParent(this); // 绑定到当前窗口
    closeButton->setFixedSize(100, 30); // 大小设置
    closeButton->move(980, 0); // 右上角
    // 样式设置
    closeButton->setStyleSheet("QPushButton { background-color : white;"\
                               "color : blue;"\
                               "border-radius : 2px;"\
                               "border : 2px solid green;"\
                               "font : 15px;}");
    // 点击时关闭
    connect(closeButton,
            &QPushButton::clicked,
            this,
            &Finddialog::shutdown_window);

    // 初始化调皮按钮
    misc_button = new QPushButton(tr("我是调皮按钮捏😊"));
    misc_button->setParent(this);
    misc_button->setFixedSize(200, 60);
    // 初始位置设置在中间
    misc_button->move(440, 330);
    // 设置初始样式
    misc_button->setStyleSheet("QPushButton { background-color : white;"\
                               "color : black;"\
                               "border-radius : 2px;"\
                               "border : 2px solid black;"\
                               "font : 15px;}");

    // 被点击时发射信号
    connect(misc_button,
            &QPushButton::clicked,
            this,
            &Finddialog::send_misc_clicked);

    // 接收到点击信号时做出反应
    connect(this,
        SIGNAL(misc_clicked(const QString&)),
        this,
        SLOT(misc_change(const QString&)));

}

// 关闭窗口的槽函数实现
// 利用了QMessageBox控件,之后会讲
// 先关注下面的调皮按钮相关内容
void Finddialog::shutdown_window()
{
    info_box = new QMessageBox();
    info_box->setWindowTitle(tr("喵喵喵?"));
    info_box->setText(tr("我说哥们你是不是点错了捏"));
    QPushButton* btn_sure = info_box->addButton("忍痛离开", QMessageBox::AcceptRole);
    QPushButton* bun_cancel = info_box->addButton("转身留下", QMessageBox::RejectRole);
    info_box->setStyleSheet("background-color:white");
    if (!info_box->exec())
    {
        this->close();
    }
}

// 初代关闭窗口的槽函数
void Finddialog::close_window()
{
    this->close();
}

// 发射点击信号
void Finddialog::send_misc_clicked()
{
    static int click_num = 0;
    emit misc_clicked(QString::number(++click_num));
}

// 调皮按钮样式改变槽函数
void Finddialog::misc_change(const QString& num_str)
{
    // 设置数字num
    int num = num_str.toInt();
    // 先随机移动,因为这一步与num无关
    // 记得固定范围防止按钮跑到看不见的地方影响操作
    misc_button->move(QRandomGenerator::global()->bounded(780), 
                      QRandomGenerator::global()->bounded(660));
    QString color = "";
    // 改变文本并标记颜色
    switch (num)
    {
    case 1:
        misc_button->setText(tr("点我干嘛?😒"));
        color = "rgba(255, 0, 0)";
        break;
    case 2:
        misc_button->setText(tr("你再点?!😡"));
        color = "rgba(255, 128, 0)";
        break;
    case 3:
        misc_button->setText(tr("tnnd,再点我就炸死你!🤬"));
        color = "rgba(255, 255, 0)";
        break;
    case 4:
        misc_button->setText(tr("绿色~~~🤢"));
        color = "rgba(0, 255, 0)";
        break;
    case 5:
        misc_button->setText(tr("不要点我啦!!😓"));
        color = "rgba(0, 255, 255)";
        break;
    case 6:
        misc_button->setText(tr("焯!🤡"));
        color = "rgba(0, 0, 255)";
        break;
    case 7:
        misc_button->setText(tr("我真的要生气啦!👿"));
        color = "rgba(128, 0, 255)";
        break;
    default:
        misc_button->setText(tr("懒得理你,开摆!🤪"));
        color = "white";
        break;
    }
    if (color != "white")
        misc_button->setStyleSheet("QPushButton { background-color : " + color + "; "\
            "color : white;"\
            "border-radius : 2px;"\
            "border : 2px solid black;"\
            "font : 15px;}");
    else
        misc_button->setStyleSheet("QPushButton { background-color : " + color + "; "\
            "color : black;"\
            "border-radius : 2px;"\
            "border : 2px solid black;"\
            "font : 15px;}");
}

先解释两个新引入的头文件——QMessageBox和QRandomGenerator

*一. QMessageBox:

用来实现提示消息框(有消息、疑问、警告、错误等预置形式),之后会细讲这个控件

效果如下:

二. QRandomGenerator

Qt框架下的随机数生成器。

常用的随机数生成语句有:

QRandomGenerator::global()->bounded(int highest);  

随机生成0-highest之间的整数

QRandomGenerator::global()->bounded(int lowest, int highest);  

随机生成lowest-highest之间的整数

接下来解释一下.h中声明的信号函数以及两个槽函数的功用和目的:

一. 信号函数misc_clicked(QString &num)

这是用来传递包含了点击次数的信号的信号函数。它不需要进行实现。

二. 槽函数send_misc_clicked()

这是用来发射调皮按钮点击信号的槽函数。

三. 槽函数misc_change(QString &num_str)

这是用来实现在点击不同次数时按钮发生变化的函数 

3.3 自定义信号的发射

接上例,下面通过上例中按钮被第几次点击的信号的发射来讲解自定义信号的发射。

首先,让我们康康信号发射的槽函数(即send_misc_clicked())的实现:

// 发射点击信号
void Finddialog::send_misc_clicked()
{
    static int click_num = 0;
    emit misc_clicked(QString::number(++click_num));
}

因为要根据点击次数来实现不同形态的改变,所以我们先来实现点击次数计算的功能。这里我们用到了静态变量的方法(用到了static的变量全局只会被初始化定义一次的特点)

随后便通过emit关键字段来发射信号,这一个信号的发射过程就完成了,从中我们可以得到发射信号的槽函数的一般逻辑:

 这里再顺便解释一下为啥我的参数设置成了QString,因为前面说了Qt框架中的字符串显示、修改等等都是依托于QString类的字符串数据,那么在信号中传出去的数字、文本等信息最好也是用QString类的,这可以方便后面使用这个信号进行其他槽函数,例如显示点击次数等等,如果这里用了int型那之后用到要显示它或者利用它设置一些参数时还要在将它转化为QString,增加了操作步骤。

同时想借此例提一下int型和QString类型的相互转换:

int转QString:

利用QString::number(int x)这个函数,x为你要转化成QString的整型变量名(当然也可以是直接的数字)比如:

QString x = QString::number(10);

QString转int:

利用QString类中的方法toInt();

例:

QString x = QString::number(10);
int x_num = x.toInt();

接下来我们还需要绑定一个信号来发射槽函数——那自然是点击事件啦!在调皮按钮被点击时就触发send_misc_clicked()函数来发射点击次数的信号,即:

connect(misc_button,
        &QPushButton::clicked,
        this,    // 注意这里仍然是用当前窗口发射信号因为该信号定义在当前的窗口类Finddialog中
        &Finddialog::send_misc_clicked);

此后再绑定一下调皮按钮的变化事件与第几次点击事件:

connect(this,    // 注意这里的this,因为信号定义在Finddialog下所以发送者也是它
        SIGNAL(misc_clicked(QString &)),
        this,
        SLOT(misc_change(QString &)));

接下来实现一下变化函数即可:

// 调皮按钮样式改变槽函数
void Finddialog::misc_change(const QString& num_str)
{
    // 设置数字num
    int num = num_str.toInt();
    // 先随机移动,因为这一步与num无关
    // 记得固定范围防止按钮跑到看不见的地方影响操作
    misc_button->move(QRandomGenerator::global()->bounded(780), 
                      QRandomGenerator::global()->bounded(660));
    QString color = "";
    // 改变文本并标记颜色
    switch (num)
    {
    case 1:
        misc_button->setText(tr("点我干嘛?😒"));
        color = "rgba(255, 0, 0)";
        break;
    case 2:
        misc_button->setText(tr("你再点?!😡"));
        color = "rgba(255, 128, 0)";
        break;
    case 3:
        misc_button->setText(tr("tnnd,再点我就炸死你!🤬"));
        color = "rgba(255, 255, 0)";
        break;
    case 4:
        misc_button->setText(tr("绿色~~~🤢"));
        color = "rgba(0, 255, 0)";
        break;
    case 5:
        misc_button->setText(tr("不要点我啦!!😓"));
        color = "rgba(0, 255, 255)";
        break;
    case 6:
        misc_button->setText(tr("焯!🤡"));
        color = "rgba(0, 0, 255)";
        break;
    case 7:
        misc_button->setText(tr("我真的要生气啦!👿"));
        color = "rgba(128, 0, 255)";
        break;
    default:
        misc_button->setText(tr("懒得理你,开摆!🤪"));
        color = "white";
        break;
    }
    if (color != "white")
        misc_button->setStyleSheet("QPushButton { background-color : " + color + "; "\
            "color : white;"\
            "border-radius : 2px;"\
            "border : 2px solid black;"\
            "font : 15px;}");
    else
        misc_button->setStyleSheet("QPushButton { background-color : " + color + "; "\
            "color : black;"\
            "border-radius : 2px;"\
            "border : 2px solid black;"\
            "font : 15px;}");
}

上面代码的逻辑其实很简单。

1. 首先利用随机数生成器实现点击一次之后随机移动一下(这里要注意范围的把控,避免出现调皮按钮躲到其他控件后面被遮挡的现象)

2. 接着利用switch-case语句来实现不同点击次数时的文本变化以及记录此时对应的背景颜色(我设想的是点击7次内是红橙黄绿青蓝紫的变化),此外如果点击超过7次我就让他变回白色

3. 最后设置一下背景色和字体色(非白底时字体设置成白色,否则设置成黑色)

最后实现的效果如下:

 想必到此,你对窗口的基本设置、按钮控件以及Qt的信号&槽机制有了一定的了解,接下来,我将开始介绍更多的控件并同时逐步构架起一个图鉴系统。


更新于2022.01.20


3.4 文本、图片的“显示屏”——QLabel

3.4.0 概述

接下来介绍一下QLabel这个控件,这个控件的功能十分强大且用途十分广泛。字面上来看这是一个标签控件,那么很自然的就能想到它可以用来显示文字(因为标签上可以写字哈哈)。但它不仅仅如此,它还可以用来显示图片等等。

3.4.1 初始化与文本设置

首先让我们来初始化一个标签,这一步和按钮的初始化是大同小异的,如:

.h文件中引入QLabel头文件并创建一个QLabel类指针的私有成员:

#include <QLabel>

// ...(中间代码同上)

// 在类的private中添加成员
private:
    QLabel* num_label;

(这里打算用它实现一下显示点击次数的功能所以命名成了num_label,控件的命名最好是功能+控件名,这样在维护和更新代码时不会太吃力)

.cpp中进行初始化:

// 点击次数标签初始化
    num_label = new QLabel(tr("我是QLabel标签"));
    num_label->setParent(this);
    num_label->setFixedSize(300, 40);
    // 默认摆在左上角,与预期一致所以不用移动

这时候Ctrl+F5运行一手效果如下:

可以看到此时左上角就出现了一行文字,并且我们注意到它并没有边框和自己的背景色,这是QLabel区别于QPushButton的特点之一——默认没有边框和背景色。

接下来我们来实现QLabel中显示调皮按钮点击次数的效果:

在上一节我们实现过了调皮按钮点击信号的定义和发射(能够传递点击次数这一信息),那么我们只要接收这一信号并提取出点击次数的信息进行显示即可。也就是说我们只需要再定义和实现一个让QLabel根据传入的点击次数信息进行文本变化的槽函数即可。

首先在.h文件中进行槽函数的声明:

public slots:
    void show_click_num(const QString& num_str);

 随后在.cpp中进行功能实现并绑定对应的信号和槽:

// 实现点击次数展示功能
void Finddialog::show_click_num(const QString& num_str)
{
    num_label->setText(tr("调皮按钮被点击:") + num_str + tr("次"));
}

// 在窗口的构造函数中进行绑定
connect(this,
        SIGNAL(misc_clicked(const QString&)),
        this,
        SLOT(show_click_num(const QString&)));

随后运行一手效果如下:

3.4.2 点缀QLabel

到这里对QLabel的文字展示功能想必你已经有了一定的了解,但是我们往往对信息的展示不仅仅满足于此,比如我们需要给标签添加一个醒目的框框和帅气的阴影,那么我们可以在标签的初始化时加上如下的代码:

(tips:线形的框框上面的setStyleSheet就可以实现,所以下面来点新东西——setFrameShape与setFrameShadow)

        // 设置框框
		num_label->setFrameShape(QFrame::Box);
        // 设置线条宽度
        num_label->setLineWidth(3);

 效果如下:

 可以看到利用setFrameShape来设置的矩形框box是默认黑色的边框,接下来我们为边框设置阴影效果:

// 设置阴影
num_label->setFrameShadow(QFrame::Raised);

这里的Raised是升起来的阴影效果,与之相反的是Sunken(标签部分陷下去)

效果如下:

 发现设置阴影之后它的颜色会和主窗口背景色同化,所以我们在QLabel初始化时给它背景色设置成白色

// 背景颜色设置
    num_label->setStyleSheet("QLabel {background-color : white;}");

之后的Raised阴影效果如下:

Sunken阴影效果如下:

字体颜色和大小的改变则利用setStyleSheet方法来实现即可

3.4.3 部分文字效果变化

但是我们常常不满足于此,在很多情形下数据的展示中数字部分的颜色常常会根据数据的数值变化来进行相应的变化。比如上例中我们希望点击次数的颜色每5次变红一点,到25次彻底变红并不再改变(彻底变红指RGB(255, 0, 0))

那么首先我们就被部分文字颜色设置给难住了,因为之前设置文字颜色都是针对整个控件中的文字的。为了满足这一想法,我们引入如下的方法:

QString num = QObject::tr("<font color=#FF0000>%1</font>").arg(num_str);

这是利用了html的网页格式写法来“撰写”一个字符串,其中color=#FF0000表示的是这个字符串一会显示出来的颜色,这里只能用16进制,rgb格式没用,我也不知道为啥。%1指代的是一个字符串变量在tr后用.arg()的方式具体声明出来。

那么根据这一个方法我们可以改写一下次数展示函数:

void Finddialog::show_click_num(const QString& num_str)
{
    QString num = QObject::tr("<font color=#FF0000>%1</font>").arg(num_str);
    num_label->setText(tr("调皮按钮被点击:")\
                          + num + \
                          tr("次"));
}

这里直接把QObject::tr("<font color=#FF0000>%1</font>").arg(num_str);替换第四行的num也是可以的,之所以先给它“装到”一个QString对象中是为了提高可读性。

效果如下: 

 可以看到数字部分已经成功变成了红色,接下来我们实现渐渐变红的过程,因为要变化5次,所以RGB值相当于从(0, 0, 0)到(51, 0, 0)再变成(102, 0, 0)最后到(255, 0, 0),但是我们只能使用十六进制的编码,所以我们在utils.h和utils.cpp中自己写几个函数实现一下RGB到十六进制值的转换。

补充:utils是我们在项目中常见的一个自己编写的代码模块,里面主要来实现一些常用的小功能,可以理解成是一个项目的小工具箱。

RGB与十六进制码转换的补充:十六进制的颜色编码和RGB值的关系很简单。颜色的十六进制编码有六位三部分,也就是三个十六进制数,分别对应十进制的RGB数值,下图很好的反应了这种关系:

所以我们可以得到一个十进制转十六进制的函数和一个RGB数值转十六进制编码的函数:

 十进制转十六进制的函数如下:

// 余数对应的数值或编码
QString D_to_H_code(int d)
{
	switch (d)
	{
	case 10:
		return "A";
		break;
	case 11:
		return "B";
		break;
	case 12:
		return "C";
		break;
	case 13:
		return "D";
		break;
	case 14:
		return "E";
		break;
	case 15:
		return "F";
		break;
	default:
		return QString::number(d);
		break;
	}
}

// 十进制转十六进制的函数
QString translate_D_to_H(int d)
{
	// 判断一下一开始的d是不是小于16的
	if (d < 16)
	{
		return "0" + D_to_H_code(d);
	}
	// 用ans保存结果
	QString ans = "";
	// 声明一个int型的栈
	QStack<int> stack;
	// 用除留余数法+栈进行进制转换
	int r = 0; 
	while (d >= 16)
	{
		// 计算余数并更新d值
		r = d % 16;
		d /= 16;
		// 余数入栈
		stack.push(r);
		// 处理最后一个d
		if (d < 16)
			stack.push(d);
	}
	// 编码依次出栈组成结果
	while (!stack.empty())
	{
		// 栈顶弹出并转换成十六进制编码
		r = stack.pop();
		ans += D_to_H_code(r);
	}
	return ans;
}

D_to_H_code(int d)函数将单个数字对应的十六进制码0-F以QString的类型返回。

而translate_D_to_H(int d)中利用除留余数法,在不断更迭数字d与余数r的同时将余数r压入中(栈是一种先入后出的数据结构,过几天有空更新一个栈的实现)。

由小学二年级的进制转换知识我们轻松的知道只要一直用代求的十进制数对要转换的进制的数字(二进制就除2,8进制就除8) 做商来更迭自己并不断求余,最后倒序将余数拼在一起就得到了新的进制编码。具体逻辑与例子如下图:

 来个例子:

这样就得到了255对应的8进制编码:377

而这种先算出来的余数后排出来的算法和栈的理念不谋而合所以我利用栈来实现这一算法。

补充:

在Qt框架下自带的栈是QStack,使用时要带上数据类型,例如声明一个储存整数数据的栈:

#include <QStack>

QStack<int> stack;

 再利用前面说的html格式QString来实现一下渐变红的效果实现:

先获取color的十六进制编码:

// RGB数值转16进制编码函数
QString translate_color_code(int r, int g, int b)
{
	// 判断数值是否合法
	bool r_islegal = r >= 0 && r <= 255;
	bool g_islegal = g >= 0 && g <= 255;
	bool b_islegal = b >= 0 && b <= 255;
	if (r_islegal && g_islegal && b_islegal)
	{
		return "#" + translate_D_to_H(r) + translate_D_to_H(g) + translate_D_to_H(b);
	}
}

随后改写一下显示数字的槽函数 :(5次递变,25次最红,所以依次RGB的r值增加51)

// 实现点击次数展示功能
void Finddialog::show_click_num(const QString& num_str)
{
    QString color = "";
    // 25次及以内才改变颜色
    if (num_str.toInt() <= 25)
        color = translate_color_code(51 * (num_str.toInt() / 5), 0, 0);
    // 超过25次就一直是红色了
    else
        color = "#FF0000";
    QString num = QObject::tr("<font color="\
                              + color.toLocal8Bit() + \
                              ">%1</font>").arg(num_str);
    num_label->setText(tr("调皮按钮被点击:")\
                          + num + \
                          tr("次"));
}

实现的效果如下:

3.4.4 图片的判断存在、读取、加载

最开始概述的时候说过QLabel是文字、图片等信息的展示框,前面用了三个小节介绍了文字展示部分的功能,接下来我们来学习一下如何利用QLabel进行图片展示。

首先想要展示一个图片就必须先正确的读取和加载它,所以接下来介绍如何在Qt框架下读取、加载图片。

判断图片是否存在

首先,图片的展示要考虑到异常处理,如果想要展示的图片并不在所指定的位置时,我们不能让程序就这么轻易的狗带,这时我们要灵活应变,展示一张默认图片或者干脆开摆直接搞成纯色。

这里就要介绍到Qt读取外部信息的重要头文件<QFile>,QFile是Qt框架下的文件流,它被广泛应用于各种文件的读取——txt文本、jpg/png/bmp图片等等。

但是判断文件是否存在却要使用<QFileInfo>下的isFile()方法(虚晃一枪想不到吧哈哈哈)

使用方法如下:

// 实例化一个QFileInfo对象
// 在其中加上文件的相对或绝对路径
QFileInfo file("./阿巴阿巴.jpg");
if (!file.isFile())
{
    // 如果该文件不存在,则执行if中的代码
}
else
{
    // 文件存在则执行else中的代码
}

来个小例程:(判断"background_image.jpg"是否在当前的目录下,不在就把窗口背景设置为白色,否则设置成天蓝色)

.h中引入<QFileInfo>

.cpp中进行判断:

QFileInfo file("./background_image.jpg");
if (!file.isFile())
{
    this->setStyleSheet("QWidget {background-color:white;}");
}
else
{
    this->setStyleSheet("QWidget {background-color:skyblue;}");
}

图片的读取与加载

QLabel中显示的图片的读取与加载是利用QPixmap这一头文件进行的。其流程为:

1.初始化一个QPixmap对象,如:

// 实例化一个对象
QPixmap pix = QPixmap();

2. 利用QPixmap的load方法进行读取和加载

// 这里的file_name是要加载的图片的地址
// 同时注意这里的file_name类型为QString
pix.load(file_name);

3.4.5 图片在QLabel上的展示

我们常常利用QLabel的setPixmap方法来展示图片,具体操作如下:

// label为用来展示图片的标签指针
// default_label为用来让图片自适应大小的标签
label->setPixmap(pix.scaled(defalut_label->size(),
					         Qt::IgnoreAspectRatio,
							 Qt::SmoothTransformation));
// 这一步自动填充背景不能省略
label->setAutoFillBackground(true);

 这里之所以用一个default_label标签来使图片自适应大小(就是让图片和所给定的标签等大小)而不是直接让图片和展示标签等大小的原因是有时候我们会给展示标签设置阴影,这时候就不兴让图片和展示标签等大小了,因为这时候阴影会遮盖部分图片,所以需要整一个尺寸略小于展示标签的default_label标签用来让图片自适应大小

而第一个枚举量Qt::IgnoreAspectRatio是指忽略长宽比来更好的适应窗口,第二个枚举量Qt::SmoothTransformation则是用以使图片更加平滑和顺眼。

接下来我们准备一个背景图片background_image,并把背景图片丢失时的背景色设置为浅绿色。

3.4.6 例子——应用QLabel为窗口添加背景

首先这个例子中是不需要进行展示标签阴影效果的,因为与窗口等大,所以不需要引入default_label控制大小,直接在.h中先声明一个成员变量background_label。

随后根据上述知识进行实现

finddialog.h中引入<QPixmap>与<QFileInfo>并且创建background_label这一QLabel*类型成员变量

#include <QFileInfo>
#include <QPixmap>

... // 中间代码略去

private:
    // 注意给个初值NULL,因为图片不存在的时候它是不初始化的
    // 所以要先给个初值避免警告(我有点强迫症稍微理解一下)
    QLabel* background_label = NULL;

在finddialog.cpp中窗口的构造函数的ui->setupUi(this);后加上如下代码

// 判断背景图片是否存在
    QFileInfo file("./background_image.jpg");
    if (!file.isFile())
    {
        this->setStyleSheet("QWidget {background-color:rgba(174, 221, 129);}");
    }
    else
    {
        // 实例化一个QPixmap对象
        QPixmap pix = QPixmap();
        // 加载背景图片
        pix.load("./background_image.jpg");
        background_label = new QLabel();
        background_label->setParent(this);
        // 与窗口等大
        background_label->setFixedSize(1080, 720);
        // 利用setPixmap方法设置背景图片'
        background_label->setPixmap(pix.scaled(background_label->size(),
                                    Qt::IgnoreAspectRatio,
                                    Qt::SmoothTransformation));
        // 这一步自动填充背景不能省略
        background_label->setAutoFillBackground(true);
    }

这里记得删掉咱们最开始给窗口设置背景颜色的那一行,效果如下:

 华科,我的华科,欸嘿嘿(做个白日梦顺便发个病不要见怪🤤)

*3.4.7 关于QWidget背景图片的其他设置方法

这时候有人就要问了,我为什么要用QLabel来做背景图片效果而不直接利用QWidget中自带的放大呢?好吧,既然你诚心诚意地问了,那我就大发慈悲地告诉你:

在QWidget中前面介绍过的setStyleSheet方法中确实可以直接设置背景图片喔!

语句如下:

// url中不用再打引号
this->setStyleSheet("QWidget {border-image:url(./background_image.jpg)}");

我们用这个语句替代前面的QLabel设置背景图片部分效果如下:

发现和我们想象的并不一样,它把所有控件的背景都设置成了指定的图片而窗口自身却没变。查阅资料后发现这个做法是把图片平铺于被设置的窗口中,常常用于子窗口中,比如在.h中添加一个子窗口成员QWidget* son_window;

随后在原本的背景图片设置语句处写上:

son_window = new QWidget();
son_window->setStyleSheet("QWidget {background-image:url(background_image.jpg)}");
son_window->show();

得到的子窗口效果如下:

3.4.8 进击!利用QLabel播放动图

前面说了QLabel是多种信息的展示框,那么有了静态图片又怎么能少得了gif动图呢?所以接下来介绍如何使用QLabel展示动图!

前面介绍的QPixmap是用来展示静态图片的,而动态图片的展示则是用到了QMovie这一头文件。具体的写法如下:

// movie为一个实例化过了的QMovie对象
movie.setFileName("./mygif.gif");
gif_label->setMovie(&movie);
movie.start();

mygif.gif是我用gifcam软件随便录制的一个动图,我们利用上述的方法,制作一个点击就会弹出一个子窗口来播放动图的按钮和一个搭载了展示动图的标签的子窗口。

子窗口我们要添加一个动图播放标签,这在原来的QWidget类中是没有的,所以我们来写一个动图播放窗口类:

第一步:反敲资源管理器中的项目,点击添加->addQtClass

 

按照类似之前建立工程的流程建立一个GifWindow类

gifwindow.h:

#pragma once

#include <QWidget>
#include "ui_gifwindow.h"
#include <QLabel>
#include <QMovie>

class GifWindow : public QWidget
{
	Q_OBJECT

public:
	GifWindow(QWidget *parent = Q_NULLPTR);
	~GifWindow();
private:
	Ui::GifWindow* ui;
	// gif展示label
	QLabel* gif_label;
	// QMovie指针
	QMovie* movie;
};

gifwindow.cpp如下:

#include "gifwindow.h"

GifWindow::GifWindow(QWidget *parent)
	: QWidget(parent)
{
	ui->setupUi(this);

	this->setFixedSize(1080, 720);
	
	// 播放标签(与窗口等大)
	gif_label = new QLabel();
	gif_label->setParent(this);
	gif_label->setFixedSize(1080, 720);
	// 播放动图
	movie = new QMovie();
	movie->setFileName("./images/mygif.gif");
	gif_label->setMovie(movie);
	movie->start();
}

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

Finddialog中播放按钮的初始化、槽函数绑定和槽函数实现如下:

// 初始化动图播放按钮
    gifplay_button = new QPushButton(tr("让我康康👀"));
    gifplay_button->setParent(this);
    gifplay_button->setFixedSize(200, 60);
    // 初始位置设置在左上角数字显示下
    gifplay_button->move(0, 50);
    // 设置初始样式
    gifplay_button->setStyleSheet("QPushButton { background-color : white;"\
        "color : black;"\
        "border-radius : 2px;"\
        "border : 2px solid black;"\
        "font : 15px;}");
    // 绑定槽函数
    connect(gifplay_button,
            &QPushButton::clicked,
            this,
            &Finddialog::playgif);

// 播放槽函数
// 实现动图播放的窗口弹出
void Finddialog::playgif()
{
    // 当前若已存在动图窗口则关闭之
    if (!gif_window == NULL)
    {
        gif_window->close();
    }
    gif_window = new GifWindow();
    gif_window->show();
}

这里为了避免一直点击就一直弹出窗口所以我在当前有动图窗口时就会关掉它再打开。效果如下:

当然,动图既然可以播放就可以暂停和停止:

暂停:movie->setPaused(bool paused);[这里的paused值设为真则暂停反之继续]

停止:movie->stop();
至此,QLabel控件的学习先告一段落,接下来将要开始学习QLineEdit以及QBrowser等控件了!

3.4.9 以上的全部代码及图片资源连接

网盘链接:百度网盘 请输入提取码

提取码:esy8

3.5 文本输入框——QLineEdit(从这里开始正式布局搜索窗口)

3.5.0 概述

在可视化界面中除了按钮外最常见的交互就是通过键盘输入文字,而这一交互便往往依托于文字编辑控件QLineEdit。接下来我们通过实现放一个输入框到界面上并通过搜索按钮来展示输入的搜索内容的例子来熟悉这个控件。

3.5.1 初始化与默认提示文字的设置

关于控件的初始化,上面已经说了很多了,无非就是先在.h中引入控件头文件随后声明指针成员变量,此后再到.cpp中的窗口构造函数中进行大小、颜色、位置等的初始设置。所以这里不再赘述。

.h部分代码如下:

#include <QLineEdit>

...// 中间部分省略

private:
    // 搜索框
    QLineEdit* search_line;
    // 搜索按钮
    QPushButton* search_button;

.cpp部分初始化代码如下:(先把调皮按钮相关代码注释掉,接下来来正经实现一个搜索框。哦对,同时把动图按钮移到左上角放着,嫌不好看也可以直接注释掉它的初始化部分)

// 初始化搜索框与按钮
    search_line = new QLineEdit();
    search_line->setParent(this);
    search_line->setFixedSize(600, 50);
    search_line->move(200, 200);
    // 设置默认提示文字(不可选择)
    search_line->setPlaceholderText(tr("搜点啥呢?"));
    
    // 按钮紧随其后
    search_button = new QPushButton(tr("🔍给爷搜!"));
    search_button->setParent(this);
    search_button->setFixedSize(100, 50);
    search_button->move(820, 200);
    // 初始是失能状态(即一开始不能点击)
    search_button->setEnabled(false);

效果如下:

这里用到的新知识:

一. 控件失能与使能的设置 

setEnabled(bool);

当括号内为真值true则控件激活,可以进行交互;反之控件失活(或者叫失能,也就是通常变成灰色,无法进行交互)

二. QLineEdit的默认提示文字设置

setPlaceholdText(const QString &);

将括号内的字符串以默认提示的方式展示在框内

3.5.2 实现当输入框内有文字输入时激活搜索按钮

在这一步很关键的一点是如何判断输入框内有文字输入,这里我的方案是利用QLineEdit自带的一个预置信号——QLineEdit::textChanged(const QString &),它会将当前QLineEdit中变化的文本传递出去。所以只要这个信号传递出的文本不为空,这时候设置搜索按钮可被点击即可。

那么有了信号,我们就再给它写一个配套激活搜索按钮的槽函数:(下面只展示槽函数的实现部分代码)

// 激活搜索按钮槽函数
void Finddialog::enable_search_button(const QString& text)
{
    if (!text.isEmpty())
    {
        // 变化的文本不为空则激活按钮
        search_button->setEnabled(true);
        // 设置背景为白色
        search_button->setStyleSheet("QPushButton {background-color:white}");
    }
    else
    {
        // 否则还是失能
        search_button->setEnabled(false);
        // 设置背景为灰色
        search_button->setStyleSheet("QPushButton {background-color:rgba(210, 210, 210)}");
    }
}

在.cpp中初始化好输入框与搜索按钮后绑定一下使能槽函数:

// 绑定按钮激活槽函数
    connect(search_line,
            SIGNAL(textChanged(const QString &)),
            this,
            SLOT(enable_search_button(const QString &)));

这里我们再恢复失能时还将背景色从激活的白色手动还原成未激活的灰色以保证其美观性。效果如下:

3.5.3 获取并展示搜索内容

接下来来利用信号与槽函数的机制实现一下点击搜索键时将框中内容显示到一个标签中。

首先,我们定义一个搜索信号并使它包含有一个搜索内容的信息:

// 搜索信号
signals:
    void find(const QString& text); 

随后,定义一个用来发射它的槽函数send_find()

public slots:
    void send_find();

实现一下:(这里的trimmed用来去除头尾空格以增强搜索的容错性)

获取QLineEdit当前的文本内容使用的是其的text()方法,其返回值为一个QString对象

void Finddialog::send_find()
{
    QString text = search_line->text().trimmed();
    emit find(text);
}

搜索按钮被点击时发射搜索信号:

// 绑定按钮点击发射搜索信号
    connect(search_button,
            &QPushButton::clicked,
            this,
            &Finddialog::send_find);

随后利用上面的QLabel部分的知识来写一个用来展示搜索内容的标签(我把它放在了搜索框上面)

.h中的声明:

private:
    // 搜索内容展示标签
    QLabel* search_show_label;
signals:
    // 搜索信号
    void find(const QString& text);
public slots:
    // 展示搜索内容
    void show_search_info(const QString& text);

.cpp中的绑定与实现:

// 在构造函数中初始化标签和绑定槽函数
    // 搜索内容展示标签
    search_show_label = new QLabel();
    search_show_label->setParent(this);
    search_show_label->setFixedSize(300, 50);
    search_show_label->move(350, 150);
    // 颜色设置
    search_show_label->setStyleSheet("QLabel {background-color : white;}");
    //阴影设置
    search_show_label->setFrameShape(QFrame::Box);
    search_show_label->setFrameShadow(QFrame::Raised);
    // 设置线条宽度
    search_show_label->setLineWidth(3);
    // 绑定展示内容函数
    connect(this,
            SIGNAL(find(const QString&)),
            this,
            SLOT(show_search_info(const QString &)));

// 外部实现槽函数
// 展示搜索内容
void Finddialog::show_search_info(const QString& text)
{
    search_show_label->setText(text);
}

实现的效果如下:

3.6 模式、对象的选择框——QCheckBox

3.6.0 概述

在一个可视化界面中常常出现一个后面缀有文字的方框供用户进行勾选,比如下图中的查找模式选择部分:

 通过这一控件可以很好的实现一个窗口内拥有多个模式的功能,同时增加了用户选择的余地并拓宽了用户对于功能选择的范围。

3.6.1 QCheckBox的初始化

控件的初始化现在想必已经轻车熟路了,我们简单的再过一遍,现在在搜索框上方增加两个QCheckBox用来选择搜索的匹配模式——精确与模糊。(原本上方的标签挪到下方)

finddialog.h中的声明:(这里为了后期修改与维护,我们将控件的初始化封装到一个函数中,前面几个控件的都可以自行封装一下)

// 头文件的引入
#include <QCheckBox>

// private中添加私有成员
private:
    QCheckBox* searchMode_box1,* searchMode_box2;
// 封装初始化函数
public:
    void searchMode_box_init();

finddialog.cpp中的初始化实现:

// 模式选择框的初始化
void Finddialog::searchMode_box_init()
{
    searchMode_box1 = new QCheckBox("精确查找");
    searchMode_box2 = new QCheckBox("模糊查找");
    searchMode_box1->setParent(this);
    searchMode_box2->setParent(this);
    // 位置调整
    searchMode_box1->move(200, 170);
    searchMode_box2->move(350, 170);
}

效果如下:

3.6.2 QCheckBox选中状态的反馈

接下来把动图播放按钮初始化注释掉,在左上角加一个QLabel来展示当前的选择状态。

首先,根据我们多年生活经验,搜索模式往往默认为模糊搜索,而精确查找的匹配程度却反而强于模糊查找。那么我们根据这一经验得到下面的逻辑:

1.未选择查找状态时或只勾选了模糊查找时查找模式设置为模糊查找。

2.只要选择了精确查找就设置为精确查找。

根据上面的逻辑我们不难想到在实现时只要用if来判断精确查找是否被选中即可,其他情况一律模糊。那么接下来介绍一下QCheckBox选中状态的获取方法:isChecked()

在我们之前定义过的find信号中加一个参数const QString& mode来传递查找模式的信息,同时在展示查找信息函数那也加一个参数const QString& mode来展示当前查找模式。

发射find信号的槽函数改写为:

// 实现信号发射
void Finddialog::send_find()
{
    QString text = search_line->text().trimmed();
    QString mode = (searchMode_box1->isChecked()) ? "精确查找" : "模糊查找";
    emit find(text, mode);
}

在左上角加一个QLabel叫mode_label,实现它的声明和初始化,不再赘述。

再实现两个选择框被点击时更新显示的模式的函数:

// 搜索模式展示的更新
void Finddialog::update_mode()
{
    QString mode = (searchMode_box1->isChecked()) ? "精确查找" : "模糊查找";
    QString text = QObject::tr("<font color=blue>%1</font>").arg(mode);
    mode_label->setText("当前搜索模式为:"+text);
}

再绑定到精确查找选择的点击上即可,因为模糊搜索点击并不影响模式:

// 模式选择框的初始化
void Finddialog::searchMode_box_init()
{
    searchMode_box1 = new QCheckBox("精确查找");
    searchMode_box2 = new QCheckBox("模糊查找");
    searchMode_box1->setParent(this);
    searchMode_box2->setParent(this);
    // 位置调整
    searchMode_box1->move(200, 170);
    searchMode_box2->move(350, 170);
    // 任何一个被点击时都更新展示框
    connect(searchMode_box1,
            &QCheckBox::clicked,
            this,
            &Finddialog::update_mode);
    connect(searchMode_box2,
            &QCheckBox::clicked,
            this,
            &Finddialog::update_mode);
}

效果如下:

3.7 Qt中的“小浏览器”——QTextBrowser

3.7.0 概述

QTextBrowser字面上看就是一个文本浏览控框控件,它相当于是一个可视化界面中的html页面。所以顾名思义,这一控件可以执行html代码,即可以通过html代码来对其文本进行样式设置等等功能。

3.7.1 基于QTextBrowser自定义一个历史记录浏览框控件

控件的初始化现在想必已经轻车熟路了,所以这里最基本的添加一个QTextBrowser的流程就不再赘述。这里为了满足后面对一个搜索历史浏览框的实现,我们基于QTextBrowser写一个History_Browser类来满足需求。(原本下方的内容展示标签注释掉)

先右键点击项目添加一个QClass,选择Qt Class,在最后的对话框中将基类Base Class处的QObject改为QTextBrowser,再将类名写为History_Browser,点击Finish得到一个自动生成的history_browser.h和一个history_browser.cpp文件,在.h中将构造函数的括号中的QObject* parent的参数删去:

#pragma once

#include <QTextBrowser>

class History_Browser : public QTextBrowser
{
	Q_OBJECT

public:
	History_Browser();
	~History_Browser();
};

并将.cpp的初始代码修改如下:

#include "history_browser.h"

History_Browser::History_Browser()
{
}

History_Browser::~History_Browser()
{
}

我们为他添加一个叫做history_length的整型成员变量用来累计历史记录的长度,并实现一个获取和设置长度的公有方法:

history_browser.h中:

#pragma once

#include <QTextBrowser>

class History_Browser : public QTextBrowser
{
	Q_OBJECT

public:
	History_Browser();
	~History_Browser();
	// 获取历史记录长度
	int get_len()
	{
		return this->history_length;
	}
	// 设置历史记录长度
	void set_len(int len)
	{
		this->history_length = len;
	}
private:
	// 用来累计历史记录长度
	int history_length = 0;
};

接下来,我们来实现它的初始化构造函数,为了能够在退出应用后仍能记忆历史记录,我选择在根目录下建立一个history.txt的文本文件用来存储和读取搜索历史。那么这里就涉及到Qt框架下对txt文本的读取,我们利用下一小节专门学习一下。

3.7.2 Qt框架下读取txt文本内容

我们利用下面三个头文件中的相关方法来完成目的:

#include <QFile>
#include <QTextStream>
#include <QTextCodec>

在.h文件中写好历史记录文本的位置:

const QString history_txt = "./history.txt";

首先在读取之前进行编码方式的设置以防止出现乱码并将文本内容读取到QFile对象file中以便后续处理,再以只读模式打开文件:

    // 读取文本内容
	QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF8"));
	QFile file(history_txt);
    // 只读方式打开文件
	file.open(QIODevice::ReadOnly | QIODevice::Text);

接下来我们通过下面的代码来将历史记录文本中的文本内容写入QTextBrowser控件中:

    QTextStream inf(&file);
	QString line = inf.readLine();
	int length = 0;
	while (!line.isNull())
	{
		//添加历史记录
		//不为空则加入并计算长度
		if (line != tr(""))
		{
			this->append(line);
			length++;
		}
        line = inf.readLine();
	}

	//设置长度 
	this->history_length = length;

	//在末尾缀入暂无历史记录
	if (!this->history_length)
	{
		//如果是空的就写入一个暂无历史记录
		this->append(tr("暂无历史记录"));
	}

这里用到的思路是:

1.通过QTextStream这一文本流的readline方法逐行读取txt文本的内容

2.对非空文本进行显示(利用了QTextBrowser的append方法)同时累积长度

3.当前行读取不到内容时退出读取循环(利用QString的isNull()方法判断)

4.当没有历史记录(即历史记录长度为0)时显示“暂无历史记录”的字样

3.7.3 在窗口中添加和初始化自定义的历史浏览框

finddialog.h中引用头文件并声明成员变量与初始化函数

#include "history_browser.h"

private:
    // 历史记录框
    History_Browser* history_browser;

public:
    // 历史记录框初始化函数
    void history_browser_init();

finddialog.cpp中实现初始化函数并在构造函数中引用之:

    // 在构造函数中引用,在其外实现
    // 历史记录框的初始化
    history_browser_init();
    
    // 构造函数外进行实现
    // 历史记录浏览框的初始化
    void Finddialog::history_browser_init()
    {
        history_browser = new History_Browser();
        history_browser->setParent(this);
        history_browser->setFixedSize(600, 200);
        history_browser->move(200, 250);
    }

效果如下:

接下来我们通过类似上面将搜索文本显现出来的槽函数写一个记忆函数(即点击搜索键时将当前搜索文本同时写入浏览框与历史记录文本)

//将当前搜索内容记录进历史记录中
void Finddialog::memorize()
{
    //获取当前内容
    QString current_text = search_line->text().trimmed();
    //如果当前为空
    if (!this->history_browser->get_len())
    {
        //则先清空框框
        this->history_browser->clear();
    }
    current_text = QString::fromStdString(current_text.toStdString());
    //缀入其中
    this->history_browser->append(current_text);
    //再将长度+1
    this->history_browser->set_len(this->history_browser->get_len() + 1);
    //写入文本文件
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF8"));
    QFile file(history_txt);
    //以读且写的方式打开
    file.open(QIODevice::ReadWrite | QIODevice::Text);
    //读取原文件内容(必须先读出来,才不会被新内容## 标题覆盖掉)
    QByteArray array = file.readAll();
    //输入流
    QTextStream out(&file);
    //写入内容
    out << current_text << "\n";
    file.close();
}

 在搜索按钮的初始化函数中绑定一下:

    // 绑定点击按钮搜索时记录搜索内容
    connect(this,
            &Finddialog::find,
            this,
            &Finddialog::memorize);

可以看到这样一来我们的历史记录浏览框会一直在这里,而根据我们多年网上冲浪经验,历史记录框这种东西应该在点击搜索框时才弹出,接下来我们来实现一下这个功能。

3.7.4 历史记录框的隐藏与失能

在程序界面刚加载出来时我们并不希望历史记录框是可见的,这时候我们可以通过下面的语句令它在一开始的时候是被“隐藏起来”的:

    // 设置隐藏和失能
    history_browser->setVisible(false);
    history_browser->setEnabled(false);

其中setVisible就是设置控件是否可见,setEnabled是设置控件是否可以交互。

那么在什么时候弹出历史记录框比较合理呢?根据我们多年的网上冲浪经验,在点击搜索框输入内容时弹出,在点击框外(搜索框和历史记录框之外)区域时收回较为合理。

先来实现弹出功能。由于前面我们实现过输入搜索内容时激活搜索键,这一逻辑条件与弹出历史记录框相匹配,所以我们只要在这一槽函数(即enable_search_button(const QString& text)这一函数)中对历史记录框进行可视化和使能设置:

 实现的效果如下:

 接下来开始实现点击空白处收回的功能,要实现这一功能我们要用到QMouseEvent这一类。

首先在finddialog.h头文件中引入<QMouseEvent>头文件,随后通过覆写mousePressEvent(QMouseEvent* event)的方法来实现:

#include <QMouseEvent>


// 在Finddialog类中:
protected:
    // 鼠标点击事件
    void mousePressEvent(QMouseEvent* event);

这一事件是标记鼠标点击的(可以不局限于左击),由于我们在Finddialog类中对这一函数进行覆写,那么在点击Finddialog窗口时的鼠标点击事件就会由我们自己写的函数来实现,而点击到窗口上其它控件时则仍遵从对应控件的点击事件。

这一特性恰好可以满足我们的需求——即在左键点击空白(即没有控件的窗口部分)时收回历史记录框,所以我们只要在实现覆写时写一个收回历史记录框的方法即可:

// 覆写鼠标点击事件
void Finddialog::mousePressEvent(QMouseEvent* event)
{
    // 如果点击其他区域时当前历史记录框被弹出
    if (history_browser->isVisible() && event->button() == Qt::LeftButton)
    {
        history_browser->setVisible(false);
        history_browser->setEnabled(false);
    }
}

这里的event->button() == Qt::LeftButton标记的就是左键点击的事件。

(注意:这一覆写的函数并不是槽函数所以不需要绑定!)

效果如下:

 同时细心的你可能注意到了当历史记录变得足够长之后,框框自动出现了一个滑动条,这就是选择QTextBrowser来显示历史记录的一个原因——它自带滑动条不用我们自己去实现一个Slider用来翻页。

相信现在你对Qt这个框架以及上述的控件有了一定的了解和应用,那么第一部分的内容到这里先告一段落,接下来会基于这些基础知识来复盘我搭建一个图鉴搜索系统的过程!(将发布到C++ Qt——从入门到入土(二)中)

感谢陪伴,完结撒花❀❀❀

对于warpAffine QTC++例程的并行化,可以使用OpenMP多线程库来实现。以下是一些可以尝试的步骤: 1. 引入OpenMP头文件,并启用OpenMP编译选项: ```c++ #include <omp.h> #pragma omp parallel for ``` 2. 通过分割任务来并行计算,例如对于warpAffine函数中的循环迭代,可以将其分割成多个任务,每个任务都在不同的线程中运行。在这里,我们可以使用for循环来创建这些任务: ```c++ #pragma omp parallel for for (int i = 0; i < dst.rows; i++) { for (int j = 0; j < dst.cols; j++) { // 计算像素位置 dst.at<cv::Vec3b>(i, j) = getAffinePixel(src, i, j, M); } } ``` 3. 在并行计算过程中,需要注意线程间的数据共享和同步问题。在这里,由于每个线程都需要访问源图像和目标图像,因此需要采用线程安全的方式来访问这些数据。可以使用OpenMP的reduction指令来实现对变量的同步操作: ```c++ #pragma omp parallel for reduction(+:sum) for (int i = 0; i < dst.rows; i++) { for (int j = 0; j < dst.cols; j++) { // 计算像素位置 cv::Vec3b pixel = getAffinePixel(src, i, j, M); dst.at<cv::Vec3b>(i, j) = pixel; // 计算颜色之和 sum += pixel[0] + pixel[1] + pixel[2]; } } ``` 4. 最后,要记得在程序退出时关闭OpenMP线程: ```c++ omp_set_num_threads(num_threads); // 并行计算 ... // 关闭线程 omp_set_num_threads(1); ``` 这些步骤可以帮助您实现warpAffine QTC++例程的并行化,提高程序的执行效率。
评论 49
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码小狗Codog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值