類 | 描述 |
---|
NumberOfSettings | 包含步驟配置工作表UFormConfig中的列數 |
Worksheet | 告訴類到哪里查找向導的每步的信息 |
CurrentPage | 在向導中存儲當前步驟的值 |
PreviousPage | 基于CurrentPage屬性計算;返回向導中前一步驟的值 |
NextPage | 基于CurrentPage屬性計算;返回向導中下一步驟的值 |
PreviousButton | 存儲用戶窗體中導航到向導的前一步的按鈕的指針 |
NextButton | 存儲用戶窗體中導航到向導的后一步的按鈕的指針 |
需要在類中添加更多的屬性。下面的只讀屬性包含cStep對象的集合,包含向導的每一步的信息。
PageSettings屬性存儲該集合,使用HRWizard用戶窗體后臺的客戶端代碼返回一個Collection對象。
PageSettings屬性的代碼如下:
Public Property Get PageSettings() As Collection Dim colReturn As Collection Dim numrows As Integer Dim row As Integer Dim col As Integer Dim sKey As String Set colReturn = New Collection numrows = m_oWorksheet.Cells(Rows.Count, 1).End(xlUp).row For row = 2 To numrows Set m_oStep = New cStep For col = 1 To m_iNumSettings Select Case col Case 1 m_oStep.Order = m_oWorksheet.Cells(row, col).Value sKey = CStr(m_oStep.Order) Case 2 m_oStep.Page = m_oWorksheet.Cells(row, col).Value Case 3 m_oStep.Caption = m_oWorksheet.Cells(row, col).Value End Select Next col colReturn.Add m_oStep, sKey Next row m_iNumSteps = colReturn.Count Set PageSettings = colReturnEnd Property |
我們首先做的是獲取工作表中已使用的區域的行數:
numrows = m_oWorksheet.Cells(Rows.Count, 1).End(xlUp).row |
注意,雖然Excel的Worksheet對象有Rows.Count方法,但是在這里不能使用(m_oWorksheet.Rows.Count)。這將返回工作表中的總行數,這樣不僅提供不正確的值,而且也會使Integer變量溢出。
接下來,循環填充cStep對象集合,代碼如下:
For row = 2 To numrows Set m_oStep = New cStep For col = 1 To m_iNumSettings Select Case col Case 1 m_oStep.Order = m_oWorksheet.Cells(row, col).Value sKey = CStr(m_oStep.Order) Case 2 m_oStep.Page = m_oWorksheet.Cells(row, col).Value Case 3 m_oStep.Caption = m_oWorksheet.Cells(row, col).Value End Select Next col colReturn.Add m_oStep, sKey Next row |
上述代碼中,首先做的是實例化一個新的cStep對象,然后移到內部循環遍歷配置工作表中的列,將它們賦給內部的cStep對象的相應屬性。這段代碼運行前,已經通過NumberOfSettings屬性設置m_iNumSettings值。
最后,將cStep對象添加到內部的集合colReturn中,在該集合中傳遞Order值作為主鍵。
注意,在外部循環中的第一行代碼,Set m_oStep=New cStep,是重要的。如果忽略該代碼,那么集合中將以四個相同的cStep對象結束(全部都包含從工作表中讀取的最后一個配置項中的數據)。這是因為m_oStep對象引用仍然是當前引用,所以每次調用時都會修改任何已存在的實例。通過使用New關鍵字,創建新的、單獨的對象實例。
最后,設置內部的m_iNumSteps變量,用來追蹤前一個和下一個可用的命令按鈕,并且最終返回集合:
m_iNumSteps = colReturn.Count Set PageSettings = colReturn |
現在,將注意力轉向PreviousButton屬性和NextButton屬性。記得這些屬性的內置變量被聲明為WithEvents。當聲明一個對象時使用WithEvents時,可以通過VB代碼窗口的對象框訪問該對象的事件代碼,如下圖所示。

