Cómo evitar el uso de Seleccionar en Excel VBA
He escuchado mucho sobre el comprensible aborrecimiento de usarlo .Select
en Excel VBA, pero no estoy seguro de cómo evitar usarlo. Descubro que mi código sería más reutilizable si pudiera usar variables en lugar de Select
funciones. Sin embargo, no estoy seguro de cómo referirme a cosas (como ActiveCell
, etc.) si no las estoy usando Select
.
Encontré este artículo sobre rangos y este ejemplo sobre los beneficios de no usar select , pero no puedo encontrar nada sobre cómo .
Respuestas
Algunos ejemplos de cómo evitar seleccionar
Utilice Dim
'd variables
Dim rng as Range
Set
la variable al rango requerido. Hay muchas formas de hacer referencia a un rango de una sola celda:
Set rng = Range("A1")
Set rng = Cells(1, 1)
Set rng = Range("NamedRange")
O un rango de varias celdas:
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)
Usted puede utilizar el acceso directo al Evaluate
método, pero esto es menos eficiente y por lo general se debe evitar en el código de producción.
Set rng = [A1]
Set rng = [A1:B10]
Todos los ejemplos anteriores se refieren a celdas en la hoja activa . A menos que desee trabajar específicamente solo con la hoja activa, es mejor atenuar también una Worksheet
variable:
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
Si no desea trabajar con el ActiveSheet
, para mayor claridad lo mejor es ser explícita. Pero tenga cuidado, ya que algunos Worksheet
métodos cambian la hoja activa.
Set rng = ActiveSheet.Range("A1")
Nuevamente, esto se refiere al libro de trabajo activo . A menos que desee trabajar específicamente solo con ActiveWorkbook
o ThisWorkbook
, es mejor atenuar también una Workbook
variable.
Dim wb As Workbook
Set wb = Application.Workbooks("Book1")
Set rng = wb.Worksheets("Sheet1").Range("A1")
Si no desea trabajar con el ActiveWorkbook
, para mayor claridad lo mejor es ser explícita. Pero cuidado, ya que muchos WorkBook
métodos cambian el libro activo.
Set rng = ActiveWorkbook.Worksheets("Sheet1").Range("A1")
También puede utilizar el ThisWorkbook
objeto para consultar el libro que contiene el código en ejecución.
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1")
Un fragmento de código común (malo) es abrir un libro, obtener algunos datos y volver a cerrar
Esto es malo:
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
Y sería mejor como:
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
Pase rangos a sus Sub
s y Function
s como variables de rango:
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
También debe aplicar métodos (como Find
y Copy
) a las variables:
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
Si está recorriendo un rango de celdas, a menudo es mejor (más rápido) copiar primero los valores del rango en una matriz variante y recorrerlo:
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 es una pequeña muestra de lo que es posible.
Dos razones principales por las que .Select
, .Activate
, Selection
, Activecell
, Activesheet
, Activeworkbook
, etc deben ser evitados
- Ralentiza tu código.
- Suele ser la principal causa de errores en tiempo de ejecución.
¿Cómo lo evitamos?
1) Trabajar directamente con los objetos relevantes
Considere este código
Sheets("Sheet1").Activate
Range("A1").Select
Selection.Value = "Blah"
Selection.NumberFormat = "@"
Este código también se puede escribir como
With Sheets("Sheet1").Range("A1")
.Value = "Blah"
.NumberFormat = "@"
End With
2) Si es necesario, declare sus variables. El mismo código anterior se puede escribir como
Dim ws as worksheet
Set ws = Sheets("Sheet1")
With ws.Range("A1")
.Value = "Blah"
.NumberFormat = "@"
End With
Un pequeño punto de énfasis que agregaré a todas las excelentes respuestas dadas anteriormente:
Probablemente, lo más importante que puede hacer para evitar usar Select es, en la medida de lo posible, usar rangos con nombre (combinados con nombres de variables significativos) en su código VBA . Este punto se mencionó anteriormente, pero se pasó por alto un poco; sin embargo, merece una atención especial.
Aquí hay un par de razones adicionales para hacer un uso liberal de rangos con nombre, aunque estoy seguro de que podría pensar en más.
Los rangos con nombre hacen que su código sea más fácil de leer y comprender.
Ejemplo:
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
Es bastante obvio lo que contienen los rangos con nombre Months
y MonthlySales
, y lo que hace el procedimiento.
¿Porque es esto importante? En parte porque es más fácil para otras personas entenderlo, pero incluso si usted es la única persona que verá o usará su código, debe usar rangos con nombre y buenos nombres de variables porque olvidará lo que pretendía hacer con él. un año después, y perderá 30 minutos simplemente averiguando qué está haciendo su código.
Los rangos con nombre garantizan que sus macros no se rompan cuando (¡no si!) Cambia la configuración de la hoja de cálculo.
Considere, si el ejemplo anterior se hubiera escrito así:
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á bien al principio, es decir, hasta que usted o un futuro usuario decida "¡vaya, creo que voy a agregar una nueva columna con el año en Columna A
!", O poner una columna de gastos entre los meses y columnas de ventas, o agregue un encabezado a cada columna. Ahora, tu código está roto. Y debido a que usó nombres de variables terribles, le llevará mucho más tiempo averiguar cómo solucionarlo de lo que debería tomar.
Si hubiera usado rangos con nombre para empezar, las columnas Months
y Sales
podrían moverse todo lo que quiera, y su código continuaría funcionando bien.
Voy a dar la respuesta corta ya que todos los demás dieron la larga.
Obtendrá .select y .activate cada vez que grabe macros y las reutilice. Cuando selecciona una celda u hoja, simplemente la activa. A partir de ese momento, siempre que use referencias no calificadas, como Range.Value
si solo usaran la celda y la hoja activas. Esto también puede ser problemático si no observa dónde se coloca su código o si un usuario hace clic en el libro de trabajo.
Por lo tanto, puede eliminar estos problemas haciendo referencia directamente a sus celdas. Que va:
'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)
O podrías
'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"
Hay varias combinaciones de estos métodos, pero esa sería la idea general expresada lo más pronto posible para personas impacientes como yo.
"... y estoy descubriendo que mi código sería más reutilizable si pudiera usar variables en lugar de funciones de selección".
Si bien no puedo pensar en más que en un puñado aislado de situaciones en las .Select
que sería una mejor opción que la referencia directa de celda, me levantaría en defensa Selection
y señalaría que no debe descartarse por las mismas razones que .Select
deben evitarse.
Hay ocasiones en las que tener subrutinas macro breves que ahorran tiempo asignadas a combinaciones de teclas de acceso rápido disponibles con solo tocar un par de teclas ahorra mucho tiempo. Ser capaz de seleccionar un grupo de celdas para promulgar el código operativo funciona de maravilla cuando se trata de datos embolsados que no se ajustan a un formato de datos de toda la hoja de trabajo. De la misma manera que puede seleccionar un grupo de celdas y aplicar un cambio de formato, seleccionar un grupo de celdas para ejecutar un código de macro especial puede ser un gran ahorro de tiempo.
Ejemplos de submarco basado en selección:
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
El código real a procesar podría ser cualquier cosa, desde una sola línea hasta varios módulos. He utilizado este método para iniciar rutinas de larga duración en una selección irregular de celdas que contienen los nombres de archivo de libros de trabajo externos.
En resumen, no descarte Selection
por su estrecha asociación con .Select
y ActiveCell
. Como propiedad de la hoja de trabajo, tiene muchos otros propósitos.
(Sí, sé que esta pregunta se trataba .Select
, no, Selection
pero quería eliminar cualquier concepto erróneo que pudieran inferir los codificadores novatos de VBA).
Evitar Select
y Activate
es el movimiento que te hace un poco mejor desarrollador de VBA. En general, Select
y Activate
se utilizan cuando se registra una macro, por lo que la Parent
hoja de trabajo o el rango siempre se considera el activo.
Así es como puede evitar Select
y Activate
en los siguientes casos:
Agregar una nueva hoja de trabajo y copiar una celda en ella:
Desde (código generado con la grabadora de macros):
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
A:
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
Cuando desee copiar el rango entre hojas de trabajo:
Desde:
Sheets("Source").Select
Columns("A:D").Select
Selection.Copy
Sheets("Target").Select
Columns("A:D").Select
ActiveSheet.Paste
A:
Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").Range("a1")
Usando rangos con nombres elegantes
Puede acceder a ellos con []
, que es realmente hermoso, en comparación con la otra forma. Compruebe usted mismo:
Dim Months As Range
Dim MonthlySales As Range
Set Months = Range("Months")
Set MonthlySales = Range("MonthlySales")
Set Months =[Months]
Set MonthlySales = [MonthlySales]
El ejemplo de arriba se vería así:
Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").[A1]
No copiando valores, sino tomándolos
Por lo general, si está dispuesto a hacerlo select
, lo más probable es que esté copiando algo. Si solo está interesado en los valores, esta es una buena opción para evitar seleccionar:
Range("B1:B6").Value = Range("A1:A6").Value
Intente siempre hacer referencia a la hoja de trabajo también
Este es probablemente el error más común en vba . Siempre que copia rangos, a veces no se hace referencia a la hoja de trabajo y, por lo tanto, VBA considera la hoja incorrecta como 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
¿Realmente nunca puedo usar .Select
ni .Activate
para nada?
- Un buen ejemplo de cuándo podría estar justificado el uso
.Activate
y.Select
es cuándo desea asegurarse de que se seleccione una hoja de trabajo específica por razones visuales. Por ejemplo, que su Excel siempre se abriría con la hoja de trabajo de portada seleccionada primero, sin tener en cuenta cuál era la hoja activa cuando se cerró el archivo.
Por lo tanto, algo como el siguiente código está absolutamente bien:
Private Sub Workbook_Open()
Worksheets("Cover").Activate
End Sub
Otro buen ejemplo es cuando necesita exportar todas las hojas en un archivo PDF, como se menciona en este caso: ¿Cómo evitar declaraciones de selección / activas en VBA en este ejemplo?
Cuando un comando sólo funciona con
ActiveWindow
igual ActiveWindow.Zoom o ActiveWindow.FreezePanes
Tenga en cuenta que a continuación estoy comparando el enfoque de selección (el que el OP quiere evitar) con el enfoque de rango (y esta es la respuesta a la pregunta). Así que no dejes de leer cuando veas la primera selección.
Realmente depende de lo que intente hacer. De todos modos, un simple ejemplo podría ser útil. Supongamos que desea establecer el valor de la celda activa en "foo". Usando ActiveCell escribirías algo como esto:
Sub Macro1()
ActiveCell.Value = "foo"
End Sub
Si desea usarlo para una celda que no es la activa, por ejemplo para "B2", debe seleccionarlo primero, así:
Sub Macro2()
Range("B2").Select
Macro1
End Sub
Usando Rangos, puede escribir una macro más genérica que se puede usar para establecer el valor de cualquier celda que desee en lo que desee:
Sub SetValue(cellAddress As String, aVal As Variant)
Range(cellAddress).Value = aVal
End Sub
Entonces puedes reescribir Macro2 como:
Sub Macro2()
SetCellValue "B2", "foo"
End Sub
Y Macro1 como:
Sub Macro1()
SetValue ActiveCell.Address, "foo"
End Sub
Indique siempre el libro de trabajo, la hoja de trabajo y la celda / rango.
Por ejemplo:
Thisworkbook.Worksheets("fred").cells(1,1)
Workbooks("bob").Worksheets("fred").cells(1,1)
Debido a que los usuarios finales siempre solo harán clic en los botones y tan pronto como el foco se mueva fuera del libro de trabajo, el código quiere trabajar, las cosas salen completamente mal.
Y nunca use el índice de un libro de trabajo.
Workbooks(1).Worksheets("fred").cells(1,1)
No sabe qué otros libros de trabajo estarán abiertos cuando el usuario ejecute su código.
Estos métodos están bastante estigmatizados, por lo que tomar la iniciativa de Vityata y Jeeped con el fin de trazar una línea en la arena:
Call .Activate
, .Select
, Selection
, ActiveSomething
métodos / propiedades
Básicamente porque se llaman principalmente para manejar la entrada del usuario a través de la interfaz de usuario de la aplicación. Dado que son los métodos a los que se llama cuando el usuario maneja objetos a través de la interfaz de usuario, son los que registra el grabador de macros, y es por eso que llamarlos es frágil o redundante en la mayoría de las situaciones: no es necesario seleccionar un objeto para realizar una acción Selection
inmediatamente después.
Sin embargo, esta definición resuelve situaciones en las que se solicitan:
Cuando llamar .Activate
, .Select
, .Selection
, .ActiveSomething
métodos / propiedades
Básicamente cuando esperas que el usuario final juegue un papel en la ejecución.
Si está desarrollando y espera que el usuario elija las instancias de objeto para que las maneje su código, entonces .Selection
o .ActiveObject
son apropiadas.
Por otro lado, .Select
y .Activate
son útiles cuando puede inferir la siguiente acción del usuario y desea que su código guíe al usuario, posiblemente ahorrándole algo de tiempo y clics del mouse. Por ejemplo, si su código acaba de crear una nueva instancia de un gráfico o la actualizó, el usuario podría querer revisarlo y usted podría llamarlo .Activate
o su hoja para ahorrarle tiempo al buscarlo; o si sabe que el usuario necesitará actualizar algunos valores de rango, puede seleccionar ese rango mediante programación.
En mi humilde opinión, el uso de .select
proviene de personas que, como yo, comenzaron a aprender VBA por necesidad a través de la grabación de macros y luego modificaron el código sin darse cuenta de que .select
y, posteriormente, selection
son solo intermediarios innecesarios.
.select
se puede evitar, como muchos ya se han publicado, trabajando directamente con los objetos ya existentes, lo que permite varias referencias indirectas como calcular i y j de una manera compleja y luego editar la celda (i, j), etc.
De lo contrario, no hay nada implícitamente incorrecto en .select
sí mismo y puede encontrar usos para esto fácilmente, por ejemplo, tengo una hoja de cálculo que completo con la fecha, activo la macro que hace algo de magia con ella y la exporta en un formato aceptable en una hoja separada, que , sin embargo, requiere algunas entradas manuales finales (impredecibles) en una celda adyacente. Así que aquí llega el momento en .select
que eso me ahorra ese movimiento adicional del mouse y el clic.
Para evitar usar el .Select
método, puede establecer una variable igual a la propiedad que desee.
► Por ejemplo, si desea el valor Cell A1
, puede establecer una variable igual a la propiedad del valor de esa celda.
- Ejemplo
valOne = Range("A1").Value
► Por ejemplo, si desea el nombre en clave de la you could set a variable equal to the
propiedad 'Sheet3 Codename` de esa hoja de trabajo.
- Ejemplo
valTwo = Sheets("Sheet3").Codename
Noté que ninguna de estas respuestas menciona la propiedad .Offset . Esto también se puede usar para evitar el uso de la Select
acción al manipular ciertas celdas, particularmente en referencia a una celda seleccionada (como el OP menciona con ActiveCell
).
Aqui hay un par de ejemplos.
También asumiré que "ActiveCell" es J4 .
ActiveCell.Offset(2, 0).Value = 12
- Esto cambiará la celda
J6
a un valor de 12 - Un menos -2 habría hecho referencia a J2
ActiveCell.Offset(0,1).Copy ActiveCell.Offset(,2)
- Esto copia la celda en la
k4
queL4
. - Tenga en cuenta que "0" no es necesario en el parámetro de compensación si no es necesario (, 2)
- Similar al ejemplo anterior, un menos 1 sería
i4
ActiveCell.Offset(, -1).EntireColumn.ClearContents
- Esto borrará los valores en todas las celdas de la columna k.
Esto no quiere decir que sean "mejores" que las opciones anteriores, sino simplemente enumerar alternativas.
¿Cómo evitar copiar y pegar?
Seamos realistas: este aparece mucho al grabar macros:
Range("X1").Select
Selection.Copy
Range("Y9).Select
Selection.Paste
Si bien lo único que quiere la persona es:
Range("Y9").Value = Range("X1").Value
Por lo tanto, en lugar de usar copiar y pegar en macros VBA, recomendaría el siguiente enfoque simple:
Destination_Range.Value = Source_Range.Value
Trabajando con la función .Parent , este ejemplo muestra cómo configurar solo una referencia myRng permite el acceso dinámico a todo el entorno sin ningún .Select, .Activate, .Activecell, .ActiveWorkbook, .ActiveSheet, etc. (No hay ninguna característica genérica .Child ).
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
La razón principal para no usar nunca Seleccionar o Hoja activa es porque la mayoría de las personas tendrán al menos otro par de libros abiertos (a veces docenas) cuando ejecuten su macro, y si hacen clic fuera de su hoja mientras su macro se está ejecutando y hacen clic en algún otro libro que tienen abierto, luego la "Hoja activa" cambia, y el libro de trabajo de destino para un comando "Seleccionar" no calificado también cambia.
En el mejor de los casos, su macro se bloqueará, en el peor de los casos, podría terminar escribiendo valores o cambiando celdas en el libro de trabajo incorrecto sin posibilidad de "Deshacer".
Tengo una regla de oro simple que sigo: agregue variables llamadas "wb" y "ws" para un objeto de libro de trabajo y un objeto de hoja de trabajo y siempre las use para hacer referencia a mi libro de macros. Si necesito hacer referencia a más de un libro, o más de una hoja, agrego más variables.
Por ejemplo,
Dim wb as Workbook
Dim ws as Worksheet
Set wb = ThisWorkBook
Set ws = wb.sheets("Output")
El comando "Establecer wb = ThisWorkbook" es absolutamente clave. "ThisWorkbook" es un valor especial en Excel, y significa el libro desde el que se está ejecutando su código VBA . Un atajo muy útil para configurar la variable de su libro de trabajo.
Una vez que haya hecho eso en la parte superior de su Sub, usarlos no podría ser más simple, solo utilícelos donde quiera que use "Selección":
Entonces, para cambiar el valor de la celda "A1" en "Salida" a "Hola", en lugar de:
Sheets("Output").Activate
ActiveSheet.Range("A1").Select
Selection.Value = "Hello"
Ahora podemos hacer esto:
ws.Range("A1").Value = "Hello"
Lo cual no solo es mucho más confiable y tiene menos probabilidades de fallar si el usuario está trabajando con varias hojas de cálculo; también es mucho más corto, rápido y fácil de escribir.
Como beneficio adicional, si siempre nombra sus variables "wb" y "ws", puede copiar y pegar código de un libro a otro y, por lo general, funcionará con los cambios mínimos necesarios, si los hubiera.