Visual C++项目开发案例精粹
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第2章 学生档案管理系统

学生档案管理系统是典型的信息管理系统,本章使用Visual C++ 6.0开发了一个简单的东方学院学生档案管理系统。

通过该系统,可以对学生的基本档案信息进行管理。另外,本系统的另一个重要功能是能够实现对学生的考试成绩进行管理,同时也能对成绩进行各种统计查询工作。

本章的学习重点:

◆ 分析、设计数据库。

◆ 数据库表的约束。

◆ 自动配置ODBC数据源。

◆ 使用列表控件显示记录。

2.1 开发背景

学生档案管理系统是一个教育单位不可缺少的部分,它的内容对于学校的决策者和管理者来说都至关重要,所以学生档案管理系统应该能够为用户提供充足的信息和快捷的查询手段。但一直以来人们使用传统人工的方式管理文件档案,这种管理方式存在着许多缺点,如:效率低、保密性差,另外时间一长,将产生大量的文件和数据,这对于查找、更新和维护都带来了不少的困难。

随着科学技术的不断提高,计算机科学日渐成熟,其强大的功能已为人们深刻认识,它已进入人类社会的各个领域并发挥着越来越重要的作用。使用计算机对学生档案信息进行管理,具有手工管理所无可比拟的优点。例如:检索迅速、查找方便、可靠性高、存储量大、保密性好、寿命长、成本低等,这些优点能够极大地提高学生档案管理的效率。

东方学院,作为一个小型的教育机构,以往对学生档案的管理采用的是人工的方式管理文件档案,无论是效率还是准确性都受到很大的限制。因此,开发一套学生档案管理软件成为很有必要的事情。同时,通过本系统的开发,数据的整理,也为学校将来接入Internet作好先期的准备工作。

2.2 系统分析

2.2.1 需求分析

对学生档案的管理,目前最主要的需求有两部分,一是学生基本信息的管理,另一个就是学生成绩的管理与统计。

对学生的基本档案,按照系、专业、班级和学生的四级模式进行管理,要包含学生的基本信息,如姓名、性别、民族、年龄、入学、毕业时间等。要求便于档案的录入和管理,同时有灵活、快捷的查询功能。

学生成绩的管理与统计,则要求能对学生所有科目的考试成绩进行入档,重要的是具有查询统计功能。如用户按照班级统计学生的平均成绩、及格率、优秀率等,统计学生个人的总成绩及排名,统计学生个人的单科成绩排名等。

2.2.2 功能分析

为了维护数据系统的安全性和数据的准确性,设置了两种用户角色管理员和普通用户。

普通用户可以在现有班级设置的基础上,对学生基本信息进行管理,另外还可以查询统计学生成绩。具体功能如下:

● 查询学生基本信息,可以查看现有系、专业、班级的所有学生个人信息。

● 添加、修改现有系、专业、班级的所有学生个人信息。

● 查询学生的成绩。

● 对成绩进行统计汇总。

系统管理员角色,则可以对整个系统进行管理操作,其在普通用户功能的基础上,还增加了如下功能。

● 行政设置,包括设置系、系下属的专业及专业下属的班级。

● 考试科目的设置,包括设置考试类型、时间段和科目名称。

● 学生成绩的录入与管理。

● 数据库的管理,包括数据库的备份与恢复。

● 用户的管理,包括新用户的注册及用户角色的设置。

2.3 系统设计

2.3.1 绘制用例图设计系统功能

学生档案管理系统包含两种用户角色,分别是普通用户和管理员。

普通用户只具有基本的学生信息管理和学生成绩查询功能。该角色只可以在现有的班级中查询、添加、删除、修改学生的基本信息,查询、统计所有学生的考试成绩,其用例图如图2-1所示。

图2-1 普通用户用例图

管理员角色相比普通用户,还具有设置院系、设置考试信息、录入成绩、用户管理和数据库管理功能,其用例图如图2-2所示。

图2-2 管理员用例图

2.3.2 绘制系统流程图

本系统需要对用户身份进行验证,验证通过后再判断用户是管理员还是普通用户,根据角色判断用户可以使用系统中的相应操作功能。另外,对操作者的操作顺序也有一定的要求,如要添加学生记录,首先需要查看该学生所在的系、所学专业,以及所在班级在数据库中是否存在。如果不存在,则首先需要添加设置这些信息,同样要录入学生成绩,其前提是该学生的基本信息在系统中已经存在。

这里以管理员角色为例,其系统流程图如图2-3所示。

图2-3 系统结构及流程图

2.3.3 开发工具和开发技术的选择

本系统的设计是在Windows XP中文版操作系统环境下,使用Visual C++ 6.0中文版开发成功的。

Visual C++是一种可视化的、面向对象和调用事件驱动方式的结构化高级程序设计,可用于开发Windows环境下的种类应用程序。它简单易学、效率高,且功能强大,可以与Windows的专业开发工具SDK相媲美。

数据库系统采用的是Micorsoft的Access,采用Access2000开发工具。

2.3.4 系统的运行环境

系统可以直接在Win98、Win2000、WinXP环境下运行。

注意,系统运行时,不必手工设置ODBC数据源,只需将该数据库文件“StudentInfoManage.mdb”复制到与系统的可执行文件“StudentInfoManage.exe”同一目录下即可。

2.3.5 系统演示

系统是通过MFC创建向导创建单文档工程。其程序的主界面是典型的Windows窗口结构,通过系统菜单进行操作。

在程序启动后,首先弹出如图2-4所示的“用户登录”对话框,只有输入正确的用户名和密码后,才能进入如图2-5所示的系统主界面。

图2-4 “用户登录”对话框

图2-5 系统的主界面窗口

进入系统后,用户就可以通过窗口的菜单进行相关操作,用户与系统的所有输入、输出操作均是通过对话框窗口来实现的。

在基础设置菜单菜项中,可以设置学院的系、专业和班级信息;学生管理菜单项可以进行学生档案的查询、添加、修改和删除操作;成绩管理菜单项,可以设置考试科目信息,录入学生成绩,对学生成绩进行各种统计分析操作;数据库管理菜单项,可以进行数据库的备份和恢复操作。

这里以学生档案管理为例,当用户执行“学生管理”→“学生档案管理”菜单命令项时,系统会弹出如图2-6所示的对话框,可以查询、添加、修改、删除学生档案。

图2-6 “学生档案”对话框

如需要添加学生档案,可以单击“增加”按钮,此时会弹出如图2-7所示的对话框,输入相关信息,单击“确定”按钮,即将学生档案添加到了系统数据库中。

图2-7 “学生档案”对话框

2.3.6 系统类库设计

学生档案管理系统的系统类库设计如下。

● 数据库操作类:数据库中的每一个表都定义了一个操作类,包括CDepartmentSet、CExamSubjectSet、CExamtimeSet、CExamtypeSet、CMajorSet、CScoreSet、CStudentSet和CuserSet,它们从CRecordSet类继承而来。

● 对话框类:用户与系统的交互、数据的显示都是通过对话框窗口来实现的,因此需要定义一些对话框类,与相应的对话框资源进行关联,包括CClassDlg、CDepartmentDlg、CExamSubjectDlg、CExamtimeDlg、CExamtypeDlg、CLoginDlg、CMajorDlg、CScoreClassStatDlg、CScoreDlg、CScoreInputDlg、CScoreQueryDlg、CScoreSingleDlg、CScoreTotalDlg、CStudentDlg、CStudentInfoDlg、CUserDlg等。

● 系统本身提供的框架类:因为系统采用的是MFC单文档工程框架,因此其包括的系统类有CMainFrame、CStudentInfoManageApp、CStudentInfoManageDoc、CStudentInfoManageView等。

2.4 数据库分析与设计

2.4.1 数据库分析

考虑到系统的数据量较少,为了便于项目开发和维护,这里后台的数据库系统设计采用的是Micorsoft的Access数据库系统。

Access2000就是关系数据库开发工具,数据库能汇集各种信息以供查询、存储和检索。Access的优点在于它能使用数据表示图或自定义窗体收集信息,数据表示图提供了一种类似于Excel的电子表格,可以使数据库一目了然。另外,Access允许创建自定义报表用于打印或输出数据库中的信息。Access也提供了数据存储库,可以使用桌面数据库文件把数据库文件置于网络文件服务器,与其他网络用户共享数据库。Access是一种关系数据库工具,关系数据库是已开发的最通用的数据库之一。如上所述,Access作为关系数据库开发具备了许多优点,可以在一个数据包中同时拥有桌面数据库的便利和关系数据库的强大功能。

2.4.2 数据库概念设计

数据库需要存储的主要是两方面的内容,学生的基本档案和学生成绩。为了维护数据的完整性和准确性,将学生基本信息实体中的系别、专业和班级提取出来,用单独的表来存储这些信息。

