实验报告的压缩包(操作txt文件,可以看到文字):
下载链接
课程设计的压缩包(操作dat文件,只能看到乱码):
下载链接
这里更新的操作方法主要是最快最方便的。内容都在课程设计压缩包里。
本实验是对前面所学知识的总结,通过一个比较完整的应用程序的设计,将学过的知识连贯起来,掌握开发一个实际应用程序的步骤,同时学会使用开发工具实现界面友好的应用程序。并通过本实验,掌握如何运用面向对象技术对具体的应用系统进行分析和设计。
1、类和对象的定义、对象的初始化和使用
2、面向对象的继承机制
3、虚函数与多态性
4、运算符重载
5、文件的使用
6、算法的使用
1、理解面向对象的特性
2、掌握面向对象程序设计的开发方法
3、有一定的分析问题和解决问题的能力
4、根据学过的知识点,充分利用已有的开发工具和素材,使程序编写具有更高的效率,能真正地解决实际问题。
这是一个复杂且多功能的学生成绩管理系统,一共用了1个主对话框和7个子对话框搭建而成。我将提供源码供大家一起学习探讨,我还会在每个代码块加相应的注释。然后,一些小细节我就不说了,相信开发学生成绩管理系统的你也已经大致了解了MFC的基本概念。有一个特殊的点,我希望大家做实验前可以注意:就是在属性那里把Unicode改成多字节,否则,你会在实验中遇到各种各样的令你头疼的不必要的错误,好了,开启我们的实验!还有要提的一点是,我没有用struct去储存每个人的信息,只是显示到主菜单界面上,只有保存才能真正储存到本地,这更大大地简化了代码量。
登录界面:
这个设计主要采用了QQ的大登录按钮界面,让人产生清晰干净的感觉。(应该没有侵权,我做的没QQ一半好看。。)
可以看到,左边使用了list control控件的report格式,右上角是个关闭输入的编辑框用于显示学生的总人数,右边则是实现八个不同功能的按钮。然后顶部显示的是我个人的用户名也是博客名:wujiekd
还有我们可以看到里面已经自动加载了当地文本的学生信息,这个功能的实现将会在导入学生信息中介绍。
添加子对话框和删除对话框:
查找子对话框:
导入子对话框:
修改子对话框:
新增修改功能,仅在课程设计第二个压缩包涵盖。
排序和退出子对话框:排序又新增了一个在list control上操作的简单方法 在最下面会有介绍
新增排序功能,直接点击list control即可。仅在课程设计第二个压缩包涵盖。
Student类 储存读取学生信息
class Student {
public: char ID[20]; //记录学号
char name[20]; //记录姓名
char xing[20]; //记录性别
char grade[20]; //记录年级
char classs[20]; //记录班别
char program[20]; //记录程序设计成绩
char math[20]; //记录数学成绩
char lishan[20]; //记录离散成绩
};
Point类 用于sort快排时记录
struct point
{
int x, y;
};
Management类中的主要变量与函数
private:
kedaadd adds; //其他功能操作的类,每个按钮对应一个子窗口
kedachange changes; //前缀keda是我的名字,后面的英文都很直接地解释相对应的功能
kedadelete deletes;
kedasave saves;
kedaimport imports;
kedafinds finds;
Student one_student;
public:
UINT m_line; //计算列表框的行数
UINT m_index; //用于计算循环的变量
UINT m_indexmax; //列数,就是8
BOOL judge;
BOOL check;
CString filename;
UINT m_stusum; //计算学生总数
UINT m_Row;
UINT m_Col;
BOOL nodata(); //添加函数
BOOL empty();
BOOL checkgrade();
BOOL checknumber(char(&c)[15]);
BOOL checkxingbie();
void deletenull(); //删除函数
void delete_q();
void find_q(); //查找函数
void reset(UINT a);
void importtt(); //导入函数
UINT importt();
void findcheck(); //修改函数
void settext(int line);
BOOL checknum();
void changename();
void resort(UINT x); //排序函数
不要小看这几行QQ/手机/邮箱,输入密码,它是用以下代码实现,在鼠标单击该编辑框其则会清空,方便使用者进行登录操作。
初始化登录框的默认文本和设置标题
BOOL CwujiekdDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
SetWindowText("学生成绩管理系统");
SetDlgItemText(IDC_EDIT2, "QQ/手机/邮箱");
SetDlgItemText(IDC_EDIT1, "输入密码");
return FALSE; // 除非将焦点设置到控件,否则返回 TRUE
}
设置背景图片
void CwujiekdDlg::OnPaint() //用来添加背景图片
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else //设置背景图
{
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap bmpBackground;
bmpBackground.LoadBitmap(IDB_BITMAP1); //IDB_BITMAP是我设置的图对应的ID
BITMAP bitmap;
bmpBackground.GetBitmap(&bitmap);
CBitmap *pbmpOld = dcMem.SelectObject(&bmpBackground);
dc.StretchBlt(0, 0, rect.Width(), rect.Height(), &dcMem, 0, 0,bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
}
}
点击登录按钮响应函数
void CwujiekdDlg::OnBnClickedOk() //按下登录键对账号密码进行判断
{
UpdateData(TRUE);
CString zhang = zhanghao;
CString mi = mima;
if (zhang == "wujiekd" && mi == "123")
{
AfxGetMainWnd()->SendMessage(WM_CLOSE);
management dlgs;
dlgs.DoModal();
}
else if (zhang == "" || mi == "")
{
MessageBox("账号或密码不能为空", "提醒");
return;
}
else
{
MessageBox("账号或密码错误,还有三次机会~","提醒"); //只是吓吓人而已,哈哈,三次锁定可以加,也挺简单的
return;
}
UpdateData(FALSE);
CDialogEx::OnOK();
}
重载该函数处理鼠标点击编辑框信息
BOOL CwujiekdDlg::PreTranslateMessage(MSG* pMsg) //重载它来处理键盘和鼠标消息,这段代码主要用于当点击编辑框,
{ //会把默认文本清除,其实是按照QQ的思路来设置的,比较巧妙,用了特殊的设置密码方法
CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT1);
CEdit* pEdit2 = (CEdit*)GetDlgItem(IDC_EDIT2);
CString s = (CString)"";
if (pMsg->message == WM_LBUTTONDOWN)
{
UpdateData(TRUE);
if (pMsg->hwnd == pEdit->GetSafeHwnd())
{
CEdit * kd = (CEdit*)GetDlgItem(IDC_EDIT1);
kd->SetPasswordChar('*');
pEdit->SetWindowText(_T(""));
if (zhanghao== s) //不要以为这里没有用,我加了很多细节
{
pEdit2->SetWindowText("QQ/手机/邮箱");
}
}
else if(pMsg->hwnd == pEdit2->GetSafeHwnd())
{
pEdit2->SetWindowText("");
if (mima == s)
{
CEdit * kd = (CEdit*)GetDlgItem(IDC_EDIT1);
kd->SetPasswordChar(NULL);
pEdit->SetWindowText("输入密码");
}
}
}
return CDialogEx::PreTranslateMessage(pMsg);
}
初始化listcontrol,设置标题,显示学生人数
BOOL management::OnInitDialog()
{
CDialogEx::OnInitDialog();
ModifyStyleEx(0, WS_EX_APPWINDOW);
CString str = "学生成绩管理系统 用户:计科184 1806100182 卢科达";
SetWindowText(str);
CRect rect;
CListCtrl* pmyListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST2); //设置风格
LONG dwStyle = GetWindowLong(pmyListCtrl->m_hWnd, GWL_STYLE);
SetWindowLong(pmyListCtrl->m_hWnd, GWL_STYLE, dwStyle | LVS_REPORT);
LONG styles = pmyListCtrl->GetExtendedStyle();
pmyListCtrl->SetExtendedStyle(styles | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_NOSCROLL);
pmyListCtrl->GetWindowRect(&rect);
m_list.InsertColumn(0, "学号", LVCFMT_CENTER, 100); //list control第一列会不在中间,但通过此方法可以很好的达到中间的目的
m_list.InsertColumn(1, "学号", LVCFMT_CENTER, 100);
m_list.InsertColumn(2, "姓名", LVCFMT_CENTER, 70);
m_list.InsertColumn(3, "性别", LVCFMT_CENTER, 70);
m_list.InsertColumn(4, "年级", LVCFMT_CENTER, 70);
m_list.InsertColumn(5, "班别", LVCFMT_CENTER, 70);
m_list.InsertColumn(6, "程序设计", LVCFMT_CENTER, 90);
m_list.InsertColumn(7, "高等数学", LVCFMT_CENTER, 90);
m_list.InsertColumn(8, "离散数学", LVCFMT_CENTER, 90);
m_list.DeleteColumn(0); //删除第一列
importt();
m_stusum=m_line;
UpdateData(FALSE);
GetDlgItem(IDC_EDIT1)->EnableWindow(FALSE);
m_edit.ShowWindow(SW_HIDE);
// TODO: 在此添加额外的初始化
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
设置管理系统主菜单背景
(1)在资源视图中加入自己喜欢的图片,要首先将格式转为24位位图,然后再在对应的对话框里添加消息OnPaint(),然后在里面添加以下代码:
void management::OnPaint() //设置背景
{
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap bmpBackground;
bmpBackground.LoadBitmap(IDB_BITMAP2); //IDB_BITMAP是我的背景图片对应的ID
BITMAP bitmap;
bmpBackground.GetBitmap(&bitmap);
CBitmap *pbmpOld = dcMem.SelectObject(&bmpBackground);
dc.StretchBlt(0, 0, rect.Width(), rect.Height(), &dcMem, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
// TODO: 在此处添加消息处理程序代码
// 不为绘图消息调用 CDialogEx::OnPaint()
}
八个按钮操作对应的函数
第一个按钮:添加学生信息
因为添加的学生信息比较多,为了防止出现各种错误,写了以下四个函数去检查这些错误,当出现错误,都会弹出相应的Message提醒使用者。
四个函数对应的处理情况如下图所示:
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人
第二个按钮:删除学生信息
这个也是加了许多判别情况,假如列表没有数据,是不允许进入该子对话框的,还有输入了列表中没有该同学的名字或学号,也是会有相对应的提醒
BOOL kedamenu::nodata()//判断列表中是否有数据
{
if (m_list.GetItemText(0, 0) != "")
{
return TRUE;
}
return FALSE;
}
void kedamenu::deletenull()//删除列表框的所有数据
{
m_list.DeleteAllItems();
m_line = 0;
m_stusum = m_line;
UpdateData(FALSE);
}
void kedamenu::delete_q()//删除单个数据+调用上面函数删除所有数据
{
if (deletes.a == 0 && deletes.b != "")
{
for (UINT i = 0; i < m_line; i++)
{
CString s = m_list.GetItemText(i, 1);
if (deletes.b == s)
{
m_list.DeleteItem(i);
m_line--;
deletes.b = "";
judge = TRUE;
m_stusum = m_line;
UpdateData(FALSE);
MessageBox("删除成功");
break;
}
}
if (judge == FALSE)
MessageBox("没有此名字的学生");
}
else if (deletes.a == 1 && deletes.b != "")
{
for (UINT i = 0; i < m_line; i++)
{
CString s = m_list.GetItemText(i, 0);
if (deletes.b == s)
{
m_list.DeleteItem(i);
m_line--;
deletes.b = "";
judge = TRUE;
m_stusum = m_line;
UpdateData(FALSE);
MessageBox("删除成功");
break;
}
}
if (judge == FALSE)
MessageBox("没有此学号的学生");
}
else if (deletes.a == 2)
{
deletenull();
}
else
{
MessageBox("请输入一项数据删除");
}
}
void kedamenu::OnBnClickedButton2()
{
if (!nodata() )
{
MessageBox("列表中没有数据~");
}
while (nodata())
{
if (deletes.DoModal() == IDOK)
{
delete_q();
}
if (deletes.FA == TRUE|| !nodata())
{
deletes.FA = FALSE;
break;
}
}
}
可以看到删除成功后,列表框已经没有该学生的信息以及学生人数已经降回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文件,所以看不到这个了。
看起来跟删除没啥区别,好像删除就是找到该行,将其删掉就行,查找利用的也是删除的查找,但是,当查找之后,我设计了一个更为直观地显示,首先,先看代码:
void kedamenu::reset(UINT i)
{
CString s1 = m_list.GetItemText(i, 0);
CString s2 = m_list.GetItemText(i, 1);
CString s3 = m_list.GetItemText(i, 2);
CString s4 = m_list.GetItemText(i, 3);
CString s5 = m_list.GetItemText(i, 4);
CString s6 = m_list.GetItemText(i, 5);
CString s7 = m_list.GetItemText(i, 6);
CString s8 = m_list.GetItemText(i, 7);
m_list.DeleteItem(i);
m_list.InsertItem(0,s1);
m_list.SetItemText(0, 1, s2);
m_list.SetItemText(0, 2, s3);
m_list.SetItemText(0, 3, s4);
m_list.SetItemText(0, 4, s5);
m_list.SetItemText(0, 5, s6);
m_list.SetItemText(0, 6, s7);
m_list.SetItemText(0, 7, s8);
m_list.SetItemData(0, COLOR_RED);
}
void kedamenu::find_q()//查找某学号或某名字的学生的信息
{
if (finds.a == 0 && finds.b != "")
{
for (UINT i = 0; i < m_line; i++)
{
CString s = m_list.GetItemText(i, 1);
if (finds.b == s)
{
reset(i);
finds.b = "";
judge = TRUE;
MessageBox("查找成功");
break;
}
}
if (judge == FALSE)
MessageBox("没有此名字的学生");
}
else if (finds.a == 1 && finds.b != "")
{
for (UINT i = 0; i < m_line; i++)
{
CString s = m_list.GetItemText(i, 0);
if (finds.b == s)
{
reset(i);
finds.b = "";
judge = TRUE;
MessageBox("查找成功");
break;
}
}
if (judge == FALSE)
MessageBox("没有此学号的学生");
}
else
{
MessageBox("请输入一项数据删除");
}
}
这里是按钮的代码,比较简短,主要功能则会调用以上两个函数,将找到的学生信息置顶,方便使用者更好地了解该学生信息。
void kedamenu::OnBnClickedButton4()
{
if (!nodata())
{
MessageBox("列表中没有数据~");
}
while (nodata())
{
if (finds.DoModal() == IDOK)
{
m_list.SetItemData(0, COLOR_DEFAULT);
find_q();
}
if (finds.FA == TRUE)
{
finds.FA = FALSE;
break;
}
}
}
但是,置顶远远不够,我在list control设置处理事件,选择NM_CUSTOMRAW,然后加入以下代码,就是为了将第一行显示出更为鲜艳的背景色
void kedamenu::OnCustomdrawList2(NMHDR *pNMHDR, LRESULT *pResult) //这个用于查找时候把提到第一行的学生信息标红
{
LPNMTVCUSTOMDRAW pNMCD = reinterpret_cast<LPNMTVCUSTOMDRAW>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
NMCUSTOMDRAW nmCustomDraw = pNMCD->nmcd;
switch (nmCustomDraw.dwDrawStage)
{
case CDDS_ITEMPREPAINT:
{
if (COLOR_RED == nmCustomDraw.lItemlParam)
{
pNMCD->clrTextBk = RGB(255, 0, 0); //背景颜色
pNMCD->clrText = RGB(255, 255, 255); //文字颜色
}
else if (COLOR_DEFAULT == nmCustomDraw.lItemlParam)
{
pNMCD->clrTextBk = RGB(255, 255, 255);
pNMCD->clrText = RGB(0, 0, 0);
}
break;
}
default:
{
break;
}
}
*pResult = 0;
*pResult |= CDRF_NOTIFYPOSTPAINT;
*pResult |= CDRF_NOTIFYITEMDRAW;
return;
}
执行效果:
当姓名或学号输入错误也会有相应的提醒,但前面已经介绍过这种提醒,这里就不放错误提醒的截图了,直接上正确的处理效果图
void kedamenu::OnBnClickedButton5()
{
kedaimport dlg;
if (dlg.DoModal() == IDOK && dlg.FA == FALSE)
{
importtt();
}
else if (dlg.FA == TRUE)
{
dlg.FA = FALSE;
int line = m_list.GetItemCount();
for (int i = 0; i < line; i++)
{
m_list.DeleteItem(0);
m_line--;
}
m_stusum = m_line;
UpdateData(FALSE);
importtt();
}
}
为什么我下面要写一个importtt()和importt(),他们为什么不写在一起,这里就是因为我在前面提到过,当成功登陆就会自动导入当地的学生信息文件,正是因为在前面该对话框初始话时调用了该importt()函数,
它的默认地址就是keda.txt对应的地址。
void management::importtt()//获得需要导入的学生信息的文件的地址
{
MessageBox("请导入学生信息!");
CFileDialog filedlg(TRUE);
filedlg.m_ofn.lpstrTitle = "请选择文件并导入";
filedlg.m_ofn.lpstrFilter = "所有文件(*.*)\0*.*\0\0";
if (IDOK == filedlg.DoModal())
{
finds.filenaem = filedlg.GetPathName();
UINT num = importt();
if (num == 0)
MessageBox("导入信息成功");
}
}
UINT management::importt()//真正的导入函数主体,管理系统主菜单初始化时就调用了该函数,使得一开始就有了25个学生信息
{
CFile file;
file.Open(finds.filenaem, file.modeReadWrite);
Student u;
UINT X = sizeof(u);
while (file.Read(&u, sizeof(u)))
{
m_list.InsertItem(m_line, u.ID);
m_list.SetItemText(m_line, 1, u.name);
m_list.SetItemText(m_line, 2, u.xing);
m_list.SetItemText(m_line, 3, u.grade);
m_list.SetItemText(m_line, 4, u.classs);
m_list.SetItemText(m_line, 5, u.program);
m_list.SetItemText(m_line, 6, u.math);
m_list.SetItemText(m_line, 7, u.lishan);
m_line++;
}
m_stusum = m_line;
UpdateData(FALSE);
file.Close();
return 0;
}
执行效果:
保留当前数据并导入:
我临时写了个三个人的da.txt,可以看到导入成功后,学生人数升至28人
清除当前数据并导入:
按下按钮则将界面全部清空,再选择文件进行导入,再导入keda.txt,
因为前面的操作没有保存,所以导入后,显示的学生人数是默认的25人。
同样为了检测各种各样出现的情况,我已经把我所能想到的情况的写了进去,因为不可以把超出规定的内容给修改进去,这是代码的严谨性
修改的代码如下:
BOOL kedamenu::checknum()//检查成绩是否超出范围
{
if (changes.new_program > 100 || changes.new_math > 100 || changes.new_lishan > 100)
{
MessageBox("成绩输入不合法");
return FALSE;
}
return TRUE;
}
void kedamenu::settext(int line)//将新的信息显示出表格里面,没有填的就跳过,只会更新新的信息
{
char c[20] = "0";
char c1[5], c2[5], c3[5];
_itoa_s(changes.new_program, c1, 10);
_itoa_s(changes.new_math, c2, 10);
_itoa_s(changes.new_lishan, c3, 10);
if (changes.new_number!= 0)
{
_itoa_s(changes.new_number, c, 10);
m_list.SetItemText(line, 0, c);
}
if(changes.new_grade!="")
m_list.SetItemText(line, 3, changes.new_grade);
if (changes.new_class != "")
m_list.SetItemText(line, 4, changes.new_class);
if (checknum())
{
m_list.SetItemText(line, 5, c1);
m_list.SetItemText(line, 6, c2);
m_list.SetItemText(line, 7, c3);
}
}
void kedamenu::findcheck()//个人信息修改函数
{
BOOL f = FALSE;
if (changes.name != "")
{
for (UINT i = 0; i < m_line; i++)
{
if (changes.name == m_list.GetItemText(i, 1))
{
settext(i);
f = TRUE;
}
if (f)
{
MessageBox("成功修改");
break;
}
}
if (!f)
{
MessageBox("查找的人名字可能不符,请检查");
}
}
else {
MessageBox("请输入修改同学的新旧姓名");
}
}
void kedamenu::changename()
{
BOOL f = FALSE;
for (UINT i = 0; i < m_line; i++)
{
if (changes.old_name == m_list.GetItemText(i, 1))
{
m_list.SetItemText(i, 1, changes.new_name);
f = TRUE;
}
if (f)
{
MessageBox("成功修改");
break;
}
}
}
void kedamenu::OnBnClickedButton6()
{
// TODO: 在此添加控件通知处理程序代码
while (nodata())
{
changes.old_name = "";
changes.new_name = "";
changes.new_number = 0;
changes.new_program = 0;
changes.new_math = 0;
changes.new_lishan = 0;
changes.new_class = "";
changes.new_grade = "";
if (changes.DoModal() == IDOK)
{
UpdateData(TRUE);
if (changes.cao == 0 && changes.old_name != "")
{
changename();
}
else if (changes.cao == 1 && changes.name != "")
{
findcheck();
}
else
{
MessageBox("请输入该学生的姓名");
}
}
if (changes.FA == TRUE )
{
deletes.FA = FALSE;
break;
}
}
if (!nodata())
{
MessageBox("信息为空,请先添加信息");
}
}
这种修改方式显然只适用于期末考试结束后或学生从大一升到大二时,大片数据地修改,所以为了使用者更好的体验性,我增加了在列表框直接修改数据的功能!!
这个的代码看起来挺少,但是背后的操作比较复杂,要在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();
}
}
非常简单的代码就可以实现了,提醒使用者记得保存后再退出该系统。
画直方图代码
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]);
}
}
六个按钮初始化代码
void C绘制直方图折线图View::OnInitialUpdate() //六个按钮
{
m_button1.Create((CString)"程序设计直方图",//按钮标题
WS_CHILD | WS_VISIBLE | WS_BORDER,//按钮风格
CRect(10, 10, 150, 40), //按钮大小
this, //按钮父指针
IDC_C_PROGRAM); //该按钮对应的ID号
m_button1.ShowWindow(SW_SHOWNORMAL);
m_button2.Create((CString)"高等数学直方图",WS_CHILD | WS_VISIBLE | WS_BORDER,CRect(10, 60, 150, 90),this,IDC_ADVANCED_MATH);
m_button2.ShowWindow(SW_SHOWNORMAL);
m_button3.Create((CString)"离散数学直方图",WS_CHILD | WS_VISIBLE | WS_BORDER,CRect(10, 110, 150, 140),this, IDC_HIGE_MATH);
m_button3.ShowWindow(SW_SHOWNORMAL);
m_button4.Create((CString)"折线图",WS_CHILD | WS_VISIBLE | WS_BORDER,CRect(150, 10, 215, 40),this, IDC_C_PROGRAM_LINE);
m_button4.ShowWindow(SW_SHOWNORMAL);
m_button5.Create((CString)"折线图",WS_CHILD | WS_VISIBLE | WS_BORDER,CRect(150, 60, 215, 90),this, IDC_ADVANCED_MATH_LINE);
m_button5.ShowWindow(SW_SHOWNORMAL);
m_button6.Create((CString)"折线图",WS_CHILD | WS_VISIBLE | WS_BORDER,CRect(150, 110, 215, 140),this, IDC_HIGE_MATH_LINE);
m_button6.ShowWindow(SW_SHOWNORMAL);
// TODO: 在此添加专用代码和/或调用基类
}
六个按钮分别的响应操作函数
void C绘制直方图折线图View::OnCProgramLine() //程序设计折线图
{
CFile file;
file.Open("C:\\Users\\Administrator\\source\\repos\\wujiekd\\keda.dat", file.modeReadWrite);
Student u;
int i = 0;
while (file.Read(&u, sizeof(u)) == sizeof(u))
{
m_Num[i] = _ttoi((CString)u.program);
i++;
}
InvalidateRect(NULL);
UpdateWindow();
Drawlist(GetDC(), m_Num, i);
file.Close();
}
void C绘制直方图折线图View::OnAdvancedMath() //高等数学直方图
{
CFile file;
file.Open("C:\\Users\\Administrator\\source\\repos\\wujiekd\\keda.dat", file.modeReadWrite);
Student u;
int i = 0;
while (file.Read(&u, sizeof(u)) == sizeof(u))
{
m_Num[i] = _ttoi((CString)u.math);
i++;
}
InvalidateRect(NULL);
UpdateWindow();
DrawScore(GetDC(), m_Num, i);
file.Close();
}
void C绘制直方图折线图View::OnAdvancedMathLine() //高等数学折线图
{
CFile file;
file.Open("C:\\Users\\Administrator\\source\\repos\\wujiekd\\keda.dat", file.modeReadWrite);
Student u;
int i = 0;
while (file.Read(&u, sizeof(u)) == sizeof(u))
{
m_Num[i] = _ttoi((CString)u.math);
i++;
}
InvalidateRect(NULL);
UpdateWindow();
Drawlist(GetDC(), m_Num, i);
file.Close();
}
void C绘制直方图折线图View::OnHigeMath() //离散数学直方图
{
CFile file;
file.Open("C:\\Users\\Administrator\\source\\repos\\wujiekd\\keda.dat", file.modeReadWrite);
Student u;
int i = 0;
while (file.Read(&u, sizeof(u)) == sizeof(u))
{
m_Num[i] = _ttoi((CString)u.lishan);
i++;
}
InvalidateRect(NULL);
UpdateWindow();
DrawScore(GetDC(), m_Num, i);
file.Close();
}
void C绘制直方图折线图View::OnHigeMathLine() //离散数学折线图
{
CFile file;
file.Open("C:\\Users\\Administrator\\source\\repos\\wujiekd\\keda.dat", file.modeReadWrite);
Student u;
int i = 0;
while (file.Read(&u, sizeof(u)) == sizeof(u))
{
m_Num[i] = _ttoi((CString)u.lishan);
i++;
}
InvalidateRect(NULL);
UpdateWindow();
Drawlist(GetDC(), m_Num, i);
file.Close();
}
绘制图像
还有不少的功能可以完善,比如统计个总分,计算个平均分什么的,但如果大家跟着我的代码去学习相信也可以充分掌握MFC的开发了,我的分享到此结束,希望大家给我多提意见,共同进步!