web-dev-qa-db-fra.com

Méthode générique pour vérifier si une clé est dans une collection dans Excel VBA

J'ai différentes collections dans mon code. Certains contiennent des objets (de différents types), d'autres ont des types (comme Long).

Existe-t-il un moyen de vérifier si la collection contient une clé qui convient aux types et aux objets?

Jusqu'à présent, j'ai deux fonctions.

Première fonction:

Private Function ContainsObject(objCollection As Object, strName As String) As Boolean
    Dim o As Object
    On Error Resume Next
    Set o = objCollection(strName)
    ContainsObject = (Err.Number = 0)
    Err.Clear
End Function

Deuxième fonction:

Private Function ContainsLong(AllItems As Collection, TheKey As String) As Boolean
    Dim TheValue As Long
    On Error Resume Next
    TheValue = AllItems.Item(TheKey)
    ContainsLong = (Err.Number = 0)
    Err.Clear
End Function

La raison des deux fonctions est que ContainsObject ne semble pas fonctionner si je passe une collection qui a des paires longues (la fonction renvoie toujours False.)

P.S .: La première fonction est une copie de la troisième réponse de Tester ou vérifier si la feuille existe

8
user2606240

Vous devriez utiliser une Variant dans la première fonction. Vous pouvez affecter une Object à une Variant, par exemple. cela ne va pas erreur:

Sub Test()
    Dim var As Variant
    Dim obj As Object
    Set obj = Application
    var = Application
    Debug.Print var
End Sub

Mais cela donnera une erreur de compilation Type Mismatch, c’est-à-dire une tentative d’assigner une Long à une Object:

Sub Test()
    Dim obj As Object
    Dim lng As Long
    lng = 3
    Set obj = lng
End Sub

Ainsi, pour une fonction générique (le long des lignes de votre code) permettant de vérifier si une clé Collection est valide, vous pouvez utiliser:

Function HasKey(coll As Collection, strKey As String) As Boolean
    Dim var As Variant
    On Error Resume Next
    var = coll(strKey)
    HasKey = (Err.Number = 0)
    Err.Clear
End Function

Code de test:

Sub Test()
    Dim coll1 As New Collection
    coll1.Add Item:=Sheet1.Range("A1"), Key:="1"
    coll1.Add Item:=Sheet1.Range("A2"), Key:="2"
    Debug.Print HasKey(coll1, "1")

    Dim coll2 As New Collection
    coll2.Add Item:=1, Key:="1"
    coll2.Add Item:=2, Key:="2"
    Debug.Print HasKey(coll2, "1")
End Sub

Il existe un article utile sur MSDN à ce sujet. Le contexte est VB6 mais concerne VBA.

12
Robin Mackenzie

Peu de fautes de frappe selon les commentaires ont déjà été corrigées lors de l'édition de votre message ..__ En réponse à votre question, j'aimerais aborder des aspects connexes.
Bien que l’utilisation de clés dans les collections présente principalement trois avantages
- Si la commande change, votre code aura toujours accès au bon article - Vous pouvez accéder directement à l'élément sans avoir à lire l'intégralité du collection
- Cela peut vous rendre le code plus lisible.

* Mais en même temps, il y a principalement trois problèmes d'utilisation de clés dans Collections

  • Vous ne pouvez pas vérifier si la clé existe

  • Vous ne pouvez pas changer la clé

  • Vous ne pouvez pas récupérer la clé

Selon l'article de Pearsons, les clés d'une collection sont en écriture seule - il n'y a aucun moyen d'obtenir une liste des clés existantes d'une collection. Passant plus loin au paragraphe cité: -

Ici, Coll est un objet Collection dans lequel nous allons stocker plusieurs Objets CFile. La collection CollKeys est utilisée pour stocker les clés de les objets CFile stockés dans la collection Coll. Nous avons besoin de cette seconde Collection parce que les clés d'une collection sont en écriture seule - il y a aucun moyen d'obtenir une liste des clés existantes d'une collection. Un de Les améliorations apportées par CFiles sont la possibilité de récupérer une liste de Touches pour la collection.