系别实体记录了学院所分的系的信息,其E-R图如图2-8所示。

图2-8 系别实体E-R图

专业实体记录了系里所分专业的信息,其E-R图如图2-9所示。

图2-9 专业实体E-R图

班级实体记录了系下所分班级信息,其E-R图如图2-10所示。

图2-10 班级实体E-R图

学生信息实体记录了班级中的学生信息,其E-R图如图2-11所示。

图2-11 学生信息实体E-R图

同样,将考试成绩实体中必要的考试类型、考试科目、考试时间段单独提取出来成为单独的实体表。考试类型实体、考试科目实体的E-R图分别如图2-12和图2-13所示。

图2-12 考试类型实体E-R图

图2-13 考试科目实体E-R图

考试时间段实体记录了考试的时间,如2009上学期、2009下学期等,其E-R图如图2-14所示。

图2-14 考试时间段实体E-R图

考试成绩实体记录了学生的考试成绩信息,其E-R图如图2-15所示。

图2-15 考试成绩实体E-R图

此外,系统还需要对登录用户进行管理,就需要一个登录用户表管理用户信息,登录用户实体的E-R图如图2-16所示。

图2-16 登录用户实体E-R图

2.4.3 数据库逻辑结构设计

根据前面设计好的各实体E-R图,就可以创建数据库的逻辑结构,下面分别介绍数据库各表的结构。

系别信息表用来记录学院中所有系的基本信息,包含代码、名称及简介等信息,该表的逻辑结构如表2-1所示。

表2-1 department(系别)数据表字段描述

专业信息表用来记录学院中各系所设置的专业信息,包含代码、名称、简介及所属系的信息,该表的逻辑结构如表2-2所示。

表2-2 major(专业)数据表字段描述

班级信息表用来记录各专业所分的班级信息,包含代码、名称、简介及所属专业和系的信息,该表的逻辑结构如表2-3所示。

表2-3 class(班级)数据表字段描述

学生基本信息表用来记录学生的基本档案,包含姓名、学号、性别、出生年月、家庭地址等基本信息。另外,还必须包含学生所在的班级、所在的专业及所在的系,该表的逻辑结构如表2-4所示。

表2-4 student(学生)数据表主要字段描述

除了表中的字段外,还包括政治面貌、学制情况、家庭住址、身份证号等基本信息,这里就不详细列出各字段了。

考试类型信息表用来记录所有的考试类型,如期中考试、期末考试、平日测验、过级考试等。包含类型代码和考试类型两个字段,其逻辑结构如表2-5所示。

表2-5 examtype(考试类型)数据表字段描述

考试科目信息表用来记录所有的考试科目,包含科目代码和考试科目两个字段,其逻辑结构如表2-6所示。

表2-6 examsubject(考试科目)数据表字段描述

考试时间段信息表用来记录所有的考试时间段,如2009 上学期、2009 下学期等,包含代码和考试时间段两个字段,其逻辑结构如表2-7所示。

表2-7 examtime(考试时间段)数据表字段描述

考试成绩信息表记录了所有的学生所有科目的考试成绩,包含记录代码、学号、姓名、所在班级、考试类型、考试科目、考试时间段、考试成绩、补考成绩,以及是否缺考等字段,其逻辑结构如表2-8所示。

表2-8 score(考试成绩)数据表字段描述

登录用户信息表用来记录系统的所有使用用户信息,包含用户名、密码及权限三个字段,其逻辑结构如表2-9所示。

表2-9 user(用户)数据表字段描述

2.4.4 数据库的创建

各表设计完毕后就可以创建数据库。在Access 2000中,新建数据库,名称为“StudentInfoManage”,然后通过创建向导创建各表,如图2-17所示。

图2-17 Access创建数据库

创建完毕后,即得到了数据库文件“StudentInfoManage.mdb”。

为了维护数据库中数据的准确性和完整性,对表中一些字段做了一定的限制。对student表而言,其class字段(所属班级)的记录必须在class表中存在。同样,class表中的major字段(所属专业)必须在major表中存在,而major表中的department字段(所属专业)必须在department表中存在。

对score表而言,其subject字段(考试科目)、type字段(考试类型)、time字段(考试时间段)的记录必须在examsubject 、examtype和examtime中存在,而code(学号)、name(姓名)、class(班级)字段,必须在student表中存在。

根据各表关系绘制关系E-R图如图2-18所示。

图2-18 各表关系E-R图

2.5 公共类(数据记录类)设计

程序开发中,对数据库的访问与操作是通过MFC ODBC类来实现的。从CRecordSet类继承了CDepartmentSet、CExamSubjectSet、CExamtimeSet、CExamtypeSet、CMajorSet、CScoreSet、CStudentSet和CUserSet类,分别用于操作数据库中对应的表。

2.5.1 创建ODBC数据源

要创建这些类,首先需要定义ODBC数据源,可以通过手工方式“控制面板”→“管理工具”→“数据源(ODBC)”来设置,也可以在程序中通过编码来实现。这里采用后者,在系统类CStudentInfoManageApp的InitInstance函数中,添加如下代码。

例程2.1代码位置:光盘\第2章\ StudentInfoManage \ StudentInfoManage.cpp

    01   BOOL CStudentInfoManageApp::InitInstance()
    02   {
    03       AfxEnableControlContainer();
    04       SQLConfigDataSource(NULL,ODBC_ADD_DSN,
    05           "Microsoft Access Driver(*.mdb)",                //驱动程序
    06           "DSN=StudentInfoManage\0"                    //数据源名称
    07           "Description=Student\0"                        //数据源描述
    08          "FileType=Access\0"                           //数据库类型
    09          "DBQ=.\\StudentInfoManage.mdb\0");               //数据库文件路径
    10   ……
    11   }

在第4行代码中通过API函数SQLConfigDataSource设置ODBC数据源,第5~9行代码分别设置相关参数,第9行代码将数据库文件的路径设置为默认目录,即VC工程所在的目录。因此,编译运行程序前,需要将数据库文件“StudentInfoManage.mdb”复制到工程的根目录下,程序运行后,即实现了ODBC数据源的自动设置。

2.5.2 创建数据记录类

设置了ODBC数据源后,就可以创建数据记录类。以CScoreSet类为例,它与数据库中的score表关联,具体创建过程如下。

在VC窗口中,执行“Insert”→“NewClass”菜单命令,在弹出的对话框中,选择CRecordset作为基类,如图2-19所示。

图2-19 NewClass对话框

单击“OK”按钮,弹出“Database Options”对话框,在ODBC下拉列表框中,会发现新创建的数据源“StudentInfoManage”,如图2-20所示。

图2-20 “Database Options”对话框

选择该数据源,单击“OK”按钮,弹出“Select Database Tables”对话框,其中列出了数据库中的所有表,如图2-21所示。选择相应的表“score”,单击“OK”按钮,即完成了CScoreSet类的创建。

图2-21 “Select Database Tables”对话框

2.6 登录模块与界面设计

系统启动首先进入登录模块,即创建“用户登录”对话框,如图2-4所示。在下拉列表框中选择登录用户名,只有输入正确的密码后,才能进入系统主界面。

2.6.1 登录模块设计

登录对话框的关联类为CLoginDlg,在对话框的初始化函数OnInitDialog中,读取user表中的所有用户记录,将用户名添加到登录对话框的下拉列表框中,代码如下。

