用项目来了解Qt的秘密(项目:驾校科目一考试系统)

文章详细描述了一个登录界面的实现过程,包括UI元素的布局、使用QDialog和QLineEdit进行邮箱验证、密码隐藏以及与考试相关的功能如时间计时和题库管理。还涉及了如何修改项目图标和打包exe文件的步骤。
摘要由CSDN通过智能技术生成

登陆界面

直接对界面ui拖动按钮编辑
在这里插入图片描述
上图的背景图片是后续加进去的,加图片的方法如下,得先将图片资源加载到项目中,添加资源。
在这里插入图片描述
在这里插入图片描述
在登陆部分设置窗口大小等等。

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

    ui->imLabel->setScaledContents(true);  // 填充属性
    this->resize(ui->imLabel->width(),ui->imLabel->height());  //当前窗体大小
    this->setWindowTitle("GG爆 驾校科目一考试登录");  //设置标题
    this->setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);    // 窗口风格
}

登录窗口运行样例:
在这里插入图片描述

验证邮箱地址

我们此处账号就是登陆自己的邮箱地址

void LoginDialog::on_loginButton_clicked()
{
    //QMessageBox::information(this,"提示","槽方法调用了");

    // 正则验证邮箱地址,用户名@域名
    // 元字符解释: ^表示规则字符串的开始,$表示规则字符串的结束
    // +表示匹配次数≥1次,*表示匹配任意次数(可以为0) {n,m}表示至少n,至多m。

    QRegExp rx("^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$");
    bool res = rx.exactMatch(ui->accountEdit->text()); // 判断邮箱是否输入正确
    if(!res){
        QMessageBox::information(this,"提示","非法邮箱地址,重新输入");
    }else {
        QMessageBox::information(this,"提示","欢迎登陆 GG爆驾校科目一考试登录系统");
    }
}

我们点击登录就可以判断我们的邮箱是否输入正确,此处我们用了QRegExp正则表达式来进行判断。

测试样例如下:
在这里插入图片描述

验证账号密码

首先我自己设置了一些数据文件,代表账号和对应密码。后续可用mysql代替,此处属于刚开始的话,设置在 .txt文件一样的。
在这里插入图片描述
此处使用了对文本的读取操作。
将文本里面的账号和密码都在字符串连链表里面存储,然后和输入进行对比看是否存在。

void LoginDialog::on_loginButton_clicked()
{
    //QMessageBox::information(this,"提示","槽方法调用了");

    // 正则验证邮箱地址,用户名@域名
    // 元字符解释: ^表示规则字符串的开始,$表示规则字符串的结束
    // +表示匹配次数≥1次,*表示匹配任意次数(可以为0) {n,m}表示至少n,至多m。

    QRegExp rx("^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$");
    bool res = rx.exactMatch(ui->accountEdit->text()); // 判断邮箱是否输入正确
    if(!res){
        QMessageBox::information(this,"提示","非法邮箱地址,重新输入");
        ui->accountEdit->clear(); // 清除
        ui->codeEdit->clear();
        ui->accountEdit->setFocus(); // 聚焦,就不用鼠标再点击了
        return;
    }else {
        //QMessageBox::information(this,"提示","欢迎登陆 GG爆驾校科目一考试登录系统");
        QString filename; // 文件名 路径
        QString strAccInput; // 账号信息
        QString strCode;   // 密码信息
        QString strLine;  // 因为读取是一行一行读取的
        QStringList strList; // 字符串链表

        filename = "D:/documents/qt_study/ExamSys/account.txt";
        strAccInput = ui->accountEdit->text();
        strCode = ui->codeEdit->text();

        // 读取文件
        QFile file(filename); // 初始化
        QTextStream stream(&file);  // 文本文件流初始化
        // 文件打开方式(只读、纯文本)
        if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
            while(!stream.atEnd()){
                // 每一行都是  ”账号,密码“  的格式
                strLine = stream.readLine();
                // 分隔符,以‘,’进行分割 ,此处我在用字符串链表进行接受
                strList = strLine.split(',');
                if(strAccInput == strList.at(0)){ //账号匹配成功
                    if(strCode == strList.at(1)){ // 密码匹配成功
                        QMessageBox::information(this,"提示","欢迎登陆 GG爆驾校科目一考试登录系统");
                        file.close();
                        return;
                    }else{
                        QMessageBox::information(this,"提示","密码错误,重新输入");
                        ui->codeEdit->clear();
                        ui->codeEdit->setFocus();
                        file.close();
                        return;
                    }
                }
            }
            // 整个文件都没匹配到
            QMessageBox::information(this,"提示","你的账号密码不存在,重新输入");
            ui->accountEdit->close();
            ui->codeEdit->close();
            ui->accountEdit->setFocus();
            file.close();
        }else{
            QMessageBox::information(this,"提示","读取账号数据文本打开失败");
        }

    }
}

