基于窗体实现Excel表格内容的查询与修改。窗体,为使用者提供了较好的交互体验,使用者可以按照自己的需要实现数据的查询与修改,类似于数据库中的视图,用简洁明了的界面提供给使用者最关注的数据,屏蔽无用的数据信息。本文用一个查询员工信息的例子,简要介绍窗体的使用方法。 员工信息查询的例子 构造这样一个实例:某企业员工信息,包含姓名、性别、出生年月、政治面貌、学历学位等,每个员工的信息组合成一条记录,所有员工信息整合在一个Excel数据表中。样式如下: 图中信息均为随机虚构,请勿对号入座。 该表中只有10名员工信息,且员工信息相对较少,因此直观上看还是比较简洁的。但是设想一下,如果该企业有上千名员工,每个员工信息包含几十甚至上百个字段,那又会是如何一番景象呢?如果去寻找某位员工的某些信息,可能体验就不是那么友好了,需要好好擦亮双眼了。 每位员工应该都有一个唯一的员工号的,作为该员工信息记录的识别码(主键)。为了方便,我在这个例子中并未添加该字段信息,后续查找也是基于姓名来的,这要求姓名不能重名,否则不能查找到所有同名员工的信息。简要说明,能认识到这点足够了,问题很小很容易解决。 想要实现的功能 日常工作中,并不是所有的员工信息都会被经常用到,恰恰相反,经常用到的字段,一般是固定的几个。当表格信息较多时,去寻找某个记录的某些字段信息,是比较耗时耗力的。如果可以根据提供的某条记录的标识符,就可以依据该标识符快速展示出属于该记录的字段数据,并且能够屏蔽其他不关心的数据,那么体验就会大幅提升。 在该例中,我们基于窗体实现了快速信息查询及修改的功能。只需要提供员工姓名,就可快速查找出预设字段的该员工的数据信息。预设字段是我们关注的字段,比如性别、身份证号、政治面貌等。同时,预设字段也可以是表格中不存在的字段,比如年龄,年龄是随时间不断变化的,即使表格中有年龄数据,也可能由于更新不及时等原因,造成数据不准确。在窗体中添加年龄项,通过一定的运算,依据表格中的出生年月计算出年龄。这样,就可以通过姓名,随时查询每个员工的最新年龄信息。 查询示例如下图所示: 通过窗体显示数据信息,既可以屏蔽部分垃圾信息,使数据显示简洁友好,又可以形成固定的计算模板,获取原表格中不存在的字段信息,无需每次查询都要重新计算。除可以显示信息外,窗体也可以作为修改表格数据的渠道,只需要在查询数据显示框内,更新最新数据,提交修改即可。与直接在原表修改相比,因为界面简洁明了,不存在干扰信息,指向性明显,很大程度上降低了修改出错的可能性。 新建窗体 下面抛开具体实例,简要介绍窗体的基本使用方法。 一、新建一个窗体 在开发工具中点击查看代码,在弹出的代码编辑框中点击插入用户窗体,可以看到如下界面: 其中,UserForm1即新建的窗体,只需要在上面添加窗体控件即可。我们可以在属性设置中给窗体修改一个名字,只需要将属性(姓名)设置为需要的名称,例如test,如图5所示: 二、窗体工具箱 默认新建窗体的时候,会同步打开工具箱,这里重点关注工具箱中的五个控件选项,依次是: 选定对象:功能类似于pdf文件中的小手,用来选中某个已建好的控件; 标签:图2中姓名性别等文字标签; 文字框:图2中性别民族等后面紧跟着用于填充具体值的文字框; 复合框:图2中姓名后面紧跟的带有下拉选项的复合框; 命令按钮:图2中查询和提交修改按钮。 三、新建窗体控件 新建一个窗体控件(标签、文本框、复合框等)的时候,只需要从工具箱中选中对应控件按钮,然后在窗体上单击即可,需要对控件进行简单设置。 新建标签:设置标签属性,如图6: 重点修改3个参数值: Caption:标签的文字内容,例如姓名; Font:调节标签文字字体及字号; TextAlign:字体对齐方式:左对齐、居中、右对齐。 其他参数请自行学习。 新建文本框:如图7,重点修改3个参数值: 其中Font和TextAlign分别对应字体字号和文字对齐方式。 属性(名称):指定控件的名称,设置名称后,引用控件后就可以通过姓名。Value和姓名。Name的方式分别引用控件的值和名称了。 新建复合框:复合框的参数设置和文本框类似,重点也是(名称)、Font和TextAlign三个参数。复合框需要指定下拉选项的可选项值,一般通过VBA代码的形式,在窗体初始化的时候指定,后续会详细介绍。 新建命令按钮:同文本框,需要设置(名称)、Font和TextAlign三个参数参数,其中属性(名称)在编写命令提交执行代码的时候,(名称)的值即点击命令按钮时执行的过程的过程名。 三、运行窗体 在窗体设计界面,直接按快捷键F5即可运行。 窗体代码控制 窗体建好后,如何实现窗体功能呢?自然是通过窗体后面编写的VBA代码实现的。 一、窗体初始化 回归到员工信息查询的实例。假如我们已经根据自身需要,建立了满足自身需求的窗体,如图8所示: 窗体建立好后,需要将其运行起来。只需要双击窗体空白处,即可跳转到窗体代码编辑页面,会自动生成一个窗体初始化过程: PrivateSubUserFormInitialize()EndSub 我们只需要在该过程中填写相关内容,完成窗体初始化。在员工信息查询实例中,初始化过程只需要执行一个操作,即复合框可选内容的填充。从功能角度来讲,就是读取所有员工姓名信息,提供姓名下拉框姓名选项。 实现过程非常的简单,只需要读取姓名存储到一个数组中,然后通过复合框名称。List数组的方式完成。具体实现过程如下: 窗体初始化PrivateSubUserFormInitialize()表格已使用区域行数DimnumAsIntegernumThisWorkbook。Sheets(2)。Range(A1)。CurrentRegion。Rows。Count姓名保存到数组中DimNamesAsVariantNamesThisWorkbook。Sheets(2)。Range(C3:Cnum)。Value窗体姓名复合框内容姓名。ListNamesEndSub 从以上语句中可以知道,员工姓名要保存在C列,且从第3行开始作为第一个员工信息(前两行是表头信息)。因为一开始不知道员工人数,因此通过读取表格总使用行数来间接确定员工人数。具体的实现方式是,新建一个动态数组,将员工姓名保存到动态数组中,然后将该数组赋值给复合框。 运行窗体,我们就可以看到如图9所示的效果。 二、添加控件动作 根据功能设计,窗体中有两个命令按钮:查询和提交修改。查询用来查找指定员工的信息,提交修改用来修改某位员工的某些字段值。 1。查询命令按钮 指定姓名可以通过下拉框选择,也可以手动输入,然后点击查询按钮,将该员工信息填充到相应的文本框中。双击查询按钮,会直接跳转到窗体代码编写页面,并自动生成一个子过程: PrivateSubSelectFormClick()EndSub 该过程的名称以命令按钮名称Click()的方式命名。这里我把查询命令按钮名称设置为SelectForm。 查询过程实现的功能: 容错机制:未指定姓名、姓名不存在等情形下的错误提示; 识别各文本框名称,并获取对应值。 如果设置ctl为文本框对象(TextBox),则文本框的名称和值分别通过ctl。Name和ctl。Value获取。 在这里,我构造了一个函数,用来查询指定姓名在工作簿中的行,具体过程如下: 查找指定姓名所在的行FunctionFindNameRow()已使用行数DimnumAsIntegernumThisWorkbook。Sheets(2)。Range(A1)。CurrentRegion。Rows。Count依次测试每个单元格的值,如果是指定值,则返回指定值所在的行DimrngAsRange,rowAsLongForEachrngInThisWorkbook。Sheets(2)。Range(C3:Cnum)Ifrng。Value姓名。ValueThenrowrng。rowExitForEndIfNextFindNameRowrowEndFunction 该函数不需要参数。姓名。Value可以直接在函数内部引用。 姓名保存在C列,也就是第3列,这一点是固定的,用作模板要求,不能变更。因为一个员工的所有信息储存在同一行内,只要获取到该员工姓名所在的行,也就知道了该员工所有信息所在的行。对于其他字段,同一个员工的信息肯定和姓名在同一行,只需要定位该字段的列,就可以定位出此字段的值在工作表中的位置。因此,我同时定义了一个函数,用来查找指定字段名所在的列,具体如下: 查找各字段所在的列(数值,第几列)FunctionFindItemColumn(item)已使用区域列数DimnumAsIntegernumThisWorkbook。Sheets(2)。Range(A1)。CurrentRegion。Columns。Count依次测试包含字段名的单元格区域,如果是指定字段名,则返回所在的列(数值,第几列)DimrngAsRange,colAsIntegerForEachrngInThisWorkbook。Sheets(2)。Range(A2,ThisWorkbook。Sheets(2)。Cells(2,num))Ifrng。ValueitemThencolrng。columnExitForEndIfNextFindItemColumncolEndFunction 该函数参数为字段名,该字段名通过窗体文本框名称给定(设置窗体的时候,将文本框名称设置为该文本框需要显示的内容对应的字段名称)。例如显示性别的文本框名称设置为性别,员工信息工作表中储存性别信息的字段名称也为性别,这两个名称需要保持一致。函数会找到性别这个字段所在的列(第几列),通过人名确定行,由此确定了该员工性别所在的行和列,也就可以提取到对应的值了。 给窗体中性别文本框赋值,只需要通过性别。Valuesht。Cells(row,col)。Value,其中,sht代表保存员工信息的工作表对象,row和col分别是通过上述函数获取的行和列。 还有最后一个问题,窗体中文本框可能有多个,我们不可能挨个指定文本框名称去获取对应的值。因此,这里我引用了Control对象,通过对窗体对象的遍历,寻找到所有类型为TextBox(文本框)的控件,获取该控件的名称,用该名称去匹配工作表中所有字段名(前面提到的将文本框名称和对应字段名设置为一致的原因),找到完全匹配的字段名所在的列,也就是该文本框需要显示的值所在的列,行通过姓名确定。行和列确定后,该文本框的值就可以直接获取了。 完整实现代码如下: 点击查询按钮后将要执行的操作PrivateSubSelectFormClick()处理未指定姓名的查询IfLen(姓名。Value)0ThenMsgBox(请先指定需要查询的人员姓名!)ExitSubEndIf以姓名作为查询条件,获取指定姓名的人员信息所在的行DimrowAsLongrowFindNameRow()处理查询不存在的情形I3ThenMsgBox(无法查询到姓名为姓名。V的人员信息,请确认后再试!)ExitSubEndIfitem指定要填充的字段名,col代表查找该字段所在的列(数字,第几列)DimitemAsString,colAsIntegerDimctlAsControl判断窗体中文本框内容是否有更改,如果有更改则更新工作簿对应字段值ForEachctlInWorkerInfo。Controls如果是窗体文本框IfTypeName(ctl)TextBoxThencolFindItemColumn(ctl。Name)如果0,表示能够在工作簿中查询到该字段,能找到才能修改I0ThenIfctl。N所在部门Thenctl。ValueThisWorkbook。Sheets(2)。Cells(row,col)。ValueElsectl。ValueThisWorkbook。Sheets(2)。Range(ThisWorkbook。Sheets(2)。Cells(row,col),ThisWorkbook。Sheets(2)。Cells(row,col))。MergeArea。Cells(1,1)。ValueEndIfEndIfEndIfNext根据出生年月计算并显示年龄Dimdate1AsString,date2AsStringdate1出生年月。Valuedate2Application。Text(Date,yyyymmdd)年龄。ValueApplication。Evaluate(DATEDIF(date1,date2,y))EndSub 再回头看一下容错机制,首先明确一点,姓名。Value的类型是String,查询姓名的时候,有可能姓名没有输入,这时候姓名。Value的长度为0,只需要检测其长度,即可实现错误识别。还有一种错误是,输入的姓名不在员工信息表中,那么自定义的函数FindNameRow返回值为0,通过检测函数返回值,即可识别错误。 2。提交修改按钮 指定姓名点击查询后,更改该员工某个字段值,或者直接指定姓名,填写需要修改的字段值,然后点击提交修改,都可以完成该功能。 该部分实现起来与查询有很多相似的地方,基本思想是核对每个文本框值与工作表中对应字段值是否一致,如果不一致,我们认为是进行了修改,只需要将工作表中对应字段值修改。这里有几个问题: 容错机制:如果没有指定姓名或者姓名不存在,处理方式同查询; 因窗体文本框值类型为String,对于工作簿中的类型为Date或者Double等类型的数据时,需要统一数据类型后再进行比较,并且修改工作表值的时候,也要按照工作表字段类型进行修改; 如果窗体文本框中的值格式不正确(非精确判断,提供一种基本的解决思路,只针对明显错误,例如日期填写为男),则进行错误提示; 依旧引用Control对象,获取文本框值和工作表中对应字段值,进行比较,如果有差异,则将文本框值(类型调整后)更新到工作表中。 该部分完整代码如下: 点击提交修改按钮后执行的操作PrivateSubSubmitModifyClick()如果没有指定员工姓名,则弹窗提醒并退出执行IfLen(姓名。Value)0ThenMsgBox请先指定员工姓名后提交修改!ExitSubEndIf以姓名作为查询条件,获取指定姓名的人员信息所在的行DimrowAsLongrowFindNameRow()处理查询不存在的情形I3ThenMsgBox(无法查询到姓名为姓名。V的人员信息,请确认后再试!)ExitSubEndIfctl代表窗体中文本框对象,col代表查找该字段所在的列(数字,第几列)DimctlAsControlDimcolAsIntegerctlvalue用于修正数据类型,msg构造弹窗修改提示信息DimctlvalueAsString,msgAsString判断窗体中文本框内容是否有更改,如果有更改则更新工作簿对应字段值ForEachctlInWorkerInfo。Controls如果是窗体文本框IfTypeName(ctl)TextBoxThencolFindItemColumn(ctl。Name)如果0,表示能够在工作簿中查询到该字段,能找到才能修改I0Then窗体文本框值的类型均为字符串,遇到其他类型数据需要更改数据类型IfTypeName(ThisWorkbook。Sheets(2)。Cells(row,col)。Value)DateThen如果出错,则报错OnErrorGoToERROR1ctlvalueCDate(ctl。Value)如果上句不出错,跳过报错语句GoToCONTINUEDOERROR1:MsgBctl。N日期格式不正确,请输入参照20190101的格式输入!Err。ClearGoToNEXTDOElseIfTypeName(ThisWorkbook。Sheets(2)。Cells(row,col)。Value)DoubleThen如果不是数值型字符串,则执行报错语句IfIsNumeric(ctl。Value)ThenctlvalueVal(ctl。Value)GoToCONTINUEDOElseGoToERROR2EndIfOnErrorGoToERROR2ctlvalueVal(ctl。Value)GoToCONTINUEDOERROR2:MsgBctl。N请输入数字!Err。ClearGoToNEXTDOElsectlvaluectl。ValueEndIfCONTINUEDO:如果窗体文本框中值有更改(工作簿中的值和窗体文本框值不同)IfThisWorkbook。Sheets(2)。Cells(row,col)。VctlvalueAndctl。N所在部门Then弹窗提示是否确认修改msg确定将ctl。N由ThisWorkbook。Sheets(2)。Cells(row,col)。V修改为ctl。V吗?IfMsgBox(msg,vbYesNo)vbYesThen不是所在部门这个字段(合并单元格需要单独处理)Ifctl。N所在部门ThenThisWorkbook。Sheets(2)。Cells(row,col)。ValuectlvalueElseThisWorkbook。Sheets(2)。Range(ThisWorkbook。Sheets(2)。Cells(row,col),ThisWorkbook。Sheets(2)。Cells(row,col))。MergeArea。Cells(1,1)。ValuectlvalueEndIfEndIfEndIfEndIfEndIfNEXTDO:Next保存更改ThisWorkbook。Save更新查询结果(重新执行一次查询)CallSelectFormClickEndSub 针对格式不正确的填充数据,纠错机制借助了好几处的goto语句实现,比较啰嗦,也许有更好的实现方式,欢迎指正。 到目前为止,窗体的基本设置就完成了,现在可以在窗体设计界面按F5运行查看了。 设置查询入口 我们并不希望每次运行都要先打开开发工具查看代码,那么,只需要再添加一个入口按钮就可以了。 我们在开发工具查看代码ThisWorkbook中添加如下过程: PublicSubShowForm()WithWorkerInfo。startupposition0。Top100。Left200。ShowEndWithEndSub 该过程指定了窗体的初始位置。然后将员工信息表保存在工作簿的第二张sheet表中,第一张sheet表设置一个窗体查询入口,如图10所示: 点击开始查询就可以弹出窗体了。 结束语 说了这么多?有什么用呢?嗯,没什么用。学习一下窗体的使用足够了。目前能够想到的用处是做一个考试模拟系统,好像还能自己用用。或者可以做一个计算器的界面,模拟一个计算器,但是好像就没什么实际用处了。