基于MFC的多功能学生成绩管理系统

发布于:2023-07-04 ⋅ 阅读:(112) ⋅ 点赞:(0)

资源下载地址:https://download.csdn.net/download/sheziqiong/85696956
资源下载地址:https://download.csdn.net/download/sheziqiong/85696956

一、实验目的

本实验是对前面所学知识的总结,通过一个比较完整的应用程序的设计,将学过的知识连贯起来,掌握开发一个实际应用程序的步骤,同时学会使用开发工具实现界面友好的应用程序。并通过本实验,掌握如何运用面向对象技术对具体的应用系统进行分析和设计。

二、基本知识点

1、类和对象的定义、对象的初始化和使用
2、面向对象的继承机制
3、虚函数与多态性
4、运算符重载
5、文件的使用
6、算法的使用

三、基本要求

1、理解面向对象的特性
2、掌握面向对象程序设计的开发方法
3、有一定的分析问题和解决问题的能力
4、根据学过的知识点,充分利用已有的开发工具和素材,使程序编写具有更高的效率,能真正地解决实际问题。

四、实验内容

这是一个复杂且多功能的学生成绩管理系统,一共用了1个主对话框和7个子对话框搭建而成。我将提供源码供大家一起学习探讨,我还会在每个代码块加相应的注释。然后,一些小细节我就不说了,相信开发学生成绩管理系统的你也已经大致了解了MFC的基本概念。有一个特殊的点,我希望大家做实验前可以注意:就是在属性那里把Unicode改成多字节,否则,你会在实验中遇到各种各样的令你头疼的不必要的错误,好了,开启我们的实验!还有要提的一点是,我没有用struct去储存每个人的信息,只是显示到主菜单界面上,只有保存才能真正储存到本地,这更大大地简化了代码量。

五、实验报告步骤及总结

学生成绩管理系统

主要界面介绍:
登录界面:

这个设计主要采用了QQ的大登录按钮界面,让人产生清晰干净的感觉。(应该没有侵权,我做的没QQ一半好看。。)

可以看到,左边使用了list control控件的report格式,右上角是个关闭输入的编辑框用于显示学生的总人数,右边则是实现八个不同功能的按钮。然后顶部显示的是我个人的用户名也是博客名:wujiekd
还有我们可以看到里面已经自动加载了当地文本的学生信息,这个功能的实现将会在导入学生信息中介绍。
添加子对话框和删除对话框

在这里插入图片描述

查找子对话框:

导入子对话框:

修改子对话框:

新增修改功能,仅在课程设计第二个压缩包涵盖。

排序和退出子对话框:排序又新增了一个在list control上操作的简单方法 在最下面会有介绍

八个按钮操作对应的函数

第一个按钮:添加学生信息

