当前位置: 首页 > article >正文

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

0. 前置知识 — SQL 在C++ Qt的应用

SQL数据库在C++ Qt中的保姆级应用详解

点赞+关注+收藏 私信获取本文的全部源码哦

1. 登录功能的实现

1.0 登录-注册模板项目

为了方便自己和大家在软件开发中的登录-注册页面构造,我开源了一套自制的模板项目供大家使用C++ Qt 登录-注册模板

1.1 登录窗口的布局

首先,我们想要通过布局和QSS实现如下的登录窗口外观。

在这里插入图片描述
Step 1. 首先,我们拆解一下上图中需要用到哪些控件:

在这里插入图片描述

  • 蓝色代表 QLabel
  • 橙色代表 QLineEdit
  • 红色代表 QPushButton

Step 2. 接着,我们分析一下窗口的布局:

显而易见的是,这里 一共有 4 行,每一行行内 水平对齐,因此可以通过 4 个水平布局实现。而每一行 居中放置于每一列,因此可以直接 将四行的水平布局依次加入一个居中放置元素的垂直布局 来实现。

1.2 登录窗口类的建立和控件的初始化

首先,现在的你已经可以驾轻就熟的建立一个基于 QMainWindow 的登录窗口了。这里我将其命名为 SignWindow。为其添加上述需要用到的控件到私有成员中如下:

private:
    QVBoxLayout* VLayout = NULL;
    QLabel* Background_Label = NULL,
    	  * Area_Label = NULL,
          * User_Label = NULL,
          * Password_Label = NULL,
          * Password_Seen_Label = NULL,
          * Register_Label = NULL;
    QLineEdit* User_Line = NULL,
             * Password_Line = NULL;
    QPushButton* Sign_Button = NULL;

这里的命名也很直白了哈哈哈,接着我们通过一个公有函数 Items_Init() 先将他们初始化到登录窗口上。

void SignWindow::Items_Init(void){
    VLayout = new QVBoxLayout();
    Background_Label = new QLabel(this);
    Area_Label = new QLabel(this);
    User_Label = new QLabel(this);
    Password_Label = new QLabel(this);
    Password_Seen_Label  = new QLabel(this);
    Register_Label = new QLabel(this);
    User_Line = new QLineEdit(this);
    Password_Line = new QLineEdit(this);
    Sign_Button = new QPushButton(this);
}

1.3 小插曲 —— 将图片设置函数封装到一个自定义库中

在上一篇博客,我们在主窗口中封装了一个为标签设置图片的函数。这是一个几乎每个窗口都能用到的功能,因此我们将他单独封装到一个库中。说白了就是 建立一对 头-源文件,还是点击左侧小锤子添加新文件,依次选择添加 HeaderSource 文件即可:

utils.h:

#ifndef UTILS_H
#define UTILS_H

#include <QString>
#include <QLabel>
#include <QPixmap>
#include <QFile>

void Set_Image_On_Label(QLabel*, QString);

#endif // UTILS_H

utils.cpp:

#include "utils.h"

void Set_Image_On_Label(QLabel* label, QString path){
    QFile file(path);
    if (!file.open(QIODevice::Append))
    {
        // Set to default color
        label->setStyleSheet("QLabel {background-color:rgba(110, 189, 221, 0.5);}");
    }
    else
    {
        label->setText("");
        // Create a QPixmap object
        QPixmap pix = QPixmap();
        // Load the image
        pix.load(path);
        // Set background image by 'setPixmap' method
        label->setPixmap(pix.scaled(label->size(),
                                    Qt::IgnoreAspectRatio,
                                    Qt::SmoothTransformation));
        // Auto Fill the Background and it's necessary
        label->setAutoFillBackground(true);
        label->setStyleSheet("QLabel {background-color:rgba(255, 255, 255, 0);}");
        label->setScaledContents(true);
    }
}

此时在任何需要为标签添加背景图片的源文件顶部引入 "utils.h" 即可调用这一函数了。

1.4 将控件添加到布局

我们通过一个自定义的公有函数 Layout_Init() 先将他们按上述布局分析的排布放入对应布局控件。