例程2.2代码位置:光盘\第2章\ StudentInfoManage \ LoginDlg.cpp

    01   BOOL CLoginDlg::OnInitDialog()
    02   {
    03       CDialog::OnInitDialog();
    04       CUserSet recordset;                           //CUserSet对象
    05       CString strSQL;
    06       UpdateData(TRUE);                           //获取控件输入
    07       strSQL="select*from user";                     //查询user表的所有记录
    08       if(!recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
    09       {
    10           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    11           return FALSE;                           //返回
    12       }
    13       while(!recordset.IsEOF())                       //不是记录集的最后
    14       {
    15           m_ctrUser.AddString(recordset.m_user);        //添加到列表框
    16           recordset.MoveNext();                     //下一记录
    17       }
    18       recordset.Close();                             //关闭记录集
    19       return TRUE;
    20   }

在第7、第8行代码中通过CUserSet对象实现查询user表的所有记录,第13~17行代码实现遍历查询结果记录集,将用户名添加到下拉组合框中。

在登录按钮响应函数OnOK中,实现验证用户,并将用户的权限传递给主框架,代码如下。

例程2.3代码位置:光盘\第2章\ StudentInfoManage \ LoginDlg.cpp

    01   void CLoginDlg::OnOK()
    02   {
    03       CUserSet recordset;                               //CUserSet对象
    04       CString strSQL;
    05       UpdateData(TRUE);                               //获取控件输入
    06       //获取主程序对象指针
    07       CStudentInfoManageApp*ptheApp=(CStudentInfoManageApp*)AfxGetApp();
    08       strSQL.Format("select*from user where user='%s'AND
    09                                passwd='%s'",m_strUser,m_strPass);
    10       if(!recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))//执行查询
    11       {
    12           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    13           return;                                    //返回
    14       }
    15       if(recordset.GetRecordCount()==0)                //查询结果记录集为空
    16       {
    17           recordset.Close();                             //关闭记录集
    18           MessageBox("密码错误,请重新输入!");          //提示对话框
    19           m_strPass="";                               //清空密码
    20           m_ctrPass.SetFocus();                         //设置输入焦点
    21           UpdateData(FALSE);                          //更新显示
    22       }
    23       else                                          //登录成功
    24       {
    25           ptheApp->m_bIsAdmin=recordset.m_isadmin;       //传递用户的权限
    26           recordset.Close();                             //关闭记录集
    27           CDialog::OnOK();                            //退出对话框
    28       }
    29   }

在第8~14行代码通过CUserSet对象从user表中查询用户输入的记录,若该记录不存,则提示密码错误,让用户重新输入(第15~22行代码);若记录存在,则获取用户权限并退出登录对话框(第23~28行代码)。

在主框架创建之前,要完成登录对话框的创建与调用。因此,登录对话框的创建要在CStudentInfoManageApp类的InitInstance函数中实现,代码如下。

例程2.4代码位置:光盘\第2章\ StudentInfoManage \ StudentInfoManage.cpp

    01   BOOL CStudentInfoManageApp::InitInstance()
    02   {
    03       AfxEnableControlContainer();
    04       ……
    05       CLoginDlg loginDlg;                              //登录对话框对象
    06       if(loginDlg.DoModal()!=IDOK)                       //弹出登录对话框
    07           return FALSE;
    08       ……
    09       m_pMainWnd->SetWindowText("XXX学院学生管理系统"); //设置窗口标题
    10       return TRUE;
    11   }

在第5、第6行代码实现创建并弹出登录对话框。

2.6.2 主界面设计

系统主界面为标准的Windows窗口界面,所有操作通过菜单项来实现。为了美化界面,在视图窗口添加了图片显示,如图2-5所示。该图片的大小可以随着视图窗口大小的改变而缩放,具体在视图类CStudentInfoManageView的OnPaint函数中实现,代码如下。

例程2.5代码位置:光盘\第2章\ StudentInfoManage \ StudentInfoManageView.cpp

    01   void CStudentInfoManageView::OnPaint()
    02   {
    03       CPaintDC*pDC=new CPaintDC(this);              //创建设备上下文
    04       CBitmap bmp;                               //CBitmap对象
    05       RECT    RectView;
    06       POINT   ptSize;
    07       CDC     dcmem;
    08       BITMAP  bm;                              //BITMAP结构
    09       int b=bmp.LoadBitmap(IDB_BITMAP_BG);       //将位图取出
    10       dcmem.CreateCompatibleDC(pDC);              //创建兼容设备上下文
    11       dcmem.SelectObject(&bmp);                   //用设备上下文选择位图
    12       dcmem.SetMapMode(pDC->GetMapMode());       //设置映射方式
    13       GetObject(bmp.m_hObject,sizeof(BITMAP),(LPSTR)&bm);//映射位图
    14       ptSize.x=bm.bmWidth;                         //图像宽度
    15       ptSize.y=bm.bmHeight;                        //图像高度
    16       pDC->DPtoLP((LPPOINT)&ptSize,1);            //设备单元转换为逻辑单元
    17       GetClientRect(&RectView);                     //客户窗口大小
    18       CRect RectBmp=RectView;                    //位图显示的大小
    19       if((RectView.right-RectView.left)>bm.bmWidth)//客户窗口宽度大于位图宽度
    20       {
    21           //水平位置居中显示
    22           RectBmp.left=RectView.left+(RectView.right-
    23                                    RectView.left-bm.bmWidth)/2;
    24           RectBmp.right=bm.bmWidth;               //右边界为位图宽度
    25       }
    26       else                                      //压缩位图宽度
    27       {
    28           RectBmp.left=RectView.left;                //左边界
    29           RectBmp.right=RectView.right-RectBmp.left;   //右边界
    30       }
    31       if((RectView.bottom-RectView.top)>bm.bmHeight)//客户窗口高度大于位图高度
    32       {
    33           RectBmp.top=RectView.top+(RectView.bottom-
    34                                    RectView.top-bm.bmHeight)/2;
    35           RectBmp.bottom=bm.bmHeight;             //下边界
    36       }
    37       else
    38       {
    39           RectBmp.top=RectView.top;                //上边界
    40           RectBmp.bottom=RectView.bottom-RectBmp.top;//下边界
    41       }
    42       //加载视图到设备上下文中
    43       pDC->StretchBlt(RectBmp.left,RectBmp.top,RectBmp.right,
    44           RectBmp.bottom,&dcmem,0,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
    45       dcmem.DeleteDC();                           //删除设备上下文
    46   }

第9~13行代码实现加载并在设备上下文中映射位图资源,第14~41行代码实现根据客户窗口的大小设置位图的显示尺寸,第43 行代码通过StretchBlt函数实现在视图窗口中显示位图。

2.7 学生档案管理模块设计

学生档案管理模块包括基础信息(如系、专业、班级等)的管理和档案信息的管理。

2.7.1 系、专业、班级的管理设计

要添加学生档案首先需要依次设置系、专业和班级信息,它们都是通过对话框窗口来实现。这里以设置专业为例,说明其实现过程。

当用户执行“基础设置”→“专业设置”菜单命令后,会弹出如图2-22所示的“专业设置”对话框,在其中可以添加、修改和删除专业记录。

图2-22 “专业设置”对话框

在CMainFrame类中为“专业设置”菜单项添加ON_COMMAND响应函数,实现创建专业设置对话框,同时为“专业设置”菜单项添加ON_UPDATE_COMMAND_UI响应函数,代码如下。

例程2.6代码位置:光盘\第2章\ StudentInfoManage \ MainFrm.cpp

    01   void CMainFrame::OnUpdateBaseMajor(CCmdUI*pCmdUI)
    02   {
    03       CStudentInfoManageApp*  ptheApp=(CStudentInfoManageApp*)AfxGetApp();
    04       pCmdUI->Enable(ptheApp->m_bIsAdmin);  //根据用户权限判断菜单项是否可用
    05   }

函数在第3行代码获取主程序对象,然后根据主程序对象中记录的用户权限设置菜单项是否可用(第4行代码)。

专业设置对话框对应的对话框类为CMajorDlg。在对话框的初始化函数OnInitDialog中,首先读取数据库department表中的数据,将数据库中现有的系别记录添加到对话框系别下拉列表框中,即用户只能在数据库中现有系中添加专业。而后,读取数据库major表中的数据,将数据库中现有的专业记录添加到对话框的列表框中显示,其具体实现参考本书所附光盘源码。

当用户单击“增加”按钮,用户就可以在对话框中添加新的专业记录,然后单击“保存”按钮,即将该专业信息添加到数据库中。“保存”按钮的响应函数为OnButtonSave,代码如下。

例程2.7代码位置:光盘\第2章\ StudentInfoManage \ MajorDlg.cpp

    01   void CMajorDlg::OnButtonSave()
    02   {
    03       UpdateData();                   //获取控件输入
    04       if(m_strName=="")               //专业名称为空
    05       {
    06           AfxMessageBox("请输入专业名称!");
    07           return;                    //返回
    08       }
    09       ……                         //同理判断专业代码和所属系是否为空
    10       CString strSQL;
    11       strSQL.Format("select*from major where code='%s'",m_strCode);//查询专业代码
    12       if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
    13       {
    14           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    15           return;                                        //返回
    16       }
    17       if(m_recordset.GetRecordCount()!=0)                   //查询记录集不为空
    18       {
    19           AfxMessageBox("当前编码已经存在!请重新输入!");
    20           m_strCode="";                              //专业代码设为空
    21           UpdateData(FALSE);                          //更新显示
    22           m_recordset.Close();                          //关闭记录集
    23           return;                                    //返回
    24       }
    25       m_recordset.AddNew();                            //添加新记录
    26       m_recordset.m_name      =   m_strName;            //专业名称
    27       m_recordset.m_code      =   m_strCode;            //专业代码
    28       m_recordset.m_department=m_strDepartment;       //所属系
    29       m_recordset.m_brief      =   m_strInfo;             //说明
    30       m_recordset.Update();                             //更新数据库
    31       m_recordset.Close();                               //关闭记录集
    32       RefreshData();                                   //更新显示列表框
    33   }

第3~9行代码判断输入的专业名称、代码、所属系是否为空,第10~24行代码实现从major表中查询要添加的专业代码是否存在,若存在,则给出提示信息并返回。第25~31行代码实现将用户添加的专业记录添加到数据库的major表中,第32 行代码通过RefreshData函数实现读取major表中记录,并把记录添加到对话框的列表框中显示。

RefreshData函数代码如下。

例程2.8代码位置:光盘\第2章\ StudentInfoManage \ MajorDlg.cpp

    01   void CMajorDlg::RefreshData()
    02   {
    03       m_ctrList.DeleteAllItems();                      //清空列表控件中的记录
    04       m_ctrList.SetRedraw(FALSE);                   //不允许重绘列表控件
    05       CString strSQL;
    06       strSQL.Format("select*from major");     //查询major表中的所有记录
    07       if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
    08       {
    09           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    10           return;                                //返回
    11       }
    12       int i=0;                                    /行号
    13       while(!m_recordset.IsEOF())                     //遍历记录集
    14       {
    15           //将记录添加到列表控件中
    16           m_ctrList.InsertItem(i,m_recordset.m_code);     //专业代码
    17           m_ctrList.SetItemText(i,1,m_recordset.m_name);  //名称
    18           m_ctrList.SetItemText(i,2,m_recordset.m_department);//所属系
    19           m_ctrList.SetItemText(i,3,m_recordset.m_brief);   //说明
    20           i++;                                  //行号加1
    21           m_recordset.MoveNext();                   //下一记录
    22       }
    23       m_recordset.Close();                          //关闭记录集
    24       m_ctrList.SetRedraw(TRUE);                    //重绘列表控件
    25   }

第5~11行代码实现从major表中查询所有的专业记录,然后在第12~22行代码实现遍历查询的记录集,依次将记录添加到列表控件中。

其他专业的修改、删除操作与此相似,就不再列出代码了。

同样,系别和班级管理的开发与专业管理相似,都是通过对话框窗口来实现的,具体实现可参考光盘源码。

2.7.2 学生档案信息管理设计

当用户执行“学生管理”→“学生档案管理”菜单命令后,菜单命令响应函数会创建学生档案信息对话框,如图2-6所示。在该对话框中,用户可以查询、添加、修改、删除学生档案信息。

学生档案管理对话框对应的对话框类为CStudentDlg,下面以添加新的用户档案为例,讲解其开发代码。

添加按钮响应函数为OnButtonNew,函数代码如下。

例程2.9代码位置:光盘\第2章\ StudentInfoManage \ StudentDlg.cpp

    01   void CStudentDlg::OnButtonNew()
    02   {
    03       CStudentInfoDlg Dlg;                          //学生档案信息对话框
    04       if(IDOK==Dlg.DoModal())                      //弹出对话框
    05       {   //添加新记录
    06           if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE))//打开数据库表
    07           {
    08               AfxMessageBox("打开数据库失败!");
    09               return;                            //返回
    10           }
    11           m_recordset.AddNew();                    //添加新记录
    12           m_recordset.m_class  =Dlg.m_strClass;        //班级
    13           m_recordset.m_department =Dlg.m_strDept;     //系
    14           m_recordset.m_sex   =Dlg.m_strSex;         //性别
    15           ……                                 //其他字段赋值
    16           m_recordset.Update();                     //更新记录集
    17           m_recordset.Close();                      //关闭记录集
    18           CString strSQL="select*from student";   //查询student表中的所有记录
    19           RefreshData(strSQL);                      //更新显示
    20       }
    21   }

