前言
受到大佬的影响,决定未来多多记录自己的学习过程。本篇博客将把 Qt 开发数据库课程设计的过程详细介绍,适合初学者。可能因为太过详细所以请利用目录。
参考资料
选题
摇了个1,和 RongLin02 的选题一样。我当场重新摇,很快啊。(抄他的代码极易雷同!望周知!)
题目 员工培训管理系统
1 系统需求分析
1.1 系统功能分析
员工培训系统需要实现的主要功能包括:
- 企业总体培训课程的设置和安排。
- 允许员工根据自己的情况选择合适的课程和上课时间。
- 对选课结果进行统计报表。允许员工对最后选课结果的查询。
- 培训考核成绩的输入和查询。
- 员工培训效果的综合报表。
- 员工个人信息的修改。
1.2 系统功能模块设计(重点,结构)
本系统涉及到员工和培训管理部门之间的交流, 因此需要根据用户的不同分成两大功能模块。这两个模块的功能和使用的权限完全不同。本系统功能模块如图 1 所示。
1.3 与其它系统的关系(无效信息)
员工培训系统可以为员工素质技能的评价提供可靠的依据,是职务评定的一个参考信息源。系统本身需要用到人事管理系统中的员工基本信息和部门信息等辅助资料,这些数据可以通过数据库直接读取。
1.4 数据流程图
员工培训管理系统的数据流程如图 2 所示。
2 数据库设计
2.1 数据库需求分析
根据系统数据流程图,我们可以列出以下系统所需的数据项和数据结构:
- 课程设置:编号、名称、简介、所用教材、上课地点、人数、上课时间
- 选课结果:记录编号、员工、课程、考核成绩、评价、考核日期。
所需的外部数据支持:
- 人员信息:员工号、密码、权限、姓名、部门、当前状态等。
- 部门设置:部门编号、名称等。
2.2 数据库概念结构设计
图 3 是本系统所需数据的 E-R 模型图。
3 各功能模块的设计与实现
3.1 功能说明
本管理系统主要分为两大部分:培训管理应用程序和学员选课应用程序。培训管理应用 程序主要用于培训中心的管理人员对培训课程和培训情况进行维护。此应用程序主要包括四项功能:课程设置、选课结果查询修改、成绩输入、培训成绩统计报表。另外,系统需要有登录窗口(用于权限认证)和导航窗口(用于连接各项功能)。学员选课应用程序包括个人信息修改、选课和成绩查询三项功能。
- 培训管理管理应用程序功能说明
- 学员选课应用程序功能说明
3.2 用户界面设计
完成数据库创建和功能说明以后,我们可以进行下一步工作,即设计用户界面。
- 培训管理应用程序登录窗体的创建
- 培训管理应用程序主窗体的创建
- 课程设置窗体的创建
- 选课结果查询窗体的创建
- 学员名单报表窗体的创建
- 考核评定结果窗体的创建
- 培训统计窗体的创建
- 培训成绩报表窗体的创建
- 学员选课客户端界面的创建
3.3 各功能模块的实现
- 培训管理应用程序数据模块的创建
- 培训管理应用程序登录程序的实现
- 课程设置模块的实现
- 选课结果查询的实现
- 学员名单报表的实现
- 考核评定结果的实现
- 培训统计的实现
- 培训成绩报表的实现
- 学员选课客户端应用程序的创建
初步设计
此为本系统的结构,从这里入手。
进入系统,首先登录。
登录系统使用数据库完成。对每个账号,设置“管理员权限”或“普通用户权限”。
如果是管理员则进入培训中心界面,用户则进入员工界面。
培训中心:
-
添加课程
课程设置:编号、名称、简介、所用教材、上课地点、人数、上课时间
编号为主键,设置所有课程的表。排序按后创建在前。
-
选课结果的查看
选课结果:记录编号、员工、课程、考核成绩、评价、考核日期。
记录编号,员工,课程编号为主键,表格界面添加成绩。
-
成绩管理
添加成绩。
-
统计分数
做平均分,一些分析等等。
用户:
-
选课
-
修改信息
人员信息:员工号、密码、权限、姓名、部门、当前状态等。
部门设置:部门编号、名称等。
-
成绩查询
准备工作
QT
触发调用机制
连接函数
connect(参数1,参数2,参数3,参数4)
- 参数1 信号的发送者,常常是按钮对象的指针
- 参数2 发送的信号(函数地址),常常是点击
- 参数3 信号的接受者,指针。
- 参数4 处理的槽函数 (函数地址)
其 实质 应该是 监听。参数3
监听参数1
的参数2
信号,监听到了,就触发参数3
的参数4
方法。
信号
//点击
void clicked(bool checked = false)
//按下
void pressed()
//弹起
void released()
//状态变化时触发
void toggled(bool checked)
自定义信号
写到类定义中signals:
下.
只需要声明不需要实现。
返回值为void
。
可以有参数,同时也可以重载。
自定义槽函数
写到类定义的public( slots):
下。
需要声明和实现。
返回void
。
可以有参数,同时也可以重载。
可以在ui界面右键按钮,转到槽。
选clicked()就好。
函数指针
函数指针的定义
//指向函数的返回类型 (*指针名)(指向函数的参数表)=指向函数名
//例如
int (*int_operator)(int, int) = int_add;
如果指向类里的函数
//指向函数的返回类型 (类名:: *指针名)(指向函数的参数表)=指向函数名
//例如
void (Teacher:: *Signal)(void)=&
注意
当自定义信号和槽函数有多个重载的时候,需要利用函数指针,明确指向函数的地址。
断开连接
如果需要断开连接的话,需要用到disconnect
函数,参数复制connect
即可。
pro文件介绍
QT += core gui //Qt包含的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于4版本以上 包含 widget模块
TARGET = MyTest //目标 生成的.exe程序的名称
TEMPLATE = app //模板 应用程序模板 Application 除了这个还有很多
SOURCES += \ //源文件
main.cpp \
mainwindow.cpp
HEADERS += \ //头文件
mainwindow.h
FORMS += \ //ui文件界面
mainwindow.ui
不需要主动管理内存,析构等。
Main 函数
QApplication a; //应用程序对象。有且仅有一个应用程序对象。
myWidget w; //窗口默认不弹出,调用 show() 方法。
w.show();
return a.exec(); //阻塞,防止窗口消失。
Q_OBJECT
类定义的第一行加上Q_OBJECT
,不需要分号。
Q_OBJECT
是Qt中的一个宏。由于Qt的语法是在c++的基础上拓展的,所以在Qt程序的编译过程中,直接用gcc这些标准编译器编译是不可行的,因为gcc不能识别这些拓展性的语法,比如信号和槽(Signal and Slot)。于是Qt引入了moc这一编译器。
moc(Meta-Object Compiler)
,即元对象编译器,Qt 程序在交由标准编译器编译之前,会使用 moc 分析 C++ 源文件,假设它发现某个头文件中包括了 Q_OBJECT这个宏(注意, moc 只处理头文件中标记了Q_OBJECT的类声明,不会处理 cpp 文件中的类似声明),则会生成另外一个 C++ 源文件,这个源文件里包括了 Q_OBJECT 宏的实现代码,并且文件名称将会是原文件名称前面加上 moc_ 。这个新的文件会和原本的c++源文件一起进入编译系统,最终被链接到二进制代码中完成编译工作。
在Qt中,QObject是所有Qt类的基类,是Qt对象模型的核心,只有继承了QObject类的类,才具有信号槽的能力。所以,为了使用信号槽,必须继承QObject。凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽,都应该添加这个宏。这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力。因此,如果你觉得你的类不需要使用信号槽,就不添加这个宏,这是错误的,因为其它很多操作都会依赖于这个宏。
反正拥有Qt开发区别于C++的地方,就需要加上这行。
按钮
pro文件引入:
QT += widgets
包含:
#include <QPushButton>
设置依赖,否则单独弹出一个窗口只有按键。
QPushButton *btn = new QPushButton;
btn -> setParent(this); //设置父类、依赖
btn -> setText(); //显示文本
或
QPushButton *btn = new QPushButton("显示文本",this);
重置窗口/按钮大小
this->resize(int kuan,int gao);
移动按钮
btn->move(int x,int y);
窗口
setWindowTitle(); //设置标题
resize(int kuan,int gao); //设置大小
setFixedSize(int kuan,int gao); //设置固定大小
菜单栏
菜单栏最多只有一个
QMenuBar * bar = MenuBar();
this->setMenuBar( bar )
QMenu * fileMenu = bar -> addMenu(“文件”) //创建菜单
QAction * newAction = fileMenu ->addAction(“新建”); //创建菜单项
fileMenu->addSeparator(); //添加分隔线
分割线示例:
工具栏
可以有多个
QToolBar * toolbar = new QToolBar(this);
addToolBar( 默认停靠区域, toolbar ); //例如:Qt::LeftToolBarArea
可以设置后期停靠区域,设置浮动,设置移动,添加菜单项 或者添加 小控件等。
状态栏
最多一个
QStatusBar * stBar = statusBar();
setStatusBar(stBar); //设置到窗口中
stBar->addWidget(label); //放左侧信息
stBar->addPermanentWidget(label2); //放右侧信息
部件
浮动窗口 可以多个
类名:QDockWidget
方法:addDockWidget( 默认停靠区域,浮动窗口指针)
设置后期停靠区域
设置核心部件
只能一个。
this->setCentralWidget(edit);
但可以多次设置。
模态对话框
出现时不能操作其它窗口。
非模态对话框
可以对其他窗口进行操作。
开始设计
建立工程
路径自选,请将test改成ETMSCPP。
这一步我们创建了一个类,继承了QWidget类,这里的Base class展开后有好几种窗口,读者可以自行搜索异同。
下一步
这里务必选择MinGW,选择几位?
查一下自己的数据库是多少位,例如我的MySQL是64位,因此我选择如图。选择好以后下一步
直到完成
就好。
创建完工程以后,会有pro文件
,sign.h/.cpp/.ui
,main.cpp
。
项目结构展示
资源导入
参考资料:Qt学习之Qt基础入门(中)
首先要将图片文件拷贝到项目位置下,我这里在项目根目录创建一个image文件夹,里边放入了一张图片
然后在开发工具中,右键项目->添加新文件 –> Qt
- >Qt recourse File
然后起名字,叫res,然后路径也选择项目根目录
然后它会生成一个res.qrc的资源文件
然后对着res.qrc
右键 -> open in editor
-> 编辑资源
在右侧,添加前缀 我这里前缀起名/
前缀添加完毕后,再点击 添加文件,然后选择要添加的图片等资源文件,可以多选,然后再 右键项目 -> 重新构建
之后如果工程中出现图片,则说明导入完成。
请参照dbc类的编写,安装好ODBC,并建立数据库,再继续阅读。dbc类的代码可以不用现在深究。
sign类的设计(登录窗口)
sign ui 设计如下
代码中设置窗口大小,在sign.cpp
中构造函数内增加如下代码
setFixedSize(480,296);//黄金分割
setPalette(QPalette(Qt::white)); //设置窗口为白色背景
在窗口中,拖出一个 label 到某个位置以显示下面两个图标(位置自行把握)
所以,这里可以知道,一个图标的显示,是使用 label 的,其实,文字显示也是 label 。
label后面有输入框,输入框是
输入框的边框要设置成,没有上左右边框,有下边框,
我们在 sign.cpp 中构造函数增加
ui->ac->setStyleSheet("border-top:0px solid;border-bottom:1px solid;border-left:0px solid;border-right: 0px solid;");
ui->pw->setStyleSheet("border-top:0px solid;border-bottom:1px solid;border-left:0px solid;border-right: 0px solid;");
两种方法设置 label 显示图片
-
代码设置
//设置图标显示,这里使用了ui设计中stylesheet来显示,因此注释掉。实际上代码中使用也可以 // ui->aclabel->setPixmap(QPixmap(":/image/accountImage.png")); // ui->pwlabel->setPixmap(QPixmap(":/image/passwordImage.png"));
我一开始采用这种方法,后来使用了方法2,这两行代码放到构造函数中去掉注释即可。
-
ui 设置
点击 ui 中的 label ,右边会有其设置,找到
stylesheet
,
选择相应的图片即可。
图片的设计
首先,密码的图标是参照QQ登录界面的
等比例画图中自绘即可(截图也随你)。
如果没记错应该是11*13大小。
账号的图标也是自绘。
按键信号和槽函数的设计(登录和注册键)
接下来在sign.h/.cpp
中同步增加两个函数(这一步可以改动)
.h 中
public:
bool signin();
bool signup();
.cpp 中
bool sign::signin()
{
//qDebug()<<"按了登录键";
ac = ui->ac->text();
pw = ui->pw->text();
int result=dbc::signin(ac,pw);
if(result==1)
{
QMessageBox::critical(this,"登录失败","该账号未注册");
return false;
}
else if(result==2)
{
QMessageBox::critical(this,"登录失败","密码错误");
return false;
}
else if(result==3)
{
//qDebug()<<"管理员成功登录";
emit adminSI(ac);
return true;
}
else
{
//qDebug()<<"普通用户成功登录";
emit sISuccess(ac);
return true;
}
}
bool sign::signup()
{
//qDebug()<<"按了注册";
ac = ui->ac->text();
pw = ui->pw->text();
if(ac.length()<=4||pw.length()<=4)
{
QMessageBox::critical(this,"注册失败","账号与密码长度均不能小于5位!");
return false;
}
bool ok=dbc::signup(ac,pw);
if(ok)
{
QMessageBox::information(this,"注册成功","注册成功!");
return true;
}
else
{
QMessageBox::critical(this,"注册失败","注册失败!");
return false;
}
}
函数中使用了dbc类中的静态方法,因此,我们需要创建一个类dbc
,它是数据库的操作类,包含整个程序中所有对数据库的操作。
关于dbc类的编写,我放到后面介绍,希望大家不要重点关注数据库的操作,因为在编写代码的时候,我们应该做到,要用什么功能就加什么功能,你很难做到先把数据库类编写好,再去写其它模块。因此等看见了数据库操作的时候,再看/编写数据库代码。
在登录函数中,使用了两个信号
- sISuccess(ac);
- adminSI(ac);
这两个信号,在.h文件中要同步加上
signals:
void sISuccess(QString ac);
void adminSI(QString ac);
sign类到这里就写好了。
总览
sign.h
#ifndef SIGN_H
#define SIGN_H
#include <QMainWindow>
#include <QSqlDatabase>
QT_BEGIN_NAMESPACE
namespace Ui { class sign; }
QT_END_NAMESPACE
class sign : public QMainWindow
{
Q_OBJECT
signals:
void sISuccess(QString ac);
void adminSI(QString ac);
public:
QString ac;
QString pw;
sign(QWidget *parent = nullptr);
~sign();
bool signin();
bool signup();
private:
Ui::sign *ui;
};
#endif // SIGN_H
sign.cpp
#include "sign.h"
#include "ui_sign.h"
#include<qdebug.h>
#include<QSqlQuery>
#include<QMessageBox>
#include<QSqlError>
#include "dbc.h"
sign::sign(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::sign)
{
ui->setupUi(this);
setFixedSize(480,296);//黄金分割
setPalette(QPalette(Qt::white)); //设置窗口为白色背景
//设置下边框
ui->ac->setStyleSheet("border-top:0px solid;border-bottom:1px solid;border-left:0px solid;border-right: 0px solid;");
ui->pw->setStyleSheet("border-top:0px solid;border-bottom:1px solid;border-left:0px solid;border-right: 0px solid;");
//设置图标显示,这里使用了ui设计中stylesheet来显示,因此注释掉。实际上代码中使用也可以
// ui->aclabel->setPixmap(QPixmap(":/image/accountImage.png"));
// ui->pwlabel->setPixmap(QPixmap(":/image/passwordImage.png"));
connect(ui->signIn,&QPushButton::clicked,this,&sign::signin);
connect(ui->signUp,&QPushButton::clicked,this,&sign::signup);
}
sign::~sign()
{
delete ui;
}
bool sign::signin()
{
//qDebug()<<"按了登录键";
ac = ui->ac->text();
pw = ui->pw->text();
int result=dbc::signin(ac,pw);
if(result==1)
{
QMessageBox::critical(this,"登录失败","该账号未注册");
return false;
}
else if(result==2)
{
QMessageBox::critical(this,"登录失败","密码错误");
return false;
}
else if(result==3)
{
//qDebug()<<"管理员成功登录";
emit adminSI(ac);
return true;
}
else
{
//qDebug()<<"普通用户成功登录";
emit sISuccess(ac);
return true;
}
}
bool sign::signup()
{
//qDebug()<<"按了注册";
ac = ui->ac->text();
pw = ui->pw->text();
if(ac.length()<=4||pw.length()<=4)
{
QMessageBox::critical(this,"注册失败","账号与密码长度均不能小于5位!");
return false;
}
bool ok=dbc::signup(ac,pw);
if(ok)
{
QMessageBox::information(this,"注册成功","注册成功!");
return true;
}
else
{
QMessageBox::critical(this,"注册失败","注册失败!");
return false;
}
}
当登录成功后,sign类的实例会发出信号。
connect函数会监听信号,当收到相应的信号,就会触发槽函数。
不同的信号可以触发不同的槽函数,同一个信号也可以触发多个槽函数,多写几个connect就行。
监听信号的connect语句写在哪?写在main函数里面。
当触发了登录成功,就要进入下一个界面。根据之前的设计图,下一个界面可能是管理员界面或者用户界面。
我先写的是用户界面,实际上顺序可以换。而且两个界面需要交替设计,用户修改个人信息,管理员增加课程,用户选课,管理员对选课记录给一个分数,用户查分。这些功能是交替进行的,因此需要交替编写。
windowforusers类的设计(用户界面)
创建一个类继承QMainWindow,然后删掉状态栏和菜单栏。
(好像QWidget就行…它就没有状态栏和菜单栏,对的…我多此一举)
首先看ui界面。
ui 界面的初始化:
WindowForUsers::WindowForUsers(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::WindowForUsers)
{
//setupUi(this)是由.ui文件生成的类的构造函数,这个函数的作用是对界面进行初始化,它按照我们在Qt设计器里设计的样子把窗体画出来,把我们在Qt设计器里面定义的信号和槽建立起来。
ui->setupUi(this);
setFixedSize(800,600);
setPalette(QPalette(Qt::white)); //设置窗口为白色背景
ui->birT->setCalendarPopup(true);
//注意ed,使用了 转到槽 所以取消了connect
//connect(ui->exitB,&QPushButton::clicked,this,&WindowForUsers::close);
}
start函数(收到登录界面的信号后需要执行的函数)
void WindowForUsers::start(QString ac)
{
this->ac=ac;
this->show();
QString welcome="欢迎您,用户"+ac;
//记得把ui拉长,以防显示不全。
ui->welcome->setText(welcome);
//qDebug()<<welcome;
QLineEdit *wid2[2];
wid2[0]=ui->idT;
wid2[1]=ui->nameT;
QWidget *wid22[5];
wid22[0]=ui->perT;
wid22[1]=ui->sexT;
wid22[2]=ui->staT;
wid22[3]=ui->compT;
wid22[4]=ui->depaT;
for(int i=0;i<2;i++) wid2[i]->setEnabled(false);
for(int i=0;i<5;i++) wid22[i]->setEnabled(false);
ui->birT->setEnabled(false);
ui->stackedWidget->setCurrentIndex(3);
//为使按键可以检查,才能触发toggled。默认不能检查,就不能toggled。
ui->changeInfo->setCheckable(true);
ui->changeInfo->setText("修改信息");
QVector<QString> info;
info.push_back(ac);
dbc::findinfo(info);
dataComplete=true;
for(auto a:info)
{
if(a.isEmpty())
dataComplete=false;
}
ui->idT->setText(info[1]);
ui->nameT->setText(info[2]);
ui->sexT->setCurrentText(info[3]);
ui->birT->setDate(QDate::fromString(info[4]));
ui->compT->setCurrentText(info[5]);
ui->depaT->setCurrentText(info[6]);
ui->perT->setCurrentText(info[7]);
ui->staT->setCurrentText(info[8]);
course=new QSqlTableModel(this);
//设置表名
course->setTable("course");
//设置编辑策略
course->setEditStrategy(QSqlTableModel::OnFieldChange);
//查找截止日期之前的课,也就是可选的课
QString timenow=QString::number(QDateTime::currentDateTime().toTime_t());
course->setFilter(QString("(exptime>"+timenow+") and (signnum<maxnum)"));
//查询
course->select();
//显示
ui->tableView->setModel(course);
//设置不可编辑
ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
//创建对象
signRecord=new QSqlTableModel(this);
//设置表名
signRecord->setTable("signrecord");
//设置编辑策略
signRecord->setEditStrategy(QSqlTableModel::OnFieldChange);
//查询
signRecord->setFilter("ac=\'"+ac+"\'");
signRecord->select();
ui->gradeView->setModel(signRecord);
//不可编辑
ui->gradeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
}
左边是一个QWidget,
这个QWidget上面是一个label,用来显示图片,把该区域的分辨率调好以后,用画图制图。然后就是三个按钮,这三个按钮是我手动放的,失策,应该做一个按钮组,QButtonGroup,但当时没学到这个东西。后面有使用。读者若会使用,可以尝试改进一下。参考 QButtonGroup的使用,使用按钮组,可以设置 在按钮组中的按钮只可单选 ,这个功能在后面是我手写的(有点麻烦,但不难)。
退出按钮是单独的,它的槽函数也很简单,就是关闭整个程序。
void WindowForUsers::on_exitB_clicked()
{
this->close();
}
上面三个按键的功能,主要是三方面:
-
翻页
-
设置按下
设置按下这里需要说明一下,按下一个键以后,理论上需要实现的效果是,此键是按下的状态且不能再按(灰色),其它的键如果是按下状态,则弹起。其实也就是个单选功能。如果学了按钮组,何至于此!
-
其还需要实现的功能
void WindowForUsers::on_selectCourse_clicked()
{
//查找截止日期之前的课,也就是可选的课
QString timenow=QString::number(QDateTime::currentDateTime().toTime_t());
course->setFilter(QString("(exptime>"+timenow+") and (signnum<maxnum)"));
//显示
course->select();
ui->stackedWidget->setCurrentIndex(0);
ui->selectCourse->setEnabled(false);
ui->checkResult->setEnabled(true);
ui->updateInformation->setEnabled(true);
}
void WindowForUsers::on_checkResult_clicked()
{
//查询
signRecord->setFilter("ac=\'"+ac+"\'");
signRecord->select();
ui->stackedWidget->setCurrentIndex(1);
ui->selectCourse->setEnabled(true);
ui->checkResult->setEnabled(false);
ui->updateInformation->setEnabled(true);
}
void WindowForUsers::on_updateInformation_clicked()
{
ui->stackedWidget->setCurrentIndex(2);
ui->selectCourse->setEnabled(true);
ui->checkResult->setEnabled(true);
ui->updateInformation->setEnabled(false);
}
四行统一的代码就是翻页和设置单选的功能。其它代码就是这个按键独有的功能。对于选课按钮,其独有的功能是实时刷新,每次点击都相当于刷新课程。
同理,查询分数的按键,也自带刷新功能。
至于修改个人信息的按键,在初始化以后就是实时的,原因读者自己理解。
右边界面
上面的图片依然是label,下面使用了stacked widget,页面为4个,下标为0,1,2,3。
接下来介绍每个界面的详细设计。
页面3
打开这个窗口,会默认显示页面3,代码在初始化函数内,
ui->stackedWidget->setCurrentIndex(3);
本页面非常简单,一句话(label)加一个按钮。
个人信息就是个push button。设置stylesheet如下:
border-top:0px solid;border-bottom:0px solid;border-left:0px solid;border-right: 0px solid;
其实就是取消边框。
勾上下划线。
页面2
页面2是修改个人信息的,其操作在start函数中和修改信息按键中有实现。主要是设置按键的Checkable属性,代码如下
ui->changeInfo->setCheckable(true);
ui->changeInfo->setText("修改信息");
Checkable属性就是让按键有按下和抬起两种状态。类似电视机的开关那样。然后根据其按下或者抬起来设置是否在编辑状态。
输入框是LineEdit
下拉框是Combo box
生日的日期输入框是Date Edit
页面0
这是选课管理的界面。
大白框是QTableView
这个类需要设置model,其model在start里有定义,相关代码如下。
course=new QSqlTableModel(this);
//设置表名
course->setTable("course");
//设置编辑策略
course->setEditStrategy(QSqlTableModel::OnFieldChange);
//查找截止日期之前的课,也就是可选的课
QString timenow=QString::number(QDateTime::currentDateTime().toTime_t());
course->setFilter(QString("(exptime>"+timenow+") and (signnum<maxnum)"));
//查询
course->select();
//显示
ui->tableView->setModel(course);
//设置不可编辑
ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
注释比较完全,不多赘述。
页面1
界面
大白框依然是QTableView,相关代码如下。
//创建对象
signRecord=new QSqlTableModel(this);
//设置表名
signRecord->setTable("signrecord");
//设置编辑策略
signRecord->setEditStrategy(QSqlTableModel::OnFieldChange);
//查询
signRecord->setFilter("ac=\'"+ac+"\'");
signRecord->select();
ui->gradeView->setModel(signRecord);
//不可编辑
ui->gradeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
windowforadmin类的设计
同样新建一个QWidget,步骤参考上面的类。
初始化函数如下。
WindowForAdmin::WindowForAdmin(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::WindowForAdmin)
{
ui->setupUi(this);
setFixedSize(800,600);
setPalette(QPalette(Qt::white)); //设置窗口为白色背景
ui->stackedWidget->setCurrentIndex(3);
ui->exptT->setCalendarPopup(true);
//设置一组按钮
bg=new QButtonGroup(ui->page_2);
bg->addButton(ui->scb1,0);
bg->addButton(ui->scb2,1);
bg->addButton(ui->scb3,2);
//设置独占,即只能选中一个按钮
bg->setExclusive(true);
//默认选中 按用户账号查找
ui->scb1->setChecked(true);
}
start函数如下。
void WindowForAdmin::start(QString ac)
{
this->ac=ac;
this->show();
//创建对象
signRecord=new QSqlTableModel(this);
//设置表名
signRecord->setTable("signrecord");
//设置编辑策略
signRecord->setEditStrategy(QSqlTableModel::OnFieldChange);
//查询整张表
signRecord->select();
ui->tableView->setModel(signRecord);
}
布局展示
类似上面的类。
旁边的按钮有翻页功能。代码如下。
void WindowForAdmin::on_addCourse_clicked()
{
ui->stackedWidget->setCurrentIndex(0);
ui->addCourse->setEnabled(false);
ui->checkCourse->setEnabled(true);
ui->exptT->setDateTime(QDateTime::currentDateTime());
ui->addAdmin->setEnabled(true);
}
void WindowForAdmin::on_checkCourse_clicked()
{
ui->stackedWidget->setCurrentIndex(1);
ui->addCourse->setEnabled(true);
ui->checkCourse->setEnabled(false);
ui->addAdmin->setEnabled(true);
}
void WindowForAdmin::on_addAdmin_clicked()
{
ui->stackedWidget->setCurrentIndex(2);
ui->addAdmin->setEnabled(false);
ui->addCourse->setEnabled(true);
ui->checkCourse->setEnabled(true);
}
页面0
按确定以后,所有信息按顺序存入数组,使用dbc类的方法存入数据库。
存储的代码如下。
void WindowForAdmin::on_OKB_clicked()
{
QVector<QString> cinfo;
cinfo.push_back(ui->nameT->text());
cinfo.push_back(ui->intrT->document()->toPlainText());
cinfo.push_back(ui->bookT->document()->toPlainText());
cinfo.push_back(ui->addrT->text());
cinfo.push_back(QString::number(ui->maxnumT->value()));
cinfo.push_back(QString::number(ui->exptT->dateTime().toTime_t()));
if(dbc::addC(cinfo))
{
QMessageBox::information(this,"课程成功增加","课程增加成功!");
this->on_CLRB_clicked();
}
else
{
QMessageBox::critical(this,"课程添加失败","课程添加失败,您可以试试减少输入字数!");
}
}
页面1
ui
大白框依然是QTableView。
查找功能改变了select语句的条件。
页面2
ui
这里稍微说一说账号系统的设计,账号系统设计,除了账号密码,还有一个属性“whetheradmin”。注册账号时默认是0。其它管理员可以设置普通用户为管理员。登录的时候检查这个属性,以确定展示哪个页面。
dbc类的编写
新建C++ Class
然后下一步
就好。
ODBC
使用ODBC连接数据库,参考 Qt连接MySQL数据库最详细的教程
首先确定自己的数据库是多少位的,切记本工程与数据库位数要对应。这点在开始设计时就说明了。
因为ODBC和数据库要一样的位数,ODBC和代码也要一样的位数。
步骤
-
打开官网
-
选择对应版本下载,我的是64位所以选择
-
安装
一直next就行。
-
左下角搜索ODBC
-
如图点击
-
如图填写
password填你的mysql密码,database是本次设计使用的创建好的数据库。Data Source Name要记住,代码里要用,可以修改,记得代码里面一起改了。
数据库的设计
数据库结构
users表
userinfo表
course表
signrecord表
代码
dbc.h代码
#ifndef DB_H
#define DB_H
#include<QSqlDatabase>
#include<QVector>
//数据库编码格式务必utf8,字段也要utf8 可以执行alter table `tablename` convert to character set utf8;
class dbc
{
public:
static QSqlDatabase db;
public:
dbc();
//初始化
static bool dbinit();
//注册
static bool signup(const QString ac,const QString pw);
//登录
static int signin(const QString ac,const QString pw);
//找个人信息
static bool findinfo(QVector<QString> &info);
//更新个人信息数据
static bool updinfo(QVector<QString> &info);
//增加课程
static bool addC(QVector<QString> &cinfo);
//选课,传入课程号,增加数据
static bool chooseC(int cno,QString ac);
//添加管理员
static bool addAdmin(QString ac);
};
#endif // DB_H
dbc.cpp代码如下
#include "dbc.h"
#include<QSqlError>
#include<QDebug>
#include<QSqlQuery>
QSqlDatabase dbc::db = QSqlDatabase::addDatabase("QODBC");
dbc::dbc()
{
//类外定义
//db = QSqlDatabase::addDatabase("QODBC");
db.setHostName("127.0.0.1"); //连接本地主机
db.setPort(3306);
db.setDatabaseName("ODBC2MySQL");
db.setUserName("root");
db.setPassword("你的密码");
bool ok = db.open();
if(!ok)
//#include<QSqlError>
//#include<QDebug>
qDebug()<<"error open database because"<<db.lastError().text();
}
bool dbc::dbinit()
{
db.setHostName("127.0.0.1"); //连接本地主机
db.setPort(3306);
db.setDatabaseName("ODBC2MySQL");
db.setUserName("root");
db.setPassword("你的密码");
bool ok = db.open();
if(!ok)
//#include<QSqlError>
//#include<QDebug>
qDebug()<<"error open database because"<<db.lastError().text();
return ok;
}
bool dbc::signup(const QString ac, const QString pw)
{
//#include<QSqlQuery>
QSqlQuery result=db.exec();
//往账户表插入数据
QString query="insert into users(ac,pw,whetheradmin) values (\'"+ac+"\',\'"+pw+"\',\'"+"0\')";
bool ok1=result.exec(query);
//往信息表插入数据
query="insert into userinfo(ac) values (\'"+ac+"\')";
//qDebug()<<query;
bool ok2=result.exec(query);
return ok1&ok2;
}
//登录函数,返回值0是成功,1是未注册,2是密码错误,3是管理员登录成功
int dbc::signin(const QString ac, const QString pw)
{
QString query="select * from users where ac=\'"+ac+"\'";
//添加 #include<QSqlQuery>
QSqlQuery result=db.exec(query);
//注意!
result.first();
//没找到
if(!result.isValid())
{
return 1;
}
if(result.value("pw").toString()!=pw)
{
return 2;
}
bool wa=result.value("whetheradmin").toBool();
if(wa)
{
return 3;
}
else
return 0;
}
//找个人信息
bool dbc::findinfo(QVector<QString> &info)
{
QString query="select * from userinfo where ac=\'"+info[0]+"\'";
//添加 #include<QSqlQuery>
QSqlQuery result=db.exec(query);
//注意!
result.first();
//info数组中已经有一个账号信息,不必再次读取写入,因此先指向结果集中的账号,进入while就跳过了。
if(!result.isValid())
{
return false;
}
for(int i=1;i<=8;i++)
{
QString name = result.value(i).toString();
info.push_back(name);
}
//for(auto a:info) qDebug()<<a;
return true;
}
//更新个人信息
bool dbc::updinfo(QVector<QString> &info)
{
QString query= "update userinfo set "
"id=\'"+info[1]+"\',"
"name=\'"+info[2]+"\',"
"sex=\'"+info[3]+"\',"
"bir=\'"+info[4]+"\',"
"comp=\'"+info[5]+"\',"
"dep=\'"+info[6]+"\',"
"per=\'"+info[7]+"\',"
"sta=\'"+info[8]+"\'"
" where ac=\'"+info[0]+"\'";
//添加 #include<QSqlQuery>
//qDebug()<<query;
QSqlQuery result=db.exec();
bool ok = result.exec(query);
query="update signrecord set name=\'"+info[2]+"\' where ac=\'"+info[0]+"\'";
ok&=result.exec(query);
return ok;
}
//加课程
bool dbc::addC(QVector<QString> &cinfo)
{
QString query= "insert into course values(null,"
"\'"
+cinfo[0]+"\',\'"
+cinfo[1]+"\',\'"
+cinfo[2]+"\',\'"
+cinfo[3]+"\',\'"
"0\',\'"
+cinfo[4]+"\',"
+cinfo[5]+")";
//qDebug()<<query;
QSqlQuery result=db.exec();
bool ok = result.exec(query);
return ok;
}
//选课
bool dbc::chooseC(int cno,QString ac)
{
QString query;
QSqlQuery result;
query="select * from signrecord where (cno="+QString::number(cno)+") and (ac=\'"+ac+"\')";
result=db.exec(query);
result.first();
//qDebug()<<query;
//找到了
if(result.isValid())
{
return false;
}
query="select cname from course where cno="+QString::number(cno);
//添加 #include<QSqlQuery>
result=db.exec(query);
result.first();
QString cname=result.value(0).toString();
// qDebug()<<cname;
query="select name from userinfo where ac=\'"+ac+"\'";
//添加 #include<QSqlQuery>
result=db.exec(query);
result.first();
QString name=result.value(0).toString();
// qDebug()<<name;
query= "insert into signrecord values(null,"
+QString::number(cno)+","
"\'"+cname+"\',"
"\'"+ac+"\',"
"\'"+name+"\',"
"null)";
bool ok=result.exec(query);
query= "update course set signnum =signnum+1 where cno="+QString::number(cno);
ok&=result.exec(query);
return ok;
}
//添加管理员
bool dbc::addAdmin(QString ac)
{
QString query="select * from users where ac=\'"+ac+"\'";
QSqlQuery result;
result=db.exec(query);
result.first();
//qDebug()<<query;
if(result.isValid())
{
query="update users set whetheradmin=1 where ac=\'"+ac+"\'";
//]qDebug()<<query;
return result.exec(query);
}
else
{
return false;
}
}
函数功能在注释中有说明,实现方式也比较简单。不再赘述。
main函数
代码如下
#include "sign.h"
#include "dbc.h"
#include "windowforusers.h"
#include "windowforadmin.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
dbc::dbinit();
sign window1;
WindowForUsers window2;
WindowForAdmin window3;
window1.show();
QObject::connect(&window1, &sign::sISuccess, &window2,&WindowForUsers::start);
QObject::connect(&window1, &sign::sISuccess, &window1,&sign::close);
QObject::connect(&window1, &sign::sISuccess, &window3,&WindowForAdmin::close);
QObject::connect(&window1, &sign::adminSI, &window2,&WindowForUsers::close);
QObject::connect(&window1, &sign::adminSI, &window1,&sign::close);
QObject::connect(&window1, &sign::adminSI, &window3,&WindowForAdmin::start);
return a.exec();
}
//ui不更新:
//直接找到对应的ui文件,右键,然后open command prompt with ->build environment
//然后输入uic mainwindow.ui -o ui_mainwindow.h,然后便可以更新ui_mainwindow.h,便可以更新最新的界面布局了。
//uic windowforusers.ui -o ui_windowforusers.h
//uic windowforadmin.ui -o ui_windowforadmin.h
总结
工程文件和数据库的建立文件我打包放在后面,有需要可以自行下载。
遇到的bug、问题,都在注释中有说明。请多看注释。
链接:https://pan.baidu.com/s/1vUst_N6eFj4iSHCXl3M8Pg
提取码:HSOL