2009-06-19 6 views
5

Je veux avoir/définir un identifiant unique pour chaque ligne de données dans ma feuille de données Excel - de sorte que je puisse l'utiliser lors du passage des données et il reste le même lorsque les lignes sont ajoutées/supprimées au-dessus.Comment obtenir/définir un ID unique pour la cellule dans Excel via VBA

Mes pensées sont à utiliser l'attribut ID de plage (msdn link)

Donc, j'ai une fonction définie par l'utilisateur (UDF) que je place dans chaque ligne qui obtient/définit l'ID comme suit:

Dim gNextUniqueId As Integer 

Public Function rbGetId(ticker As String) 
    On Error GoTo rbGetId_Error 
    Dim currCell As Range 
    'tried using Application.Caller direct, but gives same error 
    Set currCell = Range(Application.Caller.Address) 
    If currCell.id = "" Then 
     gNextUniqueId = gNextUniqueId + 1 
     'this line fails no matter what value I set it to. 
     currCell.id = Str(gNextUniqueId) 
    End If 
    rbGetId = ticker & currCell.id 
    Exit Function 

    rbGetId_Error: 
    rbGetId = "!ERROR:" & Err.Description 
End Function 

Mais cela échoue à la ligne mentionnée avec

« erreur définie par l'application ou défini objet »

Je pensais que peut-être son une de ces limites de UDFs, mais je reçois aussi la même erreur si j'essayer à partir du code déclenché à partir d'un bouton de ruban ...

Toutes les autres suggestions sur la façon de garder ids cohérentes - peut-être Je dois peupler les cellules via mon bouton ruban, trouver des cellules sans ID et générer/définir la valeur de la cellule de ces ...

EDIT: Comme Ant pensée, j'ai la feuille protégée, mais même dans une cellule déverrouillée échoue toujours. Déprotéger la feuille résout le problème .... mais j'ai utilisé "Protect UserInterFaceOnly: = True" ce qui devrait me permettre de le faire. Si j'autorise manuellement "Modifier les objets" lorsque je protège la feuille, cela fonctionne également, mais je ne vois pas d'option programmatique pour cela - et je dois appeler la fonction Protéger en AutoOpen pour activer la fonction UserInterfaceOnly ...

Je suppose que je dois activer/désactiver la protection autour de mon paramètre ID - en supposant que cela peut être fait dans un UDF ... ce qui semble ne pas pouvoir, car cela ne fonctionne pas - ni ActiveSheet.unprotect ni ActiveWorkbook.unprotect :(

Merci à l'avance Chris

+1

Votre code fonctionne correctement dans Excel 2003 et 2007. Votre livre n'est pas verrouillé ou quelque chose comme ça? – Ant

Répondre

0

J'ai trouvé que si je protège la feuille avec "Protect DrawingObjects: = False", l'UDF peut définir l'Id. Étrange.

Merci pour toute l'aide avec ceci.

1

Concur avec Ant -. votre code fonctionne très bien ici sur Excel 2003 SP3

. 0

J'ai aussi été en mesure d'utiliser:

Set currCell = Application.Caller 
If Application.Caller.ID = "" Then 
    gNextUniqueId = gNextUniqueId + 1 
    'this line fails no matter what value I set it to. 
    currCell.ID = Str(gNextUniqueId) 
End If 

Aha! Je pense que je l'ai.

Je pense que vous appelez cela à partir d'une formule matricielle, et il est appelé seulement une fois avec une plage complète. Vous ne pouvez pas obtenir un ID pour une plage - seulement une seule cellule. Cela explique pourquoi Application.Caller.ID échoue pour vous, car Range ("A1: B9"). ID génère un Application-defined or object-defined error.

Lorsque vous utilisez Range(Application.Caller.Address) pour obtenir la "cellule" vous reporte juste cette erreur à la ligne currCell.ID.

+0

Ce n'est pas une formule de gamme, juste une seule cellule. –

+0

Suite à votre EDIT ci-dessus - avez-vous essayé d'utiliser ActiveSheet.Unprotect ou ActiveWorkbook.Unprotect? Vous pouvez toujours référencer le Cell.Parent si vous voulez éviter d'être explicite avec la feuille (un mouvement sage). Je pense que parce que l'UDF fait partie de l'interface utilisateur, étant une formule, il a été empêché d'apporter des modifications à la feuille, mais ce n'est qu'une intuition. –

1

Je pense que nous avons peut-être quelques problèmes ici, mais je pense qu'ils testent des problèmes, pas des problèmes avec le code lui-même. Tout d'abord, si vous appelez la fonction à partir d'autre chose qu'une cellule, comme la fenêtre immédiate, un autre code, etc. Application.Caller ne sera pas défini. C'est ce qui génère vos erreurs d'objet non trouvées. Deuxièmement, si vous copiez/collez la cellule qui a la fonction, ils le feront en copiant/collant l'identifiant. Donc, partout où vous le collez, la sortie restera la même. Mais si vous copiez simplement le texte (au lieu de la cellule), puis collez, cela fonctionnera bien. (Y compris votre utilisation initiale de Application.Caller.)

+0

Merci pour la tête sur les fonctionnalités de copier/coller de l'ID –

4

D'accord ...

Il ne semble que si la feuille est verrouillée, les macros n'ont pas accès en écriture à l'information à faible niveau telles que l'ID.

Cependant, je ne pense pas qu'il soit possible de déprotéger la feuille dans un UDF. De par leur conception, les FDU sont fortement restreintes; Je pense qu'ayant une formule de cellule contrôlant la protection de feuille casserait le paradigme de formule qu'une formule de cellule affecte une cellule seulement. Voir this page sur le site Web de Microsoft pour plus de détails. Je pense que cela limite vos options. Vous devez soit:

  • renoncer à la protection de la feuille
  • Abandonnez l'UDF, utilisez un événement Worksheet_Change pour saisir les changements cellulaires et écrire ID il
  • utiliser une UDF qui écrit l'ID dans la valeur de la cellule, plutôt que d'enregistrer dans l'ID

L'approche UDF est pleine de problèmes car vous essayez d'utiliser un objet conçu pour le calcul d'une cellule pour marquer durablement la feuille. Néanmoins, voici un exemple d'UDF que vous pouvez utiliser pour marquer une valeur "permanente" sur une cellule, qui fonctionne sur des cellules déverrouillées d'une feuille protégée. Celui-ci ne fonctionne que pour des cellules individuelles (bien qu'il puisse être adapté pour une formule matricielle).

