10

我只是偶尔使用 VBA,每次我回到它时,我都会被以下的一些变化所吸引:

我有一个Range对象 ,currentCell我用它来跟踪我在电子表格中使用的单元格。当我更新它以指向不同的单元格时,我写道:

currentCell = currentCell.Offset(ColumnOffset:=1)

问题是我忘记了Set关键字,所以上面这行实际上是使用Range对象的默认属性:

currentCell.Value = currentCell.Offset(ColumnOffset:=1).Value

所以当前单元格的内容被新单元格中的内容覆盖,我的currentCell变量没有改变为指向一个新单元格,当我意识到我已经一百次犯了同样的错误时,我充满了愤怒.

可能没有比在我的显示器上贴一个便利贴说“你记得今天使用 Set 了吗?”更好的答案了,但如果有人有任何建议可以帮助我,我会很高兴听到他们的意见。尤其:

  • 当您隐式使用默认属性时,有什么方法可以打开警告?我从来没有故意这样使用它们,Range.Value如果我是这个意思,我总是会打电话。
  • 将变量标记为“这只应用于从电子表格中读取”有什么好的做法吗?在我编写的大多数代码中,几乎所有变量都用于收集数据,如果某些东西开始无意中编辑这样的单元格,那么得到警告会很方便。
4

6 回答 6

6

我花了一段时间才明白你是如何遇到问题的,因为尽管多年来我一直使用范围对象,但我认为我从来没有遇到过这个问题。在考虑了一些事情之后,我意识到我在使用单元格时 100% 的时间都在做以下事情——我经常使用offsetorcells函数——我很少用它Set来重新定义当前变量。

如果我有一个循环遍历电子表格,我可能会做类似的事情

Dim startRng as Range
Set startRng = range("A1")

for i = 1 to 100
   startRng.offset(i,0).value = i
   startRng.offset(i,1).value = startRng.offset(i,0).value
next i

或者

for i = 1 to 100
    cells(i,0).value = i
    cells(i,1).value = cells(i,0).value
next i

这些符号中的任何一个都意味着我几乎不需要Set与范围对象一起使用 - 几乎总是这种情况发生一次(如果有的话)并指示我将迭代或引用的范围中的第一个单元格。

它是什么也很清楚offset- 因为您指定了 a row/column- 这使得代码中发生的事情变得非常简单并且更容易跟踪,因为它引用了单个单元格。您不必追踪并追溯至您更新currentCell Range对象的最后 3 个位置。

采用使用这些风格的编码风格应该可以消除您所犯的几乎所有这些错误。当我说我不记得在编写 VBA 的这些年中曾经犯过类似的错误时,我非常认真 - 我在我的代码中连续使用offsetandcells函数(这些示例中的循环,但我在代码中的所有其他示例中使用类似的方法)而不是将范围设置为新范围。副作用是,当您在代码中设置范围时,它几乎总是紧跟在Dim语句之后并且更加清晰。

于 2012-09-09T21:55:11.843 回答
4

无论你选择做什么,恐怕你都需要便利贴。毕竟,将范围对象的值设置为另一个单元格中的值是一件非常有效且常见的事情。代码无法知道您希望它做任何事情,而不是您要求它做的事情。

您可以尝试在更新之前和之后检查范围对象的地址以确保它不同,但如果您记得这样做,您最好简单地使用set关键字以您想要的方式更新对象。

既然这个问题已经激怒了您,以至于发布您的问题,我想您将永远不会再忘记它,无论您下次访问 VBA 之前有多少时间。所以也许你根本就不需要便利贴。

于 2012-09-09T21:43:13.897 回答
3

可能没有比在我的显示器上贴一个便利贴说“你记得今天使用 Set 了吗?”更好的答案了,但如果有人有任何建议可以帮助我,我会很高兴听到他们的意见。尤其:

我会稍微改变一下 Post It 上的措辞

“你确定你没有忘记使用选项显式和错误处理吗?”