void SignWindow::Layout_Init(void){
    // Horizontal Layouts
    QVector<QHBoxLayout*> HLayouts;
    for(auto i = 0; i < 4; i++){
        HLayouts.append(new QHBoxLayout());
    }
    // First Row
    HLayouts[0]->addWidget(User_Label);
    HLayouts[0]->addWidget(User_Line);
    // Second
    HLayouts[1]->addWidget(Password_Label);
    HLayouts[1]->addWidget(Password_Line);
    HLayouts[1]->addWidget(Password_Seen_Label);
    // Third
    HLayouts[2]->addWidget(Sign_Button);
    // Fourth
    HLayouts[3]->addWidget(Register_Label);
    // Add to Vertical Layout
    for(auto i = 0; i < 4; i++){
        VLayout->addLayout(HLayouts[i]);
    }
    // Set to AreaLabel
    Area_Label->setLayout(VLayout);
}

上面用到了通过局部指针容器来批量设置水平布局,这在实际开发中是非常常见的优化写法。

1.5 自定义可点击标签 —— CLabel

接下来我们需要设置各个控件的外观样式了,所以我们希望能先在主窗口中通过 点击账户图标弹出登录窗口,你可能兴冲冲地结合前面两篇的知识直接咣咣咣为其绑定了一个由点击信号触发的窗口弹出槽函数。

但是!!! QLabel根本没有内置点击信号

因此,我们需要自定义一个基于 QLabel 的可点击标签类,步骤也很简单,首先按照建立一个 QWidget 派生类的方法建立一个名叫 CLabel 的类(名字随意,CLabel单纯因为我和我女朋友姓的缩写都是C)

接着将类中表示继承的部分换成 QLabel 再引入 QLabel 即可。那么如何让他能发出点击信号呢?这里就回归 Qt底层逻辑,即任何一个 Qt 控件在被鼠标点击时都会触发一个 鼠标点击事件。这是一个继承自最底层的类的公有槽函数,因此我们只需要覆写他,在其中发射 clicked() 信号即可。

clabel.h:

#ifndef CLABEL_H
#define CLABEL_H

#include <QLabel>
#include <QMouseEvent>

class CLabel : public QLabel
{
    Q_OBJECT
public:
    explicit CLabel(QWidget *parent = nullptr);

signals:
    void clicked();

protected:
    virtual void mousePressEvent(QMouseEvent* event);//重写mousePressEvent事件

};

#endif // CLABEL_H

clabel.cpp:

#include "clabel.h"

CLabel::CLabel(QWidget *parent)
    : QLabel{parent}
{

}

void CLabel::mousePressEvent(QMouseEvent *ev)
{
    // emit the 'clicked' signal
    if (ev->button() == Qt::LeftButton) {
        //触发clicked信号
        emit clicked();
    }
    // Send back to the parent object
    QLabel::mousePressEvent(ev);
}

这里挖个坑,后续会穿插详细介绍 鼠标点击事件 的内容,这里仅给出实际使用实例。

接着我们将主窗口中三要素的类别全部替换成 CLabel 并在批量初始化时设置一个聚焦状态将鼠标改为手状态,即 setCursor(QCursor(Qt::PointingHandCursor));

随后我们在主窗口中添加如下两个私有变量和一个公有槽函数:

  • SignWindow* SignPage

登陆页面

  • bool Sign

判断当前是否有账号登录

  • void Open_Sign_Window()

打开登录窗口的槽函数

按照教程一中介绍过的互斥窗口的逻辑实现如下打开槽函数:

void MainWindow::Open_Sign_Window(void){
    if(!Sign){
        if(SignPage != NULL){
            SignPage->close();
        }
        SignPage = new SignWindow(this);
        SignPage->show();
    }
}

此时我们将该槽函数绑定到账户标签:

connect(Nav_account_label,
        &CLabel::clicked,
        this,
        &MainWindow::Open_Sign_Window);

随后在主窗口中点击账户标签则会弹出登录窗口:

在这里插入图片描述

1.6 控件样式批量设置

关于 QSS 的样式设置详见系列教程的第一篇,此处直接给出我的样式设置代码。随后仅补充说明之前未提到过的重要属性。