從對象框中選擇m_oNextButton和m_oPreviousButton,在類模塊中插入事件處理代碼塊,并在其中添加代碼如下:
Private Sub m_oNextButton_Click() m_oNextButton.Enabled = Me.NextPage <> m_iNumSteps + 1 m_oPreviousButton.Enabled = Me.PreviousPage <> 0End Sub Private Sub m_oPreviousButton_Click() m_oPreviousButton.Enabled = Me.PreviousPage <> 0 m_oNextButton.Enabled = Me.NextPage <> m_iNumSteps + 1End Sub |
這段代碼基于cStepManager類的NextPage或PreviousPage屬性控制每個按鈕是否啟用。當該類首次在客戶端代碼中被創建時,再添加一個方法初始化按鈕:
Public Sub HandleControls() m_oPreviousButton.Enabled = Me.PreviousPage <> 0 m_oNextButton.Enabled = Me.NextPage <> m_iNumSteps + 1End Sub |
到現在為止,我們已經創建了相當數量的代碼,全都存儲在跨越許多類模塊的對象中。通過劃分功能,使維護代碼的工作非常容易。如果需要綁定列表到目前還沒有處理的數據源,只需在cListManage類中添加一個新方法。如果需要在數據處理過程中添加一個屏幕界面,則在多頁控件中設計一個新頁面,創建一個新類去存儲屏幕信息,并在配置表中添加一行。
在添加完所有的類模塊并編寫好代碼后,工程資源管理器中的類模塊文件夾應該如下圖所示。

