類 | 描述 |
---|
NumberOfSettings | 包含步驟配置工作表UFormConfig中的列數(shù) |
Worksheet | 告訴類到哪里查找向?qū)У拿坎降男畔?/td> |
CurrentPage | 在向?qū)е写鎯Ξ斍安襟E的值 |
PreviousPage | 基于CurrentPage屬性計算;返回向?qū)е星耙徊襟E的值 |
NextPage | 基于CurrentPage屬性計算;返回向?qū)е邢乱徊襟E的值 |
PreviousButton | 存儲用戶窗體中導(dǎo)航到向?qū)У那耙徊降陌粹o的指針 |
NextButton | 存儲用戶窗體中導(dǎo)航到向?qū)У暮笠徊降陌粹o的指針 |
需要在類中添加更多的屬性。下面的只讀屬性包含cStep對象的集合,包含向?qū)У拿恳徊降男畔ⅰ?br/>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 |
我們首先做的是獲取工作表中已使用的區(qū)域的行數(shù):
numrows = m_oWorksheet.Cells(Rows.Count, 1).End(xlUp).row |
注意,雖然Excel的Worksheet對象有Rows.Count方法,但是在這里不能使用(m_oWorksheet.Rows.Count)。這將返回工作表中的總行數(shù),這樣不僅提供不正確的值,而且也會使Integer變量溢出。
接下來,循環(huán)填充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對象,然后移到內(nèi)部循環(huán)遍歷配置工作表中的列,將它們賦給內(nèi)部的cStep對象的相應(yīng)屬性。這段代碼運行前,已經(jīng)通過NumberOfSettings屬性設(shè)置m_iNumSettings值。
最后,將cStep對象添加到內(nèi)部的集合colReturn中,在該集合中傳遞Order值作為主鍵。
注意,在外部循環(huán)中的第一行代碼,Set m_oStep=New cStep,是重要的。如果忽略該代碼,那么集合中將以四個相同的cStep對象結(jié)束(全部都包含從工作表中讀取的最后一個配置項中的數(shù)據(jù))。這是因為m_oStep對象引用仍然是當前引用,所以每次調(diào)用時都會修改任何已存在的實例。通過使用New關(guān)鍵字,創(chuàng)建新的、單獨的對象實例。
最后,設(shè)置內(nèi)部的m_iNumSteps變量,用來追蹤前一個和下一個可用的命令按鈕,并且最終返回集合:
m_iNumSteps = colReturn.Count Set PageSettings = colReturn |
現(xiàn)在,將注意力轉(zhuǎn)向PreviousButton屬性和NextButton屬性。記得這些屬性的內(nèi)置變量被聲明為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屬性控制每個按鈕是否啟用。當該類首次在客戶端代碼中被創(chuàng)建時,再添加一個方法初始化按鈕:
Public Sub HandleControls() m_oPreviousButton.Enabled = Me.PreviousPage <> 0 m_oNextButton.Enabled = Me.NextPage <> m_iNumSteps + 1End Sub |
到現(xiàn)在為止,我們已經(jīng)創(chuàng)建了相當數(shù)量的代碼,全都存儲在跨越許多類模塊的對象中。通過劃分功能,使維護代碼的工作非常容易。如果需要綁定列表到目前還沒有處理的數(shù)據(jù)源,只需在cListManage類中添加一個新方法。如果需要在數(shù)據(jù)處理過程中添加一個屏幕界面,則在多頁控件中設(shè)計一個新頁面,創(chuàng)建一個新類去存儲屏幕信息,并在配置表中添加一行。
在添加完所有的類模塊并編寫好代碼后,工程資源管理器中的類模塊文件夾應(yīng)該如下圖所示。

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

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

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

3、通過單擊右上方的X按鈕,停止用戶窗體的運行。
給用戶窗體添加導(dǎo)航
導(dǎo)航按鈕在向?qū)?yīng)用程序中具有移動步驟的任務(wù)。但它們也需要放置每個屏幕中的數(shù)據(jù)到其在用戶窗體的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 |
在向?qū)е幸频较乱徊街笆紫刃枰龅氖,保留在當前用戶窗體中輸入的值。StoreData方法決定用戶處于哪一步并基于該位置調(diào)用正確的存儲方法,代碼如下所示:
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 |
這段代碼簡單地從屏幕中接收數(shù)據(jù),并將其放置在cPerson里的相應(yīng)的對象中。
接下來,確定下一頁。(記住,多頁集合是基于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 |
然后,調(diào)用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參數(shù)加或減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 |
唯一的差別是傳遞關(guān)鍵字down到ShowNextPage方法以便向用戶移動到合適的方向。
下面,添加最后一個事件處理來幫助我們使用導(dǎo)航。無論何時改變多頁控件中的頁面,控件的Change事件被觸發(fā)。我們使用事件去捕捉當前頁面的值,并將其存儲在m_oWizard對象的CurrentPage屬性中。
添加下面的代碼到MultiPage1控件的Change事件:
Private Sub MultiPage1_Change() m_oWizard.CurrentPage = MultiPage1.Value + 1End Sub |
現(xiàn)在,讓我們來試試導(dǎo)航的工作。
1、在設(shè)計視圖下打開用戶窗體,單擊標準工具欄中的“運行子程序/用戶窗體”按鈕或按F5鍵。
2、打開用戶窗體后,單擊下一步按鈕移動到向?qū)е械牡诙?已在配置工作表中定義),應(yīng)該是Address屏幕。注意到兩個導(dǎo)航按鈕現(xiàn)在都能用了,如下圖所示。

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

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

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





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