void SignWindow::QSS_Init(void){
    // Main Area
    Area_Label->setFixedSize(400, 240);
    Area_Label->setObjectName("Core_Area");
    Area_Label->setStyleSheet("QLabel#Core_Area\
                             {background-color:rgba(160, 160, 160, 0.4);\
                              border-radius:15px;\
                              border-width: 1px;\
                              border-style: solid;\
                              border-color: black;}");
                             // Center Position
    Area_Label->move((this->width()-Area_Label->width())/2,
                     (this->height()-Area_Label->height())/2 + 20);

    // Item Vectors
    QVector<QLabel*> Labels;
    Labels.append(User_Label);
    Labels.append(Password_Label);
    QVector<QLineEdit*> LineEdits;
    LineEdits.append(User_Line);
    LineEdits.append(Password_Line);
    QVector<QString> LineNames = {"User Names: ", "Password: "};
    QVector<QString> LineHints = {"请输入帐号", "请输入密码"};
    QVector<QString> LineImgs = {"img/Line_Account.png", "img/Line_Password.png"};

    // Register
    Register_Label->setText("Do not have an account? Register Here!");
    Register_Label->setFont(QFont("Timers", 12, QFont::Bold));
    Register_Label->setMaximumHeight(60);
    Register_Label->setMinimumWidth(280);
    Register_Label->setAlignment(Qt::AlignCenter);
    Register_Label->setStyleSheet("QLabel {background-color:rgba(0, 0, 0, 0);\
                                           color:white;}\
                                   QLabel:hover {background-color:rgba(0, 0, 0, 0);\
                                                 color:rgba(16, 190, 221, 0.4);}");
    Register_Label->setCursor(QCursor(Qt::PointingHandCursor));

    // Style Settings for Labels
    for(auto i = 0; i < Labels.length(); i++){
        Labels[i]->setStyleSheet("QLabel {background-color:rgba(0, 0, 0, 0);\
                                          color:white;}");
        Labels[i]->setMinimumHeight(32);
        Labels[i]->setText(LineNames[i]);
        Labels[i]->setFont(QFont("Timers", 16, QFont::Bold));
    }

    // Style Settings for LineEdits
    for(auto i = 0; i < LineEdits.length(); i++){
        QString Line_Style = "QLineEdit {background-image: url(";
        Line_Style.append(LineImgs[i]);
        Line_Style.append(");background-repeat: no-repeat;background-position: left;padding: 2 10 2 30;");
        Line_Style.append("border-radius:15px;\
                           border-width: 1px;\
                           border-style: solid;\
                           border-color: black;}");
        LineEdits[i]->setStyleSheet(Line_Style);
        LineEdits[i]->setFont(QFont("Timers", 16, QFont::Fantasy));
        LineEdits[i]->setMinimumHeight(32);
        // Initialize the text
        LineEdits[i]->setPlaceholderText(LineHints[i]);
        // Limitation of the Input Length
        LineEdits[i]->setMaxLength(20);
    }

    // Style Settings for QPushButton
    Sign_Button->setStyleSheet("QPushButton {background-color:rgba(255, 255, 255, 1);\
                                             border-radius:15px;\
                                             border-width: 1px;\
                                             border-style: solid;\
                                             border-color: black;}\
                                QPushButton:hover {background-color:rgba(16, 190, 221, 0.4);\
                                                   border-radius:15px;\
                                                   border-width: 1px;\
                                                   border-style: solid;\
                                                   border-color: black;}");
    Sign_Button->setCursor(QCursor(Qt::PointingHandCursor));
    Sign_Button->setText("Log in");
    Sign_Button->setMinimumWidth(80);
    Sign_Button->setMinimumHeight(32);
    Sign_Button->setFont(QFont("Timers", 14, QFont::Bold));

    // Password Seen Label
    Password_Seen_Label->setFixedSize(32, 32);
    Set_Image_On_Label(Password_Seen_Label, "img/Unseen.png");

}

现在,效果如下:

在这里插入图片描述

1.6.1 新属性介绍

  • background-repeat

背景图片小于控件尺寸 时指定 背景图片的重复方式 以填满控件

