找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 5943|回复: 0
打印 上一主题 下一主题
收起左侧

创建向导样式的数据输入窗体

[复制链接]
跳转到指定楼层
楼主
ID:104835 发表于 2016-2-3 23:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
引言:本文来源于《Pro Excel VBA》的第4章的示例,主要为学习VBA用户窗体提供参考。本文“超长”,但如果能够仔细研读,一定会有很丰富的收获。

可以先下载示例文档:

向导样式的输入是相当普遍的技术,用于帮助用户输入较多的或复杂的数据。向导允许将数据分成相互联系的部分,通过按顺序输入数据的过程指导用户。
示例工作簿名称为HRWizard.xlsm,由2个工作表组成,名为EmpData的工作表为员工数据库工作表,名为ListMgr的工作表包含在创建数据输入窗体向导时使用的不同的列表。(其实还有一个名为UFormConfig的工作表,存放着向导步骤的信息)

EmpData工作表被分成4部分:Personal、Address、Equipment、Access,如下列图所示。

员工个人信息

员工地址信息

员工设备信息

员工访问信息
下面的向导窗体将引导用户为新员工输入信息。
布局向导窗体
1、打开VBE,添加新用户窗体。
2、将用户窗体的高度设置为320,宽度为332。
3、将用户窗体重命名为HRWizard。
4、在用户窗体顶部添加标签,将其Caption属性设置为:MyCompany – HRWizard,设置字体为大尺寸,如18pt。
5、在用户窗体中添加一个多页控件。
6、设置该控件的Height属性为216,Width属性为270。
7、将其在用户窗体中居中,在用户窗体底部留出空间。
此时的用户窗体如下图所示。

由于有4个数据集合部分,需要再添加两个页。
1、在多页控件顶部的选项卡中单击右键。
2、在快捷菜单中选择“新建页”,如下图所示。

3、重复上面的步骤。
此时的用户窗体如下图所示。

在用户窗体中添加控件
在“Page1”中添加的控件如下表所示,与EmpData工作表中个人信息列标题一致。
表:HRWizard用户窗体控件

在多页控件的下方添加四个命令按钮。

现在的用户窗体和下图相似。

在“Page2”中添加的控件如下表所示,与EmpData工作表中地址信息列标题一致。
表:Address选项卡控件设置

Page2如下图所示。

在“Page3”中添加的控件如下表所示,与EmpData工作表中设备信息列标题一致。
表:设备选项卡控件设置

Page3如下图所示。

在“Page4”中添加的控件如下表所示,与EmpData工作表中访问信息列标题一致。
表:访问选项卡控件设置

Page4如下图所示。

至此,界面设计完成。
接下来,设置一些类来使用户窗体工作。一开始,可能认为一个与数据记录相联系的类就满足要求了,但我们将在定义类时分解功能区,设计一两个类帮助定义向导步骤。最终,将有一个灵活的向导应用程序,提供非常容易修改步骤的顺序的能力,甚至添加一个步骤也相当简单。
HRWizard类
由于正收集的某些员工信息将被传递给其它部门去处理,因此在自已的类中放置从每个屏幕中获得的数据。也需要一个监控向导步骤的类,同时考虑一个帮助使用ListMgr工作表中数据填充列表的类。下表列出了每个类并描述了其功能。
表:HRWizard应用程序类模块


HRWizard商业对象
下面开始设计商业对象。这些类存储每个对象的数据,包含每个对象的一些商业规则。
在工程中添加一个新的类模块并将其命名为cPerson,再添加另外三个类模块,分别将它们命名为cAddress、cEquipment和cAccess。cPerson对象包含一个cAddress对象、cEquipment对象、一个cAccess对象。要保持它们同步,对这四个商业对象类的每一个都添加一个ID属性。
在每个类中,添加下列模块级的声明:

描述
cPerson包含新记录中的所有个人信息
cAddress包含新记录中的所有地址信息
cEquipment包含新记录中的所有设备信息
cAccess包含新记录中的所有访问信息
cStep包含向导每一步的配置值
cStepMgr 控制向导的操作及管理cStep对象的集合
cListMgr控制用户窗体中填充组合框的列表
cHRData从商业对象中将数据转移到数据库;将数据从数据库发送到商业对象
Private m_lngID As Long Public Property Get ID() As Long ID = m_lngIDEnd Property Public Property Let ID(newID As Long) m_lngID = newIDEnd Property