Classes de collection personnalisées

Une méthode consiste à parcourir les membres de la collection pour voir s'il existe une correspondance pour ce que vous recherchez et l'autre solution consiste à intercepter l'erreur Item not in collection, puis à définir un indicateur indiquant que l'élément n'existe pas. Les opinions diffèrent sur ces approches alors que certaines personnes pensent que ce n'est pas une bonne méthode pour détecter les erreurs, tandis que d'autres sections pensent qu'elle sera beaucoup plus rapide que l'itération pour toute collection de moyenne à grande taille.
Donc, si nous choisissons une méthode pour intercepter l'erreur, le nombre d'erreur obtenu dépend exactement de la cause de l'erreur. Nous avons besoin d'une routine de code pour vérifier l'erreur. De la manière la plus simple possible.

'c1 is the collection
 For i = 1 To c1.Count
     Debug.Print Err.Number, Err.Description
     If Err.Number <> 0 Then Err.Clear
 Next i

Les routines de saisie d'erreurs proposées par différents professionnels diffèrent par le numéro d'erreur qu'ils jugent important et les incluent dans leur routine. Divers numéros d'erreur courants associés à un objet de collecte sont les suivants: - 

  • Error 5 Appel ou argument de procédure non valide. Cette erreur peut également se produire si vous tentez d'appeler une procédure non valide sur la plate-forme actuelle. Par exemple, certaines procédures peuvent n'être valables que pour Microsoft Windows, ou pour Macintosh, etc.
  • error 438 "objet ne prend pas en charge cette propriété ou cette méthode. Un objet est une instance de classe. Une instance de classe prend en charge certaines propriétés définies dans cette définition de type de classe et ne les prend pas en charge.
  • Error 457 Cette clé est déjà associée à un élément de thiscollection.Vous avez spécifié une clé pour un membre de la collection qui déjà identifie un autre membre de la collection. Choisissez une clé différente .__ pour ce membre.
  • Error 91 Variable d'objet ou variable de bloc With non définie. Il existe deux étapes pour créer une variable d'objet. Tout d'abord, vous devez déclarer la variable d'objet Ensuite, vous devez affecter une référence valide à la variable object À l'aide de l'instruction Set. Vous avez tenté d'utiliser une variable d'objet .__ qui ne fait pas encore référence à un objet valide.
  • Error 450 Nombre d'arguments incorrect ou propriété non valide assignation. Le nombre d'arguments dans l'appel de la procédure était différent du nombre d'arguments requis attendu par la procédure Si vous avez essayé d'assigner une valeur à une propriété en lecture seule,

Parmi les erreurs ci-dessus, l'erreur numéro 438 a été jugée importante et l'autre est 5. J'intègre une routine de fonction dans mon programme de test d'échantillons qui a été posté par Mark Nold il y a 7 ans en 2008 vide SO question Déterminer si un objet est membre d'une collection dans VBA et lui est crédité.

Certaines erreurs telles que l'erreur 457 ne seront pas autorisées au moment de l'exécution du test du programme. J'ai essayé de remplir avec des données de clés en double, il a donné l'erreur au moment de tester le programme lui-même, comme indiqué dans l'instantané . error 457

Après le retrait, le résultat est correct, comme indiqué dans le cliché instantané. 

no error

Il peut ne pas être possible d'obtenir la liste des clés d'une collection avec une collection Vanilla sans stocker les valeurs de clé dans un tableau indépendant. La solution la plus simple consiste à ajouter une référence à Microsoft Scripting Runtime et à utiliser un dictionnaire plus performant à la place de .. .. J'ai inclus cette approche pour obtenir la liste des clés dans mon programme.
Lors du remplissage de Collection, vous devez vous assurer que la clé est le deuxième paramètre et doit être une chaîne unique.

Le code complet de mon programme est.

