2017-08-11 1 views
2

J'essayais de répondre this question lorsque je suis tombé sur un comportement VBA bizarre dans Excel. J'ai écrit un sous très simple de démontrer la question:Comportement d'adresses de cellules étranges pour des plages non contiguës: VBA

Sub debugAddresses(rng As Range) 
    Debug.Print "Whole range: " & rng.Address 
    Dim i As Long 
    For i = 1 To rng.Cells.Count 
     Debug.Print rng.Cells(i).Address 
    Next i 
End Sub 

boucle I sur chaque cellule dans un objet de plage et imprimer son adresse, le simple droit?

debugAddresses Range("B2:B3") 
' Result as expected: 
' >> Whole range: $B$2:$B$3 
' >> $B$2 
' >> $B$3 

Cependant, pour les plages non contiguës je reçois un comportement étrange:

debugAddresses Range("A1,B2") 
' Strange behaviour when getting addresses of individual cells: 
' >> Whole range: $A$1,$B$2 
' >> $A$1 
' >> $A$2 

Quelqu'un peut-il faire la lumière sur cette s'il vous plaît? Spécifiquement pourquoi les objets Cells, qui peuvent être utilisés pour l'indexation d'une plage contiguë, semblent juste étendre le premier Area sélectionné.


Edit: Il pourrait être intéressant de noter que l'utilisation d'une boucle For Each à travers les objets de plage de cellules réelles donne le résultat attendu *

Sub debugAddresses2(rng As Range) 
    Debug.Print "Whole range: " & rng.Address 
    Dim c As Range 
    For Each c In rng 
     Debug.Print c.Address 
    Next c 
End Sub 

* Voir ma réponse à un commentaire sur une solution plus robuste , comme cela (apparemment) ne peut toujours donner le résultat attendu

+1

Vous utilisez des plages non contiguës, donc 'rng.Cells (i) .Address' est probablement un raccourci pour' rng.Areas (1) .Cells (i) .Address'. –

+0

Il est étrange, car si vous essayez d'entrer des données sur ces cellules. Cela fonctionne réellement: rng.Cells = "test" – danieltakeshi

+0

@FlorentB. "est probablement sténographie" sur quoi? Vous avez des docs sur ça? Je ne pense pas non plus, parce qu'il n'y a pas assez de cellules dans '.Areas (1)' et que l'index serait hors de portée ... – Wolfie

Répondre

0

Il semble que l'observation de Florent était dans la bonne direction, et que cette méthode est l'extension de la première Area dans l'objet de gamme.

Dans une plage contiguë (par exemple "A1:B5", "C10:C100") suivant le procédé boucle sur chaque cellule dans la plage donnée, rng.

Dim j As Long 
For j = 1 To rng.Cells.Count 
    Debug.Print rng.Cells(j).Address 
Next j 

Cependant, dans des plages non contiguës, il semble que ce soit équivalent (ou raccourci pour)

For j = 1 To rng.Cells.Count 
    Debug.Print rng.Areas(1).Cells(j).Address 
Next j 

Il ne semble pas être une mention directe de cela dans the documentation mais c'est une conclusion raisonnable à tirer en regardant dans le navigateur Locals de l'éditeur VBA.

Dans l'objet plage rng, il existe une propriété Cells qui contient uniquement un "élément", qui est le premier Area. Il est donc raisonnable de supposer que cet article est ce à quoi .Cells(j) a accès.

cells

En rng on peut aussi voir la propriété Areas, qui contient 2 articles (dans cet exemple) égal au nombre de Areas dans ma gamme non contiguës.

areas


Ainsi rng.Cells(j) accède l'élément j e dans la première zone de rng. Parce que .Cells() peut dépasser la taille d'origine de rng, nous voyons les adresses répertoriées de cellules en dehors de rng.


La solution (s):

  • Soit vous assurer boucle directement à travers les objets de plage dans rng en utilisant une boucle For Each comme indiqué dans la question.
  • Ou boucle sur chaque zone, puis chaque cellule dans cette zone.

