分享

3-符合率计算

 asaser 2023-07-03 发布于四川

3.1-项目介绍

3.2-项目架构

3.2.1-系统可见部分

3.2.2-项目代码逻辑

3.3-代码

3.4-看代码前的几个基础知识

3.4.1-Sub间的调用

3.4.2-工作表某行最大列

3.4.3-代码窗口的两种视图

3.4.4-代码调试

3.4.5-代码字体等设置

3.5-本章示例代码详解

3.5.1-代码结构解析

3.5.2-代码解析:S01_初始化

3.5.3-代码解析:S02_读入数据

3.5.4-代码解析:S0201_文件处理

3.5.5-代码解析:S03_计算

3.6-本章小结

------------------------------

3.1-项目介绍

引言

本章依然采用一个小项目的方式,介绍新的知识点。本章介绍的知识点如下:

读入Excel文件

文件及文件夹操作

时间获取及字符串转换

代码调试

弹窗

某工厂有员工甲乙丙丁,分别手工制作同一种小蛋糕。现在有4个Excel文件,分别记录每位员工制作的小蛋糕重量,如图3-1所示。工厂对于小蛋糕的重量有一个标准:23~27g之间为合格,其余为不合格。通过反馈的Excel数据,分别计算出每名员工的小蛋糕重量的符合率,对于符合率较低的员工进行再培训。

符合率 = 重量在标准内的蛋糕数目 / 小蛋糕总数

小蛋糕因为口味不错,即将扩产,目前正在招聘新员工。对于编制的VBA系统有一个延伸的需求,适用于任意多人的场景。

图3-1 小蛋糕重量

3.2-项目架构

3.2.1-系统可见部分

3.2.2-项目代码逻辑

3.2.1-系统可见部分

新建立一个Excel文件来编制VBA系统,取名为小蛋糕重量符合率自动计算系统,简称符合率计算系统。该Excel文件包含两个工作表:操作界面和数据缓存,如图3-2所示。

操作界面:如图3-3所示

A2-C1048576区域,显示符合率信息,适用于更多员工的情况

K8-K9区域:质量标准放置在单元格位置,可以人工修改,标准自定义

K2-K4区域:按钮点击提示区,告知当前点击到哪一步,防止误点击

数据缓存:默认是一张空表,所有外部的Excel数据首先读入该表格,然后再进行处理,减少跨Excel的计算

图3-2 工作簿构成工作表

图3-3 操作界面

从图3-3可以看到三个按钮外有一个框,这是在开发工具菜单栏下-插入-分组框,如图3-4所示,只是为了更加好看,可加可不加。

图3-4 分组框

3.2.2-项目代码逻辑

如操作界面区域按钮所显示的,整个代码分为3个部分:

清空上一次使用的数据,该系统可能在不同时候使用,例如每周使用一次,为了避免上次数据带来的干扰,先清除,包括以下两个部分

操作界面工作表:A-C列输出符合率区域;命令提示区K2-K4

数据缓存工作表:全表清空

读入员工数据,将员工B列的有效数据依次复制到数据缓存表

计算:对数据缓存表中的每名员工依次进行计算,并将结果写入操作界面工作表

3.3-代码

初学者,建议将以下代码打印出来,拿出笔一行一行去看,当然后文我们一是一行一行解读的

Sub S01_初始化()

    ' 清空 工作表操作界面 上次遗留的信息

    Set shtFirst = ThisWorkbook.Worksheets("操作界面")

    maxRow = shtFirst.Cells(Rows.Count, "B").End(xlUp).Row

    If maxRow > 2 Then

        shtFirst.Range("A2:C" & maxRow).ClearContents

    End If

    shtFirst.Range("K2:K4").ClearContents

    '清空 工作表数据缓存 所有信息

    Set shtData = ThisWorkbook.Worksheets("数据缓存")

    shtData.Cells.ClearContents

    shtFirst.Range("K2") = "已点击"

End Sub