函数的第3、第4行代码首先创建学生信息对话框(如图2-7所示),供用户输入相关的学生档案信息,然后在第6~17行代码将对话框输入的学生档案添加到数据库的student表中,在第18、第19行代码实现将student表中的所有记录添加到列表控件中显示。RefreshData函数的实现与例程2.8类似。

2.8 学生成绩管理模块设计

学生成绩管理模块包括基本考试课程信息的设置,考试成绩查询,考试成绩录入及考试成绩的统计汇总等几个子模块。

2.8.1 考试课程信息设计

在录入学生成绩之前,首先需要设置考试课程信息。考试课程信息设计包括考试类型、考试科目和考试时间段的设计。这里以考试科目设计为例,讲解其开发过程。

当用户执行“成绩管理”→“基础设置”→“考试科目设置”菜单项后,会弹出如图2-23所示的对话框,通过该对话框用户可以添加、修改和删除考试科目信息。

图2-23 “考试科目设置”对话框

在“考试科目设置”对话框中,单击“增加”按钮,在编辑框中输入科目信息,单击“保存”按钮,即完成了考试科目的添加设置。

“保存”按钮在对话框类CExamSubjectDlg中的响应函数为OnButtonSave,代码如下。

例程2.10代码位置:光盘\第2章\ StudentInfoManage \ ExamSubjectDlg.cpp

    01   void CExamSubjectDlg::OnButtonSave()
    02   {
    03       UpdateData();                               //获取控件输入
    04       if(m_strName=="")                           //考试科目为空
    05       {
    06           AfxMessageBox("请输入考试科目!");
    07           return;                                //返回
    08       }
    09       if(m_strCode=="")                            //科目代码为空
    10       {
    11           AfxMessageBox("请输入科目代码!");
    12           return;//返回
    13       }
    14       CString strSQL;
    15       strSQL.Format("select*from examsubject where code='%s'",m_strCode);
    16       if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))//打开数据库表
    17       {
    18           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    19           return;                                //返回
    20       }
    21       if(  m_recordset.GetRecordCount()!=0)            //查询结果不为空
    22       {
    23           AfxMessageBox("当前编码已经存在!请重新输入!");
    24           m_strCode="";
    25           UpdateData(FALSE);                      //更新控件
    26           m_recordset.Close();                      //关闭记录集
    27           return;//返回
    28       }
    29       m_recordset.AddNew();                        //添加记录
    30       m_recordset.m_name =m_strName;               //科目名称
    31       m_recordset.m_code  =m_strCode;                //科目代码
    32       m_recordset.Update();                         //更新记录
    33       m_recordset.Close();                          //关闭记录集
    34       RefreshData();                               //更新显示列表
    35   }

可见,其实现过程与例程2.7 基本相同,首先判断用户输入信息的有效性,然后将其写入数据库对应的表中,最后更新显示对话框列表框中的记录。

有关其他操作按钮的响应函数,以及考试类型和考试时间段设置的具体方法与此类似,具体可参见本书所附光盘源码。

2.8.2 考试成绩查询设计

系统用户可以查询学生的所有考试成绩。当用户执行“成绩管理”→“学生成绩查询”菜单项后,会弹出如图2-24所示的对话框。通过该对话框,用户可以选择班级,查看班级中学生的各科成绩,也可以直接按姓名搜索学生成绩。

图2-24 “成绩查询”对话框

在“成绩查询”对话框中,通过班级下拉列表框选择班级,单击“显示学生列表”按钮,在对话框左侧的列表框中,显示该班级所有学生信息。在列表框中选择学生,在右侧列表框中将列出该学生的所有考试成绩。

在“成绩查询”对话框类CScoreQueryDlg的初始化函数OnInitDialog中,查询数据库中的所有班级,并添加到班级列表框中,另外对列表框做一些基本的初始化工作,具体代码就不再详细给出。

在“显示学生列表”按钮响应函数OnButtonList中,从数据库中查询所选班级的学生记录,并添加到左侧窗口的列表控件中,实现代码如下。