属性值含义
repeat-x将图片水平方向铺满
比如一张 10*10 的图片会在 100*10 的控件上从左到右重复10次
repeat-y将图片垂直方向铺满
比如一张 10*10 的图片会在 10*100 的控件上自上而下重复10次
repeat将图片在水平和垂直方向铺满
比如一张 10*10 的图片会在 100*100 的控件上重复100次
no-repeat不对图片进行重复填充
  • background-position

当背景图片在控件上排布的 对齐方式 属性值可以通过空格隔开来实现组合复用

属性值含义
top图片 向上对齐
bottom图片 向下对齐
center图片 居中对齐
left图片 居左对齐
right图片 居左对齐
  • padding

指定 输入行上文字的填充大小,可以理解为将 控件 上所有文字按照这一属性 进行移动

属性值为四个由空格隔开的数字,分别代表:

属性值含义
第一个数字文字 上方填充大小
相当于向下移动
第二个数字文字 右侧填充大小
相当于向左移动
第三个数字文字 下方填充大小
相当于向上移动
第四个数字文字 左侧填充大小
相当于向右移动

1.6.2 让布局灵活对齐的”空白组件“

可以看到由于在水平布局中单个控件会铺满整行,上述的布局中大小便显得参差不齐,那么如何让单个控件不铺满一行呢?聪明的你可能想到了利用QLabel来占坑然后设置成不可见就好了。

但事实上还有更简便的做法 —— QSpacerItem,可以理解为这是在布局中封装的一个特殊控件,他的唯一作用就是占坑,使用方法如下(不需要额外引入头文件):

Specific_Layout->addItem(new QSpacerItem(width, height));
  • Specific_Layout

需要添加占位控件的布局指针

  • width

需要占据的宽度

  • height

需要占据的高度

综上,回到之前写好的 Layout_Init() 函数中,添加一些占位控件如下:

void SignWindow::Layout_Init(void){
    // Horizontal Layouts
    QVector<QHBoxLayout*> HLayouts;
    for(auto i = 0; i < 5; i++){
        HLayouts.append(new QHBoxLayout());
    }
    // First Row
    HLayouts[0]->addWidget(User_Label);
    HLayouts[0]->addWidget(User_Line);
    HLayouts[0]->addItem(new QSpacerItem(38, 32));
    // Margin
    HLayouts[1]->addItem(new QSpacerItem(32, 32));
    // Second
    HLayouts[2]->addWidget(Password_Label);
    HLayouts[2]->addItem(new QSpacerItem(24, 32));
    HLayouts[2]->addWidget(Password_Line);
    HLayouts[2]->addWidget(Password_Seen_Label);
    // Third
    HLayouts[3]->addItem(new QSpacerItem(130, 60));
    HLayouts[3]->addWidget(Sign_Button);
    HLayouts[3]->addItem(new QSpacerItem(130, 60));
    // Fourth
    HLayouts[4]->addWidget(Register_Label);
    // Add to Vertical Layout
    for(auto i = 0; i < 5; i++){
        HLayouts[i]->setAlignment(Qt::AlignCenter);
        VLayout->addLayout(HLayouts[i]);
    }
    // Set to AreaLabel
    VLayout->setAlignment(Qt::AlignCenter);
    Area_Label->setLayout(VLayout);
}

加入了这些留白后,现在的页面便如下所示:

在这里插入图片描述

1.7 Qt 阴影特效实现错误红框提示

在点击登录按钮之后,如果输入的信息不合规范或者压根没有信息输入时,我们希望实现让输入框有一圈红色特效来醒目的提示,这个功能要如何实现呢?先来看效果图:

在这里插入图片描述
这一特效是通过 Qt 一个特殊的 阴影特效类 实现的。

1.7.1 头文件的引入

#include <QGraphicsDropShadowEffect>

1.7.2 详细指南参考链接

详情使用方法和参数说明详见 QGraphicsDropShadowEffect详解

1.7.3 红色警报阴影和无阴影

下面我们通过两个函数来得到红色阴影和没有阴影特效的 Qt 阴影特效实例,由于这个功能许多窗口都可能需要,因此我们写入 utils.cpp 中:

  • 红色警告阴影