Sub Generic_key_check()
    Dim arr As Variant
    Dim c1 As New Collection
    Dim dic As Object
    With Application
    .ScreenUpdating = False
    End With


    Set dic = CreateObject("Scripting.Dictionary")
    dic.CompareMode = vbTextCompare

    'Populate the collection
    c1.Add "sheet1", "sheet1"
    c1.Add "sheet2", "sheet2"
    c1.Add "sheet3", "sheet3"
    c1.Add "sheet4", "sheet4"
    c1.Add "sheet5", "sheet5"
    c1.Add 2014001, "Long1"
    c1.Add 2015001, "Long2"
    c1.Add 2016001, "Long3"
    c1.Add 2015002, "Long4"
    c1.Add 2016002, "Long5"

    'Populate the dictionary
    dic.Add "sheet1", "sheet1"
    dic.Add "sheet2", "sheet2"
    dic.Add "sheet3", "sheet3"
    dic.Add "sheet4", "sheet4"
    dic.Add "sheet5", "sheet5"
    dic.Add "Long1", 2014001
    dic.Add "Long2", 2015001
    dic.Add "Long3", 2016001
    dic.Add "Long4", 2015002
    dic.Add "Long5", 2016002
    ' Get a list of key items by Dictionary Method
    Dim N As Variant
    For Each N In dic.Keys
    Debug.Print "Key: " & N, "Value: " & dic.item(N)
    Next
    'Test for two types of data whether key exists or not.
    If InCollection(c1, "Long1") Then
    'If Exists("Long1", c1) Then
    Debug.Print "Good"

    Else
    ' If there is error then print out the error number and its description.
    Debug.Print Err.Number, Err.Description
    Debug.Print "Not Good"
    End If
    If InCollection(c1, "sheet2") Then
    Debug.Print "Good"

    Else
    Debug.Print Err.Number, Err.Description
    Debug.Print "Not Good"
    End If

    'Checking whether desired key has populated correctly
    Debug.Print c1("Sheet1")
    Debug.Print c1("Long3")



    'Listing out collection items to check theyexist in the collection.
    For i = 1 To c1.Count
    Debug.Print c1.item(i)
    Next i
    With Application
    .ScreenUpdating = True
    End With
    Set c1 = Nothing
End Sub
Public Function InCollection(col As Collection, key As String) As Boolean
    Dim var As Variant
    Dim errNumber As Long

    InCollection = False
    Set var = Nothing

    Err.Clear
    On Error Resume Next
    var = col.item(key)
    errNumber = CLng(Err.Number)
    On Error GoTo 0

    '5 is not in, 0 and 438 represent incollection
    If errNumber = 5 Then ' it is 5 if not in collection
    InCollection = False
    Else
    InCollection = True
    End If

End Function

La sortie finale selon le programme, comme indiqué dans la fenêtre Immédiat, a été affichée dans l’instantané.
enter image description here

4
skkakkar

La méthode de Robin échouera si la collection contient des objets plutôt que des types primitifs, car ils doivent être affectés à l'aide de Set et sinon générer une erreur entraînant le renvoi de la méthode par False. Voici un petit ajustement:

'Test if a key is available in a collection
Public Function HasKey(coll As Collection, strKey As String) As Boolean
    On Error GoTo IsMissingError
        Dim val As Variant
'        val = coll(strKey)
        HasKey = IsObject(coll(strKey))
        HasKey = True
        On Error GoTo 0
        Exit Function
IsMissingError:
        HasKey = False
        On Error GoTo 0
End Function
0
Apostle

Apôtre est presque correct avec leur réponse. La réponse de Robin ne fonctionnera pas avec des objets génériques, mais fonctionnera telle quelle, car l'objet Range d'Excel renverra la valeur de la cellule. J'adore l'utilisation d'IsObject par Apôtre (principalement parce que c'est ce que j'avais aussi compris). Le code est cependant un peu trop compliqué.

Si la clé existe dans la collection, IsObject définira la variante sur True ou False, sinon une erreur sera ignorée en laissant la variante vide.

Function HasKey(col As Collection, Key As String) As Boolean
    Dim v As Variant
  On Error Resume Next
    v = IsObject(col.Item(Key))
    HasKey = Not IsEmpty(v)
End Function
0
PaulE