基于Qt的app开发第九天

发布于:2025-05-20 ⋅ 阅读:(20) ⋅ 点赞:(0)

写在前面

        笔者的课设截止时间已经越来越近了,还有不少地方的功能没有完成,所以重构一事还是放到做完整个项目、学完设计模式再考虑。目前就是继续往屎山堆屎。

需求分析

        笔者的学长要做多线程,传数据的时候涉及到互斥锁之类的内容,当前的代码结构不能方便地实现,所以他们提了几个优化要求,本篇博客主要记录修改这些对应代码的过程

1. 新增一个主界面,登录之后先到这个界面,然后点击主界面上某个按钮之后再初始化对应板块对象

2. 用vector存储数据,一层数据存一个节点

3. 把时间控件统一成标准输入框

4. 用正则表达式限制输入范围

思路梳理

根据这几个问题分开来梳理实现思路:

1. 主界面就用mainwindow就行,在mainwindow.ui里画主界面的ui,然后在主函数里初始化Login类的对象,直接把这个对象show出去,然后要改的东西其实比较多,把构造函数中的初始化部分都去掉,在主界面的按钮控件槽函数中写跳转界面和初始化对象。Login.ui里的登录按钮槽函数内容应该改掉,把展示task.ui改成展示mainwindow.ui,初始时也不需要写那几个对象的show/hide状态了。

2. 目前已经实现了三个板块的功能,这三个板块都需要用vector做暂时的数据存储,这样方便向数据库中传输。可以开一个结构体存储这些内容,然后vector就是结构体数组。每次向表格传数据的时候顺便也向数组中传一次

3. 时间控件这个好处理,就是注意修改了控件要改命名要改对应的修改文本和传输数据的函数

4. 正则表达式笔者还没学(苦笑)不过没有关系,可以边学边做,在这里就列一下要限制输入范围的地方:账号密码的位数和形式

具体实现

具体实现还是按照从易到难来做吧,先完成简单的再做难的

(1)修改控件

把和时间相关的控件由textEdit改成dateEdit,然后改对应的命名和代码

先改待办板块的控件及代码:

控件已经改成这样,然后就去对应的cpp文件里改报错的地方

做这一步只有一个难点,那就是类型转换

当然,每当我认为一个东西是难点的时候,我就可以提升自己了。因为QDateTimeEdit有些特殊,它的内容是QDateTime类型的,但是可以和QString建立关系。

    ui->startTimeEdit->setDisplayFormat("yyyy/MM/dd HH:mm");
    ui->endTimeEdit->setDisplayFormat("yyyy/MM/dd HH:mm");

使用这两句,放在构造函数里,然后就把这两个控件的文本格式定死了

    QString format = "yyyy/MM/dd HH:mm";
    QDateTime startTime = QDateTime::fromString(shiftTaskList[1],format);
    QDateTime endTime = QDateTime::fromString(shiftTaskList[2],format);
    if (startTime.isValid())
    {
        ui->startTimeEdit->setDateTime(startTime);
    } else
    {
        qDebug() << "开始时间转换失败,原始字符串:" << shiftTaskList[1];
    }
    if (endTime.isValid())
    {
        ui->endTimeEdit->setDateTime(endTime);
    } else
    {
        qDebug() << "开始时间转换失败,原始字符串:" << shiftTaskList[2];
    }

使用fromString函数可以把第一个QString类型的参数转换为第二个参数指定的格式的QDateTime类型变量,然后把它再设置到QDateTimeEdit控件里就行

经过测试,是可以正常运行的

接下来再改规划板块,因为打卡板块本来就是QTimeEdit控件不需要改动

对着待办板块照猫画虎即可,就不细讲了

(2)用正则表达式限制输入范围

虽然我没学正则表达式,但是这个只需要改登录界面和注册界面,所以难度还是算小的

下面来讲一下正则表达式的用法:

注意这里只讲正则表达式限制输入的知识,正则表达式的用途有很多,一时半会儿是讲不清的,笔者事后有时间会去仔细学的

正则表达式的作用其实是匹配,在Qt里有验证器,验证是否匹配,如果匹配的话才显示在输入框中,否则就忽略。大致原理就是这样:通过正则表达式和验证器,来判断输入的内容是否为正则表达式匹配到的,如果是就正常显示,不是就忽略