例程2.11代码位置:光盘\第2章\ StudentInfoManage \ ScoreQueryDlg.cpp

    01   void CScoreQueryDlg::OnButtonList()
    02   {
    03       UpdateData();                               //获取控件输入
    04       if(m_strClass.IsEmpty())                        //班级输入为空
    05       {
    06           AfxMessageBox("请选择班级!");
    07           return;                                //返回
    08       }
    09       CString strSQL;
    10       m_ctrStuList.DeleteAllItems();                   //清空列表控件
    11       m_ctrStuList.SetRedraw(FALSE);             //列表控件不可重绘
    12       strSQL.Format("select*from student where class='%s'",m_strClass);
    13       if(!m_studentSet.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
    14       {
    15           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    16           return;                                //返回
    17       }
    18       int i=0;                                        //行号
    19       while(!m_studentSet.IsEOF())                //遍历记录集,添加到列表框
    20       {
    21           m_ctrStuList.InsertItem(i,m_studentSet.m_code);      //学号
    22           m_ctrStuList.SetItemText(i,1,m_studentSet.m_name);   //科目
    23           i++;                                      //行号加1
    24           m_studentSet.MoveNext();                      //下一记录
    25       }
    26       m_studentSet.Close();                              //关闭记录集
    27       m_ctrStuList.SetRedraw(TRUE);                      //重绘列表控件
    28   }

第3~8行代码判断是否选择了班级,第9~17行代码实现从student表中查询该班级的所有学生记录,然后在第18~25行代码实现遍历记录,依次将学号和姓名添加到列表控件中。

为学生列表框添加NM_LCLK(鼠标左键单击)消息响应函数OnClickList1。在该函数中,首先判断列表框中选中的学生记录,然后在数据库中查找该学生的成绩,并显示在成绩列表框中,函数实现代码如下。

例程2.12代码位置:光盘\第2章\ StudentInfoManage \ ScoreQueryDlg.cpp

    01   void CScoreQueryDlg::OnClickList1(NMHDR*pNMHDR,LRESULT*pResult)
    02   {
    03       CString strSQL;
    04       UpdateData(TRUE);                           //获取控件输入
    05       int i=m_ctrStuList.GetSelectionMark();            //得到选中的列表项
    06       //根据选中项的学号从score表中查询记录
    07       strSQL.Format("select*from score where
    08                   code='%s'",m_ctrStuList.GetItemText(i,0));
    09       RefreshData(strSQL);                 //在右侧列表控件中添加显示记录
    10       *pResult=0;
    11   }

函数第5行代码通过列表控件的GetSelectionMark函数得到选中项的序号,然后根据该序号利用GetItemText函数可以获取相关选中项列的标题(第8行代码),第6~8行代码构造依据学号从score表中查询选中学生的所有成绩记录的查询语句,并通过第9 行代码的RefreshData函数进行查询,并将结果显示在成绩列表控件中。

2.8.3 考试成绩录入设计

系统管理员可以录入学生成绩。当执行“成绩管理”→“学生成绩录入”菜单项后,会弹出如图2-25所示的对话框。通过该对话框,用户选择班级、考试类型、考试时间段,以及考试科目等信息后,单击“开始录入”按钮,在列表框中会列出该班级所有学生的该科目成绩(初始为0)。鼠标左键双击每条记录,会弹出学生成绩录入对话框,供管理员录入或修改成绩。

图2-25 “学生成绩录入”对话框

在成绩录入对话框类CScoreInputDlg的初始化函数OnInitDialog中,查询数据库中的所有班级、考试类型、考试时间段和考试类型记录,并添加到相应的列表框中,具体实现代码参见光盘源码。

在“开始录入”按钮响应函数OnButtonInput中,根据用户输入的信息,从数据库查找该班级学生的成绩,并在成绩列表框中显示,代码如下。

例程2.13代码位置:光盘\第2章\ StudentInfoManage \ ScoreInputDlg.cpp

    01   void CScoreInputDlg::OnButtonInput()
    02   {
    03       UpdateData();                           //获取控件输入
    04       if(m_strClass.IsEmpty())                   //没有选择班级
    05       {
    06           AfxMessageBox("请选择班级");
    07           return;                            //返回
    08       }
    09       ……                                 //其他输入项的选择判断
    10       CString strSQL;
    11       strSQL.Format("select*from score  where class='%s'and time='%s'and\
    12           type='%s'and subject='%s'",m_strClass,m_strTime,m_strType,m_strSubject);
    13       if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))//打开数据库
    14       {
    15           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    16           return;                                        //返回
    17       }
    18       if(m_recordset.GetRecordCount()==0)              //如果成绩记录不存在
    19       {
    20           m_recordset.Close();                      //关闭记录集
    21           CreateScoreTable();                       //添加新的学生成绩记录
    22       }
    23       else                                      //该科目成绩记录存在
    24       {
    25           m_recordset.Close();                      //关闭记录集
    26       }
    27       RefreshData(strSQL);                          //更新显示列表框
    28   }

函数的第3~9行代码首先判断是否选择了班级、考试类型、时间段和科目信息,然后在第10~17行代码实现根据用户的选择从score表中查询符合条件的所有考生成绩记录,若记录不存在,则调用CreateScoreTable函数添加新的学生成绩记录(第21行代码),最后通过RefreshData函数(第27行代码)将学生成绩记录添加到对话框中的列表控件中。

CreateScoreTable函数实现根据用户的设定,向学生成绩表中添加选定班级的所有学生的成绩记录,代码如下。

例程2.14代码位置:光盘\第2章\ StudentInfoManage \ ScoreInputDlg.cpp

    01   void CScoreInputDlg::CreateScoreTable()
    02   {
    03       CString strSQL;
    04       CStudentSet StudentSet;                            //CStudentSet对象
    05       strSQL.Format("select*from student where class='%s'",m_strClass);
    06       if(!StudentSet.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))//查询student表
    07       {
    08           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    09           return;                                    //返回
    10       }
    11       if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE))   //打开score记录集
    12       {
    13           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    14           return;                                    //返回
    15       }
    16       while(!StudentSet.IsEOF())                          //遍历student记录集
    17       {
    18           m_recordset.AddNew();                        //添加新的成绩记录
    19           m_recordset.m_code=StudentSet.m_code;           //学号
    20           m_recordset.m_class=m_strClass;                //班级
    21           m_recordset.m_name=StudentSet.m_name;          //姓名
    22           m_recordset.m_subject=m_strSubject;             //科目
    23           m_recordset.m_time=m_strTime;             //考试时间段
    24           m_recordset.m_type=m_strType;             //考试类型
    25           m_recordset.Update();                         //更新记录
    26           StudentSet.MoveNext();                        //下一记录
    27       }
    28       m_recordset.Close();                               //关闭score记录集
    29       StudentSet.Close();                                //关闭student记录集
    30   }

函数的第3~10行代码实现从student表中查询选定班级的所有学生记录,然后在第16~27行代码实现遍历查询记录集,依次为学生在score表中添加所选定的考试成绩记录(默认成绩为0)。

要添加成绩,只需在列表控件中左键双击记录,为成绩列表控件添加NM_DBLCLK(鼠标左键双击)消息响应函数OnDblclkList2。在该函数中,弹出输入对话框,管理员输入成绩,然后将成绩存储在数据库中,函数实现代码如下。

例程2.15代码位置:光盘\第2章\ StudentInfoManage \ ScoreInputDlg.cpp

    01   void CScoreInputDlg::OnDblclkList2(NMHDR*pNMHDR,LRESULT*pResult)
    02   {
    03       CString strSQL;
    04       long score=0,makeup=0;
    05       CScoreDlg Dlg;                              //成绩输入对话框
    06       UpdateData(TRUE);
    07       int i=m_ctrList.GetSelectionMark();               //获取列表控件选中项
    08       if(i<0)return;
    09       if(IDOK!=Dlg.DoModal())                      //弹出输入对话框
    10       {
    11           return;                                //没有单击确定按钮,返回
    12       }
    13       if(!Dlg.m_strScore.IsEmpty())                    //输入成绩不为空
    14           score=atol(Dlg.m_strScore);                //成绩转换为长整数
    15       if(!Dlg.m_strMakeup.IsEmpty())                  //输入补考成绩不为空
    16           makeup=atol(Dlg.m_strMakeup);             //补考成绩转换为长整数
    17       strSQL.Format("select*from score where ID=%s",m_ctrList.GetItemText(i,0));
    18       if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))//执行查询
    19       {
    20           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    21           return;                                //返回
    22       }
    23       m_recordset.Edit();                           //开始编辑score记录
    24       if(Dlg.m_bAbsent)                            //缺考复选框选中
    25           m_recordset.m_absent="是";
    26       m_recordset.m_score=score;                    //成绩
    27       m_recordset.m_makeup_score=makeup;            //补考成绩
    28       m_recordset.Update();                         //更新
    29       char buffer[20];
    30       _ltoa(m_recordset.m_score,buffer,10);              /成绩/转换为字符串
    31       m_ctrList.SetItemText(i,4,buffer);                 //更新列表项
    32       _ltoa(m_recordset.m_makeup_score,buffer,10);        //补考成绩/转换为字符串
    33       m_ctrList.SetItemText(i,5,buffer);                 //更新列表项
    34       m_ctrList.SetItemText(i,6,m_recordset.m_absent);  //更新列表项
    35       m_recordset.Close();                          //关闭记录
    36       *pResult=0;
    37   }

函数的第7行代码实现获取列表控件的选中项,第9~16行代码实现弹出输入成绩对话框,获取对话框输入的成绩和补考成绩,并转换为长整型,第17~28行代码实现从score表中查询列表控件选中项对应的记录,并更新其相关成绩字段,第29~34行代码实现更新列表控件中相关的成绩列值。

