学习Enroll例程
Visual C++提供了一個名為Enroll的例子來作為學習MFC數據庫編程的教程.Enroll分為四步,本節的任務就是指導讀者完成前三步的Enroll例程,并對其進行較徹底的剖析.通過學習這三步例程,讀者將掌握用AppWizard和ClassWizard創建MFC數據庫應用程序的方法.
在開始學習Enroll例程時,讀者也許會感到用AppWizard創建數據庫應用很容易,似乎不用學習前面幾節的內容.誠然,AppWizard自動地為應用程序加入了許多與數據庫有關的代碼,大大簡化了數據庫應用的開發.但AppWizard不是萬能的,它建立的數據庫應用往往不能滿足用戶的需要.用戶真正想知道的是如何不依賴AppWizard而編寫自己的數據庫應用程序,這也正是本章的宗旨所在.事實上,前面幾節的分析以及后面進行的對Enroll例程的分析正是為這一宗旨服務的.
在學習Enroll以前,請讀者先從Visual C++ 5.0的光盤上將Enroll(在samples / mfc / tutorial / enroll目錄下)在前三步的例程拷到硬盤上,以供參考.另外,Enroll要用到Access數據庫STDRED32.MDB,該文件在VC5.0的Stdreg例程中(在samples / mfc / database / stdreg目錄下),請讀者將該例子也拷貝到硬盤上.
10.7.1 注冊數據源
ODBC應用程序不能直接使用數據庫,用戶必需為要使用的數據庫注冊數據源.注冊數據源的工作由ODBC管理器完成,該管理器位于Windows 95控制面板的32位ODBC內.現在讓我們為Access數據庫STDREG32.MDB注冊數據源.
打開控制面板,雙擊“32位ODBC”圖標,則會顯示一個“ODBC數據源管理器”,如圖10.5所示。在管理器中選擇“用戶DSN”頁,用戶DSN只對用戶可見而且只能用戶當前機器。
圖10.5 ODBC數據源管理器
點擊“添加”按鈕,則會彈出一個“創建新數據源”對話框。在該對話框中選擇Microsoft Access Driver(*.mdb),然后按完成按鈕。接下來會顯示一個ODBC Microsoft Access 97 Setup對話框,如圖10.6所示,該對話框用來把數據庫與一個數據源名連接起來。在Data Source Name:欄中輸入Student Registration,然后點擊Select...按鈕,在隨后彈出的對話框中找到并選擇STDREG32.MDB。連按兩個OK按鈕后,一個名為Student Registration的新數據源就被注冊到了管理器中。
圖10.6 ODBC Microsoft Access 97 Setup對話框
| 提示:如果要為dBase或FoxPro數據庫注冊數據源,則應該選擇一個目錄而不是文件作為數據源。這是因為在Access中,一個MDB文件可以包含多張表,因此可以認為一個MDB文件就是一個數據庫。而在dBase或FoxPro中,一個DBF文件只能對應一張表,一個數據庫可能會包含好幾個DBF文件,所以可以認為某一個包含了若干DBF文件的目錄是一個數據庫。 |
10.7.2 Enroll的第一個版本
Enroll第一個版本如圖10.3所示,該程序具有瀏覽記錄集和修改記錄這兩個基本功能。在修改了表單中的記錄后,移動到一個新的記錄上就可以保存對原記錄的修改。注意在表單中Course和Section編輯框都是只讀的,這是因為這兩個字段的內容較重要,用戶不能隨便修改。Enroll使用了STDREG32.MDB的Section表,該表是一張課程表,其內容如表10.2所示。
現在讓我們來建立Enroll應用程序。首先,用AppWizard來完成Enroll程序框架的建立,請讀者按下面幾步進行:
啟動AppWizard,指定一個名為Enroll的MFC工程。
在MFC AppWizard的第一步選擇Single document。
在MFC AppWizard的第二步選擇Database view without file support,然后點擊Data Source...按鈕,在Database Options對話框中的ODBC組合框中選擇Student Registration數據源,如圖10.7所示。然后按“OK”按鈕,則會打開一個Select Database Table對話框,如圖10.8所示。在該對話框中選擇Section表。按OK按鈕退出。
在MFC AppWizard的第六步中,將類CEnrollSet改名為CSectionSet,將類CEnrollView改名為CSectionForm。
按Finish按鈕,建立Enroll工程。
圖10.7 Database Options對話框
圖10.8 Select Database Table對話框
打開工作區的類視圖,可以發現AppWizard自動創建了一個記錄集類CSectionSet和一個記錄視圖類CSectionForm,這兩個類分別是CRecordset和CRecordView的派生類。AppWizard也為CSectionSet類自動創建了域數據成員。
打開工作區的資源視圖,讀者不難找到一個ID為IDD_ENROLL_FORM的對話框模板,該模板將被記錄視圖用來顯示表單。清除該模板中的所有控件,并把模板的尺寸擴大到183×110,然后按圖10.3的式樣放置控件。這里可以采取一個偷懶的方法:打開VC5.0已作好的第一個版本Enroll的資源文件(Enroll.rc),找到并打開IDD_ENROLL_FORM對話框模板,按住Ctrl鍵并用鼠標選擇模板中的所有控件,然后按Ctrl+Insert鍵拷貝所選的控件。切換到自己的IDD_ENROLL_FORM對話框模板,然后按Shift+Insert鍵把剛才拷貝的控件粘貼到模板中。
接下來的任務是用ClassWizard把表單中的控件與記錄集的域數據成員連接起來,以實現控件與當前記錄的DDX數據交換。請讀者按如下步驟操作:
進入ClassWizard,選擇Member Variables頁并且選擇CSectionForm類。
在變量列表中雙擊IDC_CAPACITY項,則會顯示Add Member Variable對話框。注意該對話框的Member variable name欄顯示的是一個組合框,而不是平常看到的編輯框。如圖10.9所示,在組合框的列表中選擇m_pSet->m_Capacity。按OK按鈕確認。
仿照第2步,為其他控件連接記錄集的域數據成員。根據控件的ID,不難確定對應的域數據成員。
圖10.9 Add Member Variable對話框
在CSectionForm類的定義內可以找到下面一行:
CSectionSet* m_pSet;
可見m_pSet是CSectionForm類的成員,它指向一個CSectionSet對象。用ClassWizard可以把控件與象記錄集這樣的“外部數據”連接起來,這是ClassWizard在數據庫編程方面的一個特殊應用。
編譯并運行Enroll,讀者會驚奇的發現Enroll居然是一個相當不錯的記錄瀏覽器,并且用戶可以對記錄進行修改。
現在,讓我們來分析一下AppWizard和ClassWizard為Enroll干了哪些事情。
在文檔類CEnrollDoc的定義中,有如下一行:
CSectionSet m_sectionSet;
可見AppWizard在CEnrollDoc類中嵌入了一個CSectionSet對象。這相當于調用了構造函數CSectionSet(NULL),CSectionSet類的構造函數的聲明如下:
CSectionSet(CDatabase* pDatabase = NULL);
函數的定義在清單10.5中列出。可以看出,構造函數調用了基類的構造函數,并對域數據成員進行了初始化。通過10.5.4我們知道,若傳遞NULL參數給CRecordset的構造函數,那么CRecordset::Open函數將自動構建一個CDatabase對象,并根據CRecordset:: GetDefaultConnect返回的連接字符串建立與數據源的連接。CSectionSet提供了虛擬函數GetDefaultConnect的新版本,如清單10.6所示,在該函數中提供了數據源Student Registration。
清單10.5 CSectionSet的構造函數
CSectionSet::CSectionSet(CDatabase* pdb)
: CRecordset(pdb)
{
//{{AFX_FIELD_INIT(CSectionSet)
m_CourseID = _T("");
m_SectionNo = _T("");
m_InstructorID = _T("");
m_RoomNo = _T("");
m_Schedule = _T("");
m_Capacity = 0;
m_nFields = 6;
//}}AFX_FIELD_INIT
m_nDefaultType = snapshot;
}
清單10.6 派生類的GetDefaultConnect函數
CString CSectionSet::GetDefaultConnect()
{
return _T("ODBC;DSN=Student Registration");
}
至于記錄集的建立,實際上是在CRecordView:: OnInitialUpdate中完成的,這部分代碼對用戶是透明的,這里在清單10.7中列出。在該函數中調用CRecordset::Open來建立記錄集。在函數的開頭調用了OnGetRecordset函數來獲取與記錄視圖相連的記錄集對象。CSectionForm提供了虛擬函數OnGetRecordset的新版本,如清單10.8所示,該函數把m_pSet提交給調用者。至于m_pSet的初始化,則是在CSectionForm::OnInitialUpdate函數中完成的,如清單10.9所示。
清單10.7 CRecordView:: OnInitialUpdate函數
void CRecordView::OnInitialUpdate()
{
CRecordset* pRecordset = OnGetRecordset();
// recordset must be allocated already
ASSERT(pRecordset != NULL);
if (!pRecordset->IsOpen())
{
CWaitCursor wait;
pRecordset->Open();
}
CFormView::OnInitialUpdate();
}
清單10.8 派生類的OnGetRecordset函數
CRecordset* CSectionForm::OnGetRecordset()
{
return m_pSet;
}
清單10.9 派生類的OnInitialUpdate函數
void CSectionForm::OnInitialUpdate()
{
m_pSet = &GetDocument()->m_sectionSet;
CRecordView::OnInitialUpdate();
}
注意到在CRecordView:: OnInitialUpdate中調用CRecordset::Open時未提供任何參數,這意味著Open函數將從CRecordset::GetDefaultSQL中獲取SQL信息。CSectionSet提供了虛擬函數GetDefaultSQL的新版本,如清單10.10所示,該函數返回了“Section”表名。
清單10.10 派生類的GetDefaultSQL函數
CString CSectionSet::GetDefaultSQL()
{
return _T("[Section]");
}
至于記錄的滾動和修改的實現,請參看10.6。而與DDX和DFX有關的代碼已在清單10.3和10.2中列出。
如果讀者對上面的分析還有不明白的地方,那么請再把本章的前幾節內容再仔細閱讀一遍。
10.7.3 Enroll的第二個版本
Enroll的第二個版本向讀者演示了在一個記錄視圖中使用兩個相關聯的記錄集,以及記錄的過濾和排序技術,該版本使讀者真正接觸到了關系數據庫。本小節還將向讀者介紹如何用ClassWizard建立記錄集類,以及參數化記錄集的方法。
讀者可以先運行VC 5.0提供的Enroll例子的第二步看看。Enroll的界面有了一個變化,原來的Course編輯框被替換成了組合框,如圖10.10所示。組合框中的內容來自同一數據源的另一張表Course的CourseID字段。
圖10.10 Enroll的第二個版本
Course表的內容如表10.5所示,與表10.2相對照,讀者可以發現Course表和Section表有一個公共字段CourseID。記錄視圖程序正是利用這個公共字段把兩張表聯系起來的。例如,當用戶在Course組合框中選擇了MATH202時,程序將選擇Section表中所有CourseID為MATH202的記錄并建立新的記錄集。
事實上,在STDREG32.MDB的大部分表都共享了CourseID。在主表Course表中,每個記錄的CourseID是唯一的,我們稱其為主關鍵字(Primary key)。在Course的相關表中,CourseID不一定唯一,如Section表,我們稱相關表中的CourseID為外關鍵字(Foreign key)。通過關鍵字可以把多張表聯系到一起,這樣的數據庫就是關系數據庫。SectionNo也是一個關鍵字,在Section表中,SectioinNo字段是主關鍵字,它的值是唯一的。
表10.5 Course表
| CourseID(Text) | CourseTitle(Text) | Hours(int) |
| MATH101 | Algebra | 4 |
| MATH201 | Calculus I | 4 |
| MATH202 | Calculus II | 4 |
現在就讓我們開始在上一小節Enroll的基礎上制作新版本。若當前工程不是Enroll,請讀者打開上一小節創建的Enroll工程。
首先要把Course編輯框替換成組合框,這包括下面幾步:
打開IDD_ENROLL_FORM對話框模板并刪除Course編輯框。
在原來編輯框的位置加入一個組合框。打開該控件的屬性對話框,令其ID為IDC_COURSELIST,并在Styles頁中選擇Drop List。注意要適當調整組合框的下拉列表尺寸(點擊向下的箭頭后擴大其尺寸)。
自上到下從新安排Tab順序。
接下來要用ClassWizard做一些與新加的組合框有關的工作:
進入ClassWizard,選擇Member Variables頁并選擇CSectionForm類。單擊列表中的IDC_COURSE項并按Delete鍵刪除該項。然后雙擊IDC_COURSELIST項,在Add Member Variable對話框的組合框中選擇m_pSet->m_CourseID。
再次雙擊IDC_COURSELIST,并為CSectionForm類加入一個名為m_ctlCourseList的CComboBox類成員。
選擇Message Maps頁,為IDC_COURSELIST組合框加入CBN_SELENDOK通知消息處理函數,函數名為OnSelendokCourselist。該函數負責響應用戶在組合框中選擇的變化。
按OK按鈕退出ClassWizard。
接著,需要為Course表創建一個名為CCourseSet的記錄集類,這個工作可由ClassWizard完成,請讀者按下面幾步操作:
進入ClassWizard,點擊Add Class...按鈕并在彈出的菜單中選擇New...,然后在Create New Class對話框中的Name欄中輸入CCourseSet,在Base class欄中選擇CRecordset,按Create按鈕。
在彈出的Database Options對話框中,在ODBC組合框里選擇Student Registration數據源。然后按OK按鈕。
在彈出的Select Database Tables對話框中選擇Course表。按OK確認。
看看新建的CCourseSet類,讀者會發現ClassWizard自動為CCourseSet類創建了與Course表的字段相對應的域數據成員,并且建立了DoFieldExchange函數。ClassWizard也為記錄集類提供了新的GetDefaultConnect和GetDefaultSQL函數。
接著,在CEnrollDoc類的定義中,緊接著m_sectionSet成員,加入下面一行:
CCourseSet m_courseSet;
這樣CEnrollDoc就包含了兩個記錄集。由于CEnrollDoc類用到了CCourseSet類,所以要在所有含有#include “EnrolDoc.h”語句的CPP文件中,在#include “EnrolDoc.h”語句的前面加上如下的include語句。這些CPP文件包括CEnrollApp、CSectionForm和CEnrollDoc類所在的CPP文件。
#include "CourseSet.h"
在CSectionSet類的定義中,緊接著域數據成員,在“//}}AFX_FIELD”注釋外加入下面一行。
CString m_strCourseIDParam;
m_strCourseIDParam是記錄集的參數數據成員,其作用將在后面說明。
最后,請讀者按清單10.11和10.12修改程序。清單10.11列出的是CSectionSet類的部分源代碼,清單10.12列出的是CSectionForm類的部分代碼。
清單10.11 CSectionSet類的部分代碼
CSectionSet::CSectionSet(CDatabase* pdb)
: CRecordset(pdb)
{
. . .
m_nParams = 1; //只有一個參數數據成員
m_strCourseIDParam = "";
}
void CSectionSet::DoFieldExchange(CFieldExchange* pFX)
{
. . .
pFX->SetFieldType(CFieldExchange::param);
RFX_Text(pFX, "CourseIDParam", m_strCourseIDParam); //替換參數
}
清單10.12 CSectionForm類的部分代碼
void CSectionForm::OnInitialUpdate()
{
m_pSet = &GetDocument()->m_sectionSet;
CEnrollDoc* pDoc = GetDocument();
pDoc->m_courseSet.m_strSort = "CourseID";
if (!pDoc->m_courseSet.Open())
return;
m_pSet->m_strFilter = "CourseID = ?"; //使用參數
m_pSet->m_strCourseIDParam = pDoc->m_courseSet.m_CourseID;
m_pSet->m_strSort = "SectionNo";
m_pSet->m_pDatabase = pDoc->m_courseSet.m_pDatabase; //共享CDatabase
CRecordView::OnInitialUpdate();
m_ctlCourseList.ResetContent();
if (pDoc->m_courseSet.IsOpen())
{
while (!pDoc->m_courseSet.IsEOF())
{
m_ctlCourseList.AddString(
pDoc->m_courseSet.m_CourseID); //向表中加入CourseID字段
pDoc->m_courseSet.MoveNext();
}
}
m_ctlCourseList.SetCurSel(0);
}
void CSectionForm::OnSelendokCourselist()
{
if (!m_pSet->IsOpen())
return;
m_ctlCourseList.GetLBText(m_ctlCourseList.GetCurSel(),
m_pSet->m_strCourseIDParam);
m_pSet->Requery(); //重新查詢
if (m_pSet->IsEOF())
{
m_pSet->SetFieldNull(&(m_pSet->m_CourseID), FALSE);
m_pSet->m_CourseID = m_pSet->m_strCourseIDParam;
}
UpdateData(FALSE);
}
在CSectionForm::OnInitialUpdate函數的開頭部分,調用了CRecordset::Open建立CCourseSet記錄集,在調用Open函數之前,指定了按CourseID字段排序記錄集。關于調用Open函數的一些前因后果在前面已經解釋過了,讀者應該不難分析。接下來的代碼讀者可能就看不懂了,為什么在記錄集的m_strFilter過濾字符串中會有一個“?”號呢。
這是因為在本例中使用了“參數化記錄集”技術。在記錄集的m_strFilter和m_strSort中,可以用“?”號作為參數使用,這樣在指定過濾器和排序時可以更具靈活性。例如,在OnInitialUpdate函數中,是這樣指定過濾器的:
m_pSet->m_strFilter = "CourseID = ?";
在調用Open或Requery時,“?”將會被CSectionSet::m_strCourseIDParam中的內容取代。例如,如果指定m_strCourseIDParam為“MATH101”,則m_strFilter將變成"CourseID = MATH101"。這樣用戶只要指定了m_strCourseIDParam,就可以定制過濾器。象m_strCourseIDParam這樣的成員被稱作參數數據成員,同域數據成員一樣,它是記錄集所特有的。ClassWizard不支持參數數據成員,用戶只能手工加入之,且其名字由用戶自己確定。
參數替換的工作實際上是由CSectionSet::DoFieldExchange中的RFX函數完成的,在DoFieldExchange函數的末尾,我們加入了下面兩行:
pFX->SetFieldType(CFieldExchange::param);
RFX_Text(pFX, "CourseIDParam", m_strCourseIDParam);
DoFieldExchange可以識別域數據成員和參數數據成員。第一行調用用來表明隨后的RFX函數用于參數替換。第二行是一個用于m_strCourseIDParam參數的RFX函數。RFX第二個參數的名字可由用戶確定,這里指定其為“CourseIDParam”。
用戶可以在m_strFilter和m_strSort中使用一個或多個參數。在有多個參數的情況下,要注意RFX函數的調用次序應與參數出現的次序相對應。框架規定,用戶應該先在m_strFilter中安排參數,然后才是m_strSort。
CRecordset有兩個數據成員m_nFields和m_nParams分別用來統計域數據成員和參數數據成員的數目。前者由ClassWizard自動計數,而后者必需由用戶來維護。在CSectionSet的構造函數中,m_nParams被置為1,因為只有一個參數數據成員。
現在讓我們繼續研究OnInitialUpdate。在調用基類的OnInitialUpdate以前,在程序中有這樣一行代碼:
m_pSet->m_pDatabase = pDoc->m_courseSet.m_pDatabase;
m_pDatabase是CRecordset的公共成員,它是指向CDatabase對象的指針。如果應用程序使用了兩個以上的記錄集,在缺省情況下,每個記錄集都會創建一個代表同一數據源的CDatabase對象(參見10.5.4),這顯然沒有必要。上面一行代碼使CSectionSet記錄集共享由CCourseSet記錄集創建的CDatabase對象,這樣,當在CRecordView:: OnInitialUpdate中調用CRecordset::Open打開CSectionSet記錄集時,就不會再創建CDatabase對象了。
接下來,程序把CCourseSet的每一個記錄的CourseID字段都加入到組合框中。
當用戶在組合框中選擇了新的CourseID時,在CSectionForm::OnSelendokCourselist中,就會把用戶選擇的CourseID設置到m_strCourseIDParam中,然后調用CRecordset::Requery按照定制的過濾器和排序重新查詢和建立記錄集。如果重新建立的記錄集為空,則說明Section表中沒有與指定的CourseID相對應的記錄,這時記錄集被自動設置為NULL。在程序中,調用了CRecordset:: SetFieldNull把m_CourseID字段設置為非空。最后,調用CRecordView::UpdateData更新表單中的控件。
從程序中不難看出,使用參數化記錄集的最大好處是可以在程序運行期間方便地指定過濾器和排序,這大大提高了程序的用戶定制查詢能力。
編譯并運行Enroll,試試新增加的功能。
10.7.4 Enroll的第三個版本
Enroll的第三個版本支持記錄的添加和刪除,該版本也演示了對數據庫異常的處理。請讀者先運行VC5.0提供的第三步Enroll程序看一看。第三版Enroll的Record菜單中多了三個命令:Add、Delete和Refresh,它們分別用來添加、刪除和刷新記錄。
當用戶選擇Add命令后,就進入了添加模式。這時除Course組合框外,所有的字段都被清除。用戶可以在各編輯框內輸入新記錄的字段值,然后移動到別的記錄上,這樣就把新的記錄保存到數據源中。用戶也可以通過再次選擇Add命令來保存新加的記錄。
當用戶選擇Delete命令后,當前記錄被刪除,程序會自動滾動到下一個記錄上。
Refresh命令用來放棄記錄的修改或添加操作,同時,恢復原記錄的內容。
現在就讓我們開始在上一小節Enroll的基礎上制作新版本。若當前工程不是Enroll,請讀者打開上一小節創建的Enroll工程。
首先要為Record菜單添加三個菜單項,菜單項的各項屬性在表10.6中列出。請把這三個菜單項加到Record菜單的開頭,并且用一個分隔線和后面的命令隔開。
表10.6
| Caption | ID | Prompt |
| &Add | ID_RECORD_ADD | Add a new section |
| &Refresh | ID_RECORD_REFRESH | Cancel changes on form,or cancel Add |
| &Delete | ID_RECORD_DELETE | Delete section |
接著,用ClassWizard為上面三個命令創建處理函數,函數名為缺省的。另外,需要為記錄視圖編寫新的OnMove函數來處理滾動命令,這是因為原來的OnMove函數沒有添加記錄的功能。在ClassWizard的CSectionForm類的Messages列表中可以找到OnMove函數,請讀者雙擊并建立該函數。
在CSectionForm::OnMove函數處理滾動命令時,必需要有一個標志來判斷當前是否處于添加模式,以便向數據源中加入新記錄或進行普通的滾動處理。請讀者在CSectionForm類的定義中的適當位置加入下面兩行:
protected:
BOOL m_bAddMode;
在前兩個版本的Enroll中,Section編輯框都是只讀的,但在添加記錄時,必需允許用戶修改Section編輯框。這是因為SectionNo字段是Section表的主關鍵字,它的值必需唯一,如果在加入新記錄時不改變原來的SectionNo字段,那么將會因為主關鍵字重復而導致異常產生。顯然,我們需要一個CEdit對象來控制IDC_SECTION編輯框,請讀者用ClassWizard為CSectionForm類加入一個與IDC_SECTION對應的CEdit型成員變量,變量的名字為m_ctlSection。
最后,請讀者按清單10.13修改程序。
清單10.13 CSectionForm類的部分源代碼
CSectionForm::CSectionForm()
: CRecordView(CSectionForm::IDD)
{
. . .
m_bAddMode = FALSE;
}
void CSectionForm::OnSelendokCourselist()
{
if (!m_pSet->IsOpen())
return;
m_ctlCourseList.GetLBText(m_ctlCourseList.GetCurSel(),
m_pSet->m_strCourseIDParam);
if (!m_bAddMode)
{
m_pSet->Requery();
if (m_pSet->IsEOF())
{
m_pSet->SetFieldNull(&(m_pSet->m_CourseID), FALSE);
m_pSet->m_CourseID = m_pSet->m_strCourseIDParam;
}
UpdateData(FALSE);
}
}
void CSectionForm::OnRecordAdd()
{
if (m_bAddMode) //如果已處于添加模式,則完成添加操作
OnMove(ID_RECORD_FIRST);
CString strCurrentCourse = m_pSet->m_CourseID;
m_pSet->AddNew();
m_pSet->SetFieldNull(&(m_pSet->m_CourseID), FALSE);
m_pSet->m_CourseID = strCurrentCourse;
m_bAddMode = TRUE;
m_ctlSection.SetReadOnly(FALSE);
UpdateData(FALSE); //更新表單視圖
}
void CSectionForm::OnRecordDelete()
{
TRY
{
m_pSet->Delete();
}
CATCH(CDBException, e)
{
AfxMessageBox(e->m_strError);
return;
}
END_CATCH
m_pSet->MoveNext(); //滾動到下一個記錄
if (m_pSet->IsEOF()) //如果滾出了記錄集的邊界,則滾動到最后一個記錄
m_pSet->MoveLast();
if (m_pSet->IsBOF()) //如果記錄變空了,則清除域數據成員
m_pSet->SetFieldNull(NULL);
UpdateData(FALSE); //更新表單視圖
}
void CSectionForm::OnRecordRefresh()
{
if (m_bAddMode == TRUE)
{
m_pSet->Move(AFX_MOVE_REFRESH); //取消添加模式
m_ctlSection.SetReadOnly(TRUE);
m_bAddMode = FALSE;
}
UpdateData(FALSE); //更新表單視圖
}
BOOL CSectionForm::OnMove(UINT nIDMoveCommand)
{
if (m_bAddMode)
{
if (!UpdateData())
return FALSE;
TRY
{
m_pSet->Update();
}
CATCH(CDBException, e)
{
AfxMessageBox(e->m_strError);
return FALSE;
}
END_CATCH
m_pSet->Requery(); //重新查詢,使新加的記錄對用戶可見
UpdateData(FALSE);
m_ctlSection.SetReadOnly(TRUE);
m_bAddMode = FALSE;
return TRUE;
}
else
{
return CRecordView::OnMove(nIDMoveCommand);
}
}
我們先來看看Add命令的處理函數CSectionForm::OnRecordAdd函數。在該函數中,最重要的代碼是調用CRecordset::AddNew進入添加模式。其余代碼的解釋如下:
如果已處于添加模式,則調用CSectionForm::OnMove滾動到別的記錄上,這導致新記錄被保存到數據源中。通過滾動到別的記錄上來完成添加操作是一種常用的方法。其實,缺省的CRecordView::OnMove就可以完成這一功能(參見10.5.6和10.6),但CSectionForm::OnMove有另外的考慮(見下面的說明)。
保存當前記錄的CourseID,并將它作為新記錄的缺省值。
調用CEdit::SetReadOnly(FALSE)把Section編輯框改成可輸入的,以便用戶輸入新的值。
CSectionForm::OnMove負責處理滾動命令。與缺省的CRecordView::OnMove函數不同的是,該函數對于添加模式下的滾動進行了重新處理:
在調用CRecordset::Update把新記錄保存到數據源后,調用CRecordset::Requery重新查詢記錄集。這樣做的原因是Enroll使用的是快照型記錄集,快照不反映用戶添加的記錄,所以需要調用Requery重新查詢以把新加的記錄包含進記錄集中。在調用Requery后,會自動滾動到第一個記錄上,所以在添加模式下滾動記錄總是滾動動到第一個記錄上。
在調用CRecordset::Update時,對可能發生的異常進行了處理。這里將直接輸出異常信息。
在調用Requery后,要調用CRecordView::UpdateData(FALSE)來更新表單,并調用CEdit::SetReadOnly(TRUE)使Sectioin編輯框變成只讀的。
在Delete命令的處理函數CSectionForm::OnDelete中調用了CRecordset::Delete來刪除記錄,并對可能發生的異常進行了處理。在調用Delete后,滾動記錄到新的位置上以跳過被刪除的記錄。
Refresh命令的處理函數CSectionForm::OnRefresh用來放棄修改或添加記錄的操作。對該函數的解釋為:
如果當前處于添加模式,則調用CRecordset::Move(AFX_MOVE_REFRESH)取消添加模式并恢復域數據成員的原值(參見10.5.6),把Section編輯框設置成只讀的。
調用CRecordView::UpdateData(FALSE)恢復表單視圖中的記錄。
CSectionForm::OnSelendokCourselist函數中多了一個用來判斷當前是否處于添加模式的if語句。如果處于添加模式,那么就不能調用Requery重新查詢,因為此時Course組合框的作用僅僅是讓用戶選擇一個字段值,而不是指定過濾器。
編譯并運行Enroll,試試新增加的功能。
總結
以上是生活随笔為你收集整理的学习Enroll例程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 完结篇 | 吴恩达《序列模型》精炼笔记(
- 下一篇: 成为梵高、毕加索?你最喜欢的人脸识别与神