Sub S02_读入数据()

    Dim longName As String

    Dim shortName As String

    Dim selectFile

    Dim fso

    Dim shtFirst

    Set fso = CreateObject("Scripting.FileSystemObject")

    Set shtFirst = ThisWorkbook.Worksheets("操作界面")

    With Application.FileDialog(msoFileDialogOpen)

        .Title = "选择文件"

        .AllowMultiSelect = True

        .Filters.Clear

        .Filters.Add "Excel文件", "*.xlsx"

        .FilterIndex = 2  '默认的文件筛选条件的索引号

        .InitialFileName = ThisWorkbook.Path & "\" & "【1】输入"

        .Show

        For Each selectFile In .SelectedItems

            longName = selectFile

            shortName = fso.GetFile(longName).Name

            Call S0201_文件处理(longName, shortName)

        Next

    End With

    shtFirst.Range("K3") = "已点击"

End Sub

Sub S0201_文件处理(longName, shortName)

    Set wb = Workbooks.Open(longName) '打开工作簿

    Set sht = wb.Worksheets(1)

    '获取员工姓名

    employeeName = Split(shortName, "-")(0)

    Debug.Print (employeeName)

    '获取输入Excel最大行

    maxRow = sht.Cells(Rows.Count, "B").End(xlUp).Row

    ' 拟输入列列号

    Set shtData = ThisWorkbook.Worksheets("数据缓存")

    a1Value = shtData.Range("A1")

    If a1Value = "" Then

        inputCol = 1

    Else

        inputCol = shtData.Cells(1, Columns.Count).End(xlToLeft).Column + 1

    End If

    shtData.Cells(1, inputCol) = employeeName

    ' 复制每个员工的数据至目标工作表

    sht.Range("B2:B" & maxRow).Copy Destination:=shtData.Cells(2, inputCol)

    wb.Close

End Sub

Sub S03_计算()

    Set shtFirst = ThisWorkbook.Worksheets("操作界面")

    up_tol = shtFirst.Range("K8")

    down_tol = shtFirst.Range("K9")

    Set shtData = ThisWorkbook.Worksheets("数据缓存")

    maxCol = shtData.Cells(1, Columns.Count).End(xlToLeft).Column

    inputRow = 2

    ' 对数据缓存工作表数据按列遍历,每列按照行遍历

    For j = 1 To maxCol Step 1

        maxRow = shtData.Cells(Rows.Count, j).End(xlUp).Row

        qualifiedCount = 0

        totalCount = 0

        employeeName = shtData.Cells(1, j)

        For i = 2 To maxRow Step 1

            weightValue = shtData.Cells(i, j)

            If weightValue <= up_tol And weightValue >= down_tol Then

                qualifiedCount = qualifiedCount + 1

            End If

            totalCount = totalCount + 1

        Next i

        qualifiedRate = Round(qualifiedCount / totalCount, 3)

        ' 写入数据

        shtFirst.Cells(inputRow, "A") = inputRow - 1

        shtFirst.Cells(inputRow, "B") = employeeName

        shtFirst.Cells(inputRow, "C") = qualifiedRate

        inputRow = inputRow + 1

    Next j

    shtFirst.Range("K4") = "已点击"

End Sub

以上代码在一个模块中,如图3-5所示,共有4个Sub构成,分别如下。

S01_初始化()

Sub S02_读入数据()

S0201_文件处理(longName, shortName)

S03_计算()

操作界面按钮关联其中的S01/S02/S03即可,其中S0201在S02中进行调用。

3.4-看代码前的几个基础知识

3.4.1-Sub间的调用

3.4.2-工作表某行最大列

3.4.3-代码窗口的两种视图

3.4.4-代码调试

3.4.5-代码字体等设置

3.4.1-Sub间的调用

如上所述,本章代码涉及4个Sub过程,其中Sub-S0201被Sub-S02调用,调用方式如下:Call Sub名称

Call S0201_文件处理(longName, shortName)

一个完整的代码需要的实现的功能有很多,我们可以将所有代码写在一个Sub中,但是后期的维护优化将会非常困难。就像一台汽车有很多零件,当某个零件损坏时我们可以花很少的成本进行维修,而不是直接重新买一台汽车。如何写代码?这里我们联想到积木拼装的方法,将整个代码分解成多个功能块,每个功能块只负责实现一部分功能,所有功能块组合实现全部功能。当某个功能块出现问题,我们只需要关注该部分即可,而不需要通读全篇代码,一行一行排查。

