引言:
学习了Qt,来实现一个简单的驾校科目一考试系统,虽然很粗糙,但可以实现简单的答题,总共有10道题目,答题后点击提交能看到对应的分数。
实现:
使用的QT Creator工具,界面通过设计模式,实现的组件的拖拉,具体的功能(界面按钮、加载题目、计算分数)通过编写代码来实现。
源码:
效果:
目录:
一、登录界面
二、用户名与密码检核
三、答题界面
四、提交按钮
五、登录界面登录与取消按钮
一、登录界面
1.1 设计模式下创建界面
首先创建一个项目,并创建一个LoginDialog登录页面的类,继承于QDialog类。
目录结构如下:
点击loginDialog.ui文件进入设计模式,进行界面设计。
首先,添加一个mainLabel,作为一个背景。然后再添加两个label作为账号与密码的中文字,两个LineEdit作为账号与密码的输入框,两个pushButton作为登录与取消的按钮。
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); //以用户拒绝的状态返回,关闭窗口
}