編寫HRWizard用戶窗體代碼
現在,我們已經完成了最艱難的工作。是到將對象放進HRWizard用戶窗體里并使這些對象工作的時候了。
打開HRWizard用戶窗體代碼窗口,添加下列模塊級的變量聲明:
Dim m_oEmployee As cPersonDim m_oLM As cListManagerDim m_oWizard As cStepManagerDim m_colSteps As Collection |
雖然我們創建了9個分開的類模塊來運行我們的應用程序,但是許多類都是通過在聲明部分列出來內部使用。使用cPeason類收集新員工的數據,使用cListManager類來填充HRWizard用戶窗體中不同的組合框,使用cStepManager類決定何時且按什么順序顯示哪個屏幕,并控制導航命令按鈕的可用性。最后,使用標準的VBA Collection對象,用于存儲cStepManager對象的PageSettings集合。
初始化應用程序
在HRWizard用戶窗體的Initialize事件中,將初始化自定義的對象并添加代碼來設置向導、列表和顯示用戶窗體。
在UserForm_Initialize事件中添加下列代碼:
Private Sub UserForm_Initialize() Set m_oEmployee = New cPerson Set m_oLM = New cListManager Set m_oWizard = New cStepManager InitWizard InitLists InitFormEnd Sub |
下面,創建三個Init函數,分別設置向導、列表管理器和用戶窗體對象。
初始化向導
在用戶窗體代碼窗口添加新的子程序,將其命名為InitWizard,并添加下列代碼:
Private Sub InitWizard() With m_oWizard Set .Worksheet = Sheets("UFormConfig") .NumberOfSettings = 3 Set m_colSteps = .PageSettings Set .PreviousButton = Me.cmdPrevious Set .NextButton = Me.cmdNext .CurrentPage = MultiPage1.Value + 1 End WithEnd Sub |
上述代碼完成下列工作:
- 告訴cStepManager對象在哪里找到配置數據
Set .Worksheet = Sheets("UFormConfig") |
- 告訴cStepManager對象獲取數據的列數
- 放置頁設置到集合里
Set m_colSteps = .PageSettings |
- 設置導航按鈕
Set .PreviousButton = Me.cmdPreviousSet .NextButton = Me.cmdNext |
- 設置當前頁
.CurrentPage = MultiPage1.Value + 1 |
因為多頁控件的Page集合基于0,所以使用多頁控件的Value屬性加1來設置CurrentPage屬性。
在初始化用戶窗體之前,必須設置cStepManager對象,因為該用戶窗體使用PageSettings集合來設置它自已。
初始化組合框
下一步是將組合框綁定到它們各自的列表。該列表被存儲在ListMgr工作表中。
插入一個新的子程序,并將其命名為InitLists,添加下列代碼:
Private Sub InitLists() With m_oLM .BindListToRange "Departments", Me.cboDept .BindListToRange "Locations", Me.cboLocation .BindListToRange "NetworkLvl", Me.cboNetworkLvl .BindListToRange "ParkingSpot", Me.cboParkingSpot .BindListToRange "YN", Me.cboRemoteAccess End WithEnd Sub |
同樣,上述代碼也非常簡單,它們為應用程序中的每個列表調用cListManager對象的BindListToRange方法。
初始化用戶窗體
在設置應用程序中的最后一步是初始化用戶窗體自身。創建一個名為InitForm的新子程序,并添加下列代碼:
Private Sub InitForm() Dim iFirstPage As Integer Dim i As Integer Dim iPageCount As Integer iFirstPage = m_colSteps("1").Order - 1 Me.MultiPage1.Value = iFirstPage Me.MultiPage1.Pages((m_colSteps("1").Page) - 1).Caption = m_colSteps("1").Caption m_oWizard.HandleControls iPageCount = MultiPage1.Pages.Count For i = 1 To iPageCount - 1 MultiPage1.Pages(i).Visible = False NextEnd Sub |
這里,設置多頁控件的Value屬性為PageSetting集合(m_colSteps)的項目(其鍵值為1),并設置其標題:
iFirstPage = m_colSteps("1").Order - 1 Me.MultiPage1.Value = iFirstPage Me.MultiPage1.Pages((m_colSteps("1").Page) - 1).Caption = m_colSteps("1").Caption |
記住,我們傳遞Order屬性的值作為鍵值,這使得它非常容易去判斷要移動至哪頁。當設置多頁控件的Value屬性時,正使用相對應的值激活該頁。在這里,該值為1.
然后調用m_oWizard對象的HandleControls方法初始化導航按鈕為正確的設置:
接下來,隱藏除第一頁外的所有頁:
iPageCount = MultiPage1.Pages.Count For i = 1 To iPageCount - 1 MultiPage1.Pages(i).Visible = False Next |
記住,多頁控件的Page集合是基于0的,因此通過以1開始循環計數器,保持該頁面可見。
此時,可以運行用戶窗體。
1、在VBE中,雙擊工程資源管理器窗口的用戶窗體。
2、單擊標準工具欄中的“運行子過程/用戶窗體”按鈕或者按F5鍵,如下圖所示。

注意,下圖中在選項卡中出現的標題,并且前一步按鈕被禁用。

再看看Department組合框,綁定Departments命名區域到該組合框。

