J'ai beaucoup entendu parler de l'horreur compréhensible d'utiliser .Select
dans Excel VBA, mais je ne sais pas comment éviter de l'utiliser. Je trouve que mon code serait plus réutilisable si je pouvais utiliser des variables au lieu de fonctions Select
. Cependant, je ne suis pas sûr de savoir comment me référer à des choses (comme le ActiveCell
etc.) si je n’utilise pas Select
.
J'ai trouvé cet article sur les plages et cet exemple sur les avantages de ne pas utiliser select mais je ne trouve rien sur comment?
Quelques exemples pour éviter de sélectionner
Utiliser les variables Dim
'd
Dim rng as Range
Set
la variable à la plage requise. Il y a plusieurs façons de se référer à une plage monocellulaire
Set rng = Range("A1")
Set rng = Cells(1,1)
Set rng = Range("NamedRange")
ou une gamme multi-cellules
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)
Vous pouvez utiliser le raccourci vers la méthode Evaluate
, mais cette méthode est moins efficace et doit généralement être évitée dans le code de production.
Set rng = [A1]
Set rng = [A1:B10]
Tous les exemples ci-dessus font référence à des cellules de la feuille active. À moins que vous ne souhaitiez travailler uniquement avec la feuille active, il est également préférable de réduire également une variable Worksheet
.
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 vous faites voulez utiliser la ActiveSheet
, pour plus de clarté, mieux vaut être explicite. Attention, certaines méthodes Worksheet
changent la feuille active.
Set rng = ActiveSheet.Range("A1")
Là encore, cela fait référence au classeur actif. À moins que vous ne souhaitiez spécifiquement travailler uniquement avec les variables ActiveWorkbook
ou ThisWorkbook
, il est également préférable de réduire une variable Workbook
.
Dim wb As Workbook
Set wb = Application.Workbooks("Book1")
Set rng = wb.Worksheets("Sheet1").Range("A1")
Si vous faites voulez utiliser la ActiveWorkbook
, pour plus de clarté, mieux vaut être explicite. Attention, autant de méthodes WorkBook
changent le livre actif.
Set rng = ActiveWorkbook.Worksheets("Sheet1").Range("A1")
Vous pouvez également utiliser l'objet ThisWorkbook
pour faire référence au livre contenant le code en cours d'exécution.
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1")
Un morceau de code commun (mauvais) consiste à ouvrir un livre, obtenir des données, puis fermer à nouveau
C'est mauvais:
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
Et serait mieux comme:
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
Passez des plages à vos variables Sub
et Function
en tant que plage
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
Vous devez également appliquer des méthodes (telles que Find
et Copy
) aux 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 vous passez en boucle sur une plage de cellules, il est souvent préférable (plus rapide) de copier les valeurs de la plage dans un tableau variant puis d’en faire une boucle.
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
C'est un petit aperçu de ce qui est possible.
Deux raisons principales pour lesquelles .Select
/.Activate
/Selection
/Activecell
/Activesheet
/Activeworkbook
etc ... doit être évité
Comment l'évitons-nous?
1) Travailler directement avec les objets pertinents
Considérons ce code
Sheets("Sheet1").Activate
Range("A1").Select
Selection.Value = "Blah"
Selection.NumberFormat = "@"
Ce code peut également être écrit comme
With Sheets("Sheet1").Range("A1")
.Value = "Blah"
.NumberFormat = "@"
End With
2) Si nécessaire, déclarez vos variables. Le même code ci-dessus peut être écrit comme
Dim ws as worksheet
Set ws = Sheets("Sheet1")
With ws.Range("A1")
.Value = "Blah"
.NumberFormat = "@"
End With
Je vais donner une réponse courte puisque tout le monde a donné la longue.
Vous obtiendrez .select et .activate à chaque fois que vous enregistrez des macros et que vous les réutilisez. Lorsque vous sélectionnez une cellule ou une feuille, vous la rendez active. À partir de là, chaque fois que vous utilisez des références non qualifiées telles que Range.Value
, ils utilisent uniquement la cellule et la feuille actives. Cela peut également être problématique si vous ne regardez pas où votre code est placé ou si un utilisateur clique sur le classeur.
Vous pouvez donc éliminer ces problèmes en référençant directement vos cellules. Qui 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)
Ou tu pourrais
'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"
Il existe différentes combinaisons de ces méthodes, mais ce serait l'idée générale exprimée le plus rapidement possible pour des personnes impatientes comme moi.
"... et je constate que mon code serait plus réutilisable si je pouvais utiliser des variables au lieu de fonctions de sélection."
Bien que je ne puisse imaginer autre chose qu'une poignée de situations isolées où .Select
serait un meilleur choix que le référencement direct de cellule, je prendrais la défense de Selection
et ferais remarquer que cela ne devrait pas être invoqué pour les mêmes raisons que .Select
être évité.
Parfois, le fait de disposer de sous-routines de macros courtes et rapides, attribuées à des combinaisons de touches d'activation, disponibles en appuyant simplement sur quelques touches, permet de gagner beaucoup de temps. Le fait de pouvoir sélectionner un groupe de cellules sur lequel le code opérationnel doit s’appliquer fonctionne à merveille lorsqu’il s’agit de données empochées qui ne sont pas conformes au format de données de la feuille de calcul. De la même manière que vous pourriez sélectionner un groupe de cellules et appliquer un changement de format, la sélection d'un groupe de cellules sur lequel exécuter du code macro spécial peut vous faire gagner beaucoup de temps.
Exemples de sous-framework basé sur la sélection:
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
Le code à traiter peut être une simple ligne ou plusieurs modules. J'ai utilisé cette méthode pour lancer de longues routines d'exécution sur une sélection irrégulière de cellules contenant les noms de fichier de classeurs externes.
En bref, ne jetez pas Selection
en raison de son association étroite avec .Select
et ActiveCell
. En tant que propriété de feuille de calcul, il remplit de nombreuses autres fonctions.
(Oui, je sais que cette question portait sur .Select
, pas Selection
, mais je voulais supprimer toutes les idées fausses que des codeurs VBA novices pourraient déduire.)
Veuillez noter que dans ce qui suit, je compare l'approche Select (celle que le PO veut éviter) à l'approche Range (et c'est la réponse à la question). Alors n'arrêtez pas de lire lorsque vous voyez le premier Select.
Cela dépend vraiment de ce que vous essayez de faire. Quoi qu'il en soit, un exemple simple pourrait être utile. Supposons que vous souhaitiez définir la valeur de la cellule active sur "foo". En utilisant ActiveCell, vous écririez quelque chose comme ceci:
Sub Macro1()
ActiveCell.Value = "foo"
End Sub
Si vous souhaitez l'utiliser pour une cellule autre que la cellule active, par exemple pour "B2", vous devez d'abord la sélectionner, comme ceci:
Sub Macro2()
Range("B2").Select
Macro1
End Sub
En utilisant les plages, vous pouvez écrire une macro plus générique pouvant être utilisée pour définir la valeur de la cellule de votre choix.
Sub SetValue(cellAddress As String, aVal As Variant)
Range(cellAddress).Value = aVal
End Sub
Ensuite, vous pouvez réécrire Macro2 en tant que:
Sub Macro2()
SetCellValue "B2", "foo"
End Sub
Et Macro1 comme:
Sub Macro1()
SetValue ActiveCell.Address, "foo"
End Sub
J'espère que cela aide à éclaircir un peu les choses.
Indiquez toujours le classeur, la feuille de calcul et la cellule/plage.
Par exemple:
Thisworkbook.Worksheets("fred").cells(1,1)
Workbooks("bob").Worksheets("fred").cells(1,1)
Parce que les utilisateurs finaux cliquent toujours sur les boutons et dès que le focus disparaît du classeur, le code veut fonctionner, alors tout va mal.
Et n'utilisez jamais l'index d'un classeur.
Workbooks(1).Worksheets("fred").cells(1,1)
Vous ne savez pas quels autres classeurs seront ouverts lorsque l'utilisateur exécutera votre code.
Ces méthodes sont plutôt stigmatisées. Prenez donc l'exemple de @Vityata et de @Jeeped pour tracer une ligne dans le sable:
.Activate
, .Select
, Selection
, ActiveSomething
En gros, ils sont principalement appelés pour gérer les entrées de l'utilisateur via l'interface utilisateur de l'application. Etant donné que ce sont les méthodes appelées lorsque l'utilisateur manipule des objets via l'interface utilisateur, elles sont celles enregistrées par l'enregistreur de macros, et c'est pourquoi leur appel est soit cassant, soit redondant dans la plupart des situations: vous n'avez pas à sélectionner un objet pour exécuter une action avec Selection
juste après.
Cependant, cette définition règle les situations pour lesquelles ils sont appelés:
.Activate
, .Select
, .Selection
, .ActiveSomething
méthodes/propriétésFondamentalement, lorsque vous attendez que l'utilisateur final joue un rôle dans l'exécution.
Si vous développez et attendez de l'utilisateur qu'il choisisse les instances d'objet que votre code va gérer, alors .Selection
ou .ActiveObject
sont appropriés.
De plus, .Select
et .Activate
sont utiles lorsque vous pouvez déduire l'action suivante de l'utilisateur et que vous souhaitez que votre code guide l'utilisateur, lui permettant éventuellement de gagner du temps et de cliquer avec la souris. Par exemple, si votre code vient de créer une toute nouvelle instance d'un graphique ou d'en mettre à jour, l'utilisateur peut vouloir l'extraire et vous pouvez appeler .Activate
sur celui-ci ou sur sa feuille pour économiser le temps que l'utilisateur a recherché. ou si vous savez que l'utilisateur devra mettre à jour certaines valeurs de plage, vous pouvez sélectionner cette plage par programme.
Réponse rapide:
Pour éviter d'utiliser la méthode .Select
, vous pouvez définir une variable égale à la propriété souhaitée.
► Par exemple, si vous voulez la valeur dans Cell A1
, vous pouvez définir une variable égale à la propriété value de cette cellule.
valOne = Range("A1").Value
► Par exemple, si vous voulez le nom de code de 'Sheet3`, vous pouvez définir une variable égale à la propriété codename de cette feuille de calcul.
valTwo = Sheets("Sheet3").Codename
J'espère que ça aide. Faites moi savoir si vous avez des questions.
L'utilisation à mon humble avis de .select
vient de personnes qui, comme moi, ont commencé à apprendre VBA par nécessité en enregistrant des macros puis en modifiant le code sans se rendre compte que .select
et selection
sont juste des intermédiaires inutiles.
.select
peut être évité, comme beaucoup déjà posté, en travaillant directement avec les objets déjà existants, ce qui permet divers référencements indirects tels que le calcul complexe de i et j, puis l'édition de la cellule (i, j), etc.
Sinon, il n'y a rien de mal à implicitement avec .select
lui-même et vous pouvez trouver des utilisations à cela, par exemple. J'ai une feuille de calcul que je renseigne avec la date, active la macro qui l'exerce avec une certaine magie et l'exporte dans un format acceptable sur une feuille séparée, qui nécessite toutefois des entrées manuelles finales (imprévisibles) dans une cellule adjacente. Alors, voici le moment pour .select
qui me permet d'économiser ce mouvement de souris supplémentaire et de cliquer.
Voici un exemple qui effacera le contenu de la cellule "A1" (ou plus si le type de sélection est xllastcell, etc.). Tout est fait sans avoir à sélectionner les cellules.
Application.GoTo Reference:=Workbook(WorkbookName).Worksheets(WorksheetName).Range("A1")
Range(Selection,selection(selectiontype)).clearcontents
J'espère que ça aidera quelqu'un.