QGraphicsDropShadowEffect* red_shadow(QWidget* parent){
    QGraphicsDropShadowEffect* shadow_effect = new QGraphicsDropShadowEffect(parent);
    // Offset Settings
    shadow_effect->setOffset(0, 0);
    // Set the color of the shadow
    shadow_effect->setColor(Qt::red);
    // radius of the shadow(The larger it is, the more vague it will be)
    shadow_effect->setBlurRadius(20);

    return shadow_effect;
}
  • 没有阴影
QGraphicsDropShadowEffect* none_shadow(QWidget* parent){
    QGraphicsDropShadowEffect* shadow_effect = new QGraphicsDropShadowEffect(parent);
    // Offset Settings
    shadow_effect->setOffset(0, 0);
    // Set the color of the shadow
    shadow_effect->setColor(Qt::red);
    // radius of the shadow(The larger it is, the more vague it will be)
    shadow_effect->setBlurRadius(0);

    return shadow_effect;
}

1.7.4 输入为空警告+再输入清除

这一步的实现想必对你来说已经是小菜一碟了。你很简明扼要地发现只需要两个内置信号和两个槽函数即可实现:

  • 判断输入内容
  • 输入内容时清空阴影

很好,于是我们得到如下两个槽函数:

void SignWindow::Input_Check(void){
    // There is a blank
    if (User_Line->text()==""||Password_Line->text()==""){
        User_Line->setGraphicsEffect(red_shadow(this));
        Password_Line->setGraphicsEffect(red_shadow(this));
        QMessageBox::warning(this, "输入警告", "账号或密码不能为空");
    }
}

void SignWindow::Clear_Shadows(void){
    User_Line->setGraphicsEffect(none_shadow(this));
    Password_Line->setGraphicsEffect(none_shadow(this));
}

接下来我们进行和信号的绑定,显然输入检验是在 按下按钮后需要进行的,因此将其与按钮点击信号进行连接如下:

connect(Sign_Button,
        &QPushButton::clicked,
        this,
        &SignWindow::Input_Check);

接下来,是当任何一个输入框内容改变时则清空阴影特效,因此:

connect(User_Line,
        &QLineEdit::textChanged,
        this,
        &SignWindow::Clear_Shadows);
connect(Password_Line,
        &QLineEdit::textChanged,
        this,
        &SignWindow::Clear_Shadows);

此时效果如下:

在这里插入图片描述

1.8 登录检验

接下来让我们进一步完善上述检验输入内容的代码,从而实现登录检验功能。

首先,如果用户名和密码的输入均不为空,我们就可以接入数据库进行对比了。但是,数据库建立在了主窗口中,因此我们不能直接在登录窗口判断,那怎么办呢,很简单,需要什么控件帮忙,给他发信号即可。因此我们在登录窗口类中定义如下信号传递输入的用户名和密码信息:

void Input_Info(QVector<QString>);

可以看到这里我用了一个字符串容器来进行参数传递,接着我们在输入检验槽函数中发射他:

void SignWindow::Input_Check(void){
    // There is a blank
    if (User_Line->text()==""||Password_Line->text()==""){
        User_Line->setGraphicsEffect(red_shadow(this));
        Password_Line->setGraphicsEffect(red_shadow(this));
        QMessageBox::warning(this, "输入警告", "账号或密码不能为空");
    }
    else{
        emit Input_Info({User_Line->text(),
                         Password_Line->text()});
    }
}

接着为了方便设置登录窗口的错误提示,我们添加一个公有成员函数用于将两个输入框设置红色阴影:

void SignWindow::Wrong_Warning(void){
    User_Line->setGraphicsEffect(red_shadow(this));
    Password_Line->setGraphicsEffect(red_shadow(this));
}

现在让我们进入主窗口的代码部分,首先为主窗口添加一个响应登录信息的槽函数 Check_Account(QVector<QString>) ,结合上面的 SQL 知识进行检验如下:

void MainWindow::Check_Account(QVector<QString> info){
    // Try to find the user_name
    QString name = info[0];
    QString password = info[1];
    QString result = Get_Password(name);
    // User Do not Exist
    if(result.length()==0 || password!=result){
        QMessageBox::critical(SignPage, "错误", "账号或密码错误!");
        SignPage->Wrong_Warning();
    }
    else{
        // Update Info
        Sign = true;
        user_name = name;
        user_data.append(result);
        if(QMessageBox::information(SignPage, "登录成功", "恭喜你登陆成功!")){
            SignPage->close();
        }
    }
}