2.8.4 班级成绩的汇总统计设计

系统用户可以通过班级成绩的汇总统计,查看某一班级某门考试的平均成绩、及格率、优秀率等信息。

当执行“成绩管理”→“班级成绩汇总统计”菜单项后,会弹出如图2-26所示的对话框。当用户选择班级、考试时间段和考试类型信息后,单击“统计”按钮,在列表框中会列出所有考试科目的统计信息。

图2-26 “班级成绩汇总统计”对话框

对 话 框 对 应 的 对 话 框 类 为CScoreClassStatDlg,在对话列表框的初始化函数中,初始化下拉列表框和统计列表框。在“统计”按钮消息响应函数OnButtonStat中,通过创建信息的记录集,使用SQL语句统计汇总班级学生的各科成绩,并在列表框中显示,实现代码如下。

例程2.16代码位置:光盘\第2章\ StudentInfoManage \ ScoreClassStatDlg.cpp

    01   void CScoreClassStatDlg::OnButtonStat()
    02   {
    03       UpdateData();                                   //获取控件输入
    04       if(m_strClass.IsEmpty())                            //班级为空
    05       {
    06           AfxMessageBox("请选择班级");
    07           return;                                    //返回
    08       }
    09       ……                         //判断考试类型和时间段是否为空
    10       m_ctrList.DeleteAllItems();                          //清空列表框
    11       m_ctrList.SetRedraw(FALSE);               //不自动重绘列表控件
    12       CString strSQL;
    13       CString strValue="0";
    14       CDatabase db;                                   //CDatabase对象
    15       db.Open(_T("StudentInfoManage"));                   //打开数据库
    16       CRecordset rs1(&db);                 //创建数据库对应的CRecordset对象
    17       CRecordset rs2(&db);                 //创建数据库对应的CRecordset对象
    18       CRecordset rs3(&db);                 //创建数据库对应的CRecordset对象
    19       CRecordset rs4(&db);                 //创建数据库对应的CRecordset对象
    20       strSQL.Format("select subject,count(ID)as student,sum(score)as total from score\
    21                   where class='%s'and time='%s'and type='%s'group by subject",
    22                   m_strClass,m_strTime,m_strType);
    23       if(!rs1.Open(CRecordset::forwardOnly,strSQL))       //对数据库执行查询语句
    24       {
    25           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    26           return;                                    //返回
    27       }
    28       int i=0;
    29       int nStudent=0,nPass=0,nTotal=0,nSuper;
    30       CString strSubject="";
    31       char buffer[20];
    32       while(!rs1.IsEOF())                           //遍历查询结果记录集
    33       {
    34           rs1.GetFieldValue("subject",strValue);      //获取考试科目
    35           m_ctrList.InsertItem(i,strValue);              //添加到列表控件
    36           strSubject=strValue;
    37           rs1.GetFieldValue("student",strValue);      //获取参考人数
    38           nStudent=atoi(strValue);                    //转换为整型赋予nStudent
    39           m_ctrList.SetItemText(i,1,strValue);           //添加到列表控件
    40           strSQL.Format("select count(ID)as nAbsent from score\
    41               where class='%s'and time='%s'and type='%s'and absent='是'\
    42               and subject='%s'",m_strClass,m_strTime,m_strType,strSubject);
    43           if(!rs2.Open(CRecordset::forwardOnly,strSQL))   //执行查询缺考人数
    44           {
    45               rs1.Close();                         //查询失败,关闭记录集
    46               MessageBox("打开数据库失败!","数据库错误",MB_OK);
    47               return;                            //返回
    48           }
    49           rs2.GetFieldValue("nAbsent",strValue);     //获取缺考人数
    50           m_ctrList.SetItemText(i,2,strValue);           //添加到列表控件
    51           rs2.Close();                             //关闭记录集
    52           rs1.GetFieldValue("total",strValue);            //获取总分数
    53           nTotal=atoi(strValue);                     //转换为整数
    54           _itoa(nTotal/nStudent,buffer,10);//计算平均成绩,并转换为字符串
    55           m_ctrList.SetItemText(i,3,buffer);             //添加到列表控件
    56           strSQL.Format("select count(ID)as nPass from score\
    57               where class='%s'and time='%s'and type='%s'and score<60\
    58               and subject='%s'",m_strClass,m_strTime,m_strType,strSubject);
    59           if(!rs3.Open(CRecordset::forwardOnly,strSQL))   //查询不及格人数
    60           {
    61               rs1.Close();                         //查询失败,关闭记录集
    62               MessageBox("打开数据库失败!","数据库错误",MB_OK);
    63               return;                            //返回
    64           }
    65           rs3.GetFieldValue("nPass",strValue);           //不及格人数
    66           m_ctrList.SetItemText(i,4,strValue);           //添加到列表控件
    67           rs3.Close();                             //关闭记录集
    68           nPass=nStudent-atoi(strValue);             //及格的人数
    69           itoa((int)(nPass*100/nStudent),buffer,10);//及格百分率,转换为字符串
    70           m_ctrList.SetItemText(i,5,buffer);             //添加到列表控件
    71           strSQL.Format("select count(ID)as nSuper from score\
    72               where class='%s'and time='%s'and type='%s'and score>=80\
    73               and subject='%s'",m_strClass,m_strTime,m_strType,strSubject);
    74           if(!rs4.Open(CRecordset::forwardOnly,strSQL))   //查询成绩优秀的人数
    75           {
    76               rs1.Close();                         //查询失败,关闭记录集
    77               MessageBox("打开数据库失败!","数据库错误",MB_OK);
    78               return;                            //返回
    79           }
    80           rs4.GetFieldValue("nSuper",strValue);          //获取成绩优秀的人数
    81           m_ctrList.SetItemText(i,6,strValue);           //添加到列表控件
    82           rs4.Close();                             //关闭记录集
    83           nSuper=atoi(strValue);                    //转换为整数
    84           itoa((int)(nSuper*100/nStudent),buffer,10);     //计算优秀率转换为字符串
    85           m_ctrList.SetItemText(i,7,buffer);             //添加到列表控件
    86           i++;                                  //列表行号加1
    87           rs1.MoveNext();                         //下一记录
    88       }
    89       rs1.Close();                                 //关闭记录集
    90       m_ctrList.SetRedraw(TRUE);                    //更新显示列表控件
    91       }

在函数的第3~9行代码实现判断班级、考试类型和考试时间段输入是否为空,为空则返回。第14、第15行代码通过CDatabase对象打开数据库,第20~27行代码实现从score表中查询满足输入字段的所有考试科目、考试人数以及总分数记录集。第32~88行代码实现遍历查询记录集,将相关字段添加到列表控件中。其中,第40~51行代码实现查询该科目的缺考人数,并添加到列表控件;第52~55行代码实现计算该科目的平均成绩,并添加到列表控件;第56~67行代码实现查询该科目的不及格人数,并添加到列表控件;第68~70行代码实现计算该科目的及格率,并添加到列表控件;第71~85行代码实现查询该科目的优秀人数,计算该科目的优秀率,并添加到列表控件。

2.8.5 学生总分名次统计设计

系统用户可以通过学生总分名次统计,查看某一班级所有学生的总成绩、平均分,以及班级排名。

当执行“成绩管理”→“学生总分名次查询”菜单命令后,会弹出如图2-27所示的对话框。当用户选择班级、考试时间段和考试类型信息后,单击“统计”按钮,在列表框中会列出该班级所有的学生的总分、平均分和排名。

图2-27 “学生总分名次”对话框

对 话 框 对 应 的 对 话 框 类 为CScoreTotalDlg,在对话框的初始化函数中,初始化下拉列表框和统计列表框。在“统计”按钮消 息响应函 数OnButtonStat中,通过创建信息的记录集,使用SQL语句统计班级各学生的总成绩、平均成绩,并在列表框中显示,实现代码如下。

