QT-驾校科目一总结

引言:

学习了Qt,来实现一个简单的驾校科目一考试系统,虽然很粗糙,但可以实现简单的答题,总共有10道题目,答题后点击提交能看到对应的分数。

实现:

使用的QT Creator工具,界面通过设计模式,实现的组件的拖拉,具体的功能(界面按钮、加载题目、计算分数)通过编写代码来实现。

源码:

github地址

效果:

科目一

目录:

一、登录界面
二、用户名与密码检核
三、答题界面
四、提交按钮
五、登录界面登录与取消按钮

一、登录界面

1.1 设计模式下创建界面

首先创建一个项目,并创建一个LoginDialog登录页面的类,继承于QDialog类。
目录结构如下:
登录页面

点击loginDialog.ui文件进入设计模式,进行界面设计。
首先,添加一个mainLabel,作为一个背景。然后再添加两个label作为账号与密码的中文字,两个LineEdit作为账号与密码的输入框,两个pushButton作为登录与取消的按钮。
UI

1.2 添加背景图片

在项目中新增一个资源文件夹,放背景图片,如下所示:
添加背景图片

再次进入设计模式中,给mainLabel添加背景图片,在QLabel中的text属性中,给pixmap选择添加的图片,运行结果如下:
背景图片

1.3 登录窗口调整

在loginDialog.cpp文件中,在构造函数中设置界面标题,大小。

#include "loginDialog.h"
#include "ui_logindialog.h"

LoginDialog::LoginDialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::LoginDialog)
{
    ui->setupUi(this);  //加载UI
    ui->background->setScaledContents(true);    //图片填充label
    resize(ui->background->width(),ui->background->height());   //将窗体大小设置为与图片大小一致
    setFixedSize(width(),height()); //设置固定大小
    setWindowTitle("驾校科目一考试");  //设置窗体标题
    setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);     //设置窗体风格
}

login::~LoginDialog()
{
    delete ui;
}


运行效果如下:
修改后登录页面

二、用户名与密码检核

当用户点击登录按钮时,系统检核账号与密码是否正确(采用的读取文件中的账号与密码)

2.1 正则表达式先验证

首先在设计模式下,选择登录按钮,鼠标右键点击转到槽,系统自动添加上on_login_btn_clicked方法,在这个方法中进行账号的检核。

在loginDialog.h中on_login_btn_clicked声明:

#ifndef LOGINDIALOG_H
#define LOGINDIALOG_H

#include <QDialog>

QT_BEGIN_NAMESPACE
namespace Ui { class LoginDialog; }     //继承自UI_LoginDialog类,用于描述登录信息
QT_END_NAMESPACE

class LoginDialog : public QDialog
{
    Q_OBJECT    //实现信号与槽,可以与其他窗口进行通信

public:
    LoginDialog(QWidget *parent = nullptr);
    ~LoginDialog();

private slots:     //私有的槽方法
    void on_login_btn_clicked();	//检核输入格式

private:
    Ui::LoginDialog *ui;
};
#endif // LOGINDIALOG_H

在cpp文件中的on_login_btn_clicked方法中,先通过正则表达式先进行验证,账号只能是邮箱

void login::on_login_btn_clicked()
{
    //^为开始,$为结束
    //+表示至少匹配一次,*表示任意次数(可以是0次),{m,n}为至少匹配m次,至多批次n次
    QRegExp rex("^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*(@[A-Za-z0-9]+\.)+[A-Za-z]{2,6}$");
    bool userName = rex.exactMatch(ui->usmInput->text());

    if(ui->usmInput->text().isEmpty() || ui->pwdInput->text().isEmpty()){
        QMessageBox::information(this,"栏位未填写","账号或密码未输入,请先输入");
        ui->usmInput->setFocus();   //光标聚集到账号输入框
    }else if(!userName){
        QMessageBox::information(this,"用户名不合法","您输入的账号格式不正确,请重新输入");
        ui->usmInput->clear();  //清空账号
        ui->pwdInput->clear();  //清空密码
        ui->usmInput->setFocus();   //光标聚集到账号输入框
        return;
    }
}

2.2 读取文件验证账号与密码的正确性

在on_login_btn_clicked中修改代码,读取文件,进行验证账号与密码