在VBA中每一个Sub过程即为一个功能块,实现特定功能,对于重复且需要被在不同地方反复使用的功能模块,特别建议将其独立为一个Sub,当然每一个Sub代码都不宜过长也不宜过短,这里就要看个人的经验了。Sub之间除了顺序执行,也有可能存在一些调用关系。

那么问题来了,不同Sub之间如何调用?如果每次调用需要的功能虽然一样,但是某些变量的取值不同,就像长方形面积S = 长a * 宽b,这就像一个独立的模块,计算不同长方形该公式一直成立,但是针对具体的长方形,我们需要使用具体的长度如3m,宽度如4m。在调用S0201过程中,我们也传递了两个参数:longName, shortName。本章中4个Sub间关系如图3-6

图3-6 Sub间关系

3.4.2-工作表某行最大列

在数据缓存工作表,每一列表示一个员工生产的小蛋糕信息,那么具体有多少位员工呢?其实就是该工作表第一行非空列数。如何获取某一行最大列也是一个比较常见的需求,有效的减少不必要的循环,提升代码运行效率。

maxCol = shtData.Cells(1, Columns.Count).End(xlToLeft).Column

以上代码翻译成汉语: 第1行最大列 = 工作表.单元格(第1行,最大列).从最大列向左数到最后一列有信息的列.列号

对比如何获取最大行,是否看出了区别?

maxRow = shtYuwen.Cells(Rows.Count, "B").End(xlUp).Row

获取最大行,固定某一列,从下往上(xlUp)数到的第一个非空行,最终取值和选择列相关

获取最大列,固定某一行,从右往左(xlToLeft)数到的第一个非空列,最终取值和选择行相关

3.4.3-代码窗口的两种视图

在上一个示例中,所有的代码都是写在一个Sub内,本示例涉及到4个Sub,代码的行数有显著增长,我们在使用鼠标滚动代码过程中,会跳跃的比较快,如果在某个时间段只是关注某一个Sub,怎么让代码窗口中只显示该过程,其它过程不出现呢?

方法如下:将鼠标放置到你想显示的Sub中任一行代码,点击代码窗口左下角的过程视图按钮,如图3-7所示。

那么此时又想看整个模块的代码呢?同样点击左下角的全模块视图,如图3-8所示。

图3-7 代码窗口的过程视图按钮

图3-8 代码窗口的全模块视图按钮

又问是不是必须要人工找到自己所希望看到的Sub过程呢,假如代码非常多,是不是找起来也很费劲。其实不必,代码窗口的右上角过程框展示了该模块的所有Sub过程,可以在这里进行下拉列表选择,如图3-9所示。

图3-9 代码窗口的过程选择区

3.4.4-代码调试

写代码的过程中难免有错误,那么如何发现这些错误呢?这里介绍几个调试支持工具:

逐语句调试:平常代码点击运行,瞬间结束,完全不知道过程。通过手动点击一行一行运行判断代码是不是按照预期的方式运行。将鼠标定位到拟运行的Sub过程,点击调试工具栏的逐语句按钮,如图3-10所示

本地窗口:代码调试过程中,可以看到每个变量的取值及数据类型。执行逐行调试时,打开本地窗口,就可以看见每个变量的最新取值,如图3-11所示。本地窗口包括3列:表达式、值、类型

断点:一行一行运行代码有的时候太累,直接将代码运行可能出错的前几步,然后再分步运行。点击调试工具栏上的运行子过程按钮后,会运行到第一个断点处停下来。如果有多个断点,再点击运行子过程按钮,会运行到当前过程(或者通过Call进入下个Sub)的下个断点处。设置断点的方法,鼠标左键点击图3-12红点区域即可,在鼠标点击所在行设置断点,不同行点击即在不同行设置断点。

立即窗口:本地窗口只保留变量的最新取值,如果想将过程数据打印出来,可以使用Debug.Print将变量取值输出到立即窗口,如图3-13所示。如果发现找不到立即窗口,可以通过菜单视图-选择立即窗口

3.4.5-代码字体等设置