例程2.17代码位置:光盘\第2章\ StudentInfoManage \ ScoreTotalDlg.cpp

    01   void CScoreTotalDlg::OnButtonStat()
    02   {
    03       UpdateData();                               //获取控件输入
    04       if(m_strClass.IsEmpty())                        //班级为空
    05       {
    06           AfxMessageBox("请选择班级");
    07           return;                                //返回
    08       }
    09       ……                             //判断考试类型和时间段是否为空
    10       m_ctrList.DeleteAllItems();                      //清空列表控件
    11       m_ctrList.SetRedraw(FALSE);                   //不允许列表重绘
    12       CString strSQL;
    13       CString strValue="0";
    14       CString strTemp;
    15       CDatabase db;                               //CDatabase对象
    16       db.Open(_T("StudentInfoManage"));               //打开数据库
    17       CRecordset rs1(&db);                          //CRecordset对象
    18       CRecordset rs2(&db);                          //CRecordset对象
    19       strSQL.Format("select code,avg(score)as average,sum(score)as total from score\
    20               where class='%s'and time='%s'and type='%s'group by code\
    21               order by sum(score)desc",m_strClass,m_strTime,m_strType);
    22       if(!rs1.Open(CRecordset::forwardOnly,strSQL))       //执行查询
    23       {
    24           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    25           return;                                //查询失败返回
    26       }
    27       int i=0;
    28       rs1.m_strSort="total";
    29       char buffer[20];
    30       while(!rs1.IsEOF())                           //遍历查询结果记录集
    31       {
    32           rs1.GetFieldValue("code",strValue);           //获取学生学号
    33           m_ctrList.InsertItem(i,strValue);              //添加到列表控件
    34           strTemp.Format("select name from student where code='%s'",strValue);
    35           if(!rs2.Open(CRecordset::forwardOnly,strTemp))//查询学号对应的姓名
    36           {
    37               MessageBox("打开数据库失败!","数据库错误",MB_OK);
    38               rs2.Close();                         //查询失败,关闭记录集
    39               return;                            //返回
    40           }
    41           rs2.GetFieldValue("name",strValue);           //获取学生姓名
    42           rs2.Close();                             //关闭记录集
    43           m_ctrList.SetItemText(i,1,strValue);           //添加到列表控件
    44           m_ctrList.SetItemText(i,2,m_strClass);     //将班级添加到列表控件
    45           rs1.GetFieldValue("total",strValue);            //获取总成绩
    46           m_ctrList.SetItemText(i,3,strValue);           //添加到列表控件
    47           rs1.GetFieldValue("average",strValue);         //获取平均成绩
    48           m_ctrList.SetItemText(i,4,strValue);           //添加到列表控件
    49           itoa(i+1,buffer,10);                       //名次转换为字符串
    50           m_ctrList.SetItemText(i,5,buffer);             //添加到列表控件
    51           i++;                                  //名次加1
    52           rs1.MoveNext();                         //下一记录
    53       }
    54       rs1.Close();                                 //关闭记录集
    55       m_ctrList.SetRedraw(TRUE);                    //更新显示列表控件
    56   }

在函数的第3~9行代码实现判断班级、考试类型和考试时间段输入是否为空,为空则返回。第15、第16行代码通过CDatabase对象打开数据库,第17~26行代码实现从score表中查询满足输入字段的所有考生的学号、总成绩和平均成绩,并按总成绩的降序排列。第30~53 行代码实现遍历查询记录集,将相关字段添加到列表控件中。其中,第34~43 行代码实现从student表查询该考生的姓名,并添加到列表控件。

2.8.6 学生单科名次统计设计

系统用户可以通过学生单科名次统计,查看某一班级所有学生的某一科目的分数及排名情况。

当执行“成绩管理”→“学生单科名次查询”菜单命令后,会弹出如图2-28所示的对话框。当用户选择班级、考试时间段、考试类型和考试科目信息后,单击“统计”按钮,在列表框中会列出该班级所有的学生该学科的成绩及排名。

图2-28 “学生单科名次”对话框

对话框对应的对话框类为CScoreSingleDlg,在对话框的初始化函数中,初始化下拉列表框和统计列表框。在“统计”按钮消息响应函数OnButtonStat中,使用SQL语句统计班级各学生该学科的排名,并在列表框中显示,实现代码如下。

例程2.18代码位置:光盘\第2章\ StudentInfoManage \ ScoreSingleDlg.cpp

    01   void CScoreSingleDlg::OnButtonStat()
    02   {
    03       UpdateData();                               //获取控件输入
    04       if(m_strClass.IsEmpty())                        //班级为空
    05       {
    06           AfxMessageBox("请选择班级");
    07           return;                                //返回
    08       }
    09       ……                         //判断考试类型、时间段和科目是否为空
    10       m_ctrList.DeleteAllItems();                              //清空列表控件
    11       m_ctrList.SetRedraw(FALSE);                   //不允许列表重绘
    12       CString strSQL;
    13       strSQL.Format("select*from score where class='%s'and time='%s'\
    14                   and type='%s'and subject='%s'  \order by score desc",
    15                   m_strClass,m_strTime,m_strType,m_strSubject);
    16       if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))//执行查询
    17       {
    18           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    19           return;                                //查询失败返回
    20       }
    21       int i=0;
    22       char buffer[20];
    23       while(!m_recordset.IsEOF())                     //遍历查询结果记录集
    24       {
    25           m_ctrList.InsertItem(i,m_recordset.m_code); //添加学号到列表控件
    26           m_ctrList.SetItemText(i,1,m_recordset.m_name);//添加姓名到列表控件
    27           m_ctrList.SetItemText(i,2,m_recordset.m_class);//添加班级到列表控件
    28           _ltoa(m_recordset.m_score,buffer,10);      //将成绩转换为字符串
    29           m_ctrList.SetItemText(i,3,buffer);         //添加成绩到列表控件
    30           _itoa(i+1,buffer,10);                  //将名次转换为字符串
    31           m_ctrList.SetItemText(i,4,buffer);         //添加名次到列表控件
    32           i++;                              //名次加1
    33           m_recordset.MoveNext();               //下一记录
    34       }
    35       m_recordset.Close();                      //关闭记录集
    36       m_ctrList.SetRedraw(TRUE);                //更新显示列表控件
    37   }

在函数的第3~9行代码实现判断班级、考试类型、时间段和科目输入是否为空,为空则返回。第12~20行代码实现从score表中查询满足输入字段的所有记录,并按成绩的降序排列。第23~34行代码实现遍历查询记录集,将相关字段添加到列表控件中。

2.9 系统管理模块设计

系统管理模块包括新用户的注册,以及数据库的备份与恢复操作。

2.9.1 用户注册设计

系统管理员可以通过用户管理来实现注册新用户、删除用户,以及修改用户资料等操作。

当执行“系统管理”→“用户管理”菜单命令后,会弹出如图2-29所示的对话框。在对话框中单击“新增”按钮,输入用户资料,单击“保存”按钮就可完成添加新用户操作。同样,选中左侧列表框中的系统已有用户,就可以修改用户信息或删除该用户。

图2-29 “用户设置”对话框

对话框对应的对话框类为CUserDlg。在对话框初始化函数OnInitDialog中,从数据库中读取已有的注册用户,并将其在列表框中显示,代码如下。

例程2.19代码位置:光盘\第2章\ StudentInfoManage \ UserDlg.cpp

    01   BOOL CUserDlg::OnInitDialog()
    02   {
    03       CDialog::OnInitDialog();
    04       m_ctrList.InsertColumn(0,"用户名");                   //设置列表框
    05       m_ctrList.SetExtendedStyle(LVS_EX_FULLROWSELECT);  //列表框风格
    06       m_ctrList.SetColumnWidth(0,120);                    //列表框的宽度
    07       RefreshData();                                   //向列表框添加用户记录
    08       return TRUE;
    09   }

在第7行代码通过调用RefreshData函数实现从数据库中查询用户记录,并添加到列表框中,RefreshData函数的代码如下。

例程2.20代码位置:光盘\第2章\ StudentInfoManage \ UserDlg.cpp

    01   void CUserDlg::RefreshData()
    02   {
    03       m_ctrList.SetFocus();                          //设置列表框输入焦点
    04       m_ctrList.DeleteAllItems();                      //清空列表框
    05       m_ctrList.SetRedraw(FALSE);                   //不允许列表重绘
    06       CString strSQL;
    07       UpdateData(TRUE);
    08       strSQL="select*from user";
    09       if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))//执行查询
    10       {
    11           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    12           return;                                //查询失败返回
    13       }
    14       int i=0;                                    //列表行号
    15       while(!m_recordset.IsEOF())                     //遍历查询结果集
    16       {
    17           m_ctrList.InsertItem(i++,m_recordset.m_user);    //添加列表项
    18           m_recordset.MoveNext();                   //下一记录
    19       }
    20       m_recordset.Close();                          //关闭记录集
    21       m_ctrList.SetRedraw(TRUE);                    //更新显示列表框
    22   }

在函数的第6~13行代码实现查询user表中的所有记录,第15~20行代码实现遍历记录集,将用户名添加到列表框中。

无论是添加用户还是修改用户信息,设置完毕后,单击“保存”按钮,信息才被写入数据库。“保存”按钮的响应函数为OnOK,它首先判断用户信息的有效性,然后将其添加到数据库,代码如下。