那接下来就正式开始做

我的思路是把针对这四个输入框的初始化放在一个函数里,这个函数的主要作用是初始化四个输入框的提醒,显式告诉用户限制输入的范围,然后用正则表达式配限制器限制用户输入

#include <QRegularExpression>
#include <QRegularExpressionValidator>

要包含引用这两个头文件,一个是正则表达式,一个是验证器

//这个函数的作用是利用正则表达式限制输入框的输入范围----------------------------------------------------密码限制没实现
void Login::originalInputEdit()
{
    //初始化提醒
    ui->register_passwordInput->setPlaceholderText("要求8位以上,需要同时包括字母和数字");
    ui->register_password_confirm->setPlaceholderText("请与第一次输入的密码保持一致");
    ui->register_accountInput->setPlaceholderText("要求最长为10位");
    ui->sign_passwordInput->setPlaceholderText("请输入密码");
    ui->sign_accountInput->setPlaceholderText("请输入账号");

    //设置一个正则表达式,它可以匹配任意字符,但总长度不超过10,再设置一个限制器限制是否输入正确
    QRegularExpression accountRegex("^.{0,10}$");
    accountValidator = new QRegularExpressionValidator(accountRegex, this);
    //设置一个正则表达式,它匹配最短8位的要同时包含字母和数字组合
    QRegularExpression passwordRegex("^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]{8,}$");
    passwordValidator = new QRegularExpressionValidator(passwordRegex, this);

    // 应用到sign_passwordInput控件
    ui->sign_passwordInput->setValidator(passwordValidator);
    // 应用到sign_accountInput控件
    ui->sign_accountInput->setValidator(accountValidator);
    //应用到register_accountInput控件
    ui->register_accountInput->setValidator(accountValidator);
    //应用到register_passwordInput控件
    ui->register_passwordInput->setValidator(passwordValidator);
    //应用到register_password_confirm控件
    ui->register_password_confirm->setValidator(passwordValidator);
}

这个函数实现了账号输入的长度限制,但是密码限制失败了,可能是和学长的代码有了冲突吧,反正我仔细排查也没有排查出来,后续再改吧

(3)新增主界面

把它放到第三位还是因为vector数组存储涉及到的地方太多了,这个相对来说更简单一点

具体思路:充分利用mainwindow配套h、cpp、ui文件,用mainwindow.ui创一个主界面。因为笔者当时界面切换的思路是让mainwindow文件作媒介,接受其他板块发出的信号,现在就需要大改一下了。

首先,需要把四个板块的初始化安排到系统运行的过程中,为了防止重复初始化,需要做四个布尔变量,存储初始化情况。然后在界面切换的槽函数里做出判断,如果没有初始化的话就初始化板块对象

其次,要处理当前界面展示切换与搭配上主界面之后的逻辑矛盾。笔者原版本是将四个对象直接在构造函数里初始化,然后把其中三个hide,一个show出来,然后界面切换就是hide一个show一个。现在有了主界面那就应该在登录界面之后直接展示主界面,然后主界面要再切换其他界面。

好了,开始梳理实现顺序:先画出主界面ui,然后在main函数中初始化一个mainwindow的对象,它的构造函数里还是应该先把this hide一下,然后把login对象show出来,然后点了login的登录槽函数连接到mainwindow的connect函数,这个函数要把原来task对象show出来改成this对象show出来,然后在mainwindow的按钮槽函数里设置对应的hide和show,注意每个槽函数都要进行判断之后的初始化对象

最后有一个小小的易错点,每个界面切换都要加上判断之后的初始化对象

笔者在这里忏悔一波吧。有很多东西不做的话是想不到的。笔者已经完成了上述所有的修改,然后在运行时进程崩溃了。这是为什么呢?因为我的connect函数连接了所有板块的对象,connect是构造函数里的,如果对象在那个地方还没有初始化的话connect就站不住脚了。

所以这个功能暂时是做不出来了。只能说积累经验吧,在代码架构上问题太多了,导致后续想优化点什么也做不了。

(4)使用vector存储数据

笔者之所以认为这点最难,是因为它涉及到了三个板块

来梳理一下实现思路:主要要做的就是每次修改或者新增时要把本该传进表格的数据再传到数组中

