Excel 工具箱
我为了简化我的程序员生活而制作的 Excel 工具
目录
引言
我每天都在处理巨大的 Excel 电子表格,一个工作簿包含 50,000 个公式是很常见的。
多年来,我创建了函数和宏来简化我的 Excel 公式和用法。
我使用 VBA 来兼容 Excel 2003,因为我从那时开始接触 Excel 编程,距今已有 20 年了。
我的标准工作表设置
- 只有用户输入的单元格是解锁的,其他所有单元格都已锁定。
- 工作表已受保护,用户不会意外删除公式。
这确保了工作表有最低限度的安全性。为了更高级别的安全性,工作表受密码保护。
Samples.xls
“下载”文件中的 Samples.xls 文件展示了以下代码片段的示例用法。
Formula()
这个微小的函数没有任何其他目的,就是显示另一个单元格的公式。
它只是用于 Excel 截图的辅助工具。
' 20230717 Patrice_T
Function Formula(Adrs As Range) As String
Formula = Adrs.Formula
End Function
ExcelToolBox.zip 中的 Formula.bas.
扫描宏
主要是为了检查目的,我创建了扫描工作表的宏。有两种宏,一些宏扫描整个工作表,另一些则从活动单元格开始。
扫描整个工作表
有时,我希望宏扫描整个工作表,因为某些问题需要强制修复。
这些宏使用这个骨架。
For Each Cel In ActiveSheet.UsedRange
' Do stuf or Checks
' Set Condition
If Condition Then
Cel.Activate
MsgBox "Error at Cell " & Replace(ActiveCell.Address, "$", "")
Exit Sub
End If
Next
宏会扫描工作表中的每个单元格,检查条件,并在遇到时停止。
从活动单元格开始
有时,我希望宏从活动单元格开始扫描,因为某些条件并非总是需要修复的问题。
这些宏使用这个骨架。
Dim RowS As Long
Dim RO, CO
RowS = ActiveSheet.UsedRange.RowS.Count
RO = ActiveCell.Row
CO = ActiveCell.Column
Rupt = ActiveCell.Value
For Scan = RO To RowS
' Do stuf or Checks
' Set Condition
If Condition Then
ActiveSheet.Cells(Scan, CO).Activate
Exit Sub
End If
Next
此代码从活动单元格开始在列中扫描某个条件。
宏
宏是可以通过菜单或功能区(GUI)运行的程序。
在公式中搜索 #REF! 错误
任何包含 #REF!
的公式都存在错误,因为最初引用的单元格已被删除。这有时会在删除工作表的部分内容时发生。任何此类错误都必须纠正,因此需要对整个工作表进行扫描。
Excel 提供了这样的工具,但必须选择有问题的单元格才能查看该单元格中是否存在此类问题。当需要检查 50,000 个单元格时,这确实不方便。否则,需要使用 Excel 的错误定位功能,但在我的情况下,我添加了针对我正在处理的工作表的其他检查。
' 20220201 Patrice T
Sub ScanRef()
' TP recherche de #ref! dans la feuille
For Each Cel In ActiveSheet.UsedRange
If InStr(Cel.Formula, "#REF!") > 0 Then
Cel.Activate
MsgBox "Erreur Cellule " & Replace(ActiveCell.Address, "$", "") _
& vbLf & " Erreur #REF! dans formule (formule cassée)"
Exit Sub
End If
Next
End Sub
ExcelToolBox.zip 中的 ScanRef.bas.
搜索孤立公式
孤立公式是指引用空单元格的公式。这可能是一个问题,但不总是。宏从活动单元格开始扫描。
此截图显示了 #REF!
错误,并且该公式也是孤立的,因为它引用了 C2,而 C2 是空的。
Excel 可以突出显示此类公式,但我的宏也可以允许引用空单元格,如果它们在特定范围内。例如:在银行账户的交易列表中,有存款和取款列,持续的余额将引用空单元格,这是正常的。
正则表达式
在这个正则表达式中,我只想匹配单元格引用、范围和定义名称。但由于公式很复杂,我发现更容易匹配不需要的部分以防止误报。被匹配为不需要的匹配部分不会导致误报。
- 不需要的:匹配字符串
- 需要的:匹配可选的工作表名称和范围的单元格地址
- 不需要的:匹配函数名称
- 需要的:匹配定义名称
Dim xRegEx As Object
Set xRegEx = CreateObject("VBSCRIPT.REGEXP")
' The RegEx
' Match a string
' (""[^""]*"")
' match sheet name without space
' ([a-zA-Zé0-9_]+!)
' match sheet name with space
' ('[a-zA-Zé0-9\s_]+'!)
' match cell adress or range
' \$?[A-Z]{1,3}\$?[0-9]{1,7}(:\$?[A-Z]{1,3}\$?[0-9]{1,7})?
' match a function name
' ([a-zA-Zé0-9\._]+\()
' match defined name
' ([a-zA-Zé_][a-zA-Zé0-9_]+)
With xRegEx
.Pattern = "(""[^""]*"")|(([a-zA-Zé0-9_]+!)|('[a-zA-Zé0-9\s_]+'!))_
?\$?[A-Z]{1,3}\$?[0-9]{1,7}(:\$?[A-Z]{1,3}\$?[0-9]{1,7})?|_
([a-zA-Zé0-9\._]+\()|([a-zA-Zé_][a-zA-Zé0-9_]+)"
.Global = True
.MultiLine = True
.IgnoreCase = False
End With
ExcelToolBox.zip 中的 Orphan.bas.
定义允许孤立公式的范围
' List of ranges where orphans are allowed
' List = Array("F1:H20")
List = Array()
ExcelToolBox.zip 中的 Orphan.bas.
代码会区分匹配项。
If Cel.HasFormula Then
' c'est bien une formule
Set Tmp = xRegEx.Execute(Cel.Formula)
For i = 0 To Tmp.Count - 1
If Left(Tmp.Item(i), 1) = """" Then
' ne pas traiter les chaines de caractères
ElseIf InStr(Tmp.Item(i), "(") <> 0 Then
' nom de fonction, sauter
ElseIf InStr(Tmp.Item(i), "TRUE") <> 0 Then
' sauter
ElseIf InStr(Tmp.Item(i), "FALSE") <> 0 Then
' sauter
Else
' si Cel dans ranges de list, alors sauter la vérification
' Vérifier la formule
Set Target = Range(Tmp.Item(i).Value)
Verif = True
If ActiveSheet.Name <> Target.Worksheet.Name Then
' WS différent, sauter
Else
For Each Ligne In List
If Not Application.Intersect(Range(Ligne), Target) _
Is Nothing Then
Verif = False
End If
Next
End If
If Verif Then
If Target.Rows.Count = 1 And Target.Columns.Count = 1 Then
If Target.Formula = "" And Target.Locked Then
Cel.Activate
' MsgBox "Cellule " & Replace(Cel.Address, _
' "$", "") & "fait référence à une cellule vide" & _
' vbLf & Replace(Ref.Address, "$", ""), vbYesNoCancel
Exit Sub
End If
End If
End If
End If
Next
End If
ExcelToolBox.zip 中的 Orphan.bas.
自动化计划
由于工作表非常大,我使用计划(Plan)来隐藏/折叠部分内容,以方便导航。宏负责创建计划。
预留一列用于指示哪些行是标题或详细信息(参见 Samples.xls)。
删除现有计划
' nettoyer plan
ActiveSheet.UsedRange.ClearOutline
ExcelToolBox.zip 中的 Plan.bas 和 Plan.xls.
新建计划的设置
' options plan
With ActiveSheet.Outline
.AutomaticStyles = False
.SummaryRow = xlAbove
.SummaryColumn = xlRight
End With
ExcelToolBox.zip 中的 Plan.bas 和 Plan.xls.
定位第二行中包含计划信息的列
' recherche colonne 'Plan'
Ligne = 2
For Col = 1 To ActiveSheet.Columns.Count
If ActiveSheet.Cells(Ligne, Col).Text = "Plan" Then
Exit For
End If
Next
ExcelToolBox.zip 中的 Plan.bas 和 Plan.xls.
定位列中的第一个计划信息
If ActiveSheet.Cells(Ligne, Col).Text = "Plan" Then
' chercher début premier bloc
For Row = Ligne + 1 To ActiveSheet.Rows.Count
If ActiveSheet.Cells(Row, Col).Value = 1 Then
Exit For
End If
Next
ExcelToolBox.zip 中的 Plan.bas 和 Plan.xls.
创建组
包含 1 的单元格是组标题,包含 2 的单元格是组正文。
' groupes
Row_db = Row
While Row_db < ActiveSheet.Rows.Count And ActiveSheet.Cells(Row_db, Col).Value > 0
' chercher fin bloc
row_fn = Row_db + 1
While row_fn <= ActiveSheet.Rows.Count And ActiveSheet.Cells(row_fn, Col).Value = 2
row_fn = row_fn + 1
Wend
If row_fn > Row_db + 1 Then
' grouper bloc
ActiveSheet.Range(Cells(Row_db + 1, 1), Cells(row_fn - 1, 1)).Rows.Group
End If
Row_db = row_fn
Wend
ExcelToolBox.zip 中的 Plan.bas 和 Plan.xls.
函数
函数用于单元格公式中。
Extract()
我的用户使用毫米为单位的金属板和梁的尺寸。尺寸为宽度 * 厚度 * 长度,即 200*10*550。
我的问题是,我需要获取这三个数字进行计算,而在 Excel 中没有标准简单的解决方案,必须直接在公式中进行繁琐处理。
我的解决方案是有一个专门的函数来完成这项工作。
' Author: Patrice T
' Extracts numbers from the string
Function Extract(Chaine, Optional Pos = 1)
If IsNumeric(Chaine) Then
' c'est numerique, on retourne la valeur
Extract = Chaine
Exit Function
End If
' Set re = CreateObject("VBScript.RegExp")
Set re = New VBScript_RegExp_55.RegExp
re.Global = True
re.Pattern = "[0-9,.]+"
Set A = re.Execute(Chaine)
If A.Count >= Pos Then
Extract = Val(Replace(A(Pos - 1), ",", "."))
End If
End Function
ExcelToolBox.zip 中的 Extract.bas 和 Plan.xls.
带线性插值的 VLookUp
我有很多表格缺少一些行。与其填写所有缺失的行,不如创建一个函数来定位搜索值周围的两个最接近的值,然后进行线性插值来获得缺失的值,这样更方便。
' VLookUp with linear interpolation
' 2015/04/01 Patrice T
Function VLookUpLI(Valeur, tableau, colon, dummy)
Dim Scan
Dim val_pref, val_suff, val_min, val_max, res_min, res_max
Dim tmp_pref, tmp_suff
If InStr(Valeur, "*") = 0 Then
val_pref = Val(Valeur)
val_suff = ""
Else
val_pref = Val(Left(Valeur, InStr(Valeur, "*") - 1))
val_suff = Mid(Valeur, InStr(Valeur, "*"))
End If
For Scan = 1 To tableau.Rows.Count
Tmp = tableau.Cells(Scan, 1).Value
If VarType(Tmp) = VarType(Valeur) Then
If Tmp = Valeur Then
' la valeur existe
VLookUpLI = tableau.Cells(Scan, colon).Value
Exit Function
End If
If InStr(Tmp, "*") = 0 Then
tmp_pref = Val(Tmp)
tmp_suff = ""
Else
tmp_pref = Val(Left(Tmp, InStr(Tmp, "*") - 1))
tmp_suff = Mid(Tmp, InStr(Tmp, "*"))
End If
If tmp_pref < val_pref And tmp_suff = val_suff Then
If IsEmpty(val_min) Then
val_min = tmp_pref
res_min = tableau.Cells(Scan, colon).Value
ElseIf val_min < tmp_pref Then
val_min = tmp_pref
res_min = tableau.Cells(Scan, colon).Value
End If
End If
If tmp_pref > val_pref And tmp_suff = val_suff Then
If IsEmpty(val_max) Then
val_max = tmp_pref
res_max = tableau.Cells(Scan, colon).Value
ElseIf tmp_pref < tmp_max Then
val_max = tmp_pref
res_max = tableau.Cells(Scan, colon).Value
End If
End If
End If
Next
If IsEmpty(val_min) Or IsEmpty(val_max) Then
' valeur hors tableau
VLookUpLI = "Hors limites"
Else
' interpolation linéaire
VLookUpLI = res_min + (res_max - res_min) *
(val_pref - val_min) / (val_max - val_min)
End If
End Function
ExcelToolBox.zip 中的 VLookU.
IndirectNV()
本地的 Indirect 函数是易失性的,因为 Excel 无法知道 Indirect 参数指向的单元格是否已更改。所以 Excel 唯一的解决方案是每次工作表重新计算时都强制进行评估,这就是易失性的含义。
我创建了非易失性 Indirect 函数,因为在我的使用场景中,我知道目标单元格是恒定的。
用法与本地 Indirect 函数相同。
' 20230526 Non Volatile Indirect
' Accelerate the usage of Indirect because the target is considered as constant
Function IndirectNV(Chaine As String) As Range
Set IndirectNV = Range(Chaine)
End Function
ExcelToolBox.zip 中的 Indirect.bas.
关注点
这些工具让作为 Excel 工作表设计者的我生活更加轻松。
历史
- 2023年7月15日:初稿
- 2023年7月19日:少量修正和下载更新
- 2023年7月27日:改进说明