目录
摘要
本文章是展示本人《C++实践》课程中 MFC学生信息管理系统 的示例,基础性教程,详细地展示如何一步步开发一个基础功能的 MFC学生管理系统。
实验重点
1. 本地的ODBC数据库连接机简单使用
2. MFC基础控件的使用
程序流程图
数据库准备
本示例中用到三张表,分别是:学生表(Stu)、科目表(Course)、成绩表(Score)
这里展示在MySQL和SQL Server两种数据库中的建表过程
本地SQL Server建表
打开SSMS,新建查询,建立一个新的数据库SMSDB用于本实验
CREATE DATABASE SMSDB
use SMSDB
/*
新建并使用数据SMSDB
*/
新建三张表,注意这里有几处约束以及主外键的指定
CREATE TABLE Stu(
学号 NCHAR(10) PRIMARY KEY NOT NULL,
姓名 NCHAR(5),
专业 NCHAR(20) NOT NULL,
性别 NCHAR(2) default '男' check (性别 in ('男','女'))NOT NULL,
出生日期 DATE NOT NULL
);
CREATE TABLE Course(
课程号 NCHAR(10) PRIMARY KEY NOT NULL,
课程名称 NCHAR(20) NOT NULL,
学分 SMALLINT NOT NULL
);
CREATE TABLE Score(
学号 NCHAR(10) FOREIGN KEY REFERENCES Stu(学号) NOT NULL,
课程号 NCHAR(10) FOREIGN KEY REFERENCES Course(课程号) NOT NULL,
成绩 SMALLINT CHECK(成绩>=0 AND 成绩<=100) NOT NULL
);
/*
新建三张表
*/
插入原始示例数据(每张表各插两条)
INSERT INTO Stu VALUES(312021001,'张三','计算机科学与技术','男','2000-1-12')
INSERT INTO Stu VALUES(312020001,'aaa','建筑环境与能源应用','女','1999-4-26')
INSERT INTO Course VALUES('9001','数据结构与算法',2.0);
INSERT INTO Course VALUES('9002','线性代数',1.0);
INSERT INTO Score VALUES(312021001,9001,87);
INSERT INTO Score VALUES(312020001,9002,58);
检查原始数据
SELECT Stu.学号,姓名,性别,出生日期,专业,Course.课程号,课程名称,学分,成绩 FROM Stu,Course,Score
WHERE Stu.学号=Score.学号 AND Course.课程号=Score.课程号
结果
远程MySQL建表
1. Putty远程登陆到服务器并启动MySQL
2. 为MySQL添加一个远程登录用户,并为之授权(这里其实有些步骤的,就没展开讲了)
3. 新建数据库和相应表
4. 插入原始数据并检查
(其实直接用cmd也可以,新版的Terminal还很漂亮)
关于本地MySQL建表,其实上面的SQL语句也有了,实现并不困难,就略过
界面开发
本项目中是基于对话框的MFC项目,共包含5个主要的对话框:主对话框、登录对话框(LoginDlg)、学生管理对话框(StuDlg)、科目管理对话框(CourseDlg)、成绩管理对话框(ScoreDlg)
登陆对话框
密码编辑框为password属性
值得一提的有:
1. 为对话框添加图标
先在头文件中定义一个变量
HICON m_hIcon;
然后在窗体对应的cpp文件的构造函数中添加(IDI_ICON2为添加的ICON资源文件ID)
m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON2);
最后在窗体的OnInitDlg()函数中添加(没有这个函数可以通过类向导添加此虚函数)
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
2. 窗体打开时自动定位焦点到第一个编辑框
// GetDlgItem(USERNAME)->SetFocus();
// 不知道为什么没用
// 哈哈哈哈,ctrl+D设置tab order为1获取焦点,简直简单粗暴
这里设置tad order还能方便有使用Tab键切换编辑项习惯的用户使用
3. 登录验证的具体代码实现
“登录”按钮的消息响应函数
void Login::OnBnClickedOk()
{
// 对用户输入的账号密码进行验证
GetDlgItemText(USERNAME, m_username);
GetDlgItemText(PASSWORD, m_password);
if (m_username==L"" || m_password==L"")
{
MessageBox(L"用户名 或 密码 不能为空!",L"注意");
GetDlgItem(USERNAME)->SetFocus();
}else if (m_username.Compare(username)!=0 || m_password.Compare(password)!=0)
{
MessageBox(L"用户名 或 密码 有误,请重新输入!", L"注意");
SetDlgItemText(USERNAME, L"");
SetDlgItemText(PASSWORD, L"");
GetDlgItem(USERNAME)->SetFocus();
}
else if (m_username.Compare(username) == 0 || m_password.Compare(password) == 0)
{
CDialogEx::OnOK();
}
}
“取消”按钮的消息响应函数
void Login::OnBnClickedCancel()
{
exit(0);
// CDialogEx::OnCancel();
}
其实这一句还挺重要,点击“红叉”也会调用OnCancel()函数,确保了用户不管是点“退出”还是“红叉”都会直接结束程序,确保程序只有验证通过一个入口
4. 在主窗体前弹出的功能实现
Login login;
login.DoModal();
在主对话框的初始化函数最前面弹一个模态对话框
小结
本例中正确的“用户名”和“密码”都是在“头文件”中写死了的,事实上本窗体还有很多可以拓展的点,例如:绘制窗口背景、选择登录类型、记住账号密码、账号密码信息从数据库进行验证
主对话框
左上三个按钮分别为三个子对话框的入口,实现也是点击即创建一个模态对话框
值得注意的是List Control控件中的数据是来自先前建立的三张表的集合,在这里仅作展示,而三个子模块则本别实现对每张表的操作
本窗口的代码实现均留到后面的“学生管理”模块详解
学生管理窗口
学生管理窗口是本程序中最重要的窗体(科目和成绩管理其实没写)
右侧列表为List Control,性别是两个Radio Button,出生日期为Date Time Picker,专业为Combo Box
接下来依次讲解实现
各控件初始化
在窗体的初始化函数OnInitDlg中接着写
Profess.AddString(L"计算机科学与技术");
Profess.AddString(L"道路与桥梁工程");
Profess.AddString(L"文化与新闻传播");
Profess.AddString(L"物流与交通运输");
Profess.AddString(L"理论物理");
// Profess为专业下拉框绑定的控件变量,以上代码为下拉框中添加字段
stu_list.SetExtendedStyle(LVS_EX_CHECKBOXES);
// stu_list为列表绑定的控件变量,这一句是设置list风格:最前面带可以打勾的复选框
stu_list.InsertColumn(0, _T("学号"), 0, 125);
stu_list.InsertColumn(1, _T("姓名"), 0, 80);
stu_list.InsertColumn(2, _T("专业"), 0, 240);
stu_list.InsertColumn(3, _T("性别"), 0, 50);
stu_list.InsertColumn(4, _T("出生日期"), 0, 120);
// 为列表插入表头
效果截图(其实这时候列表中因该没数据的)
List Control从数据库获取数据并显示
重头戏,ODBC操作数据库
1. 先配置数据源
Win+S搜索ODBC数据源(没有的话要安装,这里不展开,可以参考其他博文)
添加数据源(MySQL为例)
需要填写的字段从上至下依次为:数据源名(随意)、数据库主机IP地址(本地为127.0.0.1)、端口(MySQL是3306)、MySQL用户名、MySQL用户密码、指定数据库
填完了可以点击“Test”按钮测试一下
这就算配置成功了
2. 在头文件中定义
CDatabase m_db;
在初始化函数中初始化数据库连接(这里给的是远程连接MySQL的语句,本地连接SQL Server的注释掉了)
try
{
// m_db.Open(NULL);//弹出数据源选择对话框
// m_db.Open(NULL, FALSE, FALSE, _T("DSN=SQL_Server_ODBC;UID=sa;PWD=Ue!p41SQL;DATABASE=SMSDB"));
// 本地连接SQL Server数据库
// 还是ODBC的数据源
m_db.Open(NULL, FALSE, FALSE, _T("DSN=MySQL_ODBC;UID=remote;PWD=MySQL.123;DATABASE=SMSDB"));
// 远程连接MySQL数据库
}
catch (const CDBException& e)
{
MessageBox(e.m_strError);
}
InitList();// 单独写的刷新列表数据的函数
解析一下SQL连接字段:数据源名、数据库用户名、密码、指定使用的数据库
3. 从数据库获取查询到的数据,并插入到List中
// 刷新列表数据
void StuManageDlg::InitList() {
stu_list.DeleteAllItems();// 清空列表控件上的旧数据
// 数据集对象,传入连接对象
CRecordset rs(&m_db);
BOOL ret = rs.Open(AFX_DB_USE_DEFAULT_TYPE, _T("SELECT * FROM Stu"));
int row = 0;
CString id, name, sex, birth, profess;
while (!rs.IsEOF())
{
//获取数据集中的字段数据
rs.GetFieldValue((short)0, id);
rs.GetFieldValue((short)1, name);
rs.GetFieldValue((short)2, sex);
rs.GetFieldValue((short)3, birth);
rs.GetFieldValue((short)4, profess);
//向列表控件中插入一行
stu_list.InsertItem(row, id);
stu_list.SetItemText(row, 1, name);
stu_list.SetItemText(row, 2, sex);
stu_list.SetItemText(row, 3, birth);
stu_list.SetItemText(row, 4, profess);
//移动下一行数据
rs.MoveNext();
row++;
}
//关闭记录集
rs.Close();
}
点击列表项自动填充到右侧编辑框中
这么说可能不是很直观,插一段演示视频好了
这里先用类向导生成一个消息响应函数(列表被点击NM_Click事件),然后编辑代码
// 选中将记录插入到编辑框内
void StuManageDlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
if (-1 != pNMItemActivate->iItem) // 如果iItem不是-1,就说明有列表项被选择
{
// 将选中的信息赋给选中变量(显示到对应的输入框中)
stu_id = stu_list.GetItemText(pNMItemActivate->iItem, 0);
stu_name = stu_list.GetItemText(pNMItemActivate->iItem, 1);
profess = stu_list.GetItemText(pNMItemActivate->iItem, 2);
CString stu_sex = stu_list.GetItemText(pNMItemActivate->iItem, 3);
CString stu_bir = stu_list.GetItemText(pNMItemActivate->iItem, 4);
// 清空按钮的选中状态,然后判断再选中
((CButton*)GetDlgItem(MALE))->SetCheck(0);
((CButton*)GetDlgItem(FEMALE))->SetCheck(0);
if (stu_sex.Compare(L"男")==0)
{
((CButton*)GetDlgItem(MALE))->SetCheck(1);
OnBnClickedMale();
}
else if (stu_sex.Compare(L"女") == 0)
{
((CButton*)GetDlgItem(FEMALE))->SetCheck(1);
OnBnClickedFemale();
}
//解析生日字符串 然后初始化控件
int iIndex = stu_bir.Find('-');
int iReverseIndex = stu_bir.ReverseFind('-');
CString strYear = stu_bir.Left(iIndex);
CString strDay = stu_bir.Right(stu_bir.GetLength() - iReverseIndex - 1);
CString strMonth = stu_bir.Mid(iIndex + 1, (iReverseIndex - iIndex - 1));
COleDateTime time(_ttoi(strYear), _ttoi(strMonth), _ttoi(strDay), 0, 0, 0);
Date.SetTime(time);// CDateTimeCtrl m_DateCtrl;
// 将变量值刷新到控件
UpdateData(false);
}
*pResult = 0;
}
补两个性别按钮被点击事件
void StuManageDlg::OnBnClickedMale()
{
stu_sex = L"男";
}
void StuManageDlg::OnBnClickedFemale()
{
stu_sex = L"女";
}
插入数据的代码实现
// 插入数据
void StuManageDlg::OnBnClickedInsert()
{
Profess.GetWindowTextW(profess);
CTime t;
Date.GetTime(t);
stu_bir = t.Format("%Y-%m-%d");
/*int nIndex = Profess.GetCurSel();
Profess.GetLBText(nIndex, profess);*/
//让控件的值同步到变量上
UpdateData(true);
if (stu_id.IsEmpty() || stu_name.IsEmpty() || stu_sex.IsEmpty()) {
MessageBox(_T("插入的数据不能为空!"));
return;
}
try {
CString sql;
sql.Format(_T("INSERT INTO Stu VALUES(%s,'%s','%s','%s','%s')"), stu_id, stu_name, profess,stu_sex,stu_bir);
m_db.ExecuteSQL(sql);
//刷新一下查询
InitList();
MessageBox(_T("插入数据成功!"));
}
catch (CDBException* e) {
MessageBox(e->m_strError);
}
}
删除数据的代码实现
// 删除信息
void StuManageDlg::OnBnClickedDeleteb()
{
//让控件的值同步到变量上
UpdateData(true);
if (stu_id.IsEmpty()) {
MessageBox(_T("学号不能为空!"));
return;
}
try {
CString sql;
sql.Format(_T("DELETE FROM Stu WHERE 学号=%s"), stu_id);
m_db.ExecuteSQL(sql);
//刷新一下查询
InitList();
MessageBox(_T("删除数据成功!"));
}
catch (CDBException* e) {
MessageBox(e->m_strError);
}
OnBnClickedClear();
}
修改数据的代码实现
// 修改信息
void StuManageDlg::OnBnClickedUpdate()
{
Profess.GetWindowTextW(profess);
CTime t;
Date.GetTime(t);
stu_bir = t.Format("%Y-%m-%d");
CString new_id;
GetDlgItemTextW(ID, new_id);
GetDlgItemTextW(NAME, stu_name);
//让控件的值同步到变量上
UpdateData(true);
if (stu_id.IsEmpty() || stu_name.IsEmpty() || profess.IsEmpty() || stu_sex.IsEmpty() || stu_bir.IsEmpty()) {
MessageBox(_T("修改的数据不能为空!"));
return;
}
if (stu_id != new_id)
{
MessageBox(_T("学号不能被修改!"));
return;
}
try {
CString sql;
sql.Format(_T("UPDATE Stu SET 姓名='%s',专业='%s',性别='%s' ,出生日期='%s' WHERE 学号=%s "), stu_name, profess, stu_sex,stu_bir,stu_id);
m_db.ExecuteSQL(sql);
//刷新一下查询
InitList();
MessageBox(_T("修改数据成功!"));
}
catch (CDBException* e) {
MessageBox(e->m_strError);
}
}
排序(按生日)的代码实现
// 排序
void StuManageDlg::OnBnClickedSort()
{
stu_list.DeleteAllItems();// 清空列表控件上的旧数据
// 数据集对象,传入连接对象
CRecordset rs(&m_db);
BOOL ret = rs.Open(AFX_DB_USE_DEFAULT_TYPE, _T("SELECT * FROM Stu ORDER by 出生日期"));
int row = 0;
CString id, name, sex, birth, profess;
while (!rs.IsEOF())
{
//获取数据集中的字段数据
rs.GetFieldValue((short)0, id);
rs.GetFieldValue((short)1, name);
rs.GetFieldValue((short)2, sex);
rs.GetFieldValue((short)3, birth);
rs.GetFieldValue((short)4, profess);
//向列表控件中插入一行
stu_list.InsertItem(row, id);
stu_list.SetItemText(row, 1, name);
stu_list.SetItemText(row, 2, sex);
stu_list.SetItemText(row, 3, birth);
stu_list.SetItemText(row, 4, profess);
//移动下一行数据
rs.MoveNext();
row++;
}
//关闭记录集
rs.Close();
}
搜索的代码实现
// 搜索
void StuManageDlg::OnBnClickedSearch()
{
GetDlgItemText(SearchInput, search_input);
if (search_input == L"")
{
MessageBox(L"搜索项不能为空!",L"提示");
GetDlgItem(SearchInput)->SetFocus();
}
else
{
stu_list.DeleteAllItems();// 清空列表控件上的旧数据
CString sql = L"SELECT * FROM Stu WHERE 学号 LIKE '" + search_input + L"'";
// 数据集对象,传入连接对象
CRecordset rs(&m_db);
BOOL ret = rs.Open(AFX_DB_USE_DEFAULT_TYPE, sql);
int row = 0;
CString id, name, sex, birth, profess;
while (!rs.IsEOF())
{
//获取数据集中的字段数据
rs.GetFieldValue((short)0, id);
rs.GetFieldValue((short)1, name);
rs.GetFieldValue((short)2, sex);
rs.GetFieldValue((short)3, birth);
rs.GetFieldValue((short)4, profess);
//向列表控件中插入一行
stu_list.InsertItem(row, id);
stu_list.SetItemText(row, 1, name);
stu_list.SetItemText(row, 2, sex);
stu_list.SetItemText(row, 3, birth);
stu_list.SetItemText(row, 4, profess);
//移动下一行数据
rs.MoveNext();
row++;
}
//关闭记录集
rs.Close();
// MessageBox(sql);
这里的排序和搜索都写得非常简单和单一
清空编辑框和显示所有
// 清空编辑框
void StuManageDlg::OnBnClickedClear()
{
GetDlgItem(ID)->SetWindowText(L"");
CWnd::SetDlgItemTextW(NAME, L"");
Profess.SetWindowTextW(L"");
((CButton*)GetDlgItem(MALE))->SetCheck(0);
((CButton*)GetDlgItem(FEMALE))->SetCheck(0);
COleDateTime Now = COleDateTime::GetCurrentTime();
Date.SetTime(Now);
}
void StuManageDlg::OnBnClickedShow()
{
InitList();
}
总结
当然,本项目仍旧很浅显、很基本,甚至说没能体现出MFC的精髓,但本身也作为学习者,记录沉淀也为后来的同学提供一个参考。
如有高见,抑或是讨论、问题,欢迎在评论区提出。
完整项目
当然,就算详详细细的写了这么多,为了更好的理解,实际跑一下还是必要的,所以在这里放出完整的项目文件夹供感兴趣的同学运行调试
注意,数据库在非本人主机上是没法访问的(本地数据源的配置),需要自行配置方能正常使用
MFC 简易学生成绩管理系统https://download.csdn.net/download/m0_53610390/83825115
CSDN资源下载挺坑的,这边补一个百度云盘链接
链接:https://pan.baidu.com/s/1B_pMeecw_7gD6WcQJ6dXvQ?pwd=yaos
提取码:yaos