测试样例如下:
在这里插入图片描述

密码隐藏

上面我们的密码是直接展示出来的, 我们需要对其进行隐藏,不然太容易被盗取账号了。
此处我们可以直接在ui界面直接设置。
将label 设置为 password 格式就好了
在这里插入图片描述

考试时间

此处我们新开一个考试用时窗口
在这里插入图片描述
头文件我们定义需要的:初始化函数、计时器变量等等

class ExamDialog : public QDialog
{
    Q_OBJECT   // 信号槽机制声明

public:
    ExamDialog(QWidget *parent = 0);

    void initTimer();

private:
    QTimer *m_timer; // 计时器
    int m_timeGo;   //当前考试已用时
private slots:
    void freshTime();
};

在 .cpp 文件,我们将 定点刷新和时间连接起来 connect 这样就是我们的时间。

#include "examdialog.h"

ExamDialog::ExamDialog(QWidget *parent)
    :QDialog(parent)
{
    setWindowTitle("考试已经用时0分0秒");
    initTimer();
}

void ExamDialog::initTimer()
{
    m_timeGo = 0;
    m_timer = new QTimer(this);
    m_timer->setInterval(1000);   // 指定多久触发一次,单位ms
    m_timer->start();

    connect(m_timer,SIGNAL(timeout()),this,SLOT(freshTime())); // 槽函数之间的连接
}

void ExamDialog::freshTime()
{
    // 刷新考试用时操作
    m_timeGo++;  // 每刷新就考试时间加一秒。
    QString min = QString::number(m_timeGo / 60); // 分钟
    QString sec = QString::number(m_timeGo % 60); // 秒
    setWindowTitle("考试已经用时"+min+"分"+sec+"秒");
}

结果样例:
在这里插入图片描述

布局题库

此处题库文件我们和上文账号密码文件类似,我直接使用.txt文件代替数据库
在这里插入图片描述
对题库中数据一行一行读取到文件当中去:

#include "examdialog.h"

#include <QMessageBox>
#include <QApplication>
ExamDialog::ExamDialog(QWidget *parent)
    :QDialog(parent)
{
    setWindowTitle("考试已经用时0分0秒");
    initTimer();
    initLayout();
    if(!initTextEdit()){
        QMessageBox::information(this,"提示","初始化题库失败");
        QTimer::singleShot(0,qApp,SLOT(quit())); // 间隔0秒,应用程序,执行退出操作

    }
}

void ExamDialog::initTimer()
{
    m_timeGo = 0;
    m_timer = new QTimer(this);
    m_timer->setInterval(1000);   // 指定多久触发一次,单位ms
    m_timer->start();

    connect(m_timer,SIGNAL(timeout()),this,SLOT(freshTime())); // 槽函数之间的连接
}

void ExamDialog::initLayout()
{
    m_layout = new QGridLayout(this);
    m_layout->setSpacing(10); // 设置控件间的间距
    m_layout->setMargin(10);  // 设置窗体和控件间的间隙
}

bool ExamDialog::initTextEdit()
{
    QString strLine;   // 保存文件中读取到的一行数据
    QStringList strList; // 保存读取到的答案行
    QString filename;   //文件路径
    filename = "D:/documents/qt_study/ExamSys/exam.txt";
    QFile file(filename);
    QTextStream stream(&file);

    if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
        m_textEdit = new QTextEdit(this);
        QString strText; // 用于保存显示到文本编辑器的数据 (备注和答案行我们过滤掉)

        int nLine = 0;
        while(!stream.atEnd()){
            strText += stream.readLine();
            strText += "\n";  // 换行处理
            nLine++;
        }
        m_textEdit->setText(strText);
        return true;
    }else{
        return false;
    }
}

void ExamDialog::freshTime()
{
    // 刷新考试用时操作
    m_timeGo++;  // 每刷新就考试时间加一秒。
    QString min = QString::number(m_timeGo / 60); // 分钟
    QString sec = QString::number(m_timeGo % 60); // 秒
    setWindowTitle("考试已经用时"+min+"分"+sec+"秒");
}

