web-dev-qa-db-fra.com

Comment éviter d'utiliser Select in Excel VBA

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?

454
BiGXERO

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.

463
chris neilsen

Deux raisons principales pour lesquelles .Select/.Activate/Selection/Activecell/Activesheet/Activeworkbook etc ... doit être évité

  1. Cela ralentit votre code.
  2. C’est généralement la cause principale des erreurs d’exécution.

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
180
Siddharth Rout

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.

40
MattB

"... 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.)

27
user4039065

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.

26

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.

13
user1644564

Ces méthodes sont plutôt stigmatisées. Prenez donc l'exemple de @Vityata et de @Jeeped pour tracer une ligne dans le sable:

Pourquoi ne pas appeler les méthodes/propriétés .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:

Quand appeler .Activate, .Select, .Selection, .ActiveSomething méthodes/propriétés

Fondamentalement, 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.

5
LFB

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.

  • Exemple 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.

  • Exemple valTwo = Sheets("Sheet3").Codename

J'espère que ça aide. Faites moi savoir si vous avez des questions.

4
FinPro.Online

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.

4
Eleshar

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.

0
marionffavp