Como evitar o uso de Select no Excel VBA
Já ouvi muito sobre a repulsa compreensível de usar o .Select
Excel VBA, mas não tenho certeza de como evitá-lo. Estou descobrindo que meu código seria mais reutilizável se eu pudesse usar variáveis em vez de Select
funções. No entanto, não tenho certeza de como me referir a coisas (como o ActiveCell
, etc.) se não estiver usando Select
.
Eu encontrei este artigo sobre intervalos e este exemplo sobre os benefícios de não usar o select , mas não consigo encontrar nada sobre como .
Respostas
Alguns exemplos de como evitar selecionar
Use Dim
'd variáveis
Dim rng as Range
Set
a variável para o intervalo necessário. Existem muitas maneiras de se referir a um intervalo de uma única célula:
Set rng = Range("A1")
Set rng = Cells(1, 1)
Set rng = Range("NamedRange")
Ou um intervalo de várias células:
Set rng = Range("A1:B10")
Set rng = Range("A1", "B10")
Set rng = Range(Cells(1, 1), Cells(10, 2))
Set rng = Range("AnotherNamedRange")
Set rng = Range("A1").Resize(10, 2)
Você pode usar o atalho para o Evaluate
método, mas isso é menos eficiente e geralmente deve ser evitado no código de produção.
Set rng = [A1]
Set rng = [A1:B10]
Todos os exemplos acima referem-se a células na planilha ativa . A menos que você queira trabalhar apenas com a planilha ativa, é melhor Dim uma Worksheet
variável também:
Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
Set rng = ws.Cells(1, 1)
With ws
Set rng = .Range(.Cells(1, 1), .Cells(2, 10))
End With
Se você não quer trabalhar com o ActiveSheet
, para maior clareza, é melhor ser explícito. Mas tome cuidado, pois alguns Worksheet
métodos alteram a planilha ativa.
Set rng = ActiveSheet.Range("A1")
Novamente, isso se refere à pasta de trabalho ativa . A menos que você queira trabalhar especificamente apenas com ActiveWorkbook
ou ThisWorkbook
, é melhor Dim uma Workbook
variável também.
Dim wb As Workbook
Set wb = Application.Workbooks("Book1")
Set rng = wb.Worksheets("Sheet1").Range("A1")
Se você não quer trabalhar com o ActiveWorkbook
, para maior clareza, é melhor ser explícito. Mas tome cuidado, pois muitos WorkBook
métodos mudam o livro ativo.
Set rng = ActiveWorkbook.Worksheets("Sheet1").Range("A1")
Você também pode usar o ThisWorkbook
objeto para se referir ao livro que contém o código em execução.
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1")
Um código comum (ruim) é abrir um livro, obter alguns dados e fechar novamente
Isto é mau:
Sub foo()
Dim v as Variant
Workbooks("Book1.xlsx").Sheets(1).Range("A1").Clear
Workbooks.Open("C:\Path\To\SomeClosedBook.xlsx")
v = ActiveWorkbook.Sheets(1).Range("A1").Value
Workbooks("SomeAlreadyOpenBook.xlsx").Activate
ActiveWorkbook.Sheets("SomeSheet").Range("A1").Value = v
Workbooks(2).Activate
ActiveWorkbook.Close()
End Sub
E seria melhor assim:
Sub foo()
Dim v as Variant
Dim wb1 as Workbook
Dim wb2 as Workbook
Set wb1 = Workbooks("SomeAlreadyOpenBook.xlsx")
Set wb2 = Workbooks.Open("C:\Path\To\SomeClosedBook.xlsx")
v = wb2.Sheets("SomeSheet").Range("A1").Value
wb1.Sheets("SomeOtherSheet").Range("A1").Value = v
wb2.Close()
End Sub
Passe intervalos para seus Sub
s e Function
s como variáveis de intervalo:
Sub ClearRange(r as Range)
r.ClearContents
'....
End Sub
Sub MyMacro()
Dim rng as Range
Set rng = ThisWorkbook.Worksheets("SomeSheet").Range("A1:B10")
ClearRange rng
End Sub
Você também deve aplicar métodos (como Find
e Copy
) às variáveis:
Dim rng1 As Range
Dim rng2 As Range
Set rng1 = ThisWorkbook.Worksheets("SomeSheet").Range("A1:A10")
Set rng2 = ThisWorkbook.Worksheets("SomeSheet").Range("B1:B10")
rng1.Copy rng2
Se você estiver fazendo um loop em um intervalo de células, geralmente é melhor (mais rápido) copiar os valores do intervalo para uma matriz variante primeiro e fazer um loop sobre isso:
Dim dat As Variant
Dim rng As Range
Dim i As Long
Set rng = ThisWorkbook.Worksheets("SomeSheet").Range("A1:A10000")
dat = rng.Value ' dat is now array (1 to 10000, 1 to 1)
for i = LBound(dat, 1) to UBound(dat, 1)
dat(i,1) = dat(i, 1) * 10 ' Or whatever operation you need to perform
next
rng.Value = dat ' put new values back on sheet
Esta é uma pequena amostra do que é possível.
Duas razões principais .Select
, .Activate
, Selection
, Activecell
, Activesheet
, Activeworkbook
, etc, devem ser evitados
- Isso retarda o seu código.
- Geralmente é a principal causa de erros de tempo de execução.
Como podemos evitar isso?
1) Trabalhar diretamente com os objetos relevantes
Considere este código
Sheets("Sheet1").Activate
Range("A1").Select
Selection.Value = "Blah"
Selection.NumberFormat = "@"
Este código também pode ser escrito como
With Sheets("Sheet1").Range("A1")
.Value = "Blah"
.NumberFormat = "@"
End With
2) Se necessário, declare suas variáveis. O mesmo código acima pode ser escrito como
Dim ws as worksheet
Set ws = Sheets("Sheet1")
With ws.Range("A1")
.Value = "Blah"
.NumberFormat = "@"
End With
Acrescentarei um pequeno ponto de ênfase a todas as excelentes respostas dadas anteriormente:
Provavelmente, a maior coisa que você pode fazer para evitar o uso de Select é , tanto quanto possível, usar intervalos nomeados (combinados com nomes de variáveis significativos) em seu código VBA . Este ponto foi mencionado acima, mas foi um pouco encoberto; no entanto, ele merece atenção especial.
Aqui estão algumas razões adicionais para fazer uso liberal de intervalos nomeados, embora eu tenha certeza de que poderia pensar em mais.
Intervalos nomeados tornam seu código mais fácil de ler e entender.
Exemplo:
Dim Months As Range
Dim MonthlySales As Range
Set Months = Range("Months")
' E.g, "Months" might be a named range referring to A1:A12
Set MonthlySales = Range("MonthlySales")
' E.g, "Monthly Sales" might be a named range referring to B1:B12
Dim Month As Range
For Each Month in Months
Debug.Print MonthlySales(Month.Row)
Next Month
É bastante óbvio o que os intervalos nomeados Months
e MonthlySales
contêm, e o que o procedimento está fazendo.
Por que isso é importante? Parcialmente porque é mais fácil para outras pessoas entenderem, mas mesmo se você for a única pessoa que verá ou usará seu código, você ainda deve usar intervalos nomeados e bons nomes de variáveis porque você esquecerá o que pretendia fazer com ele um ano depois, você perderá 30 minutos apenas tentando descobrir o que seu código está fazendo.
Intervalos nomeados garantem que suas macros não quebrem quando (não se!) A configuração da planilha mudar.
Considere, se o exemplo acima tivesse sido escrito assim:
Dim rng1 As Range
Dim rng2 As Range
Set rng1 = Range("A1:A12")
Set rng2 = Range("B1:B12")
Dim rng3 As Range
For Each rng3 in rng1
Debug.Print rng2(rng3.Row)
Next rng3
Este código funcionará bem no início - isto é até que você ou um futuro usuário decida "caramba, acho que vou adicionar uma nova coluna com o ano na coluna A
!", Ou colocar uma coluna de despesas entre os meses e colunas de vendas ou adicione um cabeçalho a cada coluna. Agora, seu código está quebrado. E como você usou nomes de variáveis terríveis, levará muito mais tempo para descobrir como corrigi-lo do que deveria.
Se você tivesse usado intervalos nomeados para começar, as colunas Months
e Sales
poderiam ser movidas o quanto quiser e seu código continuaria funcionando perfeitamente.
Eu vou dar uma resposta curta, já que todos os outros deram uma longa.
Você obterá .select e .activate sempre que gravar macros e reutilizá-las. Quando você seleciona uma célula ou planilha, ela apenas a torna ativa. Desse ponto em diante, sempre que você usar referências não qualificadas, Range.Value
elas usam apenas a célula e a planilha ativas. Isso também pode ser problemático se você não observar onde seu código é colocado ou se um usuário clicar na pasta de trabalho.
Portanto, você pode eliminar esses problemas referenciando diretamente suas células. Qual vai:
'create and set a range
Dim Rng As Excel.Range
Set Rng = Workbooks("Book1").Worksheets("Sheet1").Range("A1")
'OR
Set Rng = Workbooks(1).Worksheets(1).Cells(1, 1)
Ou você poderia
'Just deal with the cell directly rather than creating a range
'I want to put the string "Hello" in Range A1 of sheet 1
Workbooks("Book1").Worksheets("Sheet1").Range("A1").value = "Hello"
'OR
Workbooks(1).Worksheets(1).Cells(1, 1).value = "Hello"
Existem várias combinações desses métodos, mas essa seria a ideia geral expressa o mais brevemente possível para pessoas impacientes como eu.
"... e estou descobrindo que meu código seria mais reutilizável se eu pudesse usar variáveis em vez de funções Select."
Embora eu não consiga pensar em mais do que um punhado de situações isoladas em .Select
que seria uma escolha melhor do que a referência direta de células, eu levantaria em defesa Selection
e apontaria que ela não deve ser descartada pelas mesmas razões que .Select
devem ser evitadas.
Há momentos em que ter sub-rotinas de macro curtas e que economizam tempo atribuídas a combinações de teclas de atalho disponíveis com o toque de algumas teclas economiza muito tempo. Ser capaz de selecionar um grupo de células para decretar o código operacional em faz maravilhas ao lidar com dados embolsados que não estão em conformidade com um formato de dados de planilha. Da mesma forma que você pode selecionar um grupo de células e aplicar uma alteração de formato, selecionar um grupo de células para executar o código de macro especial pode economizar muito tempo.
Exemplos de subestrutura baseada em seleção:
Public Sub Run_on_Selected()
Dim rng As Range, rSEL As Range
Set rSEL = Selection 'store the current selection in case it changes
For Each rng In rSEL
Debug.Print rng.Address(0, 0)
'cell-by-cell operational code here
Next rng
Set rSEL = Nothing
End Sub
Public Sub Run_on_Selected_Visible()
'this is better for selected ranges on filtered data or containing hidden rows/columns
Dim rng As Range, rSEL As Range
Set rSEL = Selection 'store the current selection in case it changes
For Each rng In rSEL.SpecialCells(xlCellTypeVisible)
Debug.Print rng.Address(0, 0)
'cell-by-cell operational code here
Next rng
Set rSEL = Nothing
End Sub
Public Sub Run_on_Discontiguous_Area()
'this is better for selected ranges of discontiguous areas
Dim ara As Range, rng As Range, rSEL As Range
Set rSEL = Selection 'store the current selection in case it changes
For Each ara In rSEL.Areas
Debug.Print ara.Address(0, 0)
'cell group operational code here
For Each rng In ara.Areas
Debug.Print rng.Address(0, 0)
'cell-by-cell operational code here
Next rng
Next ara
Set rSEL = Nothing
End Sub
O código real a ser processado pode ser qualquer coisa, desde uma única linha até vários módulos. Usei esse método para iniciar rotinas de longa execução em uma seleção irregular de células contendo nomes de arquivos de pastas de trabalho externas.
Em suma, não descarte Selection
devido à sua estreita associação com .Select
e ActiveCell
. Como propriedade da planilha, ela tem muitos outros propósitos.
(Sim, eu sei que esta pergunta era sobre .Select
, Selection
mas eu queria remover quaisquer equívocos que os programadores VBA novatos possam inferir.)
Evitar Select
e Activate
é o movimento que o torna um desenvolvedor VBA um pouco melhor. Em geral, Select
e Activate
são usados quando uma macro é gravada, portanto a Parent
planilha ou intervalo é sempre considerado o ativo.
É assim que você pode evitar Select
e Activate
nos seguintes casos:
Adicionando uma nova planilha e copiando uma célula nela:
De (código gerado com gravador de macro):
Sub Makro2()
Range("B2").Select
Sheets.Add After:=ActiveSheet
Sheets("Tabelle1").Select
Sheets("Tabelle1").Name = "NewName"
ActiveCell.FormulaR1C1 = "12"
Range("B2").Select
Selection.Copy
Range("B3").Select
ActiveSheet.Paste
Application.CutCopyMode = False
End Sub
Para:
Sub TestMe()
Dim ws As Worksheet
Set ws = Worksheets.Add
With ws
.Name = "NewName"
.Range("B2") = 12
.Range("B2").Copy Destination:=.Range("B3")
End With
End Sub
Quando você deseja copiar o intervalo entre as planilhas:
A partir de:
Sheets("Source").Select
Columns("A:D").Select
Selection.Copy
Sheets("Target").Select
Columns("A:D").Select
ActiveSheet.Paste
Para:
Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").Range("a1")
Usando intervalos nomeados sofisticados
Você pode acessá-los com []
, o que é muito bonito, em comparação com o outro modo. Verifique você mesmo:
Dim Months As Range
Dim MonthlySales As Range
Set Months = Range("Months")
Set MonthlySales = Range("MonthlySales")
Set Months =[Months]
Set MonthlySales = [MonthlySales]
O exemplo acima ficaria assim:
Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").[A1]
Não copiando valores, mas tomando-os
Normalmente, se você estiver disposto select
, provavelmente está copiando algo. Se você está interessado apenas nos valores, esta é uma boa opção para evitar selecionar:
Range("B1:B6").Value = Range("A1:A6").Value
Tente sempre consultar a planilha também
Este é provavelmente o erro mais comum no vba . Sempre que você copia intervalos, às vezes a planilha não é referenciada e, portanto, o VBA considera a planilha errada a ActiveWorksheet.
'This will work only if the 2. Worksheet is selected!
Public Sub TestMe()
Dim rng As Range
Set rng = Worksheets(2).Range(Cells(1, 1), Cells(2, 2)).Copy
End Sub
'This works always!
Public Sub TestMe2()
Dim rng As Range
With Worksheets(2)
.Range(.Cells(1, 1), .Cells(2, 2)).Copy
End With
End Sub
Eu realmente nunca posso usar .Select
ou .Activate
para nada?
- Um bom exemplo de quando você pode ter justificativa para usar
.Activate
e.Select
é quando você quer ter certeza de que uma planilha específica foi selecionada por motivos visuais. Por exemplo, que seu Excel sempre abriria com a planilha de capa selecionada primeiro, desconsiderando qual era a ActiveSheet quando o arquivo foi fechado.
Assim, algo como o código abaixo está absolutamente OK:
Private Sub Workbook_Open()
Worksheets("Cover").Activate
End Sub
Outro bom exemplo é quando você precisa exportar todas as planilhas em um arquivo PDF, conforme mencionado neste caso - Como evitar instruções select / active no VBA neste exemplo?
Quando um comando só funciona com
ActiveWindow
como ActiveWindow.Zoom ou ActiveWindow.FreezePanes
Observe que a seguir estou comparando a abordagem Select (aquela que o OP deseja evitar), com a abordagem Range (e esta é a resposta à pergunta). Portanto, não pare de ler quando vir o primeiro Select.
Realmente depende do que você está tentando fazer. De qualquer forma, um exemplo simples pode ser útil. Vamos supor que você queira definir o valor da célula ativa como "foo". Usando ActiveCell, você escreveria algo assim:
Sub Macro1()
ActiveCell.Value = "foo"
End Sub
Se você deseja usá-lo para uma célula que não é a ativa, por exemplo para "B2", você deve selecioná-lo primeiro, assim:
Sub Macro2()
Range("B2").Select
Macro1
End Sub
Usando Ranges, você pode escrever uma macro mais genérica que pode ser usada para definir o valor de qualquer célula que você quiser:
Sub SetValue(cellAddress As String, aVal As Variant)
Range(cellAddress).Value = aVal
End Sub
Então você pode reescrever Macro2 como:
Sub Macro2()
SetCellValue "B2", "foo"
End Sub
E Macro1 como:
Sub Macro1()
SetValue ActiveCell.Address, "foo"
End Sub
Sempre indique a pasta de trabalho, a planilha e a célula / intervalo.
Por exemplo:
Thisworkbook.Worksheets("fred").cells(1,1)
Workbooks("bob").Worksheets("fred").cells(1,1)
Como os usuários finais sempre clicarão em botões e, assim que o foco sair da pasta de trabalho com a qual o código deseja trabalhar, as coisas darão errado.
E nunca use o índice de uma pasta de trabalho.
Workbooks(1).Worksheets("fred").cells(1,1)
Você não sabe quais outras pastas de trabalho serão abertas quando o usuário executar seu código.
Esses métodos são bastante estigmatizados, então assumindo a liderança de Vityata e Jeeped para traçar uma linha na areia:
Chamada .Activate
, .Select
, Selection
, ActiveSomething
métodos / propriedades
Basicamente porque eles são chamados principalmente para manipular a entrada do usuário por meio da interface do usuário do aplicativo. Uma vez que são os métodos chamados quando o usuário manipula objetos por meio da IU, eles são os gravados pelo gravador de macro e é por isso que chamá-los é frágil ou redundante para a maioria das situações: você não precisa selecionar um objeto de modo a realizar uma ação com o Selection
direito em seguida.
No entanto, esta definição estabelece as situações em que são necessários:
Quando chamar .Activate
, .Select
, .Selection
, .ActiveSomething
métodos / propriedades
Basicamente, quando você espera que o usuário final desempenhe um papel na execução.
Se você está desenvolvendo e espera que o usuário escolha as instâncias de objeto para seu código manipular, então .Selection
ou .ActiveObject
são apropriadas.
Por outro lado, .Select
e .Activate
são úteis quando você pode inferir a próxima ação do usuário e deseja que seu código oriente o usuário, possivelmente economizando algum tempo e cliques do mouse. Por exemplo, se o seu código acabou de criar uma nova instância de um gráfico ou atualizou um, o usuário pode querer fazer o check-out e você pode chamá .Activate
-lo ou sua planilha para economizar o tempo de pesquisa do usuário; ou se você souber que o usuário precisará atualizar alguns valores de intervalo, poderá selecionar esse intervalo de maneira programática.
O uso de IMHO .select
vem de pessoas que, como eu, começaram a aprender VBA por necessidade, gravando macros e, em seguida, modificando o código sem perceber que .select
e subsequentemente selection
é apenas um intermediário desnecessário.
.select
pode ser evitado, como muitos já postados, trabalhando diretamente com os objetos já existentes, o que permite várias referências indiretas como calcular i e j de uma forma complexa e depois editar a célula (i, j), etc.
Caso contrário, não há nada implicitamente errado com .select
ele e você pode encontrar usos para isso facilmente, por exemplo, eu tenho uma planilha que eu preencho com a data, ativo a macro que faz alguma mágica com ela e a exporta em um formato aceitável em uma planilha separada, que , entretanto, requer algumas entradas manuais finais (imprevisíveis) em uma célula adjacente. Então, chega o momento em .select
que me poupa aquele movimento adicional do mouse e clique.
Para evitar o uso do .Select
método, você pode definir uma variável igual à propriedade que deseja.
► Por exemplo, se você deseja o valor em, Cell A1
pode definir uma variável igual à propriedade de valor dessa célula.
- Exemplo
valOne = Range("A1").Value
► Por exemplo, se você quiser o codinome da you could set a variable equal to the
propriedade 'Sheet3 Codename` dessa planilha.
- Exemplo
valTwo = Sheets("Sheet3").Codename
Percebi que nenhuma dessas respostas menciona a propriedade .Offset . Isso também pode ser usado para evitar o uso da Select
ação ao manipular certas células, particularmente em referência a uma célula selecionada (como o OP menciona com ActiveCell
).
Aqui estão alguns exemplos.
Também assumirei que o "ActiveCell" é J4 .
ActiveCell.Offset(2, 0).Value = 12
- Isso mudará a célula
J6
para um valor de 12 - Um menos -2 teria referenciado J2
ActiveCell.Offset(0,1).Copy ActiveCell.Offset(,2)
- Isso copiará a célula
k4
paraL4
. - Observe que "0" não é necessário no parâmetro de deslocamento se não for necessário (, 2)
- Semelhante ao exemplo anterior, um menos 1 seria
i4
ActiveCell.Offset(, -1).EntireColumn.ClearContents
- Isso limpará os valores em todas as células da coluna k.
Isso não quer dizer que sejam "melhores" do que as opções acima, mas apenas listar alternativas.
Como evitar copiar e colar?
Vamos enfrentá-lo: este aparece muito ao gravar macros:
Range("X1").Select
Selection.Copy
Range("Y9).Select
Selection.Paste
Embora a única coisa que a pessoa queira é:
Range("Y9").Value = Range("X1").Value
Portanto, em vez de usar copiar e colar em macros VBA, aconselho a seguinte abordagem simples:
Destination_Range.Value = Source_Range.Value
Trabalhando com o recurso .Parent , este exemplo mostra como definir apenas uma referência myRng permite o acesso dinâmico a todo o ambiente sem qualquer .Select, .Activate, .Activecell, .ActiveWorkbook, .ActiveSheet e assim por diante. (Não há nenhum recurso .Child genérico .)
Sub ShowParents()
Dim myRng As Range
Set myRng = ActiveCell
Debug.Print myRng.Address ' An address of the selected cell
Debug.Print myRng.Parent.name ' The name of sheet, where MyRng is in
Debug.Print myRng.Parent.Parent.name ' The name of workbook, where MyRng is in
Debug.Print myRng.Parent.Parent.Parent.name ' The name of application, where MyRng is in
' You may use this feature to set reference to these objects
Dim mySh As Worksheet
Dim myWbk As Workbook
Dim myApp As Application
Set mySh = myRng.Parent
Set myWbk = myRng.Parent.Parent
Set myApp = myRng.Parent.Parent.Parent
Debug.Print mySh.name, mySh.Cells(10, 1).Value
Debug.Print myWbk.name, myWbk.Sheets.Count
Debug.Print myApp.name, myApp.Workbooks.Count
' You may use dynamically addressing
With myRng
.Copy
' Pastes in D1 on sheet 2 in the same workbook, where the copied cell is
.Parent.Parent.Sheets(2).Range("D1").PasteSpecial xlValues
' Or myWbk.Sheets(2).Range("D1").PasteSpecial xlValues
' We may dynamically call active application too
.Parent.Parent.Parent.CutCopyMode = False
' Or myApp.CutCopyMode = False
End With
End Sub
A principal razão para nunca usar Select ou Activesheet é porque a maioria das pessoas terá pelo menos outro par de pastas de trabalho abertas (às vezes dezenas) quando executam sua macro, e se clicam fora de sua planilha enquanto sua macro está em execução e clicam em algum outro o livro que eles abriram, então a "Folha de atividades" muda e a pasta de trabalho de destino para um comando "Selecionar" não qualificado também muda.
Na melhor das hipóteses, sua macro irá travar; na pior, você pode acabar escrevendo valores ou alterando células na pasta de trabalho errada sem nenhuma maneira de "Desfazê-los".
Eu tenho uma regra de ouro simples que sigo: adicionar variáveis chamadas "wb" e "ws" para um objeto Workbook e um objeto Worksheet e sempre usá-los para se referir ao meu livro de macros. Se eu precisar consultar mais de um livro ou mais de uma folha, adiciono mais variáveis.
Por exemplo,
Dim wb as Workbook
Dim ws as Worksheet
Set wb = ThisWorkBook
Set ws = wb.sheets("Output")
O comando "Set wb = ThisWorkbook" é absolutamente fundamental. "ThisWorkbook" é um valor especial no Excel e significa a pasta de trabalho a partir da qual seu código VBA está sendo executado . Um atalho muito útil para definir a variável da pasta de trabalho com.
Depois de fazer isso no topo de seu Sub, usá-los não poderia ser mais simples, basta usá-los onde quer que você usaria "Seleção":
Portanto, para alterar o valor da célula "A1" em "Saída" para "Olá", em vez de:
Sheets("Output").Activate
ActiveSheet.Range("A1").Select
Selection.Value = "Hello"
Agora podemos fazer isso:
ws.Range("A1").Value = "Hello"
O que não é apenas muito mais confiável e menos provável de travar se o usuário estiver trabalhando com várias planilhas; também é muito mais curto, rápido e fácil de escrever.
Como um bônus adicional, se você sempre nomear suas variáveis como "wb" e "ws", poderá copiar e colar o código de um livro para outro e geralmente funcionará com o mínimo de alterações necessárias, se houver.