现在,让我们集中开发cPerson类。每个类实质上对应着先前我们设计的每一个界面。
在cPerson类中添加下列模块级变量声明:

Private m_sFName As StringPrivate m_sMidInit As StringPrivate m_sLName As StringPrivate m_dtDOB As DatePrivate m_sSSN As StringPrivate m_sJobTitle As StringPrivate m_sDepartment As StringPrivate m_sEmail As StringPrivate m_oAddress As cAddressPrivate m_oEquipment As cEquipmentPrivate m_oAccess As cAccess

注意,除了从屏幕设计中的数据输入项外,还包括包含地址、设备和访问信息的对象。
这里首先要做的是初始化cPerson类,设置一些默认值。在Class_Initialize事件中,添加下列代码:

Private Sub Class_Initialize() m_lngID = RandomNumber(100000, 999999) Set m_oAddress = New cAddress Set m_oEquipment = New cEquipment Set m_oAccess = New cAccess SetObjectIDsEnd Sub

上述代码中,设置了私有的ID变量m_lngID为随机的6位数字,并初始化私有的商业对象变量。然后调用私有的函数SetObjectIDs设置所有四个商业对象的ID值为相同的值。添加下列代码到cPerson类中生成随机数字和同步ID字段:

Private Function RandomNumber(upper As Long, lower As Long) As Long '生成一个介于upper和lower之间的随机数 Randomize RandomNumber = Int((upper - lower + 1) * Rnd + lower)End Function Private Sub SetObjectIDs() m_oAddress.ID = m_lngID m_oEquipment.ID = m_lngID m_oAccess.ID = m_lngIDEnd Sub

在ID Property Let函数中添加对上面的过程的调用。这样,如果手工对ID字段赋值,那么所有的商业对象都获取这个新值。最终的ID Property Let过程代码如下:

Public Property Let ID(newID As Long) m_lngID = newID SetObjectIDs '保持所有对象同步使用相同的IDEnd Property

cPerson类的剩余部分非常直观。最终的cPerson类的代码如下:

Property Get FName() As String FName = m_sFNameEnd Property Property Let FName(newFName As String) m_sFName = newFNameEnd Property Property Get MidInit() As String MidInit = m_sMidInitEnd Property Property Let MidInit(newMidInit As String) m_sMidInit = newMidInitEnd Property Property Get LName() As String LName = m_sLNameEnd Property Property Let LName(newLName As String) m_sLName = newLNameEnd Property Property Get DOB() As Date DOB = m_dtDOBEnd Property Property Let DOB(newDOB As Date) m_dtDOB = newDOBEnd Property Property Get SSN() As String SSN = m_sSSNEnd Property Property Let SSN(newSSN As String) m_sSSN = newSSNEnd Property Property Get JobTitle() As String JobTitle = m_sJobTitleEnd Property Property Let JobTitle(newJobTitle As String) m_sJobTitle = newJobTitleEnd Property Property Get Department() As String Department = m_sDepartmentEnd Property Property Let Department(newDepartment As String) m_sDepartment = newDepartmentEnd Property Property Get Email() As String Email = m_sEmailEnd Property Property Let Email(newEmail As String) m_sEmail = newEmailEnd Property Property Get Address() As cAddress Set Address = m_oAddressEnd Property Property Set Address(newAddress As cAddress) Set m_oAddress = newAddressEnd Property Property Get Equipment() As cEquipment Set Equipment = m_oEquipmentEnd Property Property Set Equipment(newEquipment As cEquipment) Set m_oEquipment = newEquipmentEnd Property Property Get Access() As cAccess Set Access = m_oAccessEnd Property Property Set Access(newAccess As cAccess) Set m_oAccess = newAccessEnd Property

至此,已经完成Person数据元素的添加,以及3个对象类属性。同时,想要添加一个属性,返回员工的全名。下面的代码在cPerson中添加只读的FullName属性:

Property Get FullName() As String Dim sReturn As String Dim blnMidInit As Boolean  blnMidInit = Len(m_sMidInit & "") > 0  If blnMidInit Then sReturn = m_sFName & " " & m_sMidInit & " " & m_sLName Else sReturn = m_sFName & " " & m_sLName End If  FullName = sReturnEnd Property

这就是我们所需要的cPerson类。
下面列出其它3个类的代码。
cAddress类:

Private m_lngID As LongPrivate m_sStreetAddress As StringPrivate m_sStreetAddress2 As StringPrivate m_sCity As StringPrivate m_sState As StringPrivate m_sZipCode As StringPrivate m_sPhoneNumber As StringPrivate m_sCellPhone As String Public Property Get ID() As Long ID = m_lngIDEnd Property Public Property Let ID(newID As Long) m_lngID = newIDEnd Property Public Property Get StreetAddress() As String StreetAddress = m_sStreetAddressEnd Property Public Property Let StreetAddress(newAddress As String) m_sStreetAddress = newAddressEnd Property Public Property Get StreetAddress2() As String StreetAddress2 = m_sStreetAddress2End Property Public Property Let StreetAddress2(newAddress2 As String) m_sStreetAddress2 = newAddress2End Property Public Property Get City() As String City = m_sCityEnd Property Public Property Let City(newCity As String) m_sCity = newCityEnd Property Public Property Get State() As String State = m_sStateEnd Property Public Property Let State(newState As String) m_sState = newStateEnd Property Public Property Get ZipCode() As String ZipCode = m_sZipCodeEnd Property Public Property Let ZipCode(newZipCode As String) m_sZipCode = newZipCodeEnd Property Public Property Get PhoneNumber() As String PhoneNumber = m_sPhoneNumberEnd Property Public Property Let PhoneNumber(newPhoneNumber As String) m_sPhoneNumber = newPhoneNumberEnd Property Public Property Get CellPhone() As String CellPhone = m_sCellPhoneEnd Property Public Property Let CellPhone(newCellPhone As String) m_sCellPhone = newCellPhoneEnd Property

cEquipment类:

Private m_lngID As LongPrivate m_sPCType As StringPrivate m_sPhoneType As StringPrivate m_sLocation As StringPrivate m_sFaxYN As String Public Property Get ID() As Long ID = m_lngIDEnd Property Public Property Let ID(newID As Long) m_lngID = newIDEnd Property Public Property Get PCType() As String PCType = m_sPCTypeEnd Property Public Property Let PCType(newPCType As String) m_sPCType = newPCTypeEnd Property Public Property Get PhoneType() As String PhoneType = m_sPhoneTypeEnd Property Public Property Let PhoneType(newPhoneType As String) m_sPhoneType = newPhoneTypeEnd Property Public Property Get Location() As String Location = m_sLocationEnd Property Public Property Let Location(newLocation As String) m_sLocation = newLocationEnd Property Public Property Get FaxYN() As String FaxYN = m_sFaxYNEnd Property Public Property Let FaxYN(newFaxYN As String) m_sFaxYN = newFaxYNEnd Property

cAccess类:

Private m_lngID As LongPrivate m_sBuilding As StringPrivate m_iNetworkLevel As IntegerPrivate m_sRemoteYN As StringPrivate m_sParkingSpot As String Public Property Get ID() As Long ID = m_lngIDEnd Property Public Property Let ID(newID As Long) m_lngID = newIDEnd Property Public Property Get Building() As String Building = m_sBuildingEnd Property Public Property Let Building(newBuilding As String) m_sBuilding = newBuildingEnd Property Public Property Get NetworkLevel() As Integer NetworkLevel = m_iNetworkLevelEnd Property Public Property Let NetworkLevel(newNetworkLevel As Integer) m_iNetworkLevel = newNetworkLevelEnd Property Public Property Get RemoteYN() As String RemoteYN = m_sRemoteYNEnd Property Public Property Let RemoteYN(newRemoteYN As String) m_sRemoteYN = newRemoteYNEnd Property Public Property Get ParkingSpot() As String ParkingSpot = m_sParkingSpotEnd Property Public Property Let ParkingSpot(newParkingSpot As String) m_sParkingSpot = newParkingSpotEnd Property

