学生考试管理系统ADO版
作者:Liu_Sir
以前在Delphi下做数据处理,对VC中ADO类的感觉比较麻烦,于是就试着参考别人的ado类封装了两个类,原来的类为 Carlos Antollini
的两个ADO类,版本1.2(VC知识库上有下载),修改了一下,然后继承了一个CADOStorage类,又看过刘永超老师《一个简单的学生成绩管理程序》,想重新用自己的想法设计一下,然后就写了这个小系统:界面结构采用现在数据处理软件常用的MDI形式,如图一:
![](/images/load.gif)
图一 程序运行画面
我是按照需求分析,数据库设计和一表一类的思想设计了这个系统,下面就从三个主要方面(数据库设计,类设计,编程实现)分析我的设计思路:
![](/images/load.gif)
我采用的是ACCESS数据库,对每个表都基本上按照 E-R 关系模式设计,表结构及关系图如图二所示:
![](/images/load.gif)
图二 数据库关系图
![](/images/load.gif)
采用 Rose 设计了一部分,然后再编程完善后,用 Rose 的逆向工程生成的,基本上包括了现在使用的所有类及其之间的关联关系,如图三:
![](/images/load.gif)
图三 类之间的关联关系
![](/images/load.gif)
主要考虑了以下几个方面:
1.基本表数据的录入
只有输入班级信息,年级信息等一些基础信息后,系统才可以进行其它,如考试信息的管理,那么怎样录入这些基础数据,如果一表建一类的话(这样做最好),如果基础表太多,这样系统编程工作量就会增加,所以想了想,能不能让系统根据数据库的关联关系去选择外键字段然后生成输入界面,再根据用户操作生成sql语句,去更新数据库,如添加记录。如图四所示:
![](/images/load.gif)
![](/images/load.gif)
图四 添加记录
图五 添加记录
图四中的年级编号在年级表中索引得到;图五中的班级编号在班级表中索引得到,课程编号在课程表中索引得到,该怎么做?
(1)、数据库结构方面:参考了用友数据库的一些表的设计,然后就想把数据表<表说明>中增加了字段:外键个数,字段0,外键0,外部表0,显示字段0…..字段n:(现在数据库预设计为一个表可以最多有4个外键)。
(2)、程序类方面:设计了一个记录字段信息(CFieldRecord)以及一个基本表类(CBaseTbl):
//记录信息量
struct CFieldRecord
{
char FieldName[20];//字段名(如果为外键字段,则为显示字段)
char Value[255]; //值
bool IsBool; //是否布尔
bool IsStrType; //是否字符串
bool IsVisible; //是否显示
char DisplayName[20]; //对应ID值(用于外键)
char FKtbl[20]; //外键表名或基本表名
CFieldRecord* pFK; //外键信息
};
//基本表的处理
class CBaseTbl : public CObject
{
public:
CBaseTbl();
virtual ~CBaseTbl();
CList<CString,CString& m_TblList;//名称列表
void GetTblnames();
//单表记录数组,默认记录最后一条
bool GetFieldRecord(CPtrArray& FieldArray,CString ctblname,CString constr="");
bool RemoveRecord(int CurRecordPos);
protected:
CADOStorage m_Storage;
//单表记录数组,默认记录最后一条
void AddExtraFieldRecord(CPtrArray& FieldArray,CString ctblname);
private:
CString m_TblName;
public:
//选择联合表
bool SelectUnionTbl(CString StrTblname,CString& Realtbl,bool ByRealTblName=false);
void RefreshList(CListCtrl& List1);
void ReQuery(CString TblName);
void ExecSql(CString SqlStr);
};
(3)、如在基本表视中点击添加数据过程:
①、调用过程:
//选择添加记录
void CStudentScoreView::OnADDRecord()
{
ASSERT(this-m_hWnd);
CBMDialog dialog1;
CStudentScoreDoc* pDoc=this-GetDocument();
pDoc-m_BaseTblList.GetFieldRecord(dialog1.RecordArray,this-m_CurTblName);
dialog1.ISADD=true;
dialog1.m_hParent=this-m_hWnd;
dialog1.m_OprTblName=this-m_CurTblName;
if(dialog1.DoModal()==IDOK)
{
AfxMessageBox("添加记录!");
//ASSERT(dialog1.m_hWnd);
}
}
申明为:文档类中 CBaseTbl m_BaseTblList;//表信息实例
对话框类中 CPtrArray RecordArray;//字段数组(用于保存字段的类型及外键信息)
传递参数:表名 this-m_CurTblName,
是否添加信息:dialog1.ISADD=true;
视的句柄的 Dialog1.m_hParent=this-m_hWnd;
以及最关键的一个量 dialog1.RecordArray(this-m_CurTblName 的记录及外键信息)
进行初始化后传递给窗体类CBMDialog;
那么pDoc-m_BaseTblList.GetFieldRecord(dialog1.RecordArray,this-m_CurTblName);如何实现把this-m_CurTblName的字段信息添加到RecordArray列表中去呢?
咱们看一下这个方法的定义:
//单表记录数组
bool CBaseTbl::GetFieldRecord(CPtrArray& FieldArray,CString ctblname,CString constr)
{
ASSERT(ctblname.Trim()!="");
//1.判断及清理工作
…省略
CFieldRecord* fldrec;
CADOFieldInfo fldinfo;
CString fldvalue;
//2.判断记录位置
bool IsZeroRecord;//记录数为0
…省略
int count=this-m_Storage.GetFieldCount();
//3.添加字段记录到指针数组
for(int i=0;i<count;i++)
{
fldrec=new CFieldRecord();
if(IsZeroRecord)
{
fldvalue="";
}
else
{
this-m_Storage.GetFieldValue(i,fldvalue);
}
this-m_Storage.GetFieldInfo(i,&fldinfo);
strcpy(fldrec-FKtbl,ctblname);
strcpy(fldrec-FieldName,fldinfo.m_strName);
strcpy(fldrec-Value,fldvalue);
switch(fldinfo.m_nType)
{
case VT_DATE:
fldrec-IsStrType=true;
fldrec-IsBool =false;
break;
…
default:
fldrec-IsStrType=false;
fldrec-IsBool =false;
break;
}
fldrec-pFK=NULL;
fldrec-IsVisible=true;
FieldArray.Add(fldrec);
}
//添加外键信息
this-AddExtraFieldRecord(FieldArray,ctblname);
return true;
}
基本过程就是两个:
一是添加fldrec=new CFieldRecord()字段信息;
二是查询是否有外键this-AddExtraFieldRecord(FieldArray,ctblname);并把外键信息添加到fldrec的链表尾部,然后在加入到
FieldArray.Add(fldrec);
②、有了这些工作下面的显示界面如图四、五就简单了,就是逆向的读取 FieldArray 的过程:
在CBMDialog类中:
//动态创建组件
CFieldRecord* pRecord;
int top=0;
for(int i=1;i<=this-RecordArray.GetCount();i++)
{
top=(i-1)*22;
pRecord=(CFieldRecord*)RecordArray.GetAt(i-1);
this-CreateStatic(pRecord,top,70+i);
//布尔或有关联外键
if((pRecord-IsBool)||(pRecord-pFK!=NULL))
this-CreateCombo(pRecord,top,10+i);
else
this-CreateEdit(pRecord,top,10+i);
}
注:有关联外件就读取关联表显示字段的索引信息,然后添加到 ComboBox 中,
显示出界面:
如何修改和添加记录呢?
//发送修改或添加消息
void CBMDialog::SendChangeMsg(void)
{
CWnd* pWnd;
CString value,Msg;
CFieldRecord* pRecord;
for(int i=1;i<=this-RecordArray.GetCount();i++)
{
pWnd=this-GetDlgItem(10+i);
if(pWnd)
{
pRecord=(CFieldRecord*)RecordArray.GetAt(i-1);
if ((!pRecord-IsBool)&&pWnd-IsKindOf(RUNTIME_CLASS(CComboBox)))
{ //非布尔类型,且有关联字段时
CComboBox* pCombo=(CComboBox*) pWnd;
CFldValue* p;
int index=pCombo-GetCurSel();
p=(CFldValue*)pCombo-GetItemDataPtr(index);
value=p-FieldValue;
}
else
pWnd-GetWindowText(value);
if(value.Trim()=="")
{
Msg=(CString)pRecord-FieldName+"不能为空!";
AfxMessageBox(Msg);
pWnd-SetFocus();
return;
}
else
{
if((i==1)&&(!this-ISADD))//如果是修改的话,则不对第一字段操作
{}
else
strcpy(pRecord-Value,value);
}
}
}
//发送字符串
CString cSend;
if(this-ISADD)
{
cSend=this-GenerateInsertSql();
}
else
{
cSend=this-GenerateUpdateSql();
}
LPARAM lparam=(LPARAM)&cSend;
::SendMessage((HWND)this-m_hParent,WM_USER+51,0,lparam);
}
这是基本的信息,根据显示内容生成sql语句,发送消息给基本表视,完成数据库更新操作。
③、视的更新过程:
LRESULT CStudentScoreView::OnExecSql(WPARAM wParam,LPARAM lParam)
{
CString c;
c=*((CString*)lParam);
CStudentScoreDoc* pDoc=this-GetDocument();
pDoc-m_BaseTblList.ExecSql(c);
this-RefreshShow();
//AfxMessageBox(c);
return 0;
}
2.关于考试过程的编码
主要是依靠几个类来实现:
* CExam 考试类 用于考试的信息登记
* CExamClass 考试班级类
* CExamStudent 考试学生类
* CExamSubject 考试科目类
他们都聚合了一个数据集TADOStorage,然后通过数据集完成考试信息的记录,计算:
class CExam : public CObject
{
public:
CExam();
virtual ~CExam();
CString m_No; //考试编号
COleDateTime m_Date; //考试日期
CString m_TermNo; //学期编号
CStringList& GetTerms();
CStringList* GetNos();
protected:
CADOStorage m_Storage;//数据库连接
CString m_TblName; //表名称
private:
CStringList m_TermList;//学期列表
CStringList m_NoList; //编号列表
public:
void ClearTermList(void);
void ClearNoList(void);
void GetExamByNo(CString No);//获取考试信息
bool IsNoExisted(CString No);//编号是否存在
void AddExam();
void DeleteExamByNo(CString No);
};
(1)、在文档类中申明CExam m_Exam.添加记录
void CExamView::OnBnClickedButton2()
{
CEdit* pEdit=(CEdit*) this-GetDlgItem(IDC_EDIT1);
CString ExamNo;
pEdit-GetWindowText(ExamNo);
CExamDoc* pDoc=this-GetDocument();
if(pDoc-m_Exam.IsNoExisted(ExamNo))
{
AfxMessageBox("不能添加重复记录!");
return;
}
else
{
//添加记录
pDoc-m_Exam.m_No=ExamNo;
CDateTimeCtrl* pPicker=
(CDateTimeCtrl*)this-GetDlgItem(IDC_DATETIMEPICKER1);
pPicker-GetTime(pDoc-m_Exam.m_Date);
CComboBox* pComb=(CComboBox*)this-GetDlgItem(IDC_COMBO1);
pComb-GetLBText(pComb-GetCurSel(),pDoc-m_Exam.m_TermNo);
pDoc-m_Exam.AddExam();
}
}
(2)、然后看: pDoc-m_Exam.AddExam();
void CExam::AddExam()
{
CString FldList,ValueList;
FldList="考试编号,考试日期,学期编号";
ValueList="''''"+this-m_No+"'''',''''"+this-m_Date.Format()+"'''',''''"+this-m_TermNo+"''''";
this-m_Storage.ExecInsertSql(this-m_TblName,FldList,ValueList);
}
这是一个与数据库交互的过程。
其它类的处理过程类似,就是对数据库表的操作用类来封装,去执行。当然怎么做可能有一些不妥之处,如一个类包含一个数据集,可能占有存储空间比较大,对象如果进行拷贝复制的话
,有可能占有系统的存储空间;所以有的人建议把对数据库的操作让一个代理类去实现,当然各有优缺点,这也是对OR
Map争论比较多的一个问题,感觉java
Bean实体在这方面做的更好一些。我编程的时间也不算太长,考虑上的深度不够和自己的水平有限,当然还有很多不足之处,甚至BUGS,当然,希望大家多提出批评意见。