清晰好看的代码让工作事倍功半,尤其对于后续的阅读来说。如何设置呢?菜单工具-选项,如图3-14所示。


../_images/3-14.png


图3-14 工具-选项


在弹出的窗口中,设置编辑器,建议不要勾选自动语法检测,不要勾选要求变量声明,Tab宽度设置为4。


../_images/3-15.png


3-15 编辑器建议设置


在弹出的窗口中,设置编辑器格式,如字体等。根据个人喜好及屏幕大小合理设置。


../_images/3-16.png

3.5-本章示例代码详解

3.5.1-代码结构解析

3.5.2-代码解析:S01_初始化

3.5.3-代码解析:S02_读入数据

3.5.4-代码解析:S0201_文件处理

3.5.5-代码解析:S03_计算

3.5.1-代码结构解析

本章涉及代码3大主体功能,接下来的文章会依次介绍:

1)S01_初始化。因为该工具会在不同时间段重复使用,为了防止上次的数据影响到本次的使用,有必要进行初始化,删除之前遗留的不必要数据

2)S02_读入数据。被统计人员的数据,是单独放在一个个Excel文件中,需要逐个录入。这里数据源的标准化是需要事前规范好的。在做任何信息化系统前,数据源的标准化都是需要认真考虑的,标准化的数据将会大大降低系统编制的复杂程度

3)S03_计算。对导入的数据分别计算符合率。所有的操作都是对数据缓存工作表的操作,大大提升处理速度。

3.5.2-代码解析:S01_初始化

该过程的实现的功能如下:

1)清空上一次遗留的合格率信息,即清除操作界面工作表A2至C列最大行区域

2)虽然本示例只有3个按钮,但是为了防止重复点击和遗漏点击,每点击一个按钮需要做出提示,即分别在操作界面工作表K2:K4区域记录已点击。在本过程中首先将上一次操作的信息清除掉,即操作界面工作表K2:K4区域清空

3)清空数据缓存工作表

4)第1个按钮点击后在K2单元格写入已点击

代码前面的序号和 | 只是方便记录其代码行数,方便后续写代码说明,真实使用过程中无需序号和前面的 |。

代码如下

1   |Sub S01_初始化()

2   |   ' 清空 工作表操作界面 上次遗留的信息

3   |   Set shtFirst = ThisWorkbook.Worksheets("操作界面")

4   |   maxRow = shtFirst.Cells(Rows.Count, "B").End(xlUp).Row

5   |   If maxRow > 2 Then

6   |       shtFirst.Range("A2:C" & maxRow).ClearContents

7   |   End If

8   |   shtFirst.Range("K2:K4").ClearContents

9   |   

10  |   '清空 工作表数据缓存 所有信息

11  |   Set shtData = ThisWorkbook.Worksheets("数据缓存")

12  |   shtData.Cells.ClearContents

13  |   

14  |   shtFirst.Range("K2") = "已点击"

15  |

16  |End Sub

以上代码的中文解析:

1   |Sub S01_初始化()

2   |   ' 注释

3   |   定义工作表shtFirst,表示操作界面工作表

4   |   获取操作界面工作表B列最大行maxRow

5   |   判断开始:maxRow是否大于2:如果小于2表示该表没有数据,无需操作

6   |       操作界面工作表的A2至C列最大行区域全部清空,删除数据

7   |   判断结束:

8   |   操作界面工作表的K2至K4区域数据全部删除

9   |   

10  |   '注释

11  |   定义工作表shtFirst,表示数据缓存工作表

12  |   清空数据缓存工作表所有数据

13  |   

14  |   操作界面工作表的K2单元格赋值"已点击"

15  |

16  |End Sub

关键代码解读

清空工作表某区域单元格的数据,构成方式:单元格区域.ClearContents,其实从英文释义也可以快速理解为清空内容。关于单元格区域本Sub中有两种方式,如下所示

shtFirst.Range(“A2:C” & maxRow).ClearContents

shtData.Cells.ClearContents

第一种是明确单元格区域Range(“A2:C” & maxRow),这种方式其实就是指定一个矩形的左上角和右下角,如图3-17所示。选择单元格区域A2至C11矩形区域,只需指定左上角及右下角位置即可

第二种指的是全表,使用表.Cells即可。

