前言:在使用Excel时,很多用户都希望有一个合适的数据输入窗体,在该窗体中输入数据后,这些数据会自动放置在工作表的相应单元格中,或者将工作表中的数据提取到窗体中。这里,我找到了Dick的一系列文章并将它们进行了整理,与大家分享。
演示——Excel的记录单
Excel提供了一个名为“记录单”的功能,用来让用户在窗体中输入数据并将数据放入工作表相应位置。如图1所示,单击“数据——记录单”,调出相应的对话框。

图1:Excel的“记录单”
此时,可以在对话框中输入相应的数据,单击“新建”按钮后,所输入的数据会放置到工作表中。然而,记录单有很大的局限,它限制用户能够做的事情并且不能完全定制。
(注意,要调出“记录单”对话框,必须将活动单元格置于列表区域内。如果列表字段多于32个,将不能使用“数据——记录单”命令,用户必须直接在工作表中输入数据。
此外,John Walkenback还开发了一个优秀的记录单增强版本,有兴趣的朋友可以在网上找到该插件。这个增强版本的记录单能够满足绝大多数Excel用户的需要。)
下面介绍的不是一个通用目的的数据记录单,而是为特定的数据库建立的数据录入窗体。其主要目的是为了说明,用户可以使用用户窗体定制满足自身实际情况的输入窗体。
创建用户窗体
本示例的工作表如图1所示,包含着人员的联系信息。本示例的用户窗体如图2所示,用来输入和编辑信息。