3、通過單擊右上方的X按鈕,停止用戶窗體的運行。
給用戶窗體添加導航
導航按鈕在向導應用程序中具有移動步驟的任務。但它們也需要放置每個屏幕中的數據到其在用戶窗體的cPerson對象里的位置的能力。
在cmdNext_Click中添加下列代碼:
Private Sub cmdNext_Click() Dim iNext As Integer StoreData iNext = m_oWizard.NextPage Me.MultiPage1.Value = m_colSteps(CStr(iNext)).Order - 1 Me.MultiPage1.Pages((m_colSteps(CStr(iNext)).Page) - 1).Caption = m_colSteps(CStr(iNext)).Caption ShowNextPage "up"End Sub |
在向導中移到下一步之前首先需要做的是,保留在當前用戶窗體中輸入的值。StoreData方法決定用戶處于哪一步并基于該位置調用正確的存儲方法,代碼如下所示:
Private Sub StoreData() Select Case m_oWizard.CurrentPage Case 1 StorePerson Case 2 StoreAddress Case 3 StoreEquipment Case 4 StoreAccess End SelectEnd Sub |
上述代碼中的存儲方法的代碼如下:
Private Sub StorePerson() With m_oEmployee .FName = Me.txtFname.Value .MidInit = Me.txtMidInit.Value .LName = Me.txtLname.Value If Len(Me.txtDOB.Value & "") > 0 Then .DOB = Me.txtDOB.Value End If .SSN = Me.txtSSN.Value .Department = Me.cboDept.Text .JobTitle = Me.txtJobTitle.Value .Email = Me.txtEmail.Value End WithEnd Sub Private Sub StoreAddress() With m_oEmployee.Address .StreetAddress = Me.txtStreetAddr.Value .StreetAddress2 = Me.txtStreetAddr2.Value .City = Me.txtCity.Value .State = Me.txtState.Value .ZipCode = Me.txtZip.Value .PhoneNumber = Me.txtPhone.Value .CellPhone = Me.txtCell.Value End WithEnd Sub Private Sub StoreEquipment() Dim opt As MSForms.OptionButton With m_oEmployee.Equipment For Each opt In Me.fraPCType.Controls If opt.Value = True Then .PCType = opt.Caption Exit For End If Next For Each opt In Me.fraPhoneType.Controls If opt.Value = True Then .PhoneType = opt.Caption Exit For End If Next .Location = Me.cboLocation.Text If Me.chkFaxYN = True Then .FaxYN = "Y" Else .FaxYN = "N" End If End WithEnd Sub Private Sub StoreAccess() Dim opt As MSForms.OptionButton With m_oEmployee.Access If Len(Me.cboNetworkLvl.Text & "") > 0 Then .NetworkLevel = CInt(Me.cboNetworkLvl.Text) End If .ParkingSpot = Me.cboParkingSpot.Text .RemoteYN = Me.cboRemoteAccess.Text For Each opt In Me.fraBuilding.Controls If opt.Value = True Then .Building = opt.Caption Exit For End If Next End WithEnd Sub |
這段代碼簡單地從屏幕中接收數據,并將其放置在cPerson里的相應的對象中。
接下來,確定下一頁。(記住,多頁集合是基于0的,因此從Order屬性中減1以獲得下一頁的值)
iNext = m_oWizard.NextPage Me.MultiPage1.Value = m_colSteps(CStr(iNext)).Order - 1 Me.MultiPage1.Pages((m_colSteps(CStr(iNext)).Page) - 1).Caption = m_colSteps(CStr(iNext)).Caption |
然后,調用ShowNextPage方法,告訴它我們想移動的方式:
ShowNextPage方法的代碼如下:
Private Sub ShowNextPage(Direction As String) Dim iCurrPage As Integer Dim iUpDown As Integer iCurrPage = MultiPage1.Value If LCase(Direction) = "up" Then iUpDown = 1 Else iUpDown = -1 End If MultiPage1.Pages(iCurrPage + iUpDown).Visible = True MultiPage1.Pages(iCurrPage).Visible = FalseEnd Sub |
這個方法查找CurrentPage屬性的值,基于傳遞給該方法的Direction參數加或減1。
cmdPrevious按鈕的Click事件看起來非常相似:
Private Sub cmdPrevious_Click() Dim iPrevious As Integer StoreData iPrevious = m_oWizard.PreviousPage Me.MultiPage1.Value = m_colSteps(CStr(iPrevious)).Order - 1 Me.MultiPage1.Pages((m_colSteps(CStr(iPrevious)).Page) - 1).Caption = m_colSteps(CStr(iPrevious)).Caption ShowNextPage "down"End Sub |
唯一的差別是傳遞關鍵字down到ShowNextPage方法以便向用戶移動到合適的方向。
下面,添加最后一個事件處理來幫助我們使用導航。無論何時改變多頁控件中的頁面,控件的Change事件被觸發。我們使用事件去捕捉當前頁面的值,并將其存儲在m_oWizard對象的CurrentPage屬性中。
添加下面的代碼到MultiPage1控件的Change事件:
Private Sub MultiPage1_Change() m_oWizard.CurrentPage = MultiPage1.Value + 1End Sub |
現在,讓我們來試試導航的工作。
1、在設計視圖下打開用戶窗體,單擊標準工具欄中的“運行子程序/用戶窗體”按鈕或按F5鍵。
2、打開用戶窗體后,單擊下一步按鈕移動到向導中的第二步(已在配置工作表中定義),應該是Address屏幕。注意到兩個導航按鈕現在都能用了,如下圖所示。