La première option est plus concise, mais Shai souligne que pour être méthode complètement sûre, la plus robuste est de faire les deux For Each boucles comme il peut y avoir des cas de pointe plus complexes qui ne sont pas pris avec le single boucle.

1

Essayez d'utiliser le code Sub debugAddresses modifié ci-dessous:

Sub debugAddresses(rng As Range) 

    Dim RngA As Range 
    Dim C As Range 

    For Each RngA In rng.Areas 
     For Each C In RngA.Cells 
      Debug.Print C.Address 
     Next C 
    Next RngA 

End Sub 
+0

Donc, cela fonctionne, mais savez-vous * pourquoi * l'original n'a pas fonctionné? Même dans des zones distinctes, les adresses ne devraient pas changer de manière sûre? Ce n'est pas comme si les cellules étaient toutes $ 1 à $ 1 dans leurs propres zones ... – Wolfie

+0

Voir également mon edit, une boucle 'For For' encore plus simple fonctionne, mais pas l'indexation des cellules ... – Wolfie

+0

@Wolfie concernant votre première question, Je double toujours pour chaque boucle, une à travers la zone, et l'autre pour les cellules. –

0

Voici votre code "fixe". en ajoutant simplement une autre boucle For

Sub debugAddresses(rng As Range) 

    Debug.Print "Whole range: " & rng.Address 

    For Each r In rng ' this loops through the range even if separated cells 

    Dim i As Long 
     For i = 1 To r.Cells.Count 'changed to r instead of rng 
      Debug.Print r.Cells(i).Address 'changed to r instead of rng 
     Next i 
    Next r 

End Sub 


Ainsi .Range œuvres en entrant l'adresse de cellule ex. "B1", ou en utilisant R1C1 signifiant colonne colonne ex. 1,2.

Mais vous ne pouvez pas utiliser un seul R1C1 à l'intérieur de .Range, car la plage ici est une plage de cellules. Donc, pour utiliser correctement R1C1 dans .Range, vous devez spécifier 2 d'entre eux.

donc .Range("B5:B10") est égal à Range(Cells(5,2),Cells(10,2))

Qu'est-ce que tu as fait était de spécifier une plage, puis de qui a créé une autre plage en utilisant des cellules. Très semblable à offset.

Alors Range("A1,B2") puis en ajoutant Cells(1) puis Cells(2) ajoute des lignes à la première plage qui est "A1" ou des décalages.

Sub selector() 
Set Rng = Range("A1") 
Rng.Select 
Rng.Cells(4, 4).Select 

End Sub 

Cela compense 4 colonnes et 4 rangées de A1

+0

Je ne pense pas que vous ayez tout à fait raison, il est parfaitement valable de spécifier une gamme comme j'ai fait, avec une seule gamme adresse et une virgule à l'intérieur, rien à voir avec R1C1. Vous pouvez le voir dans la première instruction de débogage qui montre que l'adresse de plage est comme souhaité. Ce que vous avez fait dans le bloc de code est simplement de reproduire une partie du problème pour une cellule individuelle, lorsque je boucle spécifiquement dans le nombre de cellules * contenues par * 'rng'. – Wolfie

+0

Je ne suis pas en train d'argumenter sur sa validité, je suis en train de faire valoir son bon usage. Vous avez utilisé '.Cells' sur' .Range' et attendez-vous à rester dans '.Range'? Alors pourquoi ne pas simplement utiliser '.Range' par lui-même OU' .Cells'? Pourquoi les utiliser tous les deux? '.Range.Cell' agit comme un décalage. Pour faire défiler vos cellules à portée, utilisez Pour chaque r dans la plage – fcsr

+0

Dans un bloc contigu, '.Range.Cells' agit comme un outil d'indexation, où oui vous pouvez boucler de manière fiable jusqu'au nombre de cellules dans l'objet plage et attendre pour rester dans la gamme. C'est pourquoi ma question concerne spécifiquement pourquoi ce comportement se produit dans des plages non contiguës, c'est-à-dire la structure utilisée par VBA pour accéder aux objets 'Cells' dans une plage. Dans ma question, je montre que je sais que je peux utiliser «Pour chacun». – Wolfie