否则相信我没有更好的办法!话虽如此,我想确认“使用 Set”是您最不担心的问题。你最担心的应该是“编写好的代码”,这不是一朝一夕的事。这一切都来自实践。

我对所有初学者的建议。永远不要假设!例如,.Value是范围的默认属性,所以

Range("A1") = "Blah" 

是正确的。但仍然避免使用它。

  1. 始终完全限定您的变量
  2. 始终使用选项显式
  3. 始终使用错误处理

例如,这有效。

Option Explicit

Sub Sample()
    Dim ws As Worksheet
    Dim rng As Range

    On Error GoTo Whoa

    Set ws = ThisWorkbook.Sheets("Sheet1")
    Set rng = ws.Range("A1")
    rng.Value = "Blah"

    Exit Sub
Whoa:
    MsgBox Err.Description
End Sub

现在让我们在不使用 Set 命令的情况下尝试上面的代码。试试下面的代码。发生什么了?

Option Explicit

Sub Sample()
    Dim ws As Worksheet
    Dim rng As Range

    On Error GoTo Whoa

    ws = ThisWorkbook.Sheets("Sheet1")
    rng = ws.Range("A1")
    rng.Value = "Blah"

    Exit Sub
Whoa:
    MsgBox Err.Description
End Sub

推荐阅读。

主题:“犯错”是人之常情

链接http ://siddharthout.wordpress.com/2011/08/01/to-err-is-human/

于 2012-09-10T06:32:50.590 回答
3

当您隐式使用默认属性时,有什么方法可以打开警告?

不。

将变量标记为“这只应用于从电子表格中读取”有什么好的做法吗?

好吧,您可以制定自己的变量命名约定,就像让错误的代码看起来错误一样,但您仍然必须直观地检查自己的代码,编译器不会帮助您做到这一点。所以我不会太依赖这个。

更好的选择是完全避免重复重新定义currentCell使用的需要.Offset

取而代之的是,将感兴趣的整个范围读取到 Variant 数组,在该数组上进行操作,然后在完成修改后将其重新放到工作表上。

Dim i As Long
Dim j As Long
Dim v As Variant
Dim r As Range

Set r = Range("A1:D5") 'or whatever

v = r.Value 'pull from sheet

For i = 1 To UBound(v, 1)
    For j = 1 To UBound(v, 2)
        'code to modify or utilise element v(i,j) goes here
    Next j
Next i

r.Value = v 'slap v back onto sheet (if you modified it)

瞧。不使用默认属性或任何可能被混淆的东西。作为奖励,这将加快您的代码执行速度。

于 2012-09-10T06:50:17.860 回答
1

我认为 enderland 强调了基本的解决方案,即在一个循环中处理多个单元格。为了更进一步,我建议使用 For Next 循环在单元格中循环。我写的最常见的代码可能是这样的:

Dim cell as Excel.Range
Dim rngCellsToProcess as Excel.Range

Set rngCellsToProcess = 'whatever you set it to
For each cell in rngCellsToProcess 
   'do something
Next cell

这不会消除对 Set 的需要,但可能有助于提醒您使用它,同时让您更清楚正在发生的事情。

于 2012-09-09T23:11:57.603 回答
0

也许编写您自己的自定义函数并改用它?

Sub offset_rng(ByRef my_rng As Range, _
    Optional row As Integer, Optional col As Integer)
    Set my_rng = my_rng.Offset(row, col)
End Sub

可以这样使用:

Sub test()
    Dim rng As Range
    Set rng = Range("A1")
    offset_rng my_rng:=rng, col:=1
    rng.Value = "test1"
    offset_rng my_rng:=rng, col:=1
    rng.Value = "test2"
    offset_rng my_rng:=rng, col:=1
    rng.Value = "test3"
    offset_rng my_rng:=rng, col:=1
    rng.Value = "test4"
End Sub
于 2012-09-09T21:12:01.750 回答