管理列表
在HRWizard用户窗体中输入的一些数据是通过组合框控件显示给用户的。HRWizard工作簿文件包含一个名为ListMgr的工作表,其中包含每个列表的数据。这些数据存储在ListMgr工作表的命名区域。
cListManager类包含的函数可以从这些命名区域中填充组合框,同时也有一个将列表绑定到VBA Collection对象的方法。
插入一个新的类模块,将其命名为cListManager,在其中添加下面两个方法:

Public Sub BindListToRange(ListRangeName As String, TheCombo As MSForms.ComboBox) TheCombo.RowSource = ListRangeNameEnd 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 iEnd Sub

BindListToRange方法接受区域名称字符串值和ComboBox对象,设置组合框的RowSource属性为命名区域。BindListToCollection方法简单地遍历集合并调用组合框的AddItem方法。
数据类
数据类被命名为cHRData,这是一个专门为HRWizard应用程序设计的类。
插入一个新的类模块,将其命名为cHRData。在其中添加下面的模块级变量、一个属性和一个方法。

Private m_oWorksheet As WorksheetPrivate m_lngNewRowNum As LongPrivate m_oEmployee As cPersonPrivate m_oXL As cExcelUtils Public Property Get Worksheet() As Worksheet Set Worksheet = m_oWorksheetEnd Property Public Property Set Worksheet(newWorksheet As Worksheet) Set m_oWorksheet = newWorksheetEnd Property Public Function SaveEmployee(Employee As cPerson) As Boolean Dim blnReturn As Boolean  If m_oWorksheet Is Nothing Then GoTo Exit_Function End If  m_lngNewRowNum = m_oXL.FindEmptyRow(m_oWorksheet) Set m_oEmployee = Employee  SaveEmpData SaveAddressData SaveEquipmentData SaveAccessData Exit_Function: SaveEmployee = blnReturn Exit FunctionEnd Function

添加下列类初始化和清理代码:

Private Sub Class_Initialize() Set m_oXL = New cExcelUtilsEnd Sub Private Sub Class_Terminate() Set m_oXL = NothingEnd Sub

Worksheet属性让我们定义工作簿中存储数据的地方。当传递cPerson对象时SaveEmployee方法为我们做一些事情:

Public Function SaveEmployee(Employee As cPerson) As Boolean

检查是否设置了Worksheet属性,以便知道在哪里保存数据:

If m_oWorksheet Is Nothing Then GoTo Exit_Function End If

使用cExcelUtils对象找到第一个空行:

m_lngNewRowNum = m_oXL.FindEmptyRow(m_oWorksheet)

接下来,将传递给该方法的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 WithEnd 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 WithEnd 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 WithEnd 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 WithEnd Sub

注意,用于获取cPerson对象的内部的Address、Equipment、Access对象数据的语法:

m_oEmployee.Address.StreetAddressm_oEmployee.Equipment.PCTypem_oEmployee.Access.Building

在一个对象里使用另一个对象可以灵活地分类对象中的信息。
管理向导
创建两个类来帮助管理向导应用程序。第一个非常简单,包含每步的配置数据,接着创建一个类,包含这些“向导步骤”对象的集合,管理向导过程的操作。
插入一个新的类模块,将其命名为cStep,添加下列代码:

Private m_iOrder As IntegerPrivate m_iPage As IntegerPrivate m_sCaption As String Public Property Get Order() As Integer Order = m_iOrderEnd Property Public Property Let Order(newOrder As Integer) m_iOrder = newOrderEnd Property Public Property Get Page() As Integer Page = m_iPageEnd Property Public Property Let Page(newPage As Integer) m_iPage = newPageEnd Property Public Property Get Caption() As String Caption = m_sCaptionEnd Property Public Property Let Caption(newCaption As String) m_sCaption = newCaptionEnd Property

HRWizard.xlms工作簿包含一个名为UFormConfig的工作表,该工作表包含向导中每个步骤的信息。在这里,可以修改步骤的顺序或者插入一个新步骤。
下表列出了cStep类的属性及其描述。
表:cStep属性