测试样例:
存在文字乱码,框也很小的问题。
在这里插入图片描述
接下来我们对其进行优化:

    QString strLine;   // 保存文件中读取到的一行数据
    QStringList strList; // 保存读取到的答案行
    QString filename;   //文件路径
    filename = "D:/documents/qt_study/ExamSys/exam.txt";
    QFile file(filename);
    QTextStream stream(&file);
    // 添加这行代码
    stream.setCodec("UTF-8"); // 设置文字格式
    
    if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
        m_textEdit = new QTextEdit(this);
        QString strText; // 用于保存显示到文本编辑器的数据 (备注和答案行我们过滤掉)

        int nLine = 0;
        while(!stream.atEnd()){
            strText += stream.readLine();
            strText += "\n";  // 换行处理
            nLine++;
        }
        m_textEdit->setText(strText);
		// 添加这行代码
        m_layout->addWidget(m_textEdit,0,0,1,10); // 将 m_textEdit 在文本编辑器什么位置,占据1行10
        return true;
    }

测试样例:
在这里插入图片描述
接下来我们把答案给忽略,不能显示出来:
我们过滤了第一行和答案行

        while(!stream.atEnd()){
            // 过滤首行
            if(nLine == 0){ // 第一行是备注,不需要显示
                stream.readLine(); // 读取一下就行了
                nLine++;
            }

            // 过滤答案行
            if((nLine >= 6 && nLine <= 6*9 && (nLine % 6 == 0))
                || nLine == 6*9 +4){
                strLine = stream.readLine();
                strList = strLine.split(" ");
                m_answer.append(strLine.at(1)); // 链表添加答案
                strText += "\n";
                nLine++;
                continue;
            }
            strText += stream.readLine();
            strText += "\n";  // 换行处理
            nLine++;
        }

接下来设置字体大小、窗口背景颜色、窗口按钮

    //设置字体大小
    QFont font;
    font.setPointSize(12);
    setFont(font);

    //设置窗体背景颜色
    setPalette(QPalette(QColor(209,215,215)));
    setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint); // 设置窗口按钮

测试效果:
在这里插入图片描述

布局按钮

定义如下按键:

private:
    QTimer *m_timer; // 计时器
    int m_timeGo;   //当前考试已用时

    QTextEdit *m_textEdit; // 考试题库显示
    QLabel *m_titleLabels[10];  // 题目标签   因为我就10个题,所以此处都给固定大小了
    QButtonGroup *m_btGroups[9]; // 单选按钮分组
    QRadioButton *m_radioBtns[32]; // 单选题按钮
    QCheckBox *m_checkBtns[4];   // 多选题按钮
    QRadioButton *m_radioA;  // 判断题 A 选项
    QRadioButton *m_radioB;  // 判断题 B 选项
    QGridLayout *m_layout;  // 布局管理器
    QStringList m_answer; // 答案链表

接下来就是对按钮标签的布局定义:

void ExamDialog::initButtons()
{
    QStringList strList = {"A","B","C","D"};
    for(int i = 0;i<10;i++)
    {
        // 题目标签
        m_titleLabels[i] = new QLabel(this);
        m_titleLabels[i]->setText("第"+QString::number(i+1)+"题");
        m_layout->addWidget(m_titleLabels[i],1,i);

        //判断题
        if(i == 9){
            m_radioA = new QRadioButton(this);
            m_radioB = new QRadioButton(this);

            m_radioA->setText("正确");
            m_radioB->setText("错误");

            m_layout->addWidget(m_radioA,2,9);
            m_layout->addWidget(m_radioB,3,9);

            m_btGroups[8] = new QButtonGroup(this);
            m_btGroups[8]->addButton(m_radioA);
            m_btGroups[8]->addButton(m_radioB);
            break;
        }
        if(i < 8){
            m_btGroups[i] = new QButtonGroup(this);
        }
        //选择题
        for(int j = 0; j<4 ; j++)
        {
            // 多选题
            if(i == 8){
                m_checkBtns[j] = new QCheckBox(this);
                m_checkBtns[j]->setText(strList.at(j));
                m_layout->addWidget(m_checkBtns[j],2+j,8);
                continue;
            }

            //单选题
            m_radioBtns[4*i+j] = new QRadioButton(this);
            m_radioBtns[4*i+j]->setText(strList.at(j));
            m_layout->addWidget(m_radioBtns[4*i+j],2+j,i);

            m_btGroups[i]->addButton(m_radioBtns[4*i+j]);
        }

    }
    QPushButton *submition = new QPushButton(this);
    submition->setText("提交");
    submition->setFixedSize(100,35); // 按钮大小;
    m_layout->addWidget(submition,6,9);
}

测试结果:
在这里插入图片描述

提交分数

先建立槽函数:

   //建立槽函数,点击提交,就统计分数
    connect(submition,SIGNAL(clicked(bool)),this,SLOT(getScore()));

下面是对分数的统计:

