登陆界面
直接对界面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文件就能直接运行这个程序了。