因为添加的学生信息比较多,为了防止出现各种错误,写了以下四个函数去检查这些错误,当出现错误,都会弹出相应的Message提醒使用者。
四个函数对应的处理情况如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LGcJhstM-1655638134094)(https://www.writebug.com/myres/static/uploads/2022/6/19/9e02cc65a80ed814f2c0b9d660e47ac0.writebug)]

BOOL kedamenu::empty()//判定数据是否存在没填的
{
	if (adds.name == "" || adds.grade == "" || adds.classs == "" || adds.number == 0)
	{
		MessageBox("数据框有空,请填写");
		return FALSE;
	}
	return true;
}
BOOL kedamenu::checkgrade() //检查分数是否在规定范围以内
{
	if (adds.program > 100 || adds.math > 100 || adds.lishan > 100)
	{
		MessageBox("分数大于指定最大分数");
		return FALSE;
	}
	return TRUE;
}
BOOL kedamenu::checknumber(char(&c)[15])//检测学号是否相等
{
	_itoa_s(adds.number, c, 10);
	for (UINT i = 0; i < m_line; i++)
	{
		CString ID;
		ID += c;
		if (ID == m_list.GetItemText(i, 0))
		{
			char cid[100];
			_itoa_s(i + 1, cid, 10);
			CString s = "学号与第";
			s += cid;
			s += "个同学相同,请重新输入";
			MessageBox(s);
			return FALSE;
		}
	}
	return TRUE;
}
BOOL kedamenu::checkxingbie()//检查是否已选性别
{
	if (adds.xing == 0)
	{
		adds.m_set = "男";
		return TRUE;
	}
	else
		if (adds.xing == 1)
		{
			adds.m_set = "女";
			return TRUE;
		}
		else
		{
			MessageBox("请选择性别");
			return FALSE;
		}
}

添加学生信息按钮的代码:当检查没有错误后,然后才会执行把他显示到列表框中

void kedamenu::OnBnClickedButton1()//将数据加载至列表框
{
	// TODO: 在此添加控件通知处理程序代码
	while (1)
	{
		adds.xing = -1;

		if (adds.DoModal() == IDOK && empty() && adds.FA == FALSE && checkgrade())
		{
			char c[15];

			if (checknumber(c) && checkxingbie())
			{
				char c1[5], c2[5], c3[5];
				_itoa_s(adds.program, c1, 10);
				_itoa_s(adds.math, c2, 10);
				_itoa_s(adds.lishan, c3, 10);
				m_list.InsertItem(0, c);                   //每次都是把新的信息加到第一行
				m_list.SetItemText(0, ++m_index, adds.name);
				m_list.SetItemText(0, ++m_index, adds.m_set);
				m_list.SetItemText(0, ++m_index, adds.grade);
				m_list.SetItemText(0, ++m_index, adds.classs);
				m_list.SetItemText(0, ++m_index, c1);
				m_list.SetItemText(0, ++m_index, c2);
				m_list.SetItemText(0, ++m_index, c3);

				m_line++;

				m_stusum = m_line;                     //用于显示
				UpdateData(FALSE);
				m_index = 0;
				check = FALSE;
				adds.name = "";
				adds.grade = "";
				adds.classs = "";
				adds.number = 0;
				adds.program = 0;
				adds.math = 0;
				adds.lishan = 0;
			}
		}		
		if (adds.FA == TRUE)
		{
			adds.FA = FALSE;
			break;
		}
	}
}

可以看到,已经成功添加,学生人数上升到26人

第二个按钮:删除学生信息

这个也是加了许多判别情况,假如列表没有数据,是不允许进入该子对话框的,还有输入了列表中没有该同学的名字或学号,也是会有相对应的提醒

可以看到删除成功后,列表框已经没有该学生的信息以及学生人数已经降回25人

第三个按钮:保存学生信息

这个也是加了许多判别情况,如果没有输入文件名,返回是可以正常退出的,这里的keda.txt是默认的路径,下面的导入将会介绍,然后我们试图把该文本保存进ke.txt。

void management::OnBnClickedButton3()//保存按钮响应函数
{
	BOOL jdg = FALSE;

	kedasave saves;
	CFileDialog filesaves(FALSE);
	filesaves.m_ofn.lpstrTitle = "选择文件并保存";
	filesaves.m_ofn.lpstrFilter = "所有文件(*.*)\0*.*\0\0";
	if (filesaves.DoModal() == IDOK)
	{
		filename = filesaves.GetPathName();
		finds.filenaem = filename;

	}	
	if (saves.DoModal() == IDOK && nodata())
	{
		if (filename == "")MessageBox("请选择保存的文件名");
		else {
			CFile file;
			file.Open(filename, file.modeWrite | file.modeCreate);
			
			for (UINT i = 0; i < m_line; i++)
			{			
				for (UINT j = 0; j < 8; j++)
				{
					CString s = m_list.GetItemText(i, j);
					int strLength2 = s.GetLength() + 1;
					char chArray[20];
					memset(chArray, 0, sizeof(chArray) ); //将数组的垃圾内容清空.
					strncpy_s(chArray, s, strLength2);

					file.Write(chArray, sizeof(chArray));
				}
			}
			MessageBox("数据存储成功");
			file.Close();
		}
	}
	if (!nodata())
	{
		MessageBox("列表中没有数据,请先添加");
	}
	check = TRUE;
}

执行效果:
可以看到完整地将列表里的学生个人信息保存到了ke.txt里面!!

上面这个图片是用旧的代码(txt文件)保存的,写的代码为了方便,新的采用了dat文件,所以看不到这个了。

第四个按钮:查找学生信息
看起来跟删除没啥区别,好像删除就是找到该行,将其删掉就行,查找利用的也是删除的查找,但是,当查找之后,我设计了一个更为直观地显示,首先,先看代码:

执行效果:
只修改名字


修改其他信息

额外的修改功能

这种修改方式显然只适用于期末考试结束后或学生从大一升到大二时,大片数据地修改,所以为了使用者更好的体验性,我增加了在列表框直接修改数据的功能!!
这个的代码看起来挺少,但是背后的操作比较复杂,要在list control上加多一个edit control,还得在初始该对话框时把这个编辑框设置不可见,然后设置焦点,失去焦点时的响应进行修改,显示

void kedamenu::OnDblclkList2(NMHDR *pNMHDR, LRESULT *pResult)       //双击响应
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	CRect rc;
	m_Row = pNMListView->iItem;
	m_Col = pNMListView->iSubItem;

	if (pNMListView->iSubItem != -1) 
	{
		m_list.GetSubItemRect(m_Row, m_Col, LVIR_LABEL, rc);
		m_edit.SetParent(&m_list);
		m_edit.MoveWindow(rc);
		m_edit.SetWindowText(m_list.GetItemText(m_Row, m_Col));
		m_edit.ShowWindow(SW_SHOW);
		m_edit.SetFocus();
		m_edit.ShowCaret();
		m_edit.SetSel(-1);
	}
	// TODO: 在此添加控件通知处理程序代码
	*pResult = 0;
}