图3-17 单元格区域表示

3.5.3-代码解析:S02_读入数据

该过程的实现的功能如下:

1)弹出文件选择框

2)获取已选择文件信息

3)将选择文件的绝对地址和文件名信息作为参数传给过程S0201_文件处理

4)在K3单元格写入已点击

代码如下

1   |   Sub S02_读入数据()

2   |       Dim longName As String

3   |       Dim shortName As String

4   |       Dim selectFile

5   |       Dim fso

6   |       Dim shtFirst

7   |       

8   |       Set fso = CreateObject("Scripting.FileSystemObject")

9   |       Set shtFirst = ThisWorkbook.Worksheets("操作界面")

10  |       

11  |       With Application.FileDialog(msoFileDialogOpen)

12  |           .Title = "选择文件"

13  |           .AllowMultiSelect = True

14  |           .Filters.Clear

15  |           .Filters.Add "Excel文件", "*.xlsx"

16  |           .FilterIndex = 2  '默认的文件筛选条件的索引号

17  |           .InitialFileName = ThisWorkbook.Path & "\" & "【1】输入"

18  |           .Show

19  |           

20  |           For Each selectFile In .SelectedItems

21  |               longName = selectFile

22  |               shortName = fso.GetFile(longName).Name

23  |               Call S0201_文件处理(longName, shortName)

24  |           Next

25  |       End With

26  |       

27  |       shtFirst.Range("K3") = "已点击"

28  |   End Sub

代码中文解析

1   |   Sub S02_读入数据()

2   |       申明变量longName为字符串类型

3   |       申明变量shortName为字符串类型

4   |       申明变量selectFile

5   |       申明变量fso

6   |       申明变量shtFirst

7   |       

8   |       定义变量fso为FileSystemObject对象,用于对文件或者文件夹进行操作

9   |       定义工作表shtFirst,表示操作界面工作表

10  |       

11  |       With开始:使用FileDialog(msoFileDialogOpen)打开一个窗口

12  |           定义打开窗口标题为:选择文件

13  |           是否允许多选文件:是

14  |           清空文件选项

15  |           新增Excel文件,尾椎为xlsx的选项

16  |           可省略,应与filter联合使用,此处未发挥作用 

17  |           初始打开位置为:当前文件下【1】输入文件夹

18  |           显示

19  |           

20  |           循环开始:对于选择的文件进行遍历循环

21  |               定义变量longName为选择的文件

22  |               获取文件的名称

23  |               调用过程S0201_文件处理,变传递2个参数

24  |           循环结束

25  |       With结束

26  |       

27  |       工作表shtFirst的K3单元格赋值已点击

28  |   End Sub

关键代码解读

1)第11-25行:With…End With,第11行至25行,等价为以下代码。目标在于重复的代码只需要写一次,了解即可,新手无需深究。

Set mso = Application.FileDialog(msoFileDialogOpen)

mso.Title = "选择文件"

mso.AllowMultiSelect = True

mso.Filters.Clear

mso.Filters.Add "Excel文件", "*.xlsx"

mso.FilterIndex = 2

mso.InitialFileName = ThisWorkbook.Path & "\" & "【1】输入"

mso.Show

For Each selectFile In mso.SelectedItems

    longName = selectFile

    shortName = fso.GetFile(longName).Name

    Call S0201_文件处理(longName, shortName)

Next

2)第8行:Set fso = CreateObject("Scripting.FileSystemObject"),定义了一个文件系统对象。用来对文件和文件夹进行操作,例如获取文件名称等。对于很多常见操作,如文件操作、字典操作等,VBA提供了一些工具包,通过引用这些工具包就可以使用,大大提升工作效率。

关于工具包的引用有两种方法,一种通过代码的方式,如本文中所示。另外通过菜单工具-引用,如图3-18所示。

在弹出的窗口中勾选Microsoft Scripting Runtime,如图3-19所示。另外需要在代码中定义fso,然后就可以使用相关功能。推荐使用第一种方法,这样将程序发给其他人的时候可以直接使用。第二中方法,当程序文件发送给另外人员使用时,也许要使用人员的电脑上在VBE界面中操作绑定。shortName = fso.GetFile(longName).Name,通过该方式获取文件的名称。