由于这是当登录窗口打开才可能触发的槽函数,因此我们在登录窗口的打开槽函数中进行如下连接:

connect(SignPage,
        &SignWindow::Input_Info,
        this,
        &MainWindow::Check_Account);

此时效果演示如下:

在这里插入图片描述

1.9 密码的显示与不显示切换

这里主要通过直接调用 QLineEditsetEchoMode 方法来修改显示模式。其显示模式为 **QLineEdit::EchoMode**这一枚举量 ,共有以下四种:

枚举名称用途说明
QLineEdit::Normal正常显示,输入什么便显示什么
QLineEdit::NoEcho无论怎么输入什么也不显示
适用于连密码长度都需要保密的场景
QLineEdit::Password输入的字符会以 “●” 的样式显示
QLineEdit::PasswordEchoOnEdit输入时当前字符正常显示,随后以 “●” 的样式显示

综上,我们的需求即是,点击小眼睛时候将显示模式在 QLineEdit::PasswordEchoOnEditQLineEdit::Normal 之间切换。这可以轻松的通过如下的槽函数实现(记得将小眼睛的标签类型换成我们自定义的可点击标签):

void SignWindow::Switch_Password_Input_Mode(void){
    if(Password_Line->echoMode()==QLineEdit::PasswordEchoOnEdit){
        Password_Line->setEchoMode(QLineEdit::Normal);
        Set_Image_On_Label(Password_Seen_Label, "img/Seen.png");
    }
    else{
        Password_Line->setEchoMode(QLineEdit::PasswordEchoOnEdit);
        Set_Image_On_Label(Password_Seen_Label, "img/Unseen.png");
    }
}

接着在初始化小眼睛标签后如下进行槽函数连接:

connect(Password_Seen_Label,
        &CLabel::clicked,
        this,
        &SignWindow::Switch_Password_Input_Mode);

此时效果如下:

在这里插入图片描述

2. 杂谈

2.1 局部布局指针可能造成的野指针错误

在进行布局时,如果在某个成员函数中单独构造一个 单独的布局指针,例如:

QVBoxLayout* VLayout = new QVBoxLayout();

这是一种不稳定的写法,有概率造成窗口因为野指针而崩溃。因此推荐采用本文的写法,也即 将这种整体布局的布局控件单独设置成当前窗口的私有成员

2.2 关于 QtCreator 提升开发效率的两个快捷键

  • F4

在 头/源文件 中快速切换

  • Shift+F2

选中函数/类名 在 定义/实现 中快速切换


http://www.kler.cn/a/416048.html

相关文章:

  • 模拟器快速上手,助力HarmonyOS应用/服务高效开发
  • 音视频相关的一些基本概念
  • Cesium CZML绘制Entity
  • houdini肌肉刷pin点的方法
  • 详解登录MySQL时出现SSL connection error: unknown error number错误
  • Spring Boot整合EasyExcel
  • oracle中删除指定前缀的表
  • Oracle 插入数据的存储过程
  • CAN收发器芯片TJA1042规格书解读
  • C# 进行AI工程开发
  • Web3开发指南:构建去中心化应用(DApps)的关键步骤
  • Java基础常用类库全解析
  • kafka消费者组和分区数之间的关系是怎样的?
  • 【AIGC】2023-ICCV-用于高保真语音肖像合成的高效区域感知神经辐射场
  • 将有序数组转换为二叉搜索树python
  • [Redis#7] set | 命令 | 集合 | 用户画像 | UV
  • 【随笔】AI大模型对软件开发的影响
  • 编码器接口测速
  • 宠物领养平台:SpringBoot技术解密
  • 【vue for beginner】Vue该怎么学?
  • 看华为,引入IPD的正确路径
  • 清朗行动再开展,算法安全如何保障
  • springboot+redis+lua实现分布式锁
  • 在PHP中使用正则表达式来处理数据类型验证和提取
  • 强化安全责任意识,传音开展第四届信息及隐私安全文化宣传周活动
  • 数字3D虚拟展厅成熟运用于旅游业