void LoginDialog::on_login_btn_clicked()
{
    //正则表达式检核邮箱
    //^表示表达式开始,$表示表达式结束,+表示匹配次数≥1,*表示可以匹配任意次数,{n,m}表示匹配次数至少n次,至多m次
    QRegExp ex("^[A-Za-z0-9]+([_\.][a-zA-Z0-9]+)*@([A-Za-z0-9]+\.)+[A-Za-z]{2,6}$");
    bool re = ex.exactMatch(ui->username->text());

    if(re){
        QString fileName;   //文件名
        QString username;   //账号
        QString password;   //密码
        QString strline;    //读取整行
        QStringList list;   //存放读取的数据

        fileName = "user.txt";
        username = ui->username->text();    //读取账号
        password = ui->password->text();    //读取密码
        QFile file(fileName);   // 加载文件
        QTextStream stream(&file);
        if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
            while(!stream.atEnd()){
                strline = stream.readLine();
                list = strline.split(",");   //以逗号分隔数据存入到list中
                if(username == list.at(0)){
                    if(password == list.at(1)){
                        QMessageBox::information(this,"提示","欢迎进入科目一考试!");
                        file.close();
                        return;
                    }else{
                        QMessageBox::information(this,"提示","密码输入错误,请重新输入!");
                        ui->password->clear();
                        ui->password->setFocus();
                        file.close();
                        return;
                    }
                }
            }
            QMessageBox::information(this,"提示","账号输入错误,请重新输入!");
            ui->username->clear();
            ui->password->clear();
            ui->username->setFocus();
            file.close();
        }else{
            QMessageBox::information(this,"提示","文件读取失败!");
            return;
        }

    }else{
        QMessageBox::information(this,"提示","账号邮箱输入错误,请重新输入!");
        ui->username->clear();     //清除栏位内容
        ui->username->setFocus();  //重新聚焦
        return;
    }
}

三、答题界面

3.1 考试时间

在进入考试系统后,系统开始计算考生的考试时间,动态显示在答题页面的窗体上。
创建一个ExamSystem类,继承于QDialog类,声明一个计时器和一个初始化计时器的方法。
examSystem.h:

#ifndef ExamSystem_H
#define ExamSystem_H
#include <QDialog>
#include <QTimer>

class ExamSystem: public QDialog
{
    Q_OBJECT
public:
    ExamSystem(QWidget *parent = nullptr);
	void initTimer();	//初始化计时器
    
private slots:
    void refreshTime();

private:
	QTimer *m_timer;                //计时器
    int m_timeGo;                   //已用时
};

#endif // ExamSystem_H

examSystem.cpp:

#include "examSystem.h"

Exam::ExamSystem(QWidget *parent):QDialog(parent)
{
    setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);     //设置窗体为关闭类型
    setWindowTitle("已用时0分0秒");
    initTimer();
}

void ExamSystem::initTimer()
{
    m_timeGo=0;
    m_timer = new QTimer(this);     //为当前窗体作计时器
    m_timer->setInterval(1000);     //以1秒作为计时器间隔
    m_timer->start();   //启动计时器
    connect(m_timer,SIGNAL(timeout()),this,SLOT(freshTime()));  //将计时器与当前窗体进行信号与槽连接接
}

void ExamSystem::refreshTime()
{
    useTime++;
    QString min = QString::number(useTime/60);  //整数转为string类型
    QString sec = QString::number(useTime%60);
    setWindowTitle("已用时"+min+"分"+sec+"秒");
}

3.2 加载题目

题目也是放在文件中,通过读取文件,解析后显示在窗体上。

首先在examSystem.h中声明考试界面相关的组件(题目显示区域的label,答案显示区域的选项与选择按钮)。读取题目的同时,也对文件中的答案进行记录。

#ifndef EXAMSYSTEM_H
#define EXAMSYSTEM_H

#include <QDialog>
#include <QTimer>
#include <QGridLayout>
#include <QTextEdit>
#include <QRadioButton>
#include <QCheckBox>
#include <QLabel>
#include <QPushButton>
#include <QButtonGroup>
#include <QStringList>

