学习Excel技术,关注微信公众号:
excelperfect
在开始阅读本文前,请先学习下列内容:
Excel实战技巧66:创建向导样式的数据输入窗体1
Excel实战技巧66:创建向导样式的数据输入窗体2
Excel实战技巧66:创建向导样式的数据输入窗体3
继续介绍组成示例应用程序的类模块代码。
管理列表
在HRWizard用户窗体中输入的一些数据是通过组合框控件显示给用户的。HRWizard工作簿文件包含一个名为ListMgr的工作表,其中包含每个列表的数据,这些数据存储在ListMgr工作表的命名区域。
cListManager类包含的函数可以从这些命名区域中获取数据填充组合框,同时也有一个将列表绑定到VBA Collection对象的方法。
插入一个新的类模块,将其命名为cListManager,在其中添加下面两个方法:
Public Sub BindListToRange(ListRangeName As String, TheCombo As MSForms.ComboBox)
TheCombo.RowSource = ListRangeName
End Sub
Public Sub BindListToCollection(TheCollection As Collection, TheCombo As MSForms.ComboBox)
Dim iNumItems As Integer
Dim i As Integer
iNumItems = TheCollection.Count
For i = 1 To iNumItems
TheCombo.AddItem TheCollection(i)
Next i
End Sub
BindListToRange方法接受命名区域名称字符串值和ComboBox对象,设置组合框的RowSource属性为命名区域。BindListToCollection方法简单地遍历集合并调用组合框的AddItem方法添加数据项。
数据类
数据类被命名为cHRData,这是一个专门为HRWizard应用程序设计的类。
插入一个新的类模块,将其命名为cHRData。在其中添加下面的模块级变量、一个属性和一个方法。
Private m_oWorksheet As Worksheet
Private m_lngNewRowNum As Long
Private m_oEmployee As cPerson
Private m_oXL As cExcelUtils
Public Property Get Worksheet() As Worksheet
Set Worksheet = m_oWorksheet
End Property
Public Property Set Worksheet(newWorksheet As Worksheet)
Set m_oWorksheet = newWorksheet
End Property
Public Function SaveEmployee(Employee As cPerson) As Boolean
Dim blnReturn As Boolean
If m_oWorksheet Is Nothing Then
GoTo Exit_Function
EndIf
m_lngNewRowNum =m_oXL.FindEmptyRow(m_oWorksheet)
Set m_oEmployee = Employee
SaveEmpData
SaveAddressData
SaveEquipmentData
SaveAccessData
Exit_Function:
SaveEmployee = blnReturn
Exit Function
End Function
添加下列类初始化和清理代码:
Private Sub Class_Initialize()
Set m_oXL = New cExcelUtils
End Sub
Private Sub Class_Terminate()
Set m_oXL = Nothing
End Sub
Worksheet属性让我们定义工作簿中存储数据的地方。当传递cPerson对象时SaveEmployee方法为我们做一些事情:
Public Function SaveEmployee(EmployeeAs cPerson) As Boolean
检查是否设置了Worksheet属性,以便知道在哪里保存数据:
If m_oWorksheet Is Nothing Then
GoTo Exit_Function
End If
使用cExcelUtils对象找到第一个空行:
m_lngNewRowNum =m_oXL.FindEmptyRow(m_oWorksheet)
(注:cExcelUtils对象是一个通用类,用来实现通用的功能。在上文表中未列出该类名,但在示例工作簿中含有该类模块)
接下来,将传递给该方法的cPerson对象赋值给私有的用于不同的保存函数的模块级cPerson对象:
Set m_oEmployee = Employee
最后,触发一些保存函数,每个数据对象一个:
SaveEmpData
SaveAddressData
SaveEquipmentData
SaveAccessData
Save方法简单地将存储在cPerson对象(及其内部的数据对象)中的数据转换到EmpData工作表中的单元格。在cHRData类模块中添加下列Save方法:
Private Sub SaveEmpData()
With m_oWorksheet
.Cells(m_lngNewRowNum, 1).Value =m_oEmployee.ID
.Cells(m_lngNewRowNum, 2).Value =m_oEmployee.FName
.Cells(m_lngNewRowNum, 3).Value =m_oEmployee.MidInit
.Cells(m_lngNewRowNum, 4).Value =m_oEmployee.LName
.Cells(m_lngNewRowNum, 5).Value =m_oEmployee.DOB
.Cells(m_lngNewRowNum, 6).Value =m_oEmployee.SSN
.Cells(m_lngNewRowNum, 7).Value =m_oEmployee.JobTitle
.Cells(m_lngNewRowNum, 8).Value =m_oEmployee.Department
.Cells(m_lngNewRowNum, 9).Value =m_oEmployee.Email
End With
End Sub
Private Sub SaveAddressData()
With m_oWorksheet
.Cells(m_lngNewRowNum, 10).Value =m_oEmployee.Address.StreetAddress
.Cells(m_lngNewRowNum, 11).Value =m_oEmployee.Address.StreetAddress2
.Cells(m_lngNewRowNum, 12).Value =m_oEmployee.Address.City
.Cells(m_lngNewRowNum, 13).Value =m_oEmployee.Address.State
.Cells(m_lngNewRowNum, 14).Value =m_oEmployee.Address.ZipCode
.Cells(m_lngNewRowNum, 15).Value =m_oEmployee.Address.PhoneNumber
.Cells(m_lngNewRowNum, 16).Value =m_oEmployee.Address.CellPhone
End With
End Sub
Private Sub SaveEquipmentData()
With m_oWorksheet
.Cells(m_lngNewRowNum, 17).Value =m_oEmployee.Equipment.PCType
.Cells(m_lngNewRowNum, 18).Value =m_oEmployee.Equipment.PhoneType
.Cells(m_lngNewRowNum, 19).Value =m_oEmployee.Equipment.Location
.Cells(m_lngNewRowNum, 20).Value =m_oEmployee.Equipment.FaxYN
End With
End Sub
Private Sub SaveAccessData()
With m_oWorksheet
.Cells(m_lngNewRowNum, 21).Value =m_oEmployee.Access.Building
.Cells(m_lngNewRowNum, 22).Value =m_oEmployee.Access.NetworkLevel
.Cells(m_lngNewRowNum, 23).Value =m_oEmployee.Access.RemoteYN
.Cells(m_lngNewRowNum, 24).Value =m_oEmployee.Access.ParkingSpot
End With
End Sub
注意,用于获取cPerson对象的内部的Address、Equipment、Access对象数据的语法:
m_oEmployee.Address.StreetAddress
m_oEmployee.Equipment.PCType
m_oEmployee.Access.Building
在一个对象里使用另一个对象可以灵活地分类对象中的信息。
管理向导
创建两个类来帮助管理向导应用程序。第一个非常简单,包含每步的配置数据,接着创建一个类,包含这些“向导步骤”对象的集合,管理向导过程的操作。
插入一个新的类模块,将其命名为cStep,添加下列代码:
Private m_iOrder As Integer
Private m_iPage As Integer
Private m_sCaption As String
Public Property Get Order() AsInteger
Order = m_iOrder
End Property
Public Property Let Order(newOrder As Integer)
m_iOrder = newOrder
End Property
Public Property Get Page() AsInteger
Page = m_iPage
End Property
Public Property Let Page(newPage As Integer)
m_iPage = newPage
End Property
Public Property Get Caption()As String
Caption = m_sCaption
End Property
Public Property Let Caption(newCaptionAs String)
m_sCaption = newCaption
End Property
HRWizard.xlms工作簿包含一个名为UFormConfig的隐藏的工作表,该工作表包含向导中每个步骤的信息。在这里,可以修改步骤的顺序或者插入一个新步骤。
下表列出了cStep类的属性及其描述。
表:cStep属性
图20
下面,设置一个类来管理向导中的步骤。在这个类中,将创建cStep对象的集合,用于追踪我们在处理过程中的哪一步以及共有多少步。
插入一个新的类模块,将其命名为cStepManager,添加下列模块级的变量声明:
Dim m_oStep As cStep
Dim m_iNumSettings As Integer
Dim m_iNumSteps As Integer
Dim m_iCurrentPage As Integer
Dim m_iPreviousPage As Integer
Dim m_iNextPage As Integer
Dim WithEvents m_oPreviousButton As MSForms.CommandButton
Dim WithEvents m_oNextButton As MSForms.CommandButton
Dim m_oWorksheet As Worksheet
通过接下来的一些Integer变量,cStep对象m_oStep用于填充向导步骤的集合。告诉有多少步骤,每步有多少属性,基于用户在向导的位置追踪当前、下一个、前一个步骤。
接下来,有两个设置为MSForms.CommandButton对象类型的变量,它们被声明为WithEvents。我们让cStepManager类维护这些按钮的状态。WithEvents声明来捕获它们的Click事件,并在类里面执行操作。使用Click事件基于用户在向导中的位置决定是否启用按钮。
在cStepManager中添加下面的代码:
Public Property Get NumberOfSettings() As Integer
NumberOfSettings = m_iNumSettings
End Property
Public Property Let NumberOfSettings(newNum As Integer)
m_iNumSettings = newNum
End Property
'worksheet属性:获取/设置包含步骤信息的工作表
Public Property Get Worksheet() As Worksheet
Set Worksheet = m_oWorksheet
End Property
Public Property Set Worksheet(newWorksheet As Worksheet)
Set m_oWorksheet = newWorksheet
End Property
Public Property Get CurrentPage() As Integer
CurrentPage = m_iCurrentPage
End Property
Public Property Let CurrentPage(newPage As Integer)
m_iCurrentPage = newPage
End Property
Public Property Get PreviousPage() As Integer
PreviousPage = m_iCurrentPage - 1
End Property
Public Property Get NextPage()As Integer
NextPage = m_iCurrentPage + 1
End Property
Public Property Set PreviousButton(newPreviousBtn As MSForms.CommandButton)
Set m_oPreviousButton = newPreviousBtn
End Property
Public Property Set NextButton(newNextBtn As MSForms.CommandButton)
Set m_oNextButton = newNextBtn
End Property
下表列出了cStepManager类的属性及其描述。
表:cStepManager属性
图21
下面,需要在该类中添加更多的属性。
下面的只读属性PageSettings包含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 = colReturn
End 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时,可以通过VBE代码窗口的对象框访问该对象的事件代码,如下图22所示。
图22
从对象框中选择m_oNextButton和m_oPreviousButton,在类模块中插入事件处理代码块,并在其中添加代码如下:
Private Sub m_oNextButton_Click()
m_oNextButton.Enabled = Me.NextPage<> m_iNumSteps + 1
m_oPreviousButton.Enabled = Me.PreviousPage<> 0
End Sub
Private Sub m_oPreviousButton_Click()
m_oPreviousButton.Enabled = Me.PreviousPage<> 0
m_oNextButton.Enabled = Me.NextPage<> m_iNumSteps + 1
End Sub
这段代码基于cStepManager类的NextPage或PreviousPage属性控制每个按钮是否启用。当该类首次在客户端代码中被创建时,再添加一个方法初始化按钮:
Public Sub HandleControls()
m_oPreviousButton.Enabled = Me.PreviousPage<> 0
m_oNextButton.Enabled = Me.NextPage<> m_iNumSteps + 1
End Sub
到现在为止,我们已经创建了相当数量的代码,全都存储在跨越许多类模块的对象中。通过划分功能,使维护代码的工作非常容易。如果需要绑定列表到目前还没有处理的数据源,只需在cListManager类中添加一个新方法。如果需要在数据处理过程中添加一个用户界面,则在多页控件中设计一个新页面,创建一个新类去存储用户信息,并在配置表中添加一行。
在添加完所有的类模块并编写好代码后,工程资源管理器中的类模块文件夹应该如下图23所示。
图23
至此,该应用程序的类模块代码编写完毕,可以开始着手编写用户窗体代码了。