Dim fso As New Scripting.FileSystemObject

../_images/3-19.png


图3-19 勾选需要的引用


3)第11行: Application.FileDialog(msoFileDialogOpen)。目标在于打开选择文件的窗口。包括以下常用属性,实际效果如图3-20

  • Title,窗体名称

  • AllowMultiSelect,是否允许多选

  • Filters.Add,支持选择的文件格式

  • InitialFileName,默认打开的位置

  • SelectedItems,已选中的文件


../_images/3-20.png


图3-20 选择文件窗口


若希望支持多种文件的选择,.Filters.Add "Excel文件/txt文件", "*.xlsx;*.txt",修改Filters.Add后面的内容即可,不同格式间用;隔开。


../_images/3-21.png


图3-21 多类型文件选择


4)第17行ThisWorkbook.Path,获取当前Excel文件所在文件夹地址

3.5.4-代码解析:S0201_文件处理

S0201_文件处理(longName, shortName),这里的longNameshortName称为参数,在本书之前介绍的Sub过程括号中无任何信息。

当一个需求比较复杂时,我们会将其分隔成几个Sub过程去完成,某一个过程对应的功能可能被重复调用,只是每次使用的参数不同,这样我们就需要带参数的Sub过程。如本示例中,需要将不同员工的数据读入数据缓存工作表。对于每个员工的操作基本是一样的,每名员工信息占据一列,依次写入。

基本逻辑过程如下:

1)打开员工的数据工作簿

2)获取当前数据缓存工作表最大列maxCol

3)将新员工信息写入maxCol+1列

代码如下

1       Sub S0201_文件处理(longName, shortName)
2           Set wb = Workbooks.Open(longName) '打开工作簿
3           Set sht = wb.Worksheets(1)
4           '获取员工姓名
5           employeeName = Split(shortName, "-")(0)
6           Debug.Print (employeeName)
7           '获取输入Excel最大行
8           maxRow = sht.Cells(Rows.Count, "B").End(xlUp).Row
9           
10          ' 拟输入列列号
11          Set shtData = ThisWorkbook.Worksheets("数据缓存")
12          a1Value = shtData.Range("A1")
13          If a1Value = "" Then
14              inputCol = 1
15          Else
16          		inputCol=shtData.Cells(1,Columns.Count).End(xlToLeft).Column + 1
17          End If
18          
19          shtData.Cells(1, inputCol) = employeeName
20         
21          ' 复制每个员工的数据至目标工作表
22          sht.Range("B2:B" & maxRow).Copy Destination:=shtData.Cells(2, inputCol)
23
24          wb.Close
25      End Sub

代码中文解析

1       Sub S0201_文件处理(longName, shortName)
2           打开文件地址为longName的Excel工作簿,用变量wb表示
3           定义工作表sht,表示wb工作簿的第1个工作表,该工作簿只含有1个工作表
4           '获取员工姓名
5           表示文件名的字符串,使用Split函数进行分割,获取员工姓名
6           输出显示员工姓名
7           '获取输入Excel最大行
8           sht工作表B列最大行
9           
10          ' 拟输入列列号
11          定义数据缓存工作表为shtData
12          获取shtData工作表A1单元格内容a1Value
13          判断开始:a1Value是否为空
14              如果是,则inputCol = 1
15          如果不是
16              inputCol为shtData工作表第1列最大行+1
17          循环结束
18          
19          shtData单元格(行号=1,列号=inputCol)赋值员工姓名employeeName
20          
21          ' 复制每个员工的数据至目标工作表
22          将员工工作表B2至B最大行区域复制,在单元格(行号=2,列号=inputCol)处粘贴
23
24          关闭工作簿wb
25      End Sub

其实就是将员工生产的小蛋糕重量信息分别复制到数据缓存工作表,因为不知道员工有多少信息(即多少行),以及不知道数据缓存工作表已有多少信息。所以需要找到每名员工有多少行信息,以及当前数据缓存表中当前写到哪一列。

关键代码解读