class ExamSystem : public QDialog
{
    Q_OBJECT

public:
    ExamSystem(QWidget *parent=nullptr);
    void initTimer();
    void initLayout();	//初始化布局
    bool initText();	//初始化题目

private:
    QTimer *m_timer;                //计时器
    int m_timeGo;                   //已用时
    QGridLayout *m_layout;          //布局对象
    QTextEdit *text;                //题目内容
    QLabel *titleLabel[10];         //选项标题
    QRadioButton *radioButton[32];  //32个单选选项
    QCheckBox *checkBox[4];         //4个多选选项
    QRadioButton *radioA;           //判断题A选项
    QRadioButton *radioB;           //判断题B选项
    QPushButton *submitBtn;         //提交按钮
    QButtonGroup *btnGroup[9];      //按钮分组
    QStringList answerList;         //存放答案

private slots:
    void freshTime();
};

#endif // EXAMSYSTEM_H

在examSystem.cpp中实现initLayout和initText方法

void ExamSystem::initLayout(){
	setPalette(QPalette(QColor(209,215,255)));		//设置窗体颜色
    setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);     //设置窗体风格
    resize(800,900);    //设置窗体大小
    setFixedSize(width(),height());
  
    m_layout = new QGridLayout(this);   //布局添加到此窗口上
    m_layout->setSpacing(10);
    m_layout->setMargin(10);
}

//初始化文字,从文件中读取题库
bool ExamSystem::initText(){
	//设置页面显示的字体大小
    QFont font;
    font.setPointSize(12);
    setFont(font);

    int line = 0;     //记录文件中有多少行
    QString fileName("exam.txt");
    QFile file(fileName);
    QTextStream stream(&file);
    stream.setCodec("UTF-8");

    if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
        text = new QTextEdit(this); //在当前窗口显示文字
        text->setReadOnly(true);    //唯独不可编辑

        QString str;    //保存数据
        QString strLine; //保存答案
        QStringList strList;
        while(!stream.atEnd()){
            //过滤第一行
            if(line == 0){
                stream.readLine();
                line++;
                continue;
            }

            //过滤答案
            if(line>=6 && line<=58 && (line % 6 == 0 || line == 6*10-2)){
                strLine = stream.readLine();
                strList = strLine.split(" ");
                answerList.append(strList.at(1));
                line++;
                str += "\n";
                continue;
            }

            str += stream.readLine();
            str += "\n";
            line++;
        }
        text->setText(str);
        m_layout->addWidget(text,0,0,1,10);     //以一行10列在(0,0)处显示
        file.close();
        return true;
    }else{
        QMessageBox::information(this,"提示","文件打开失败,请检查文件!");
        return false;
    }
}

同时在构造函数中调用initLayout方法和initText方法

ExamSystem::ExamSystem(QWidget *parent):QDialog(parent)
{
    setWindowTitle("已用时0分0秒");
    initTimer();    

    initLayout();
    if(!initText()){
        QMessageBox::information(this,"提示","初始化文件失败,请检查文件!");
    }
}

3.3 加载选项

在examSystem.h中声明initButtons方法,进行选项的加载。

在examSystem.cpp中实现initButtons方法。

void ExamSystem::initButtons()
{
    QStringList selectList = {"A","B","C","D"};
    for(int i=0; i<10; i++){
        titleLabel[i] = new QLabel(this);
        titleLabel[i]->setText("第"+QString::number(i+1)+"题");
        m_layout->addWidget(titleLabel[i],1,i);

        if(i == 9){
            radioA = new QRadioButton(this);
            radioB = new QRadioButton(this);
            radioA->setText("正确");
            radioB->setText("错误");
            m_layout->addWidget(radioA,2,9);
            m_layout->addWidget(radioB,3,9);

            //按钮添加分组
            btnGroup[8] = new QButtonGroup(this);
            btnGroup[8]->addButton(radioA);
            btnGroup[8]->addButton(radioB);
        }else{
            if(i!=8){
                btnGroup[i] = new QButtonGroup(this);
            }

            for(int j=0; j<4; j++){
                if(i == 8){
                    //               for(int j=0; j<4; j++){
                    checkBox[j] = new QCheckBox(this);
                    checkBox[j]->setText(selectList.at(j));
                    m_layout->addWidget(checkBox[j],j+2,i);
                }
                else{
                    radioButton[4*i+j] = new QRadioButton(this);
                    radioButton[4*i+j]->setText(selectList.at(j));
                    m_layout->addWidget(radioButton[4*i+j],j+2,i);

                    btnGroup[i]->addButton(radioButton[4*i+j]);
                }
            }
        }/*else{
            for(int k=0; k<4; k++){
                radioButton[(k+1)*(i+1)-1] = new QRadioButton(this);
                radioButton[(k+1)*(i+1)-1]->setText(selectList.at(k));
                m_layout->addWidget(radioButton[(k+1)*(i+1)-1],k+2,i);
            }
        }*/
    }
    submitBtn = new QPushButton(this);
    submitBtn->setText("提交");
   
    m_layout->addWidget(submitBtn,6,9);
}

