作者從 Delphi 轉戰 VC++,參考 Carlos Antollini 的 ADO 封裝類 (v1.2) 並衍生出 CADOStorage,結合「一表一類」與 MDI 介面風格,打造一套學生考試管理系統。本文完整記錄資料庫設計、類結構、動態介面生成與考試流程編碼三大核心環節。

表說明 中繼資料表:新增 外鍵個數、字段n、外鍵n、外部表n、顯示字段n (單表最多 4 個外鍵)
使用 Rose 正向/逆向工程產出,涵蓋:
CBaseTbl:基礎表通用操作類,聚合 CADOStorageCFieldRecord:欄位資訊結構體,含外鍵鏈表指標CExam、CExamClass、CExamStudent、CExamSubject,各自聚合 CADOStoragestruct 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:
bool GetFieldRecord(CPtrArray& FieldArray, CString ctblname, CString constr="");
void AddExtraFieldRecord(CPtrArray& FieldArray, CString ctblname); // 掛載外鍵
bool SelectUnionTbl(CString StrTblname, CString& Realtbl, bool ByRealTblName=false);
void RefreshList(CListCtrl& List1);
void ReQuery(CString TblName);
void ExecSql(CString SqlStr);
protected:
CADOStorage m_Storage;
private:
CString m_TblName;
};
bool CBaseTbl::GetFieldRecord(CPtrArray& FieldArray, CString ctblname, CString constr) {
// 1. 清理舊資料、判斷記錄位置(IsZeroRecord)
int count = m_Storage.GetFieldCount();
for (int i=0; i<count; i++) {
CFieldRecord* fldrec = new CFieldRecord();
CString fldvalue;
if (IsZeroRecord) fldvalue = _T("");
else m_Storage.GetFieldValue(i, fldvalue);
CADOFieldInfo fldinfo;
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;
}
fldrec->pFK = NULL;
fldrec->IsVisible = true;
FieldArray.Add(fldrec);
}
// 2. 遞迴掛載外鍵資訊
AddExtraFieldRecord(FieldArray, ctblname);
return true;
}
// CBMDialog::OnInitDialog 片段
for (int i=1; i<=RecordArray.GetCount(); i++) {
int top = (i-1)*22;
CFieldRecord* pRecord = (CFieldRecord*)RecordArray.GetAt(i-1);
CreateStatic(pRecord, top, 70+i); // 標籤
// 外鍵或布林 -> ComboBox,其餘 -> Edit
if (pRecord->IsBool || pRecord->pFK != NULL)
CreateCombo(pRecord, top, 10+i);
else
CreateEdit(pRecord, top, 10+i);
}
ComboBox 會自動載入外鍵參考表的顯示欄位索引值。
void CBMDialog::SendChangeMsg() {
CString value, Msg;
for (int i=1; i<=RecordArray.GetCount(); i++) {
CWnd* pWnd = GetDlgItem(10+i);
CFieldRecord* pRecord = (CFieldRecord*)RecordArray.GetAt(i-1);
if (!pRecord->IsBool && pWnd->IsKindOf(RUNTIME_CLASS(CComboBox))) {
CComboBox* pCombo = (CComboBox*)pWnd;
CFldValue* p = (CFldValue*)pCombo->GetItemDataPtr(pCombo->GetCurSel());
value = p->FieldValue; // 取得外鍵實際 ID
} else {
pWnd->GetWindowText(value);
}
if (value.Trim() == _T("")) { AfxMessageBox(pRecord->FieldName + _T("不能為空!")); return; }
if (!(i==1 && !ISADD)) strcpy(pRecord->Value, value); // 修改模式下不動主鍵
}
CString cSend = ISADD ? GenerateInsertSql() : GenerateUpdateSql();
::SendMessage(m_hParent, WM_USER+51, 0, (LPARAM)&cSend);
}
LRESULT CStudentScoreView::OnExecSql(WPARAM, LPARAM lParam) {
CString c = *((CString*)lParam);
GetDocument()->m_BaseTblList.ExecSql(c);
RefreshShow();
return 0;
}
class CExam : public CObject {
public:
CString m_No; COleDateTime m_Date; CString m_TermNo;
CStringList& GetTerms(); CStringList* GetNos();
void GetExamByNo(CString No);
bool IsNoExisted(CString No);
void AddExam(); void DeleteExamByNo(CString No);
protected:
CADOStorage m_Storage; CString m_TblName;
private:
CStringList m_TermList, m_NoList;
};
void CExam::AddExam() {
CString FldList = _T("考試編號,考試日期,學期編號");
CString ValueList = _T("'") + m_No + _T("','") + m_Date.Format() + _T("','") + m_TermNo + _T("'");
m_Storage.ExecInsertSql(m_TblName, FldList, ValueList);
}
其他類 (CExamClass、CExamStudent、CExamSubject) 採相同模式,封裝各自資料表操作。
CADOStorage,記憶體佔用較大;物件複製時連線狀態易發生衝突。