图2:数据输入窗体设计
将上面的窗体命名为UContact,其中各控件的名称和Tag属性分别为:
名称(Tag属性)
txtName(0)
cmbXb(1)
txtAddress(2)
txtCity(3)
cmbState(4)
txtZip(5)
scbContact()
cmdSave()
cmdClose()
lblName()
lblXb()
lblAddress()
lblCity()
lblState()
lblZip()
其中,Tag属性指出了工作表中数据相对于列A的偏移量。例如,txtName的Tag属性为0,表明在其中输入的数据就在A列。括号中为空表明该控件没有设置Tag属性。
装载用户窗体时
下面的过程在用户窗体装载时,从工作表中读取数据并填充窗体中的相应控件。
Private Sub PopulateRecord()
Dim lRow As Long
Dim ctlInfo As Control
‘存储当前记录所在的行
lRow = Me.scbContact.Value
With wksContacts.Range("A1")
‘遍历控件
For Each ctlInfo In Me.Controls
‘如果Tag属性为数值,则该控件是数据输入控件
If IsNumeric(ctlInfo.Tag) Then
‘从工作表中获取数据
ctlInfo.Text = .Offset(lRow, ctlInfo.Tag).Value
End If
Next ctlInfo
End With
‘标记清除的记录
Me.IsDirty = False
End Sub
其中,wksContacts为工作表的对象名称。使用Tag属性存储每个字段相对于列A的偏移量,并且使用IsNumeric函数进行测试,以方便以后对窗体的扩展,例如设置Tag属性为字符串型时。代码最后一行中的IsDirty将在随后的内容中讨论。
将窗体数据输入到工作表中
下面的过程与刚讲述的过程几乎相同,只是数据传输的方向相反,即将窗体中的数据输入到工作表相应的单元格中。
Private Sub SaveRecord(Optional ByVal lOffset As Long = 0)
Dim lRow As Long
Dim ctlInfo As Control
‘存储当前记录所在的行
lRow = Me.scbContact.Value + lOffset
With wksContacts.Range("A1")
‘遍历控件
For Each ctlInfo In Me.Controls
‘仅限于对数据输入控件
If IsNumeric(ctlInfo.Tag) Then
‘将值写入单元格
.Offset(lRow, ctlInfo.Tag).Value = ctlInfo.Text
End If
Next ctlInfo
End With
‘重新初始化滚动条设置
DefineScroll
‘标记清除的记录
Me.IsDirty = False
End Sub
在代码中,初始化滚动条以便其值与工作表中的记录保持同步。
重定义滚动条
Private Sub DefineScroll()
Dim rBottom As Range
Dim lRecordCnt As Long
With wksContacts
‘查找列A中最后的单元格
Set rBottom = .Range("A" & .Rows.Count).End(xlUp)
‘如果数据库为空
If rBottom.Row = 1 Then
lRecordCnt = 1 ‘设置一条记录-即新记录
Else
‘设置所有的记录数再加一条新记录
lRecordCnt = .Range("A2", rBottom).Rows.Count + 1
End If
End With
‘设置最小和最大值
Me.scbContact.Min = 1: Me.scbContact.Max = lRecordCnt
End Sub
无论何时将数据写入工作表还是当窗体打开时从工作表中装载数据,我们都需要合适地设置滚动条的最小值和最大值。这样,能够确保从窗体中能够访问所有的记录,同时又能在工作表中添加新记录。
确认记录是否发生了变化
当用户窗体显示记录时,需要知道用户是否对该记录作出了修改,因为仅希望“保存”按钮在记录发生改变时启用。同时,在关闭没有保存的记录时,希望能够警告用户。
这里使用了一个名为CControlEvents的类模块来完成这些任务。
Public WithEvents gTextBox As MSForms.TextBox
Public WithEvents gCombo As MSForms.ComboBox
Private Sub gCombo_Change()
UContact.IsDirty = True
End Sub
Private Sub gTextBox_Change()
UContact.IsDirty = True
End Sub
不能够处理通用的控件对象,因此必须为每窗体中的每类控件(此处即文本框和组合框)创建一个变量。使用其Change事件来对记录作出标记。
众所周知,用户窗体本身就是一个带有用户界面的内置类模块,这意味着能够从用户窗体中创建自定义属性。上面的事件代码使用了在用户窗体中创建的IsDirty属性。
Private mbIsDirty As Boolean
Property Get IsDirty() As Boolean
IsDirty = mbIsDirty
End Property
Property Let IsDirty(bDirty As Boolean)
mbIsDirty = bDirty
Me.cmdSave.Enabled = bDirty
End Property
Property Get过程允许读取IsDirty的值。Property Let过程在模块级变量中存储记录的状态,同时修改保存按钮的Enabled的属性。除非记录发生了变化,否则我们不希望用户启用保存按钮。
用户窗体的初始化事件
下面,我们探讨用户窗体的初始化事件,例如当窗体开启时将会发生什么。首先,声明一个模块级的变量:
Private mcControls As Collection
然后,在用户窗体的Initialize事件代码中输入代码:
Private Sub UserForm_Initialize()
Dim ctlInfo As Control
Dim clsEvents As CControlEvents
Set mcControls = New Collection
‘使用隐藏的工作表中的数据填充组合框
Me.cmbXb.List = wksData.Range("Xb").Value
Me.cmbState.List = wksData.Range("States").Value
‘遍历窗体中的控件
For Each ctlInfo In Me.Controls
‘Tag属性为数值的控件是数据输入控件,是我们操作的控件
If IsNumeric(ctlInfo.Tag) Then
‘创建新类
Set clsEvents = New CControlEvents
Select Case TypeName(ctlInfo)
Case "TextBox"
Set clsEvents.gTextBox = ctlInfo
mcControls.Add clsEvents, CStr(ctlInfo.Tag)
Case "ComboBox"
Set clsEvents.gCombo = ctlInfo
mcControls.Add clsEvents, CStr(ctlInfo.Tag)
End Select
End If
Next ctlInfo
DefineScroll
‘以第一条记录开始
Me.scbContact.Value = Me.scbContact.Min
End Sub
其中,wksData是存放组合框中所需要数据的工作表的对象名称,分别为性别组合框存储的数据的名称为“Xb”,为“省份”组合框存储的数据的名称为“States”。
上述代码遍历用户窗体中的数据输入控件并创建类的实例,以便触发事件且修改IsDirty属性,同时设置滚动条的初始值。
注意,此时会触发PopulateRecord过程执行两次,即执行DefineScroll过程时和为滚动条设置值时,都会触发滚动条的Change事件。
滚动条的Change事件
滚动条能够帮助用户在记录之间移动。当然,不一定需要滚动条。
首先,声明一个模块级的变量:
Private mlLastScrollValue As Long
然后,在滚动条scbContact的Change事件中输入代码:
Private Sub scbContact_Change()
Dim sPrompt As String
Dim sTitle As String
Dim lResp As Long
sPrompt = "保存修改"
sTitle = "记录已经改变"
If Me.IsDirty Then
lResp = MsgBox(sPrompt, vbYesNo, sTitle)
If lResp = vbYes Then
SaveRecord CLng(Me.scbContact.Value > mlLastScrollValue)
End If
End If
PopulateRecord
mlLastScrollValue = Me.scbContact.Value
End Sub
上述过程检查是否需要保存当前记录并给出提示信息,然后基于滚动条的当前值填充记录。模块级的变量mlLastScrollValue包含滚动条先前的
值,以便SaveRecord过程确定是否用户向上或向下滚动过。通常,必须知道是否保存前一条或后一条记录,因为滚动条的值已经变化。
命令按钮的Click事件
“关闭”按钮:
Private Sub cmdClose_Click()
Unload Me
End Sub
“保存”按钮:
Private Sub cmdSave_Click()
If Me.IsDirty Then
SaveRecord
End If
End Sub
最后的成果
下图3是最终的用户窗体运行后的效果:

图3:自定义的用户输入窗体
拖动右侧的滚动条可以在记录之间移动,当拖动到最下方时,可以新建记录。
实例扩展
当然,您可以以上面的示例为基础,修改或扩展窗体,根据实际需要增强其应用功能。 注:完美Excel整理,同时发表于完美Excel:http://www.excelperfect.com
|