1)第2行Set wb = Workbooks.Open(longName)longName为对应Excel文件的绝对地址。记得使用完毕后,务必关闭该Excel文件,wb.Close。之前的示例中所有的操作都是在代码所在Excel工作簿,使用ThisWorkbook表征就可以,当拟操作的Excel不是当前代码所在Excel文件,需要先获取拟操作的Excel对象,也就是这里的wb。

2)第3行Set sht = wb.Worksheets(1),获取wb工作簿的第1个Excel工作表。在之前的示例中,我们通过工作表名称获取工作表信息,Worksheets(“工作表名”),同样可以通过位置获取。该方法请慎用,当Excel工作簿中包括多个工作表时,若人工移动Excel工作表,同样的位置信息表示的会是不同的工作表,所以建议在工作簿中只含有一个工作表时使用该方法。

3)第5行employeeName = Split(shortName, "-")(0),字符串分割函数,当文件名为“员工丙-小蛋糕重量.xlsx”时,以-为分隔符,生成一个数组,如图3-22所示,获取数组第一个元素(从0开始计数)为员工姓名,可以从图3-23有直观的感受。可以看到数据源的标准化是很重要的,如果员工反馈的Excel文件是任意命名的,那么就不能获取其姓名,标准化的数据源大大减少代码体量。


../_images/3-22.png


图3-22 Split函数

../_images/3-23.png


图3-23 Split分割的效果图


数组是什么?可以看做一个容器,可以将数据放入进去暂存,当程序运行结束后,自动消失,但对它的读写速度远远高于对于Excel文件的读写。这里先只介绍一维数组,类似于Excel中的一行,每个单元格可以分别写入信息,如图3-24所示。通过关键字Array定义一个新数组,数组有如下特征:

  • 从本地窗口中也可以看出数组是从0开始计数的

  • 数组中每个元素的数据类型可以不同

  • Ubound获取数组最大序列号

  • Lbound获取数组最小序列号

  • Debug.Print(a),在程序运行过,输出变a到立即窗口中。但不能直接输出数组,可以输出其每一个元素。数组元素获取,arr(i),i表示在数组的位置


../_images/3-24.png


图3-24 一维数组介绍


4)第22行: sht.Range("B2:B" & maxRow).Copy Destination:=shtData.Cells(2, inputCol),复制工作表区域,Copy前为拟复制区域,Destination:=后为复制到的新位置第一个单元格。

3.5.5-代码解析:S03_计算

该Sub过程的作用是计算出每一个员工的符合率。基本逻辑过程如下:

1)获取小蛋糕重量的标准信息,即公差上下限,超出则该小蛋糕不合格

2)两层循环,分别计算出每个员工生产的小蛋糕的重量的符合率:

2.1)外层循环,对数据缓存工作表的列进行循环,即对每名员工进行循环

2.2)内层循环,一个员工有多个数据,对每一行进行循环,计算该名员工生产小蛋糕的符合率

3)将符合率信息写入操作界面

代码如下

1       Sub S03_计算()

2           Set shtFirst = ThisWorkbook.Worksheets("操作界面")

3           up_tol = shtFirst.Range("K8")

4           down_tol = shtFirst.Range("K9")

5           

6           Set shtData = ThisWorkbook.Worksheets("数据缓存")

7           

8           maxCol = shtData.Cells(1, Columns.Count).End(xlToLeft).Column

9           

10          inputRow = 2

11

12          ' 对数据缓存工作表数据按列遍历,每列按照行遍历

13          For j = 1 To maxCol Step 1

14              maxRow = shtData.Cells(Rows.Count, j).End(xlUp).Row

15              qualifiedCount = 0

16              totalCount = 0

17              employeeName = shtData.Cells(1, j)

18              

19              For i = 2 To maxRow Step 1

20                  weightValue = shtData.Cells(i, j)

21                  If weightValue <= up_tol And weightValue >= down_tol Then

22                      qualifiedCount = qualifiedCount + 1

23                  End If

24                  

25                  totalCount = totalCount + 1

26              Next i

27              

28              qualifiedRate = Round(qualifiedCount / totalCount, 3)

29              

30              ' 写入数据

31              shtFirst.Cells(inputRow, "A") = inputRow - 1

32              shtFirst.Cells(inputRow, "B") = employeeName

