Simpler/cleaner 在 VBA 中使用大量命名单元格时指定范围的方法?
Simpler/cleaner way to specify ranges when using lots of named cells in VBA?
在我正在开发的一个相当大的 Excel 程序中,我在电子表格中有很多命名单元格,这些单元格填充了数据,以便在用户决定移动时帮助保持格式一致周围的模板设置,并避免在各种潜艇中硬编码 row/column 数字。不幸的是,这导致我的代码中有很多丑陋的范围引用,如下例所示。是否有一种 simpler/cleaner 看起来更易读的方式来编写我缺少的这些内容?
'Examples of ugly range references:
Range(Range("GS_BeginData").Offset((counter + m), -1), Range("GS_BeginData").Offset((counter + m), 2))
Range(Range("GS_BeginData").Offset(counter, 1), Range("GS_BeginData").Offset((counter + fileCount), 1))
Range("SS_Unit", Range("SS_Unit").Offset(0, 1))
Range("SS_BeginData", Range("SS_BeginData").End(xlDown))
Cells((Range("SS_BeginData").Row + i), (Range("SS_BeginData").Column + 1))
我想我总是可以在 Subs 的开头为这些不同的命名单元格和范围设置简单的命名变量,以清理代码的外观,但我有很多,我真的不想在我所有的 Subs 的开头添加大量的变量声明行。
更容易阅读的方式 -- 是的。根据需要声明和分配对象变量。至少,您可以定义 "GS_BeginData" 和 "SS_Unit" 以及 "SS_BeginData" 的范围,这将使您的代码更具可读性和可维护性。
这样,如果您更改了命名范围引用的 names,您只需要更新分配给初始对象的几行代码,而不是每个字面引用,例如 "GS_BeginData"
,等等
Dim GSBegin as Range, SSBegin as Range, SSUnit as Range
Set GSBegin = Range("GS_BeginData")
Set SSBegin = Range("SS_BeginData")
Set SSUnit = Range("SS_Unit")
那么,你丑陋的引用就可以修改了:
Range(GSBegin.Offset((counter + m), -1), GSBegin.Offset((counter + m), 2))
Range(GSBegin.Offset(counter, 1), GSBegin.Offset((counter + fileCount), 1))
SSUnit.Offset(0, 1)
Range(SSBegin, SSBegin.End(xlDown))
Cells((SSBegin.Row + i), (SSBegin.Column + 1))
- 使用变量! (
Dim
的代价很小)
- 利用其他 Range 属性。在这些情况下
Resize
很有用
.
Dim GS_BeginData As Range
Set GS_BeginData = Range("GS_BeginData")
'Set r = Range(Range("GS_BeginData").Offset((counter + m), -1), Range("GS_BeginData").Offset((counter + m), 2))
Set r = GS_BeginData.Offset(counter + m, -1).Resize(, 3)
'Set r = Range(Range("GS_BeginData").Offset(counter, 1), Range("GS_BeginData").Offset((counter + fileCount), 1))
Set r = GS_BeginData.Offset(counter, 1).Resize(fileCount, 1)
Dim SS_Unit As Range
Set SS_Unit = Range("SS_Unit")
'Set r = Range("SS_Unit", Range("SS_Unit").Offset(0, 1))
Set r = SS_Unit.Resize(, 2)
Set r = SS_Unit.Resize(, SS_Unit.Columns.Count + 1) ' if SS_Unit has more than 1 column
Dim SS_BeginData As Range
Set SS_BeginData = Range("SS_BeginData")
'Set r = Range("SS_BeginData", Range("SS_BeginData").End(xlDown))
Set r = Range(SS_BeginData, SS_BeginData.End(xlDown))
'Set r = Cells((Range("SS_BeginData").Row + i), (Range("SS_BeginData").Column + 1))
Set r = SS_BeginData.Offset(i, 1).Resize(1, 1)
我不使用命名范围,因为它们难以维护并且很快就会变得一团糟。我总是使用这两个功能:
Function RowNumber(Header As String, _
Optional Sh As Worksheet, _
Optional FromColumn As Integer = 1, _
Optional IgnoreError As Boolean = False) As Integer
If Sh Is Nothing Then Set Sh = ActiveSheet
Dim R As Integer
For R = 1 To Sh.UsedRange.Row + Sh.UsedRange.Rows.Count - 1
If Sh.Cells(R, FromColumn) = Header Then RowNumber = R: Exit Function
Next R
If Not IgnoreError Then MsgBox "Row """ & Header & """ not found", vbCritical
End Function
Function ColumnNumber(Header As String, _
Optional Sh As Worksheet, _
Optional FromRow As Integer = 1, _
Optional IgnoreError As Boolean = False) As Integer
If Sh Is Nothing Then Set Sh = ActiveSheet
Dim C As Integer
For C = 1 To Sh.UsedRange.Column + Sh.UsedRange.Columns.Count - 1
If Sh.Cells(FromRow, C) = Header Then ColumnNumber = C: Exit Function
Next C
If Not IgnoreError Then MsgBox "Column """ & Header & """ not found", vbCritical
End Function
然后我得到类似这样的值:
Value = Sh.Cells(RowNumber("Header", Sh), 2)
我有时会使用另一个版本 memoization。
如果你真的在许多不同的 Subs 中使用它,你也可以创建一个函数来 DRY 你的代码,比如。
Function GSBeginOffsetRange(a As Long, b As Long, c As Long, d As Long) As Range
Dim GSBegin As Range
Set GSBegin = Range("GS_BeginData")
Set GSBeginOffsetRange = Range(GSBegin.Offset(a, b), GSBegin.Offset(c, d))
End Function
这将使您的第一行示例:
GSBeginOffsetRange((counter + m), -1, (counter + m), 2)
您能否简单地列出所有命名范围,然后遍历该列表中的每个项目?
在我正在开发的一个相当大的 Excel 程序中,我在电子表格中有很多命名单元格,这些单元格填充了数据,以便在用户决定移动时帮助保持格式一致周围的模板设置,并避免在各种潜艇中硬编码 row/column 数字。不幸的是,这导致我的代码中有很多丑陋的范围引用,如下例所示。是否有一种 simpler/cleaner 看起来更易读的方式来编写我缺少的这些内容?
'Examples of ugly range references:
Range(Range("GS_BeginData").Offset((counter + m), -1), Range("GS_BeginData").Offset((counter + m), 2))
Range(Range("GS_BeginData").Offset(counter, 1), Range("GS_BeginData").Offset((counter + fileCount), 1))
Range("SS_Unit", Range("SS_Unit").Offset(0, 1))
Range("SS_BeginData", Range("SS_BeginData").End(xlDown))
Cells((Range("SS_BeginData").Row + i), (Range("SS_BeginData").Column + 1))
我想我总是可以在 Subs 的开头为这些不同的命名单元格和范围设置简单的命名变量,以清理代码的外观,但我有很多,我真的不想在我所有的 Subs 的开头添加大量的变量声明行。
更容易阅读的方式 -- 是的。根据需要声明和分配对象变量。至少,您可以定义 "GS_BeginData" 和 "SS_Unit" 以及 "SS_BeginData" 的范围,这将使您的代码更具可读性和可维护性。
这样,如果您更改了命名范围引用的 names,您只需要更新分配给初始对象的几行代码,而不是每个字面引用,例如 "GS_BeginData"
,等等
Dim GSBegin as Range, SSBegin as Range, SSUnit as Range
Set GSBegin = Range("GS_BeginData")
Set SSBegin = Range("SS_BeginData")
Set SSUnit = Range("SS_Unit")
那么,你丑陋的引用就可以修改了:
Range(GSBegin.Offset((counter + m), -1), GSBegin.Offset((counter + m), 2))
Range(GSBegin.Offset(counter, 1), GSBegin.Offset((counter + fileCount), 1))
SSUnit.Offset(0, 1)
Range(SSBegin, SSBegin.End(xlDown))
Cells((SSBegin.Row + i), (SSBegin.Column + 1))
- 使用变量! (
Dim
的代价很小) - 利用其他 Range 属性。在这些情况下
Resize
很有用
.
Dim GS_BeginData As Range
Set GS_BeginData = Range("GS_BeginData")
'Set r = Range(Range("GS_BeginData").Offset((counter + m), -1), Range("GS_BeginData").Offset((counter + m), 2))
Set r = GS_BeginData.Offset(counter + m, -1).Resize(, 3)
'Set r = Range(Range("GS_BeginData").Offset(counter, 1), Range("GS_BeginData").Offset((counter + fileCount), 1))
Set r = GS_BeginData.Offset(counter, 1).Resize(fileCount, 1)
Dim SS_Unit As Range
Set SS_Unit = Range("SS_Unit")
'Set r = Range("SS_Unit", Range("SS_Unit").Offset(0, 1))
Set r = SS_Unit.Resize(, 2)
Set r = SS_Unit.Resize(, SS_Unit.Columns.Count + 1) ' if SS_Unit has more than 1 column
Dim SS_BeginData As Range
Set SS_BeginData = Range("SS_BeginData")
'Set r = Range("SS_BeginData", Range("SS_BeginData").End(xlDown))
Set r = Range(SS_BeginData, SS_BeginData.End(xlDown))
'Set r = Cells((Range("SS_BeginData").Row + i), (Range("SS_BeginData").Column + 1))
Set r = SS_BeginData.Offset(i, 1).Resize(1, 1)
我不使用命名范围,因为它们难以维护并且很快就会变得一团糟。我总是使用这两个功能:
Function RowNumber(Header As String, _
Optional Sh As Worksheet, _
Optional FromColumn As Integer = 1, _
Optional IgnoreError As Boolean = False) As Integer
If Sh Is Nothing Then Set Sh = ActiveSheet
Dim R As Integer
For R = 1 To Sh.UsedRange.Row + Sh.UsedRange.Rows.Count - 1
If Sh.Cells(R, FromColumn) = Header Then RowNumber = R: Exit Function
Next R
If Not IgnoreError Then MsgBox "Row """ & Header & """ not found", vbCritical
End Function
Function ColumnNumber(Header As String, _
Optional Sh As Worksheet, _
Optional FromRow As Integer = 1, _
Optional IgnoreError As Boolean = False) As Integer
If Sh Is Nothing Then Set Sh = ActiveSheet
Dim C As Integer
For C = 1 To Sh.UsedRange.Column + Sh.UsedRange.Columns.Count - 1
If Sh.Cells(FromRow, C) = Header Then ColumnNumber = C: Exit Function
Next C
If Not IgnoreError Then MsgBox "Column """ & Header & """ not found", vbCritical
End Function
然后我得到类似这样的值:
Value = Sh.Cells(RowNumber("Header", Sh), 2)
我有时会使用另一个版本 memoization。
如果你真的在许多不同的 Subs 中使用它,你也可以创建一个函数来 DRY 你的代码,比如。
Function GSBeginOffsetRange(a As Long, b As Long, c As Long, d As Long) As Range
Dim GSBegin As Range
Set GSBegin = Range("GS_BeginData")
Set GSBeginOffsetRange = Range(GSBegin.Offset(a, b), GSBegin.Offset(c, d))
End Function
这将使您的第一行示例:
GSBeginOffsetRange((counter + m), -1, (counter + m), 2)
您能否简单地列出所有命名范围,然后遍历该列表中的每个项目?