bool ExamDialog::hasNoSelect()
{
    int radioSelects = 0;
    for(int i = 0 ;i < 8; i++){
        if(m_btGroups[i]->checkedButton()){
            radioSelects++;
        }
    }
    if(radioSelects != 8){
        return true;
    }
    int checkSelect = 0;
    for(int i = 0 ; i< 4; i++){
        if(m_checkBtns[i]->isChecked()){
            checkSelect++;
        }
    }
    if(checkSelect == 0 || checkSelect == 1 ){
        return true;
    }
    if(!m_radioA->isChecked() && !m_radioB->isChecked()){
        return true;
    }
    return false;
}

void ExamDialog::freshTime()
{
    // 刷新考试用时操作
    m_timeGo++;  // 每刷新就考试时间加一秒。
    QString min = QString::number(m_timeGo / 60); // 分钟
    QString sec = QString::number(m_timeGo % 60); // 秒
    setWindowTitle("考试已经用时"+min+"分"+sec+"秒");
}

void ExamDialog::getScore()
{
    if(hasNoSelect()){
        QMessageBox::information(this,"提示","还有未完成的题目","是");
        return;
    }

    // 完成就记分
    int scores = 0;
    for(int i = 0; i< 10; i++)
    {
        // 单选记分
        if(i<8){
            if(m_btGroups[i]->checkedButton()->text() == m_answer.at(i)){
                scores += 10;
            }
        }
        // 多选记分
        if(i == 8){
            QString answer = m_answer.at(i);
            bool hasA = false;
            bool hasB = false;
            bool hasC = false;
            bool hasD = false;

            if(answer.contains("A"))  hasA =true;
            if(answer.contains("B"))  hasB =true;
            if(answer.contains("C"))  hasC =true;
            if(answer.contains("D"))  hasD =true;

            bool checkA = m_checkBtns[0]->checkState();
            bool checkB = m_checkBtns[1]->checkState();
            bool checkC = m_checkBtns[2]->checkState();
            bool checkD = m_checkBtns[3]->checkState();

            if(hasA != checkA) continue;
            if(hasB != checkB) continue;
            if(hasC != checkC) continue;
            if(hasD != checkD) continue;

            scores +=10;
        }
        if(i == 9){
            if(m_btGroups[8]->checkedButton()->text() == m_answer.at(i)){
                scores +=10;
            }
        }
    }
    QString str =QString::number(scores)+"分,是否重新考试?";
    int res = QMessageBox::information(this,"提示",str,QMessageBox::Yes | QMessageBox::No);
    if(res == QMessageBox::Yes)
    {
        return;
    }else{
        close();
    }

}

测试样例:
在这里插入图片描述

窗口交互

exec,它是一个模态的方式显示,属于当你使用一个对话框窗口,则其他窗口只会等待,不会显示出来。
而show,它是属于允许处理多个任务,展示多个窗口。
此处我们不使用show来展示窗口,而是使用 .exec()

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    LoginDialog logDialog;   // 登录窗口对象
    int res = logDialog.exec();   // 以模态形式进行运行
    if(res == QDialog::Accepted)
    {
        ExamDialog *examDialog;
        examDialog = new ExamDialog; // 方法二:我们开辟新内存
    //    return a.exec(); 方法一
    }else{
        return 0;
    }
    
    return a.exec();
    
    // ExamDialog w;
    // w.show();
}

在登录页面,我们点击登录就输入 :

done(Accepted);

点击取消则页面关闭:

    done(Rejected);

在题库框架中,我们初始化就展示窗口:
在这里插入图片描述

完整测试样例

登录界面:
在这里插入图片描述
登陆成功进入题库页面:
在这里插入图片描述
点击yes 则重新做题,点击No 则直接退出。

发布项目

上面我们账号密码和题库都是使用的绝对路径,不利于别人的再使用。
在这里插入图片描述

我们将运行地址直接修改问文件代码处,这样,我们所有地址,就可以直接加文件名称就好了。
在这里插入图片描述
那我们怎么设计我们的项目图标呢?
提供以下网站,可以借鉴,将图片转换为图标。
在线转icon网址:https://app.xunjiepdf.com/img2icon/
然后再 .pro文件加入
在这里插入图片描述
可以看到图标已经修改:
在这里插入图片描述
文件中的exe文件图标也会修改
在这里插入图片描述
接下来如何打包呢?
将release里面的 .exe文件复制进你需发送的文件夹,和自己的数据文件。
在这里插入图片描述
接下来,这几个主要核心文件都是安装qt位置下的文件,一起放进来,这些库文件都是看自己缺少什么库,就加载什么库。(我这里手动加入的,可以网上搜索一下,很多加入方法)
在这里插入图片描述
此时,直接点击exe文件就能直接运行这个程序了。

  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值