四、提交按钮

在initButtons方法中,添加信号连接,当点击提交的时候计算分数

void ExamSystem::initButtons()
{
    QStringList selectList = {"A","B","C","D"};
    for(int i=0; i<10; i++){
        titleLabel[i] = new QLabel(this);
        titleLabel[i]->setText("第"+QString::number(i+1)+"题");
        m_layout->addWidget(titleLabel[i],1,i);

        if(i == 9){
            radioA = new QRadioButton(this);
            radioB = new QRadioButton(this);
            radioA->setText("正确");
            radioB->setText("错误");
            m_layout->addWidget(radioA,2,9);
            m_layout->addWidget(radioB,3,9);

            //按钮添加分组
            btnGroup[8] = new QButtonGroup(this);
            btnGroup[8]->addButton(radioA);
            btnGroup[8]->addButton(radioB);
        }else{
            if(i!=8){
                btnGroup[i] = new QButtonGroup(this);
            }

            for(int j=0; j<4; j++){
                if(i == 8){
                    //               for(int j=0; j<4; j++){
                    checkBox[j] = new QCheckBox(this);
                    checkBox[j]->setText(selectList.at(j));
                    m_layout->addWidget(checkBox[j],j+2,i);
                }
                else{
                    radioButton[4*i+j] = new QRadioButton(this);
                    radioButton[4*i+j]->setText(selectList.at(j));
                    m_layout->addWidget(radioButton[4*i+j],j+2,i);

                    btnGroup[i]->addButton(radioButton[4*i+j]);
                }
            }
        }/*else{
            for(int k=0; k<4; k++){
                radioButton[(k+1)*(i+1)-1] = new QRadioButton(this);
                radioButton[(k+1)*(i+1)-1]->setText(selectList.at(k));
                m_layout->addWidget(radioButton[(k+1)*(i+1)-1],k+2,i);
            }
        }*/
    }
    submitBtn = new QPushButton(this);
    submitBtn->setText("提交");
    m_layout->addWidget(submitBtn,6,9);
    
    connect(submitBtn,SIGNAL(clicked(bool)),this,SLOT(getScore()));	//点击提交按钮需触发事件
}

4.1 判断是否还有未做完的题目

在计算分数之前,判断是否还有未做完的题目,即只有对每个分组进行判断,是否有未做选择的按钮。

在examSystem.h中声明hasNoSelect方法,在examSystem.cpp中实现hasNoSelect方法。

//判断是否有未选择的题目
bool ExamSystem::hasNoSelect()
{
    int checkNumber = 0;    //用来统计多选题勾选了几个答案
    int radioNumber = 0;    //用来统计单选题勾选了几个答案

    for(int i=0; i<8; i++){
        if(btnGroup[i]->checkedButton()) radioNumber++;
    }

    //多选题
    for(int j=0; j<4; j++){
        if(checkBox[j]->checkState()) checkNumber++;
    }

    if(radioNumber != 8 && checkNumber<=1){
        return true;
    }

    //判断题
    if(!radioA->isChecked() && !radioB->isChecked()) return true;

    return false;
}

4.2 计算分数

在examSystem.h中声明getScore方法,进行分数计算。

在examSystem.cpp中实现getScore方法,并在方法中先调用hasNoSelect方法,来先进行判断是否有未完成的题目。