void kedamenu::OnKillfocusEdit2()               //失去焦点时响应
{
	CString tem;
	m_edit.GetWindowText(tem);   
	m_list.SetItemText(m_Row, m_Col, tem);  
	m_edit.ShowWindow(SW_HIDE);                
	// TODO: 在此添加控件通知处理程序代码
}

执行效果:

就是这么简单地把数据给修改了呢

第七个按钮:排序学生信息

我这里只排序了学号,用了快排sort,得先声明包含头文件。因为排序后,他的顺序为我想要的从小到大的新的顺序,为了记录旧的顺序和学号,所以我建立了一个结构point记录这两个数据,并且因为每次排序的人数会发生变化,所以我用了动态结构数组,
还有需要注意的是,用完得记得delete,防止内存泄露!
代码如下:


BOOL  cmp(point pt1, point pt2)   //利用快排函数sort()的时候会用到,升序
{
	return pt1.y < pt2.y;
}

BOOL  ccmp(point pt1, point pt2)   //利用快排函数sort()的时候会用到,降序
{
	return pt1.y > pt2.y;
}

void management::resort(UINT x)   //排序函数
{
	point *keda = new point[m_line];                  //建立动态数组

	for (UINT i = 0; i < m_line; i++)
	{
		CString a = m_list.GetItemText(i, x);
		UINT AA = _ttoi(a);
		keda[i].x = i;
		keda[i].y = AA;
	}
	if(x==0)
		sort(keda, keda + m_line, cmp);           //快排
	else 
		sort(keda, keda + m_line, ccmp);

	for (int i = 0; i < m_line; i++)
	{
		CString a = m_list.GetItemText(keda[i].x, 0);
		m_list.InsertItem(i+m_line, a);
		for (UINT j = 1; j < 8; j++)
		{
			CString a = m_list.GetItemText(keda[i].x, j);
			m_list.SetItemText(i + m_line, j, a);
		}
	}
	for (int i = 0; i <m_line; i++)
	{
		m_list.DeleteItem(0);
	}
	delete []keda;                             //防止内存泄露
}


void management::OnBnClickedButton7()        //排序按钮响应函数
{
	if (!nodata())
	{
		MessageBox("信息为空,请先添加信息");
	}
	else resort(0);
}

我已经极大地优化了该代码,使得它更短小精悍
执行效果:

执行完学号从小到大依次排序,这里主要就排序了学号,排序科目的成绩也是可以实现的,只要把相应的代码中的学号改成某科的成绩也是可以运行的,但我的排序主要是想让三个专业分开来,所以没有实现科目成绩排序的代码。

额外的排序功能

void management::OnColumnclickList2(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);

	resort(pNMLV->iSubItem);

	// TODO: 在此添加控件通知处理程序代码
	*pResult = 0;
}

可以看到离散数学从高往低排列了。。

第八个按钮:退出管理系统

void kedamenu::OnBnClickedButton8()
{
	if (MessageBox("请确认保存学生信息再退出!", "警告",MB_ICONEXCLAMATION | MB_OKCANCEL)==IDCANCEL)
	{
		CDialog::OnClose();
	}
	else 
	{
		CDialogEx::OnCancel();
	}
}

非常简单的代码就可以实现了,提醒使用者记得保存后再退出该系统。

3.绘制直方图折线图源代码

画直方图代码