然后这里应该怎么实现呢?我的思路是开一个结构体存储对应的内容,然后数组就是这个结构体的数组,新建就push一个,修改的话修改对应节点的数据。

那可以直接列实现顺序了:开一个结构体,这个结构体需要包括对应板块中的数据,然后开一个vector,设置数据类型是这个结构体,表格的行号对应这一个节点的下标

先改待办板块

 struct TaskShift
    {
        QString taskName;
        QString startTime;
        QString endTime;
        QString category;
        QString priority;
        QString remindTime;
    };
    
    QVector<TaskShift> shiftTaskVector;

在类的声明中的私有成员里写这样一段,这个数组就可以存每次传进去的内容

//这段代码的作用是把当前传进表格的内容也传进数组中
        TaskShift t;
        t.taskName=name;
        t.startTime=startTime;
        t.endTime=endTime;
        t.category=category;
        t.priority=priority;
        t.remindTime=remindTime;
        shiftTaskVector.push_back(t);

这段代码写在保存新建操作里,就是往这个数组中存一个节点进去

//这段代码的作用是把当前传进表格的内容也传进数组中
        TaskShift t;
        t.taskName=name;
        t.startTime=startTime;
        t.endTime=endTime;
        t.category=category;
        t.priority=priority;
        t.remindTime=remindTime;
        shiftTaskVector[currentRow]=t;

这段代码写在保存修改操作里,因为下标和行号都是从0开始,所以选中这一行的行号和数组的下标是对应的

再改打卡板块,打卡板块的运行流程实际上和待办没什么区别,就不细说了

规划板块需要单独思考一下,一方面它的视图是一列一列的,一方面它有两个表格

这里应该开两个结构体和两个vector,第一个结构体存第三个界面传到第二个界面的数据,第二个结构体存第二个界面传到第一个界面的数据。然后数组的下标就对应的是表格的列

笔者在做这一部分有了一个更深刻的理解

多线程要传的数据实际上还是第一个界面的内容,所以这两个界面其实是包含关系,第一个界面要传的数据包含第二个界面,而这需要使第一个界面的结构体里存第二个界面的结构体数组

struct TaskShift
    {
        QString startTime;
        QString endTime;
        QString event;
        QString isSuccess;
    };
    QVector<TaskShift>shiftTaskVector;
    struct PlanShift
    {
        QVector<TaskShift> shiftTaskVector;
        QString title;
        QString evalution;
    };
    QVector<PlanShift>shiftPlanVector;

在类里声明这两个结构体和对应的数组

笔者这里可能会出错,趁着现在脑子比较清醒,还记着实现这个地方的思路,就在这里留下我的代码

//这几句的内容是把要传递的数组、标题和自我评价放到数组中
        PlanShift p;
        p.title=title;
        p.evalution=evalution;
        p.shiftTaskVector=shiftTaskVector;
        shiftPlanVector.push_back(p);
        shiftTaskVector.clear();

这是新建时的思路,因为第二个界面的数据实际上没用,第一个界面的数据才需要传到服务器,所以第二个界面的存储数组要清空,以便于下一次使用

//这几句的内容是把要传递的数组、标题和自我评价放到数组中
        PlanShift p;
        p.title=title;
        p.evalution=evalution;
        p.shiftTaskVector=shiftTaskVector;
        shiftPlanVector[currentCol]=p;
        shiftTaskVector.clear();

这个步骤我不知道对不对,因为这个要传数据的时候才能看出会不会有bug,到那个时候我可能已经忘了我是怎么做的,所以留在这里,等真的出bug了再来排查

篇末总结

这次修改任务做完我也学到了很多,虽然这个项目时间跨度很大,但是我学到的东西确实很多,每次写这么一篇博客我都能学到很多东西

首先就是团队协作,做项目肯定不是一个人的事情,我很幸运处女作就是团队协作的作品,而且团队成员都比我要强,学长会根据他们的需求让我改动代码

其次是一些厚积薄发类的感悟:

类型转换在编程中是一种很常见很重要的操作,不要觉得这是什么很高大上的事情,这就是基础中的基础

失败乃是常态,改来改去才是程序员最常见的工作

代码结构一定要做好,后续修改也会很容易

设计模式真的能提高一个程序员的下限,不能急于求成了,笔者要先沉淀好设计模式再去学java


网站公告

今日签到

点亮在社区的每一天
去签到