大纲
效果:
一.创建项目
新建一个项目:
1.新建->application->Qt widgets Application
2.为项目起一个名字:ExamSys
3.选择组件: MinGW 32bit
4.选择类信息:
类名:LoginDialog
基类:QDialog(对话框类)
二.登录界面
界面控件
首先在设计模式下:
加入两个标签(Label),两个按钮(QPushButton),两个线(QLineEdit),分别把他们的的Text设置为如果的文字。
背景
1.将要加载的图片放入项目的文件夹中,
2.切换到编辑模式,右击ExamSys项目,选择添加新文件
3.选择Qt->Qt Resource File,建立名称为image
4.建立好之后在前缀那一行只输入一个/,之后点击添加文件,将文件加载进来
5.在设计模式中,加入一个标签,在Qlabel中选择pixmap将图片放入,将它放到最后面
6.调整图片与标签与对话框三者之间的大小:
图片与对话框调整:
在logindialog.cpp:
ui->imgLabel->setScaledContents(true); //对图片进行填充设置
this->resize(ui->imgLabel->width(),ui->imgLabel->height());
//将窗口的大小设置为图片的大小
图片与标签调整:
在设计模式下:在QWidget中将X,Y设置为0
标题栏
this->setWindowTitle("驾校科目一考试登录"); //对标题进行设置
this->setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);
//只剩下一个关闭按钮
三.验证账号和密码
登录按钮
右击登录按钮->转到槽->clicked(),之后它会创建一个方法
验证
1.在项目文件夹中放入存放数据的文件(本程序加入一个名为"account.txt"的文件)
2.利用正则表达式查看账号格式是否正确
3.如果正确了在判断用户输入的账号密码是否在文件中
//需要包含以下两个头文件
#include "QFile"
#include "QTextStream"
void LoginDialog::on_LoginButton_clicked()
{
QRegExp rx("^[A-Za-z0-9]+([_\\.][A-Za-z0-9]+)*@([A-Za-z0-9\\-]+\\.)+[A-Za-z]{2,6}$");
/*开始的用户名一定是字母且不止一个,所以匹配多次.
* 用户名中可能会有_或.所以匹配可以为0次.
* 中间的@是一定存在的.
* 域名会有字母或数字下划线.
* 之后一定有.最后那一段不会太长,所以匹配2-6次({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{
QString filename; //账号密码数据文件
QString strAccInput;//用户输入的账号
QString strCode; //用户输入的密码
QString strLine; //文件里的一行数据
QStringList strList; //分割读取的一行数据(字符串链表)
filename = "../account.txt";
strAccInput = ui->accountEdit->text();
strCode = ui->codeEdit->text();
QFile file(filename); //将文件赋给到file对象中
QTextStream stream(&file); //给file对象插入一个流
/*
利用file对象的打开属性,以文本只读形式打开
不断的循环遍历文件中的每一行数据,之后末尾
将一行数据分别两段,分别对比账号和密码
为什么 if(strAccInput == strList.at(0))没有else
因为账号要 遍历文件中全部数据才能知道有没有,所以是循环结束还没有找到,才断定为账号不成立
而密码是与账号一对一匹配的,所以如果账号正确了,密码就是账号数据这一行,不需要遍历全部,所以有else
*/
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,"提示","欢迎登录科目一考试系统!");
file.close();
return;
}else{
QMessageBox::information(this,"提示","密码输入错误,请重新输入");
ui->codeEdit->clear();
ui->codeEdit->setFocus();
file.close();
return;
}
}
}
QMessageBox::information(this,"提示","您输入的账号有误,请重新输入!");
ui->accountEdit->clear();
ui->codeEdit->clear();
ui->accountEdit->setFocus();
file.close();
return;
}else{
QMessageBox::information(this,"提示","文件读取失败");
return;
}
}
}
四.考试计时
先建立一个考试窗口类:
右击项目名->文件和类->C+±>C++ Class
类名:examdialog | baseclass: QDialog
examdialog.h
#ifndef EXAMDIALOG_H
#define EXAMDIALOG_H
#include<QDialog>
#include<QTimer>
class ExamDialog : public QDialog
{
Q_OBJECT
public:
ExamDialog(QWidget* parent = 0);//构造函数
void initTimer(); //初始化计时器
private:
QTimer *m_timer; //计时器
int m_timeGO; //考试已用时
private slots:
void freshTime();
};
#endif // EXAMDIALOG_H
examdialog.cpp
#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);
m_timer->start();
//连接信号与槽 connect(发送信号者,发送信号,响应信号者,响应的槽方法)
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+"秒");
}
main.cpp
#include "logindialog.h"
#include <QApplication>
#include <examdialog.h>
int main(int argc, char *argv[])
{
#if(QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
//支持高分屏自动缩放
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QApplication a(argc, argv);//应用程序类创建一个对象
// LoginDialog w; //登录窗对象
// w.show(); //显示
ExamDialog w;
w.show();
return a.exec();
}
实例:
五.初始化题库
在文件夹中添加一个"exam.txt"文件
examdialog.h中添加
头文件:
#include<QTextEdit>
#include<QLabel>
#include<QRadioButton>
#include<QCheckBox>
#include<QGridLayout>
方法:
void initLayout(); //初始化布局管理器
bool initTextEdit();//初始化文本编译器
字段:
QTextEdit *m_textEdit; //考试题库显示
QLabel *m_titleLabels[10];//题目标签
QRadioButton *m_radioBtns[32];//单选题按钮
QCheckBox *m_checkBtns[4];//多选题按钮
QRadioButton *m_radioA; //判断题A选项
QRadioButton *m_radioB; //判断题B选项
QGridLayout *m_layout; //布局管理器
QStringList m_answerList; //答案
examdialog.cpp中
添加的头文件:
#include<QFile>
#include<QTextStream>
#include<QMessageBox>
#include<QApplication>
方法实现:
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("../exam.txt");
QFile file(filename);
QTextStream stream(&file);
stream.setCodec("UTF-8");
if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
m_textEdit = new QTextEdit(this);
m_textEdit->setReadOnly(true);
QString strText;//用于保存显示到文本编译器的数据
int nLines = 0;
while(!stream.atEnd()){
//过滤首行
if(nLines == 0){
stream.readLine();
nLines++;
continue;
}
//过滤答案行
if((nLines>=6 && nLines<=6*9&&(nLines %6 == 0))
||(nLines == 6*9+4)){
strLine =stream.readLine();
strList = strLine.split(" ");
m_answerList.append(strList.at(1));
strText+="\n";
nLines++;
continue;
}
strText+=stream.readLine();
strText+="\n";
nLines++;
}
m_textEdit->setText(strText);
m_layout->addWidget(m_textEdit,0,0,1,10);
file.close();
return true;
}else{
return false;
}
}
//并且更新一下构造函数
ExamDialog::ExamDialog(QWidget *parent):QDialog(parent)
{
//设置字体大小
QFont font;
font.setPointSize(12);
setFont(font);
//设置窗体背景颜色
setPalette(QPalette(QColor(209,215,255)));
setWindowTitle("考试已用时: 0分0秒");
setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);
resize(800,900);
initTimer();
initLayout();
if(!initTextEdit()){
QMessageBox::information(this,"提示","初始化题库数据文件失败");
QTimer::singleShot(0,qApp,SLOT(quit()));
}
}
效果:
六.按钮布局
examdialog.h中添加
头文件:#include<QButtonGroup>
方法:
void initButtons(); //初始化按钮及标签
字段:
QButtonGroup *m_btnGroups[9]; //单项按钮分组
examdialog.cpp中添加
头文件:#include<QPushButton>
S
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_btnGroups[8] = new QButtonGroup(this);
m_btnGroups[8]->addButton(m_radioA);
m_btnGroups[8]->addButton(m_radioB);
break;
}
if(i<8) m_btnGroups[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);
}else{//多项选择
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_btnGroups[i]->addButton(m_radioBtns[4*i+j]);
}
}
}
QPushButton *submitBtn = new QPushButton(this);
submitBtn->setText("提交");
submitBtn->setFixedSize(100,35);
m_layout->addWidget(submitBtn,6,9);
}
图示:
七.提交题目
在void ExamDialog::initButtons()中添加槽方法
examdialog.h中添加
方法:
bool hasNoSelect(); //判断题目是否有未完成的
槽方法:
void getScore();
examdialog.cpp中添加
bool ExamDialog::hasNoSelect()
{
int radioSelects = 0;
for(int i=0;i<8;i++){
if(m_btnGroups[i]->checkedButton())//判断是否一组中有被选中的
radioSelects++;
}
//单选题有未完成的
if(radioSelects!=8)
return true;
int checkSelects=0;
for(int i=0;i<4;i++){
if(m_checkBtns[i]->isChecked())
checkSelects++;
}
//多选题有未完成的
if(checkSelects==0)
return true;
//判断题有未完成的
if(!m_radioA->isChecked() &&!m_radioB->isChecked())
return true;
return false;
}
void ExamDialog::getScore()
{
if(hasNoSelect()){
QMessageBox::information(this,"提示","您有未完成的题目,请完成考试!","是");
return;
}
int scores=0;
for(int i=0;i<10;i++){
//单选题计分
if(i<8)
if(m_btnGroups[i]->checkedButton()->text() == m_answerList.at(i))
scores+=10;
//多项选择题计分
if(i==8){
QString answer = m_answerList.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_btnGroups[8]->checkedButton()->text() == m_answerList.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()处于一个循环等待事件的状态,接下来就等待接受用户和系统的消息并进行处理,里面就包含所谓的信号槽机制
首先在登录类lodDialog窗口中进行操作,如果点击确定后密码账号正确,
那么利用done()把该窗口关闭,并以接受方式返回状态,
之后创建一个ExamDialog类窗口(构造函数中,调用了show方法),进入考试界面
如果点击取消done会关闭当前窗口并且以用户拒绝状态返回,就结束了
1.给取消按钮创建一个槽方法
在方法中
void LoginDialog::on_cannelButton_clicked()
{
done(Rejected);//关当前窗口并且以用户拒绝状态返回
}
2.在on_loginBtn_clicked()中添加一句:
主函数:
int main(int argc, char *argv[])
{
#if(QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
//支持高分屏自动缩放
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QApplication a(argc, argv);//应用程序类创建一个对象
LoginDialog lodDialog; //登录窗对象
int res = lodDialog.exec(); //显示
if(res == QDialog::Accepted)
{
ExamDialog *examDialog;
examDialog = new ExamDialog;
}else{
return 0;
}
//ExamDialog w;
//w.show();
return a.exec();
}
九.发布
1.先将运行的工作目录中的Debug删除
把构建改成Release这种适用于发布,其中的调试信息少,所以所占用的空间比较少
2.
3.准备一个图标,一般是icon后缀
把它加入ExamSys.pro文件中
4.在桌面建立一个"科目一考试系统"将可执行文件和数据文件拖入其中,再从Qt文件中拖入几个链接库
但是这种发布模式,只能在有QT环境的情况下使用
十.全部代码
ExamSys.pro
#-------------------------------------------------
#
# Project created by QtCreator 2021-05-29T08:13:05
#
#-------------------------------------------------
QT += core gui
RC_ICONS +=login.ico
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = ExamSys
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
logindialog.cpp \
examdialog.cpp
HEADERS += \
logindialog.h \
examdialog.h
FORMS += \
logindialog.ui
RESOURCES += \
image.qrc
examdialog.h
#ifndef EXAMDIALOG_H
#define EXAMDIALOG_H
#include<QDialog>
#include<QTimer>
#include<QTextEdit>
#include<QLabel>
#include<QRadioButton>
#include<QCheckBox>
#include<QGridLayout>
#include<QButtonGroup>
class ExamDialog : public QDialog
{
Q_OBJECT
public:
ExamDialog(QWidget* parent = 0);
void initTimer(); //初始化计时器
void initLayout(); //初始化布局管理器
bool initTextEdit();//初始化文本编译器
void initButtons(); //初始化按钮及标签
bool hasNoSelect(); //判断题目是否有未完成的
private:
QTimer *m_timer; //计时器
int m_timeGO; //考试已用时
QTextEdit *m_textEdit; //考试题库显示
QLabel *m_titleLabels[10];//题目标签
QRadioButton *m_radioBtns[32];//单选题按钮
QCheckBox *m_checkBtns[4];//多选题按钮
QRadioButton *m_radioA; //判断题A选项
QRadioButton *m_radioB; //判断题B选项
QGridLayout *m_layout; //布局管理器
QStringList m_answerList; //答案
QButtonGroup *m_btnGroups[9]; //单项按钮分组
private slots:
void freshTime();
void getScore();
};
#endif // EXAMDIALOG_H
logindialog.h
#ifndef LOGINDIALOG_H
#define LOGINDIALOG_H
#include <QDialog>
namespace Ui {
class LoginDialog;
}
class LoginDialog : public QDialog
{
Q_OBJECT
public:
explicit LoginDialog(QWidget *parent = 0);
~LoginDialog();
private slots:
void on_LoginButton_clicked();
void on_cannelButton_clicked();
private:
Ui::LoginDialog *ui;
};
#endif // LOGINDIALOG_H
examdialog.cpp
#ifndef LOGINDIALOG_H
#define LOGINDIALOG_H
#include <QDialog>
namespace Ui {
class LoginDialog;
}
class LoginDialog : public QDialog
{
Q_OBJECT
public:
explicit LoginDialog(QWidget *parent = 0);
~LoginDialog();
private slots:
void on_LoginButton_clicked();
void on_cannelButton_clicked();
private:
Ui::LoginDialog *ui;
};
#endif // LOGINDIALOG_H
logindialog.cpp
#include "logindialog.h"
#include "ui_logindialog.h"
#include"QMessageBox"
#include "QFile"
#include "QTextStream"
LoginDialog::LoginDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::LoginDialog)
{
ui->setupUi(this);//初始化界面
ui->imgLabel->setScaledContents(true); //对图片进行填充设置
this->resize(ui->imgLabel->width(),ui->imgLabel->height());
setFixedSize(width(),height()); //固定窗口大小
this->setWindowTitle("驾校科目一考试登录"); //对标题进行设置
this->setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);
}
LoginDialog::~LoginDialog()//自然回收
{
delete ui;
}
void LoginDialog::on_LoginButton_clicked()
{
QRegExp rx("^[A-Za-z0-9]+([_\\.][A-Za-z0-9]+)*@([A-Za-z0-9\\-]+\\.)+[A-Za-z]{2,6}$");
/*开始的用户名一定是字母且不止一个,所以匹配多次.
* 用户名中可能会有_或.所以匹配可以为0次.
* 中间的@是一定存在的.
* 域名会有字母或数字下划线.
* 之后一定有.最后那一段不会太长,所以匹配2-6次({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{
QString filename; //账号密码数据文件
QString strAccInput;//用户输入的账号
QString strCode; //用户输入的密码
QString strLine; //文件里的一行数据
QStringList strList; //分割读取的一行数据(字符串链表)
filename = "account.txt";
strAccInput = ui->accountEdit->text();
strCode = ui->codeEdit->text();
QFile file(filename); //将文件赋给到file对象中
QTextStream stream(&file); //给file对象插入一个流
/*
利用file对象的打开属性,以文本只读形式打开
不断的循环遍历文件中的每一行数据,之后末尾
将一行数据分别两段,分别对比账号和密码
为什么 if(strAccInput == strList.at(0))没有else
因为账号要 遍历文件中全部数据才能知道有没有,所以是循环结束还没有找到,才断定为账号不成立
而密码是与账号一对一匹配的,所以如果账号正确了,密码就是账号数据这一行,不需要遍历全部,所以有else
*/
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,"提示","欢迎登录科目一考试系统!");
file.close();
done(Accepted);//关闭当前窗体,并且以指定的方式(状态)返回
return;
}else{
QMessageBox::information(this,"提示","密码输入错误,请重新输入");
ui->codeEdit->clear();
ui->codeEdit->setFocus();
file.close();
return;
}
}
}
QMessageBox::information(this,"提示","您输入的账号有误,请重新输入!");
ui->accountEdit->clear();
ui->codeEdit->clear();
ui->accountEdit->setFocus();
file.close();
return;
}else{
QMessageBox::information(this,"提示","文件读取失败");
return;
}
}
}
void LoginDialog::on_cannelButton_clicked()
{
done(Rejected);//关当前窗口并且以用户拒绝状态返回
}
mian.cpp
#include "logindialog.h"
#include <QApplication>
#include <examdialog.h>
int main(int argc, char *argv[])
{
#if(QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
//支持高分屏自动缩放
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QApplication a(argc, argv);//应用程序类创建一个对象
LoginDialog lodDialog; //登录窗对象
int res = lodDialog.exec(); //显示
if(res == QDialog::Accepted)
{
ExamDialog *examDialog;
examDialog = new ExamDialog;
}else{
return 0;
}
//ExamDialog w;
//w.show();
return a.exec();
}