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écalage
pour 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.