J'ai une sélection non contiguë couvrant des lignes et des colonnes, et je veux faire une boucle For Each dessus. Excel VBA fait cela en bouclant d'abord la colonne 1, puis 2,3 etc .; mais je veux qu'il boucle d'abord le long de la ligne.
(Ma feuille ressemble à l'image ci-dessous, je dois parcourir la sélection (version) de chaque colonne à tour de rôle et récupérer le n ° de document et d'autres informations. Le nombre de lignes et de colonnes de version dans la feuille est non corrigé).
À moins d'écrire une fonction de tri assez volumineuse et de créer un tableau de références, je me demandais s'il y avait un moyen 'intégré' de faire cela?
Je n'ai pas besoin de code, juste une explication.
3 Réponses :
L'ordre dans lequel un For Each itère une collection d'objets dépend de l'implémentation (IOW blâme Excel, pas VBA) et, bien que probablement déterministe et prévisible, il n'y a rien dans sa spécification qui garantit un ordre d'itération spécifique. Donc, le code VBA écrit pour itérer une collection d'objets ne doit pas être écrit avec l'hypothèse d'un ordre d'itération spécifique, car c'est quelque chose qui peut très bien changer entre les versions de la bibliothèque de types impliquée (ici Excel).
On ne sait pas très bien ce que la forme de votre Plage / Sélection est, mais si vous avez besoin d'itérer les cellules sélectionnées dans un ordre spécifique, une boucle For Each doit ne pas être utilisé, du moins pas pour itérer les cellules en soi.
Puisque les plages ne sont pas contiguës, la plage aura plusieurs zones ; vous voudrez itérer le Selection.Areas , et pour chaque zone sélectionnée, itérer les cellules dans un ordre particulier. For Each est, de loin, le moyen le plus efficace d'itérer une collection d'objets , qui est Range.Areas .
Debug.Print area.Cells(currentRow, 1).Offset(ColumnOffset:=10).Address
Au lieu de imbriquant les boucles , créez une procédure distincte qui prend currentArea comme paramètre - qui procédure est l'endroit où vous allez itérer les cellules individuelles:
Debug.Assert TypeOf Selection Is Excel.Range
Dim currentArea As Range
For Each currentArea In Selection.Areas
ProcessContiguousArea currentArea
Next
La boucle externe ressemble maintenant à ceci:
Private Sub ProcessContiguousArea(ByVal area As Range)
Dim currentRow As Long
For currentRow = 1 To area.Rows.Count
Debug.Print area.Cells(currentRow, 1).Address
Next
End Sub
La procédure ProcessContiguousArea est libre de faire tout ce qu'elle doit faire avec une zone contiguë donnée, en utilisant une boucle For pour itérer la plage par lignes, sans avoir à se soucier de la adresse de la zone sélectionnée: en utilisant Range.Cells (RowIndex, ColumnIndex) , la ligne 1 / colonne 1 représente la cellule en haut à gauche de cette plage, quel que soit l'emplacement de cette plage dans le w orksheet.
Les cellules non sélectionnées sont accessibles avec Range.Offset:
Debug.Assert TypeOf Selection Is Excel.Range
Dim currentArea As Range
For Each currentArea In Selection.Areas
'todo
Next
La ligne de cellule en haut à gauche de la zone sur la feuille de calcul est renvoyée par area.Row , et la colonne de la cellule en haut à gauche de la zone sur la feuille de calcul est récupérée avec area.Column .
En parcourant d'abord les lignes ( i ), vous obtiendrez la " séquence par ligne ", par exemple. A1, B1, C1, ...
Sub NonContiguous()
Dim i As Long
Dim j As Long
Dim k As Long
With Selection
For k = 1 To .Areas.Count
With .Areas(k)
For i = .Row To .Rows.Count + .Row - 1
For j = .Column To .Columns.Count + .Column - 1
Debug.Print .Parent.Cells(i, j).Address & " = " _
& .Parent.Cells(i, j)
Next
Next
End With
Next
End With
End Sub
Je suis très tenté de voter pour, car il s'agit d'une approche très similaire à la mienne (la clé étant Selection.Areas , manquée par l'autre réponse), cependant des boucles triple imbriquées et un déréférencement répétitif du .Areas (k) référence, combinée aux mauvais noms de variables et aux types de données déroutants utilisés (il n'y a pas vraiment de raison d'utiliser Integer ailleurs que dans les appels d'API Win32 qui le nécessitent) , me retiennent. Pour l'itération en boucle des zones est une opportunité manquée: un For Each ne déréférencerait la zone actuelle qu'une seule fois par itération, contre 6 fois ici. Le type de Sélection est également supposé.
@Mathieu Guindon: Croyez-le ou non, je ne sais pas de quoi vous parlez. Je reconnais les problèmes suivants que vous abordez: «Déréférencement répétitif» (probablement lié à For Each), «Integer Anywhere» et «Type de sélection». J'apprécierais des éclaircissements ou des liens vers eux.
Bien que je comprenne pourquoi i, j et k sont de «mauvais noms de variables», je voulais juste dire que je les utilise (aussi) tout le temps dans des boucles imbriquées. Il est logique pour moi de parcourir des matrices de données avec des variables à une seule lettre car cela rappelle l'algèbre linéaire. Juste mon avis.
Il existe un vieil et excellent article Coding Horror sur " code de flèche "qui fait un meilleur travail que je ne pourrais jamais pour expliquer comment les structures imbriquées peuvent être" aplaties ". @ MBB70 nous sommes tous coupables ;-) ... Je souhaite juste que les réponses VBA sur SO haussent un peu la barre, afin que les références en ligne pour VBA (en particulier pour les nouveaux arrivants) illustrent un meilleur code que ce que le " VBA craint " trope veut nous faire croire le code VBA ressemble à (je sais, vœux pieux).
Également Integer vs Long . Comme pour le déréférencement multiple / répétitif, le bloc With .Areas (k) qui a été ajouté l'adresse correctement, mais au détriment d'une imbrication accrue. Ce que je voulais dire à propos du type de Selection , c'est que si le code est exécuté alors que la Selection est un Chart ou une Shape < / code>, puis .Areas lancera l'erreur 438, car les appels de membres contre Selection sont résolus au moment de l'exécution (puisqu'il s'agit de Variant / Object ); l'assigner à un Range , ou Debug.Assert son type, élimine l'hypothèse. Quoi qu'il en soit, voté maintenant :)
@Mathieu Guindon: Merci beaucoup pour l'aide étendue (ressources). En référence à Integer vs Long : j'ai l'impression de vivre sous un rocher. Mais le problème est qu'un grand pourcentage de mes messages contiennent des entiers. Je suppose que je vais devoir faire quelques retouches sérieuses. Il y a des années, j'ai lu un article similaire: Byte vs Integer .
Ceci est basé sur la suggestion d'urdearboy:
1. boucle sur les colonnes
2. dans une colonne, boucle sur les cellules
Sub disjoint()
Dim r As Range, rInt As Range
Dim nLastColumn As Long
Dim nFirstColumn As Long, msg As String
Dim N As Long
Set r = Range("C3,C9,E6,E13,E15,G1,G2,G3,G4")
nFirstColumn = Columns.Count
nLastColumn = 0
msg = ""
For Each rr In r
N = rr.Column
If N < nFirstColumn Then nFirstColumn = N
If N > nLastColumn Then nLastColumn = N
Next rr
For N = nFirstColumn To nLastColumn
Set rInt = Intersect(Columns(N), r)
If rInt Is Nothing Then
Else
For Each rr In rInt
msg = msg & vbCrLf & rr.Address(0, 0)
Next rr
End If
Next N
MsgBox msg
End Sub
Bouclez les colonnes, puis imbriquez une boucle de lignes. Vous pouvez utiliser
Décalagepour déplacer des colonnes sur la même ligne pour faire référence à d'autres cellules.Je me demande si les plages nommées aideraient ou non. Je n'ai pas essayé mais peut-être que tu pourrais le regarder
Découvrez stackoverflow.com/questions/13039437 /…
Veuillez modifier votre message pour inclure votre boucle
For Each... et décrire la forme du plage en cours d'itération, ou inclure une capture d'écran.