void ExamSystem::getScore()
{
    if(hasNoSelect()){
        QMessageBox::information(this,"提示","您有未完成的题目,请完成后再点击提交。","是");
        return;
    }

    int score = 0;

    //判断题
    if(btnGroup[8]->checkedButton()->text() == answerList.at(9))
        score += 10;

    //用来判断多选题是否勾选选项
    bool selectA = checkBox[0]->checkState();
    bool selectB = checkBox[1]->checkState();
    bool selectC = checkBox[2]->checkState();
    bool selectD = checkBox[3]->checkState();

    //用来判断答案中选项
    bool answerA = answerList[8].contains("A");
    bool answerB = answerList[8].contains("B");
    bool answerC = answerList[8].contains("C");
    bool answerD = answerList[8].contains("D");

    if(selectA == answerA && selectB == answerB && selectC == answerC && selectD == answerD) score += 10;

    for(int i=0; i<8; i++){
        if(btnGroup[i]->checkedButton()->text() == answerList.at(i)) score += 10;
    }

	//弹出提示框,返回为点击的按钮
    int res = QMessageBox::information(this,"提示","您的分数为:"+QString::number(score),QMessageBox::Yes);

    if(res == QMessageBox::Yes){
        close();	//当点击确认时,关闭页面
    }
}

五、登录界面登录与取消按钮

当点击登录按钮时,进行一个页面跳转,即登录成功后显示答题界面。

5.1 登录按钮

首先在loginDialog.cpp中的on_login_btn_clicked中添加done方法,表示用户以一个接受的状态返回,同时关闭窗口。

void LoginDialog::on_login_btn_clicked()
{
    //正则表达式检核邮箱
    //^表示表达式开始,$表示表达式结束,+表示匹配次数≥1,*表示可以匹配任意次数,{n,m}表示匹配次数至少n次,至多m次
    QRegExp ex("^[A-Za-z0-9]+([_\.][a-zA-Z0-9]+)*@([A-Za-z0-9]+\.)+[A-Za-z]{2,6}$");
    bool re = ex.exactMatch(ui->username->text());

    if(re){
        QString fileName;   //文件名
        QString username;   //账号
        QString password;   //密码
        QString strline;    //读取整行
        QStringList list;   //存放读取的数据

        fileName = "user.txt";
        username = ui->username->text();    //读取账号
        password = ui->password->text();    //读取密码
        QFile file(fileName);   // 加载文件
        QTextStream stream(&file);
        if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
            while(!stream.atEnd()){
                strline = stream.readLine();
                list = strline.split(",");   //以逗号分隔数据存入到list中
                if(username == list.at(0)){
                    if(password == list.at(1)){
                        QMessageBox::information(this,"提示","欢迎进入科目一考试!");
                        file.close();
                        done(Accepted); //以用户接受的状态返回,并关闭窗口
                        return;
                    }else{
                        QMessageBox::information(this,"提示","密码输入错误,请重新输入!");
                        ui->password->clear();
                        ui->password->setFocus();
                        file.close();
                        return;
                    }
                }
            }
            QMessageBox::information(this,"提示","账号输入错误,请重新输入!");
            ui->username->clear();
            ui->password->clear();
            ui->username->setFocus();
            file.close();
        }else{
            QMessageBox::information(this,"提示","文件读取失败!");
            return;
        }

    }else{
        QMessageBox::information(this,"提示","账号邮箱输入错误,请重新输入!");
        ui->username->clear();     //清除栏位内容
        ui->username->setFocus();  //重新聚焦
        return;
    }
}

在main.cpp中将登录窗口以模态方式运行,接收登录窗口关闭时的返回状态,进而显示答题界面

#include "loginDialog.h"
#include "examsystem.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    LoginDialog loginDialog;
    int res = loginDialog.exec();     //登录窗口以模态方式运行
    
    //当为接受状态时,显示答题界面,当为其他状态时,程序停止运行
    if(res == QDialog::Accepted){
        ExamSystem *examSystem;
        examSystem = new ExamSystem;	//调用ExamSystem构造函数,在构造函数中添加show方法,将窗体显示
    }else {
        return 0;
    }

    return a.exec();
}

5.2 取消按钮

同样,在设计模式中也给取消按钮,鼠标右击转到槽,进行添加槽方法on_cancel_btn_clicked

on_cancel_btn_clicked的实现:

void LoginDialog::on_cancel_btn_clicked()
{
    done(Rejected); //以用户拒绝的状态返回,关闭窗口
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值