void C绘制直方图折线图View::DrawScore(CDC* pDC, int * fScore, int nNum) //除了加了个坐标轴其余跟课本一样
{
	// fScore是成绩数组指针,nNum是学生人数
	int nScoreNum[] = { 0, 0, 0, 0, 0 };  // 各成绩段的人数的初 始值 
	// 下面是用来统计各分数段的人数   
	for (int i = 0; i < nNum; i++)
	{
		int nSeg = (int)(fScore[i]) / 10; // 取数的"十"位上的值   
		if (nSeg < 6) nSeg = 5; // <60分    
		if (nSeg == 10) nSeg = 9;// 当为100分,算为>90分数段   
		nScoreNum[nSeg - 5] ++;   // 各分数段计数   
	}
	int nSegNum = sizeof(nScoreNum) / sizeof(int); // 计算有多少个分数段

	// 求分数段上最大的人数 
	int nNumMax = nScoreNum[0];
	for (int i = 1; i < nSegNum; i++)
	{
		if (nNumMax < nScoreNum[i])
			nNumMax = nScoreNum[i];
	}
	CRect rc;
	GetClientRect(rc);
	rc.DeflateRect(250, 40);      // 缩小矩形大小  
	int nSegWidth = rc.Width() / nSegNum;   // 计算每段的宽度  
	int nSegHeight = rc.Height() / nNumMax;   // 计算每段的单位高度  
	COLORREF crSeg = RGB(0, 0, 200);  // 定义一个颜色变量   
	CBrush brush1(HS_FDIAGONAL, crSeg);
	CBrush brush2(HS_BDIAGONAL, crSeg);
	CPen   pen(PS_INSIDEFRAME, 2, crSeg);
	CBrush* oldBrush = pDC->SelectObject(&brush1);   // 将brush1选入设备环境   
	CPen* oldPen = pDC->SelectObject(&pen);  // 将pen选 入设备环境  
	CRect rcSeg(rc);
	rcSeg.right = rcSeg.left + nSegWidth;   // 使每段的矩形宽度等于nSegWidth  
	CString strSeg[] = { (CString)"<60", (CString)"60-70", (CString)"70-80", (CString)"80-90", (CString)">=90" };
	CRect rcStr;
	for (int i = 0; i < nSegNum; i++)
	{  // 保证相邻的矩形填充样式不相同    
		if (i % 2)
			pDC->SelectObject(&brush2);
		else
			pDC->SelectObject(&brush1);
		rcSeg.top = rcSeg.bottom - nScoreNum[i] * nSegHeight - 2;  // 计算每段矩形的高度   
		pDC->Rectangle(rcSeg);
		if (nScoreNum[i] > 0)
		{
			CString str;
			str.Format((CString)"%d人", nScoreNum[i]);
			pDC->DrawText(str, rcSeg, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
		}
		rcStr = rcSeg;
		rcStr.top = rcStr.bottom + 2;
		rcStr.bottom += 20;
		pDC->DrawText(strSeg[i], rcStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
		rcSeg.OffsetRect(nSegWidth, 0);  // 右移矩形   
	}
	pDC->SelectObject(oldBrush);  // 恢复原来的画刷属性   
	pDC->SelectObject(oldPen);    // 恢复原来的画笔属性 

	CDC* pControlDC = pDC;                                          //画坐标轴
	pControlDC->SelectStockObject(BLACK_BRUSH);//设置画刷
	CString str;
	pControlDC->MoveTo(250, 20);  //画y轴
	pControlDC->LineTo(250, 440);
	pControlDC->MoveTo(250, 440);//画x轴
	pControlDC->LineTo(900, 440);
}

画折线图代码

void C绘制直方图折线图View::Drawlist(CDC* pDC, int* fScore, int nNum)//画折线图
{
	// fScore是成绩数组指针,nNum是学生人数
	int nScoreNum[] = { 0, 0, 0, 0, 0 };  // 各成绩段的人数的初始值 
	// 下面是用来统计各分数段的人数   
	for (int i = 0; i < nNum; i++)
	{
		int nSeg = (int)(fScore[i]) / 10; // 取数的"十"位上的值   
		if (nSeg < 6) nSeg = 5; // <60分    
		if (nSeg == 10) nSeg = 9;// 当为100分,算为>90分数段   
		nScoreNum[nSeg - 5] ++;   // 各分数段计数   
	}
	CDC* pControlDC = pDC;
	pControlDC->SelectStockObject(BLACK_BRUSH);//设置画刷
	CString str;
	CString strSeg[] = { (CString)"<60", (CString)"60-70", (CString)"70-80", (CString)"80-90", (CString)">=90" };
	pControlDC->MoveTo(250, 40);//画线的开始位置
	pControlDC->LineTo(250, 380);
	pControlDC->MoveTo(250, 380);//画线的开始位置
	pControlDC->LineTo(900, 380);
	pControlDC->MoveTo(250, 380);//折线的开始位置
	for (int i = 0; i < 5; i++)
	{
		pControlDC->LineTo(i * 140 + 300, 380 - (380 * nScoreNum[i] / nNum));//换两点之间的线
		str.Format("%d人", nScoreNum[i]);
		pControlDC->TextOut(i * 140 + 300, 380 - (380 * nScoreNum[i] / nNum) - 20, str);//在线的上方输出文字
		pControlDC->TextOut(i * 140 + 300, 390, strSeg[i]);
	}
}

就画个高等数学的好了,ok。

资源下载地址:https://download.csdn.net/download/sheziqiong/85696956
资源下载地址:https://download.csdn.net/download/sheziqiong/85696956


网站公告

今日签到

点亮在社区的每一天
去签到