例程2.21代码位置:光盘\第2章\ StudentInfoManage \ UserDlg.cpp

    01   void CUserDlg::OnOK()
    02   {
    03       UpdateData();
    04       if(m_ctrUser.IsWindowEnabled())             //增加新用户
    05       {
    06           if(m_strUser=="")                        //用户名为空
    07           {
    08               MessageBox("请填写用户名!");
    09               m_ctrUser.SetFocus();
    10               return;                            //返回
    11           }
    12       }
    13       else                                      //修改用户信息
    14       {
    15           if(m_strUser=="")                        //用户名为空
    16           {
    17               MessageBox("请选择一个用户!");
    18               return;                            //返回
    19           }
    20       }
    21       ……                     //判断密码是否为空,两次输入密码是否一致
    22       CString strSQL;
    23       strSQL.Format("select*from user where user='%s'",m_strUser);
    24       if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))//执行查询
    25       {
    26           MessageBox("打开数据库失败!","数据库错误",MB_OK);
    27           return;                                //返回
    28       }
    29       if(m_ctrUser.IsWindowEnabled())             //增加新用户
    30       {
    31           if(m_recordset.GetRecordCount()!=0)          //用户已经存在
    32           {
    33               m_recordset.Close();                  //关闭记录集
    34               MessageBox("该用户已经存在!");
    35               return;                            //返回
    36           }
    37           m_recordset.AddNew();                    //添加新记录
    38           m_recordset.m_user=m_strUser;             //用户名
    39           m_recordset.m_passwd=m_strPass;           //密码
    40           m_recordset.m_isadmin=m_bIsAdmin;     //是否为管理员
    41           m_recordset.Update();                     //更新记录
    42           MessageBox("用户添加成功!请记住用户名和密码!");
    43           m_recordset.Close();                      //关闭记录集
    44       }
    45       else
    46       {                                        //修改用户信息
    47
    48           if(m_recordset.GetRecordCount()==0)          //用户不存在
    49           {
    50               m_recordset.Close();                  //关闭记录集
    51               MessageBox("该用户不存在!请更新数据库");
    52               return;                            //返回
    53           }
    54           m_recordset.Edit();                       //编辑记录集
    55           m_recordset.m_user=m_strUser;             //用户名
    56           m_recordset.m_passwd=m_strPass;           //密码
    57           m_recordset.m_isadmin=m_bIsAdmin;     //是否是管理员
    58           m_recordset.Update();                     //更新记录集
    59           MessageBox("用户修改成功!请记住用户名和密码!");
    60           m_recordset.Close();                      //关闭记录集
    61       }
    62       m_ctrUser.EnableWindow(FALSE);
    63       RefreshData();                               //更新列表框
    64   }

在函数的第3~21行代码实现判断用户的输入是否有效,第29~44行代码实现添加新用户,第46~61行代码实现修改用户记录。

2.9.2 备份/恢复数据库

系统提供了简单的备份/恢复数据库功能,系统管理员可以通过“数据库管理”菜单项实现备份或恢复数据库。

数据库备份操作由CMainFrame类的OnDatabaseBackup菜单响应函数实现,它将数据库文件“StudentInfoManage.mdb”复制到backup文件夹下,并重命名为“StudentInfoManage.bak”,代码如下。

例程2.22代码位置:光盘\第2章\ StudentInfoManage \ MainFrame.cpp

    01   void CMainFrame::OnDatabaseBackup()
    02   {
    03       if(AfxMessageBox("您确定要备份数据库吗?",MB_OKCANCEL)==IDCANCEL)
    04       {
    05           return;                                    //返回
    06       }
    07       if(CopyFile(".\\StudentInfoManage.mdb",
    08                   ".\\backup\\StudentInfoManage.bak",FALSE))
    09           AfxMessageBox("数据库备份成功!");
    10       else
    11           AfxMessageBox("数据库备份失败!");
    12   }

在函数的第7行代码通过CopyFile函数实现数据库文件的复制。

数据库备份操作由CMainFrame类的OnDatabaseRecover菜单响应函数实现,它将备份的数据库文件“StudentInfoManage.bak”复制到可执行文件的目录下,并覆盖原来的数据库文件“StudentInfoManage.mdb”,代码如下。

例程2.23代码位置:光盘\第2章\ StudentInfoManage \ MainFrame.cpp

    01   void CMainFrame::OnDatabaseRecover()
    02   {
    03       if(AfxMessageBox("还原数据库将覆盖原来的数据库。您确定要还原吗?",
    04                       MB_OKCANCEL)==IDCANCEL)
    05       {
    06           return;                            //返回
    07       }
    08       if(CopyFile(".\\backup\\StudentInfoManage.bak",
    09                   ".\\StudentInfoManage.mdb",FALSE))
    10           AfxMessageBox("数据库还原成功!");
    11       else
    12           AfxMessageBox("数据库还原失败!");
    13   }

当然,要成功实现数据库备份,必须在系统可执行文件的目录下,创建新的文件夹,名称为“backup”。

2.10 开发技巧和难点分析

2.10.1 列表控件的使用

系统中,数据的显示与操作基本上都使用的是列表控件。MFC的CListCtrl类提供了对列表控件的封装,下面简单介绍一下使用该类的成员函数实现对列表控件常用的操作。

(1)添加、删除列表控件中的列

当以报告格式显示列表控件时,一般会显示一列表项和多列子项。在初始化列表控件时,先要调用InsertColumn函数添加各个列,该函数的原型如下。

    int InsertColumn(int nCol,LPCTSTR lpszColumnHeading,int nFormat = LVCFMT_LEFT,int nWidth =
-1, int nSubItem = -1 );

其中

● 参数nCol为列的位置,从零开始。

● 参数lpszColumnHeading为显示的列名。

● 参数nFormat为显示对齐方式,可以取值LVCFMT_LEFT、 LVCFMT_RIGHT、LVCFMT_CENTER。

● 参数nWidth为列宽度,取值-1,表明采用自动设置。

● 参数nSubItem为分配给该列的子列索引,取值-1,表明没有子列。

其使用可参见前面的实例。

如果要删除某列,应调用DeleteColumn函数,其原型如下:

    BOOL DeleteColumn( int nCol );

参数nCol为列的位置索引。

(2)添加、删除列表项

要在列表控件中添加新的列表项,可调用InsertItem函数。函数的原型如下:

    int InsertItem(int nItem,LPCTSTR lpszItem,int nImage );

● nItem指明插入项的位置。

● lpszItem为显示字符。

● nImage为位图序列的索引。

如果需要在列表项显示图标,则需要首先创建一个CImageList对象,使该对象包含用做显示图标的位图序列。然后调用SetImageList函数来为列表视图设置位图序列,该函数原型如下:

    CImageList* SetImageList( CImageList* pImageList, int nImageList );

● 参数pImageList指向一个CImageList对象。

● 参数nImageList用来指定图标的类型,若其值为LVSIL_NORMAL,则位图序列用做显示大图标,若值为LVSIL_SMALL,则位图序列用做显示小图标。

而对于有多列的列表控件中就需要为每一项指明其在每一列中的显示字符,通过调用SetItemText函数实现。函数原型如下:

    BOOL SetItemText( int nItem, int nSubItem, LPTSTR lpszText );

● nItem为设置的项的位置。

● nSubItem为列位置。

● lpszText为显示字符。

要删除某列表项,可调用DeleteItem(int nItem)函数;要删除所有的项,则可调用DeleteAllItems()函数。

(3)获取选中项

可以使用SetItemState函数选中和取消选中列表控件中的一项,典型实现如下:

            int nIndex = 0;
            m_list.SetItemState(nIndex, LVIS_SELECTED|LVIS_FOCUSED,
LVIS_SELECTED|LVIS_FOCUSED);                                  //选中第一行
            m_list.SetItemState(nIndex,0,LVIS_SELECTED|LVIS_FOCUSED);           //取消选中

获取列表视图控件中选中的行,可以遍历各列表项,通过GetItemState函数来判断各选项的状态。

2.10.2 MFC ODBC编程模式

系统对数据库的操作采用的是MFC ODBC编程,其数据库开发的一般过程可简要表示如下。

(1)注册数据源。使用ODBC数据源管理器或者自动注册的方式注册数据源,本实例采用的是后者。

(2)添加记录集类。使用类向导向工程中添加记录集的派生类,同时指定(1)中注册的数据源,这样框架会自动完成对数据库表的绑定。

(3)创建记录集对象。只是简单地声明一个(2)中派生的记录集对象。

(4)操作数据库。操作数据库前,可以通过设置记录集成员m_strFilter以便得到期望的记录,然后就可以对数据库进行遍历、添加、编辑和删除操作。

(5)关闭记录集。简单地调用记录集对象的Close函数即可关闭记录集。

以上是MFC ODBC最简单的编程模式,根据需要还可以对其进行扩展以实现更加复杂的操作。