33              shtFirst.Cells(inputRow, "C") = qualifiedRate

34              

35              inputRow = inputRow + 1

36              

37          Next j

38          

39          shtFirst.Range("K4") = "已点击"

40      End Sub

代码中文解析

1       Sub S03_计算()

2           定义工作表shtFirst,表示操作界面工作表

3           获取公差上限

4           获取公差下限

5           

6           定义工作表shtData,表示数据缓存工作表

7           

8           获取数据缓存工作表第1行最大非空列maxCol

9           

10          初始化inputRow=2,用于向操作界面工作表写入符合率信息,确定行信息

11

12          ' 对数据缓存工作表数据按列遍历,每列按照行遍历

13          循环开始:j从1开始到maxCol,步长为1

14              获取数据缓存表第j列最大行maxRow

15              计数:设置合格蛋糕数为qualifiedCount = 0

16              计数:设置蛋糕总数totalCount = 0

17              获取员工姓名employeeName

18              

19              循环开始:i从2开始到maxRow,步长为1

20                  获取第j列第i行蛋糕重量weightValue

21                  判断开始:如果蛋糕重量小于等于公差上限,且大于等于公差下限,表示重量达标

22                      合格蛋糕数+1

23                  判断结束

24                  

25                  蛋糕总数+1

26              循环结束

27              

28              符合率=合格蛋糕数/蛋糕总数,保留3位小数

29              

30              ' 写入数据

31              在操作界面工作表的inputRow行A列写入序号 = inputRow - 1

32              在操作界面工作表的inputRow行B列写入该原工姓名

33              在操作界面工作表的inputRow行C列写入符合率

34              

35              inputRow+1

36              

37          循环结束

38          

39          操作界面的K4单元格赋值已点击

40      End Sub

在本段代码中,有多处寻找位置的代码

例如计算完某员工生产的小蛋糕重量符合率后,如何写入到正确的位置。设置一个变量存储将要写入的行号,从第2行开始写,每写一次,该变量+1,这样就保证了一行一行向下写,不会出现空行或者覆盖上一次写入的情况

其余就是通过最大行最大列来确定位置,进而确认循环范围

关键代码解读

1)第21行:If weightValue <= up_tol And weightValue >= down_tol Then。这里使用了大于等于和小于等于,它们都属于比较运算符。比较运算符返回的结果是布尔数,即False或者True,可以直接用于判断语句,不同的编程语言可能会使用不同的符号表示同一个比较运算符,VBA中使用的比较运算符如下:

1.1)大于:>

1.2)小于:<

1.3)等于:=

1.4)大于等于:>=

1.5)小于等于:<=

1.6)不等于:<>

当有两个条件都必须满足时,可以通过And连接两个条件,同理如果不只2个条件,只需在不同条件间用And连接即可。那么大家自然会问,如果两个条件满足其中任意一个时,怎么写?请看以下示例,输出结果为3

Sub test3()

    a = 3

    If a > 5 Or a <= 5 Then

        Debug.Print (a)

    Else

        Debug.Print ("不满足条件")

    End If

End Sub

2)第28行:qualifiedRate = Round(qualifiedCount / totalCount, 3),该代码的效果是保留几位小数。请看以下示例,输出结果分别为:1.3 ,1.33 ,1.333

Sub test2()

    a = 4 / 3

    b1 = Round(a, 1)

    b2 = Round(a, 2)

    b3 = Round(a, 3)

    Debug.Print (b1)

    Debug.Print (b2)

    Debug.Print (b3)

End Sub

3.6-本章小结

本章使用一个示例展示了以下知识点,回顾一下是否都已经掌握了,如果没有,不妨回看一下:

1)Sub过程间的调用

2)某工作表某列的非空最大列数获取

3)单元格区域内容清空

4)打开文件窗口

5)FSO文件操作

6)打开另外的Excel文件获取数据

7)复制单元格区域到另外的位置

8)比较运算符

9)小数点位数控制

在Sub过程S03_计算中,可以发现使用到的代码,基本在之前都有过介绍了,其实对于一般需求使用的VBA功能是非常有限的,所以VBA学习起来并不会很复杂,如果只是完成常规的工作。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多