下面,设置一个类来管理向导中的步骤。在这个类中,将创建cStep对象的集合,用于追踪我们在处理过程中的哪一步以及共有多少步。
插入一个新的类模块,将其命名为cStepManager,添加下列模块级的变量声明:

描述
Order包含向导处理的顺序里步骤的位置
Page包含与多页控件中相应的页面一致的页号
Caption显示在当前活动页控件中的文本
Dim m_oStep As cStepDim m_iNumSettings As IntegerDim m_iNumSteps As IntegerDim m_iCurrentPage As IntegerDim m_iPreviousPage As IntegerDim m_iNextPage As IntegerDim WithEvents m_oPreviousButton As MSForms.CommandButtonDim WithEvents m_oNextButton As MSForms.CommandButtonDim m_oWorksheet As Worksheet

通过接下来的一些Integer变量,cStep对象m_oStep用于填充向导步骤的集合。告诉有多少步骤,每步有多少属性,基于用户在向导的位置追踪当前、下一个、前一个步骤。
接下来,有两个设置为MSForms.CommandButton对象类型的变量,它们被声明为WithEvents。我们让cStepManager类维护这些按钮的状态。WithEvents声明来捕获它们的Click事件,并在类里面执行操作。使用Click事件基于用户在向导中的位置决定是否启用按钮。
在cStepManager中添加下面的代码:

Public Property Get NumberOfSettings() As Integer NumberOfSettings = m_iNumSettingsEnd Property Public Property Let NumberOfSettings(newNum As Integer) m_iNumSettings = newNumEnd Property '工作表属性:获取/设置包含步骤信息的工作表Public Property Get Worksheet() As Worksheet Set Worksheet = m_oWorksheetEnd Property Public Property Set Worksheet(newWorksheet As Worksheet) Set m_oWorksheet = newWorksheetEnd Property Public Property Get CurrentPage() As Integer CurrentPage = m_iCurrentPageEnd Property Public Property Let CurrentPage(newPage As Integer) m_iCurrentPage = newPageEnd Property Public Property Get PreviousPage() As Integer PreviousPage = m_iCurrentPage - 1End Property Public Property Get NextPage() As Integer NextPage = m_iCurrentPage + 1End Property Public Property Set PreviousButton(newPreviousBtn As MSForms.CommandButton) Set m_oPreviousButton = newPreviousBtnEnd Property Public Property Set NextButton(newNextBtn As MSForms.CommandButton) Set m_oNextButton = newNextBtnEnd Property

下表列出了cStepManager类的属性及其描述。
表:cStepManager属性


需要在类中添加更多的属性。下面的只读属性包含cStep对象的集合,包含向导的每一步的信息。
PageSettings属性存储该集合,使用HRWizard用户窗体后台的客户端代码返回一个Collection对象。
PageSettings属性的代码如下:

描述
NumberOfSettings包含步骤配置工作表UFormConfig中的列数
Worksheet告诉类到哪里查找向导的每步的信息
CurrentPage在向导中存储当前步骤的值
PreviousPage基于CurrentPage属性计算;返回向导中前一步骤的值
NextPage基于CurrentPage属性计算;返回向导中下一步骤的值
PreviousButton存储用户窗体中导航到向导的前一步的按钮的指针
NextButton存储用户窗体中导航到向导的后一步的按钮的指针
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对象获取数据的列数
    .NumberOfSettings = 3
  • 放置页设置到集合里
    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方法初始化导航按钮为正确的设置:

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 "up"

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等。
  • 如果调试总是有错,但总是觉得是对的,就是总是找不出错误在哪儿,累了,休息一会,做点别的事儿,再回来。不要觉得自已没错,既然程序运行有错,那一定是有错。休息一下,换换脑筋,就会发现并改正错误了。
  • 示例中建立起了自已的对象层次模型。
  • 通过使用类,可以更好地组织和管理代码。虽然编写类模块可能会花费一些时间,但这些努力绝对是值得的。并且,很多类都可以在其它地方重复使用。
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

返回列表 发新帖
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表