3、單擊前一步按鈕導航回到Personal屏幕,此時前一步按鈕不再是活動的了。
4、單擊下一步按鈕直至最后一個屏幕(已在配置工作表中定義),應該是NetWork Access屏幕,此時下一步按鈕不再能夠使用,如下圖所示。

5、通過單擊右上方的X按鈕,停止用戶窗體的運行。
保存員工記錄
至此,我們已經做了大量的工作,獲得了一些完美干凈的功能從自定義對象提供給用戶窗體。唯一沒有做的就是將數據保存到EmpData工作表。
一般來說,可以創建一個子程序,將其命名如SaveData(),將從cmdSave_Click事件中調用該程序,但是cHRData類已經具有了SaveEmployee方法。我們可以直接從cmdSave_Click中調用而不需要再創建保存函數。
在cmdSave_Click事件中插入下列代碼:
Private Sub cmdSave_Click() Dim oHRData As cHRData Set oHRData = New cHRData Set oHRData.Worksheet = Sheets("EmpData") oHRData.SaveEmployee m_oEmployee Set oHRData = NothingEnd Sub |
在設置Worksheet屬性之后,以便于cHRData對象知道在哪里保存數據,調用SaveEmployee方法,傳遞m_oEmployee對象,那里包含要保存的所有數據。
清理
我們幾乎已經獲得了一個完整的應用程序。下面讓我們添加Cancel按鈕的代碼并在用戶窗體的Terminate事件中放置清理代碼。
在cmdCancel按鈕的Click事件中添加下面的代碼行:
Private Sub cmdCancel_Click() Unload MeEnd Sub |
這行代碼簡單地卸載用戶窗體而不保存任何數據。
現在我們清除HRWizard用戶窗體使用的對象。在UserForm_Terminate事件處理中添加下列代碼:
Private Sub UserForm_Terminate() Set m_oEmployee = Nothing Set m_oLM = Nothing Set m_oWizard = NothingEnd Sub |
下面再添加一個簡單的函數用來打開向導窗體。在VBE中,添加一個標準模塊,在其中添加下列代碼:
Sub StartWizard() HRWizard.ShowEnd Sub |
測試HRWizard應用程序
測試時間到了!我們在向導中的每一屏幕中輸入數據,并將其保存到EmpData工作表中。
從Excel工作簿中,從宏對話框中運行StartWizard子程序,如下圖所示。

下圖中顯示了一些簡單的輸入值以及在EmpData工作表中保存的數據。





學習小結
- 學習優秀的示例是一種好的學習方法。不僅能夠開闊視野,而且能夠學到好的編程習慣和好的技巧,并且在實踐中借鑒他人的做法,能夠增加經驗,少走彎路。
- 輸入時時常編譯代碼,及時找出一些錯誤,例如變量名拼寫錯誤、過程名相同、缺少End With等。
- 如果調試總是有錯,但總是覺得是對的,就是總是找不出錯誤在哪兒,累了,休息一會,做點別的事兒,再回來。不要覺得自已沒錯,既然程序運行有錯,那一定是有錯。休息一下,換換腦筋,就會發現并改正錯誤了。
- 示例中建立起了自已的對象層次模型。
- 通過使用類,可以更好地組織和管理代碼。雖然編寫類模塊可能會花費一些時間,但這些努力絕對是值得的。并且,很多類都可以在其它地方重復使用。