以前在Delphi下做數據處理,對VC中ADO類的感覺比較麻煩,於是就試著參考別人的ado類封裝了兩個類,原來的類為 Carlos Antollini 的兩個ADO類,版本1.2(VC知識庫上有下載),修改了一下,然後繼承了一個CADOStorage類,又看過劉永超老師《一個簡單的學生成績管理程序》,想重新用自己的想法設計一下,然後就寫了這個小系統:界面結構采用現在數據處理軟件常用的MDI形式,如圖一:
圖一 程序運行畫面
我是按照需求分析,數據庫設計和一表一類的思想設計了這個系統,下面就從三個主要方面(數據庫設計,類設計,編程實現)分析我的設計思路:
一.數據庫關系圖:
我采用的是ACCESS數據庫,對每個表都基本上按照 E-R 關系模式設計,表結構及關系圖如圖二所示:
圖二 數據庫關系圖
二. 類結構圖
采用 Rose 設計了一部分,然後再編程完善後,用 Rose 的逆向工程生成的,基本上包括了現在使用的所有類及其之間的關聯關系,如圖三:
圖三 類之間的關聯關系
三. 編程實現部分
主要考慮了以下幾個方面:
1.基本表數據的錄入
只有輸入班級信息,年級信息等一些基礎信息後,系統才可以進行其它,如考試信息的管理,那麼怎樣錄入這些基礎數據,如果一表建一類的話(這樣做最好),如果基礎表太多,這樣系統編程工作量就會增加,所以想了想,能不能讓系統根據數據庫的關聯關系去選擇外鍵字段然後生成輸入界面,再根據用戶操作生成sql語句,去更新數據庫,如添加記錄。如圖四所示:
圖四 添加記錄
圖五 添加記錄
圖四中的年級編號在年級表中索引得到;圖五中的班級編號在班級表中索引得到,課程編號在課程表中索引得到,該怎麼做?
(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,當然,希望大家多提出批評意見。