5
votes

Boucle For Each non contiguë par ligne au lieu de colonne

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.

 Sélection non contiguë


4 commentaires

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.


3 Réponses :


2
votes

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 .


0 commentaires

1
votes

Non contiguë

En parcourant d'abord les lignes ( i ), vous obtiendrez la " séquence par ligne ", par exemple. A1, B1, C1, ...

Le Code

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

6 commentaires

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 .



0
votes

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

entrez la description de l'image ici


0 commentaires