Public Function CellMark() 

    Dim currCell As Range 
    Set currCell = Range(Application.Caller.Address) 

    Dim myId As String 
    ' must be text; using .value will cause the formula to be called again 
    ' and create a circular reference 
    myId = currCell.Text 

    If (Trim(myId) = "" Or Trim(myId) = "0") Then 
     myId = "ID-" & Format(CStr(gNextUniqueId), "00000") 
     gNextUniqueId = gNextUniqueId + 1 
    End If 

    CellMark = myId 

End Function 

Ceci est cependant très imparfait. L'utilisation de la copie ou de la boîte de saisie conservera toutefois la valeur copiée précédente. Ce n'est qu'en définissant explicitement les cellules comme une nouvelle formule que cela fonctionnera. Mais si vous entrez à nouveau la formule dans la cellule (il suffit de cliquer dessus, appuyez sur ENTRER), une nouvelle valeur est calculée, ce qui correspond au comportement standard de la cellule.

Je pense que l'événement Worksheet_Change est le chemin à parcourir, qui a beaucoup plus de latitude. Voici un exemple simple qui met à jour l'ID de toute modification de cellule. Il pourrait être adapté à votre scénario particulier. Cette fonction doit être ajoutée à toutes les feuilles de calcul pour lesquelles le comportement de définition d'ID est requis.

Private Sub Worksheet_Change(ByVal Target As Range) 

    Dim currCell As Range 
    Set currCell = Target.Cells(1, 1) 

    Dim currId As String 
    currId = currCell.ID 

    If Trim(currCell.ID) = "" Then 
     Target.Parent.Unprotect 
     currCell.ID = CStr(gNextUniqueId) 
     Target.Parent.Protect 
     gNextUniqueId = gNextUniqueId + 1 
    End If 

End Sub 

Dernière note; Dans tous les cas, votre compteur d'ID sera réinitialisé si vous rouvrez la feuille de calcul (au moins sous les détails limités présentés dans votre exemple).

Espérons que cela aide.

+2

Merci beaucoup pour cette analyse détaillée, mais j'ai trouvé que si je protège la feuille avec "Protect DrawingObjects: = False", l'UDF peut définir l'Id. Étrange ... –

+0

Darn, bien fait de trouver cela. Êtes-vous complètement trié maintenant ou y a-t-il encore des problèmes de simulation? –

1

Le problème est avec Application.Caller.

Comme vous l'appelez à partir d'une fonction définie par l'utilisateur, il va vous transmettre une description d'erreur. Voici la remarque dans le fichier d'aide.

Remarques

Cette propriété renvoie des informations sur la façon dont a été appelé Visual Basic, comme indiqué dans le tableau suivant.

appelant - Valeur de retour

  • une fonction personnalisée enregistrée dans une seule cellule - Un objet Range précisant que la cellule
  • Une fonction personnalisée qui fait partie d'une formule de matrice dans une plage de cellules - Une gamme objet spécifiant que la plage de cellules
  • Un Auto_Open, Auto_Close, Auto_Activate ou Auto_Deactivate macro - le nom du document sous forme de texte
  • Une macro définie soit par le OnDoubleClick ou OnEntry propriété - le nom de l'identificateur d'objet graphique ou une cellule refere nce (le cas échéant) auquel s'applique la macro
  • La boîte de dialogue Macro (menu Outils) ou tout appelant non décrit ci-dessus - Le #REF! valeur d'erreur

Puisque vous appelez à partir d'une fonction définie par l'utilisateur, ce qui se passe est Application.Caller retourne une chaîne d'un code d'erreur à votre plage variable curCell. Cela ne provoque PAS une erreur que votre gestionnaire d'erreurs détecterait. Qu'est-ce qui se passe après que vous faites référence à curCell, ce n'est plus une plage. Sur ma machine, il essaie de configurer curCell = Range ("Erreur 2023"). Quel que soit cet objet, il ne possède plus d'attribut ID et lorsque vous essayez de le définir, il vous envoie cette erreur d'objet.

Voici ce que je voudrais essayer ...

  1. Essayez de supprimer votre gestionnaire d'erreur et voir si VBA vomit des exceptions sur la gamme (Application.Caller.Address). Cela ne va pas le réparer, mais cela pourrait vous orienter dans la bonne direction.

  2. Soit à travers la logique ou Application.ActiveCell ou, cependant, vous voulez le faire, référencez la cellule directement. Par exemple Range ("A1") ou Cellules (1,1). Application.Caller.Address ne semble pas être une bonne option à utiliser.

  3. Essayez l'option Explicit. Cela pourrait faire en sorte que la ligne où vous définissez curCell génère une erreur car Range (Application.Caller.Address) ne semble pas renvoyer une plage, qui est le type de données de curCell.

Questions connexes