Passer en argument d'une fonction un type personnalisé inconnu

inteloide2 Messages postés 16 Date d'inscription jeudi 30 décembre 2010 Statut Membre Dernière intervention 1 septembre 2012 - 26 août 2012 à 18:56
inteloide2 Messages postés 16 Date d'inscription jeudi 30 décembre 2010 Statut Membre Dernière intervention 1 septembre 2012 - 1 sept. 2012 à 10:40
Bonjour,

Je cherche (en VBA) à passer en paramètre d'une fonction un type de donnée personnalisée que je ne connais pas lors de la création de la fonction.

Dans l'exemple si dessous, j'ai deux tableaux de type personnalisé différents mais je souhaite que ma fonction (qui enregistre ces tableaux) puisse le faire peux importe le tableau (et donc le type de donnée) que je passe en paramètres.

A chaque fois j'ai l'erreur suivante :
Seules les types définis par l'utilisateur et qui sont définis dans les
modules d'objets publics peuvent être convertis depuis un variant, ou passés
à des fonctions à liaison tardive


Voici mon code :
Private Type maison
    propriétaire As String
    numero As Long
End Type

Private Type pièces
    nom As String
    superficie As Long
    num_maison As Long
End Type

Public bd_maisons(50) As maison
Public bd_pièces(50) As pièces

Public Sub Enregistrer_bd(ByRef bd)
Open "D:\test.txt" For Binary As #1
    For i = 0 To UBound(bd)
        Put #1, , bd(i)
    Next i
Close #1
End Sub


et pour lancer :
Private Sub CommandButton1_Click()
bd_maisons(0).numero = 1234
bd_maisons(0).propriétaire = toto
Call Enregistrer_bd(bd_maisons)
End Sub


PS : c'est un code simplifié : dans la réalité, j'ai un grand nombre de tableaux avec des types personnalisés différents.

L'erreur se produit à l'appel de la fonction.

Merci pour votre aide.
A voir également:

17 réponses

ucfoutu Messages postés 18038 Date d'inscription lundi 7 décembre 2009 Statut Modérateur Dernière intervention 11 avril 2018 211
26 août 2012 à 19:19
Bonjour,
impossible de te répondre avec précision sans savoir avec précision où tu as mis ce code et d'où tu appelles la fonction !


________________________
Réponse exacte ? => "REPONSE ACCEPTEE" pour faciliter les recherches.
Pas d'aide en ligne installée ? => ne comptez pas sur moi pour simplement vous dire ce qu'elle contient. Je n'interviendrai qu'en cas de nécessité de développ
0
inteloide2 Messages postés 16 Date d'inscription jeudi 30 décembre 2010 Statut Membre Dernière intervention 1 septembre 2012
26 août 2012 à 19:23
J'ai mis dans un module (pas un module de classe) le code :
Public Type maison
    propriétaire As String
    numero As Long
End Type

Public Type pièces
    nom As String
    superficie As Long
    num_maison As Long
End Type

Public bd_maisons(50) As maison
Public bd_pièces(50) As pièces

Public Sub Enregistrer_bd(ByRef bd)
Open "D:\test.txt" For Binary As #1
    For i = 0 To UBound(bd)
        Put #1, , bd(i)
    Next i
Close #1
End Sub


Et, associé à bouton dans une feuille :
Private Sub CommandButton1_Click()
bd_maisons(0).numero = 1234
bd_maisons(0).propriétaire = "toto"
Call Enregistrer_bd(bd_maisons)
End Sub


voili voilou
0
ucfoutu Messages postés 18038 Date d'inscription lundi 7 décembre 2009 Statut Modérateur Dernière intervention 11 avril 2018 211
26 août 2012 à 20:18
Tu ne peux faire cela avec un tableau de structure.
Il te faut nécessairement traiter chaque structure en dur dans la sub.
Au passage, en outre : bd(i) ===>> Gné (alors ? structure ou pas structure ?) !!!



________________________
Réponse exacte ? => "REPONSE ACCEPTEE" pour faciliter les recherches.
Pas d'aide en ligne installée ? => ne comptez pas sur moi pour simplement vous dire ce qu'elle contient. Je n'interviendrai qu'en cas de nécessité de développ
0
inteloide2 Messages postés 16 Date d'inscription jeudi 30 décembre 2010 Statut Membre Dernière intervention 1 septembre 2012
26 août 2012 à 20:51
Excuse moi, mais qu'est-ce qu'une "structure" en vba ?
Ce n'est pas un type personnalisé tels que déclaré en tête de module dans mon exemple ?
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
ucfoutu Messages postés 18038 Date d'inscription lundi 7 décembre 2009 Statut Modérateur Dernière intervention 11 avril 2018 211
26 août 2012 à 22:27
maison, par exemple, est une structure (une variable de type personnalisé).
On ne peut affecter à une variable une structure. On ne peut donc passer un tableau de structure à une procédure. On ne peut qu'utiliser le tableau de structure lui-même (sous son seul nom) dans une procédure.
On pourrait faire une petite usine à gaz, à l'aide d'une collection, mais ce serait une usine à gaz ! et en plus, pour, dis-tu :
j'ai un grand nombre de tableaux avec des types personnalisés différents.

oriente-toi donc plutôt vers des tableaux à plusieurs dimensions ... oublie les tableaux de structure pour faire ce que tu veux faire.




________________________
Réponse exacte ? => "REPONSE ACCEPTEE" pour faciliter les recherches.
Pas d'aide en ligne installée ? => ne comptez pas sur moi pour simplement vous dire ce qu'elle contient. Je n'interviendrai qu'en cas de nécessité de développ
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
27 août 2012 à 01:39
Salut

En fait, double problème.
Passer en argument une variable typée sans connaitre ça structure (de quoi elle est composée) est peut-être possible en typant la variable As Variant, mais, c'est sûr, pas un tableau composé de cette structure/type.
De toute façon, un second problème se posera : Comment espères-tu que l'instruction Put fonctionne en lui fournissant une structure ?
Perso, ça m'étonnerait que cela fonctionne, surtout avec un paramètre de type String dans le lot (*).
Donc, puisqu'il te faudra surement énumérer les paramètres de ta structure/type dans le programme, ça ne sert à rien de vouloir passer une variable générique.

(*) Les variables String n'ont pas de longueur prédéfinie, comme le sont les Byte, Integer, Long ...
Donc, impossible de vouloir stocker une chaine dont la longueur est variable dans un accès séquentiel d'un fichier. Dans ton cas, Open de type Binary, il faudra éventuellement terminer ta chaine avec un Chr$(0) pour marquer la fin de la chaine, ou bien, comme cela est fait dans de très nombreuses structures de fichier (Zip et autre), prévoir une variable chiffre supplémentaire dans laquelle tu stockeras la longueur de la chaine qui suit immédiatement.
Sans cela, tu seras incapable de relire les données.

Vala
Jack, MVP VB
NB : Je ne répondrai pas aux messages privés

Le savoir est la seule matière qui s'accroit quand on la partage (Socrate)
0
ucfoutu Messages postés 18038 Date d'inscription lundi 7 décembre 2009 Statut Modérateur Dernière intervention 11 avril 2018 211
27 août 2012 à 08:30
Bonjour, jack
C'était le sens de ma réponse. Je ne voulais pas entrer dans les détails.
Même sans tableau (une structure isolée), cela ne marcherait pas car sa procédure (à laquelle il passe l'argument bd) ignorerait totalement le type de cette variable (un simple msgbox typename(bd) lui aurait permis de découvrir cet aspect). Et sans conn aitre un type personnalisé, ===>> échec forcé.
en ce qui concerne ton point 2, c'était le sens de ma remarque ici :

en outre : bd(i) ===>> Gné (alors ? structure ou pas structure ?) !!!



Je n'ai du coup même plus jugé utile de parler de ton point 3 et de lui dire qu'il convenait de toutes manières de donner une longueur à ses champs string (au sein même de la structure).



________________________
Réponse exacte ? => "REPONSE ACCEPTEE" pour faciliter les recherches.
Pas d'aide en ligne installée ? => ne comptez pas sur moi pour simplement vous dire ce qu'elle contient. Je n'interviendrai qu'en cas de nécessité de développ
0
ucfoutu Messages postés 18038 Date d'inscription lundi 7 décembre 2009 Statut Modérateur Dernière intervention 11 avril 2018 211
27 août 2012 à 12:08
L'essentiel ayant été dit : on va passer au ludique ===>>
Oui, on peut y arriver. Y compris sans collection. Mais à quel prix et avec quels détours et lenteurs ?

Comment ?
En jouant avec les composants de VBAProject pour ajouter dynamiquement du code dans une procédure, puis l'exécuter.
Ce code, ainsi ecrit en chaines de caractères interprétées, contiendra ainsi la variable personnalisée à traiter, mais devra également contenir le nom de chque membre de la structure !
Il est alors ainsi exécutable.
Certes ! mais ce serait écrire à chaque coup une procédure dynamique "paramétrée" pour éviter d'écrire à chaque coup directement un code distinct accompagnant des expressions conditionnelles !
Pure histoire de fou !



________________________
Réponse exacte ? => "REPONSE ACCEPTEE" pour faciliter les recherches.
Pas d'aide en ligne installée ? => ne comptez pas sur moi pour simplement vous dire ce qu'elle contient. Je n'interviendrai qu'en cas de nécessité de développ
0
inteloide2 Messages postés 16 Date d'inscription jeudi 30 décembre 2010 Statut Membre Dernière intervention 1 septembre 2012
27 août 2012 à 22:04
Bon, je vais la jouer Dr House : crtiquer vos réponses et m'en servir pour trouver la solution.

Comment espères-tu que l'instruction Put fonctionne en lui fournissant une structure ?

=> Et bien ça marche nikel. Fait l'essai...Je n'ose même pas imager vos codes si vous n'encoder pas comme ça...

Donc, impossible de vouloir stocker une chaine dont la longueur est variable dans un accès séquentiel d'un fichier. Dans ton cas, Open de type Binary, il faudra éventuellement terminer ta chaine avec un Chr$(0)

=> Dans l'aide de VBA il est marqué que le mode binary sert justement à ça.... En fait le put met avant la string la longeur de celle çi...(Donc le chr$(0), j'en rigole encore).

Même sans tableau (une structure isolée), cela ne marcherait pas car sa procédure (à laquelle il passe l'argument bd) ignorerait totalement le type de cette variable (un simple msgbox typename(bd) lui aurait permis de découvrir cet aspect).

=> Jamais vous lisez l'aide ????
TypeName(varname)

L'argument varname est une valeur de type Variant pouvant contenir toute variable à l'exception d'une variable de type défini par l'utilisateur.




Je n'ai du coup même plus jugé utile de parler de ton point 3 et de lui dire qu'il convenait de toutes manières de donner une longueur à ses champs string

=> je juge utile de dire que cette affirmation est fausse (voir points précédents)


En jouant avec les composants de VBAProject pour ajouter dynamiquement du code dans une procédure, puis l'exécuter.

=> faire un code de fou pour généré un code, effectivement c'est une histoire de fou, je confirme. Mieux vaut programmer directement une fonction pour chaque type de données.


En fait il s'agit d'un problème de late binding. (Google vous expliquera mieux que moi)

Bon, maintenant pour apporter une solution :
- les collections : on ne peut pas ajouter de variables de type personnalisé dans une collection. Donc, il faut ajouter les éléments du type dans une collection et faire des collections de collections ou faire des tableaux de collections.

- les modules de classe : idem, pas possible de mettre un type personnalisé dedans. donc il faut faire comme pour les collections. Mais bon, le code et la vitesse d'éxécution ne seront pas tip top.

- l'adressage de mémoire : voir ici. C'est du code VB mais on doit pouvoir assez facilement le traduire (pour la fonction VarPtr.

- l'utilisation de tableau comme suggéré par ucfoutu, sans faire appel à des types personnalisés, mais là on perd la facilité de programmation qu'ils représentent, notament pour le type de données : je fais un tableau de quoi ? string ? mais si je veux y mettre des string et des booleans par exemple ? Bein c'est possible mais il y a du code en plus.

=> En bref, à moins que d'autre personnes ai des idées, pas de solution miracle, justes des pistes. Je n'ai pas encore fait mon choix...mais bon j'ai n'ai pas de patient mourrant contrairement à House ;o)
0
ucfoutu Messages postés 18038 Date d'inscription lundi 7 décembre 2009 Statut Modérateur Dernière intervention 11 avril 2018 211
28 août 2012 à 07:56
Ah ! Tiens donc ! Nous n'avons jamais utilisé un fichier text stryucturé !
Je vais donc personnellement te laisser là !

En te précisant quand-même ceci :
Pour faire valablement ce que tu dis, c'est For Random, que le fichier doit être ouvert. Par valablement : entendre : pouvoir traiter comme base de données.
Et puisque tu sais lire l'aide VB ===>> voilà ===>>


Longueur d'enregistrement incorrecte (erreur 59)
Voir aussi Particularités

La longueur d'une variable d'enregistrement dans une instruction Get ou Put doit être celle spécifiée dans son instruction Open correspondante. Causes et solutions de cette erreur :

La longueur de la variable d'enregistrement diffère de la longueur spécifiée dans l'instruction Open correspondante.
Vérifiez que la somme des tailles des variables de longueur fixe dans le type défini par l'utilisateur définissant le type de la variable de l'enregistrement a la valeur indiquée dans la clause Len de l'instruction Open. Dans l'exemple suivant, supposons que RecVar est une variable du type approprié. Vous pouvez utiliser la fonction Len pour spécifier la longueur, de la façon suivante :

Open MyFile As #1 Len = Len(RecVar)

La variable dans une instruction Put est (ou inclut) une chaîne de longueur variable.
Le descripteur à 2 octets étant toujours ajouté à une chaîne de longueur variable placée dans un fichier à accès aléatoire avec Put, la chaîne de longueur variable doit être plus courte d'au moins 2 caractères que la longueur d'enregistrement spécifiée dans la clause Len de l'instruction Open.

La variable dans une instruction Put est (ou inclut) une variable de type Variant.
Comme les chaînes de longueur variable, les types de données Variant requièrent un descripteur à 2 octets. Les variables de type Variant contenant des chaînes de longueur variable requièrent un descripteur à 4 octets. Par conséquent, pour les chaînes de longueur variable dans une variable de type Variant, la chaîne doit être plus courte d'au moins 4 octets que la longueur d'enregistrement spécifiée dans la clause Len.

Pour plus d'informations, sélectionnez l'élément en question et appuyez sur F1 (sous Windows) ou AIDE (sur Macintosh).


Bonne chance
________________________
Réponse exacte ? => "REPONSE ACCEPTEE" pour faciliter les recherches.
Pas d'aide en ligne installée ? => ne comptez pas sur moi pour simplement vous dire ce qu'elle contient. Je n'interviendrai qu'en cas de nécessité de développ
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
28 août 2012 à 12:35
Salut Greg (tu permets que je t'appelle Greg ?)

Lecture instructive et merci d'avoir remis en doute nos acquits; on en apprend tous les jours.

En ce qui concerne l'écriture d'une structure en Binary dans un fichier, là, je reste pentois, je ne pensais pas que cela puisse fonctionner.
Par contre, cela va se compliquer sévèrement plus tard :
Certe, tu peux écriture des structures à la queue leu-leu dans un fichier binaire, mais c'est tout : Tu ne pourras pas dire "je vais modifier la 3ème donnée", pour deux raisons :
- la première c'est que, dans un accès Binary, tu ne peux te déplacer (Seek) qu'en précisant des octets de décalage. Tes données ayant des longueurs aléatoires, tu ne pourras pas atteindre le Xème enregistrement facilement (sauf en relisant toutes les données précédentes)
- la seconde, c'est que tu ne pourras pas remplacer un texte en plein milieu du fichier.
Même si tu arrivais à pointer sur le début de la Xème donnée à modifier, la longueur de la nouvelle donnée devra être de la même taille que l'ancienne.
Si plus petite, il y aura un 'vide' jusqu'à la donnée suivante;
Si plus grande, tu empièteras sur la donnée suivante
Dans les deux cas, les données suivantes ne pourront plus être lues.

Alors, pour en revenir à ta question initiale, vouloir mélanger 2 structures dans le même fichier me parait risqué : Si tes 2 structures ont des types | un nombre de paramètres différents, comment les relire ?

Sur la question purement technique du passage de paramètre typé par un Variant (late binding), j'ai fait quelques essais et c'est vrai que la même erreur revient, même en déclarant le type et la variable en Public dans un module, comme le suggère le message d'erreur.
Je ne connais pas de solution.
Dans l'aide de VB6 (pas VBA), je lis "Le type Variant prend désormais en charge les types définis par l'utilisateur"
mais il semble que non.

Pour les classes, en fait, c'est un peu la même chose qu'une variable typée : Tu définis dans la classe différentes variables de différents types auquelles tu accèdes grace à des Properties.
L'avantage de la classe, c'est que, à chaque fois que tu entres ou que tu demandes une des valeurs, tu peux gérer du code (pour faire des calculs ou autre).
Une fois la classe fondée, tu peux facilement l'utiliser comme une variable typée dans une collection.
Une collection peut accueillir n'importe quel type de variables ou d'objet, même complexe.
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
28 août 2012 à 12:38
"Une collection peut accueillir n'importe quel type de variables ou d'objet, même complexe."
Mais d'un seul type/format
0
ucfoutu Messages postés 18038 Date d'inscription lundi 7 décembre 2009 Statut Modérateur Dernière intervention 11 avril 2018 211
29 août 2012 à 11:14
Oui ...
Petite expérience "parlante" (du moins en ce qui me concerne) :

Private Type toto
  el1 As Integer
  el2 As String
  el3 As Variant
End Type
Private Type voila
  el1 As Integer
  el2 As String
End Type

Private Sub Commandbutton1_Click()
  Dim usons_toto As toto, usons_voila As voila
  usons_toto.el1 = 3
  usons_toto.el2 = "coucou"
  usons_toto.el3 = 5
  usons_voila.el1 = 3
  usons_voila.el2 = "bonjour"
  'et maintenant assignons une variable autre à usons_toto et voyons
  Dim autre As toto
  autre = usons_toto
  MsgBox autre.el2 ' on voit bien que pas de problème
  
  ' mais dès lors : impossible de donner à la variable autre une autre affectation
  'la ligne qui suit provoquera une erreur, si on la décommente :
  
  ' Dim autre As voila
  
  'Or, c'est bien ce qu'il faudrait pouvoir faire, si l'on veut passer, par exemple, à une procédure
  ' Je ne suis pas allé plus loin (pas voulu, compte tenu de certaines "circonstances")
  ' que ceux qui le veulent cherchent donc à supprimer de la mémoire la variable autre avant de la redéclarer
  ' c'est peut-être le bon chemin.
End Sub

Cette expérience ne concerne que l'aspect passage de variable et ne change rien à ce que j'ai dit du reste (utilisation d'un fichier text structuré).
________________________
Réponse exacte ? => "REPONSE ACCEPTEE" pour faciliter les recherches.
Pas d'aide en ligne installée ? => ne comptez pas sur moi pour simplement vous dire ce qu'elle contient. Je n'interviendrai qu'en cas de nécessité de développ
0
inteloide2 Messages postés 16 Date d'inscription jeudi 30 décembre 2010 Statut Membre Dernière intervention 1 septembre 2012
30 août 2012 à 19:27
Bon,
Merci à ucfoutu pour la copie de l'aide, mais remarque bien que la clause len n'est pas obligatoire...alors je ne m'embête pas avec ça dans mon cas car les données sont de longeurs variables.
Pour ton bout de code, pourquoi vouloir transformer "autre" dans le type "voilà" ?
Dans le passage de paramètres dans une fonction, il ne faut que des types "standard", donc transformer un type personnalisé en type personnalisé...désolé mais je ne vois pas le lien.

Pour répondre à Jack, effectivement on ne peut se déplacer dans un fichier. Quant à remplacer, supprimer des données, une seule solution passer par un nouveau fichier...pas top j'en convient (long pour les gros fichiers).

Au final, je vais ce que vais faire:
-déclarer mes bases de données comme "public"
-pour la fonction, je passe en paramètre un string qui reprend le nom de la base
-puis après le open fichier for binary as #1, je met un select case sur le nom de la base de donnée.
Donc au final qqchose comme ça :
public bd_maisons() as maison
public bd_pièces() as pièce

public function enregistrer_db(nom_db as string, fichier as string, nb_éléments as long)
open fichier for binary as #1
  for i = 0 to nb_éléments
   if nom_bd="bd_maisons"  then put #1,,bd_maisons(i)
   if nom_bd="bd_pièces" then put #1,,bd_pièces(i)
   next i
close #1
end function



C'est pas le pied, mais c'est mieux que plusieurs fonctions...
0
ucfoutu Messages postés 18038 Date d'inscription lundi 7 décembre 2009 Statut Modérateur Dernière intervention 11 avril 2018 211
30 août 2012 à 22:02
Ah !
Je regrette que tu n'aiues pas su capter ce que révélait mon exemple.
Le parametre passé à la procédure "commune" doit , conbtrairement à ce qu'exprime mal ou à tord l'aide VB, correspondre à une tructure "autre". Et on passe une variable "as autre" pour qu'il connaisse la structure de ce qui est passé.
Mais voilà : une fois autre affectée à une structure, impossible de l'affecter à une autre structure (mon exemple). Impossible, donc, de modifier la structure de autre le as autre en définition du paramètre passé. Impossible, donc, d'utiliser une procédure commune pour différentes variables structurées.

En ce qui concerne le second point : fais comme tu veux, ma foi, mais tu perds ainsi la possibilité de traiter des données structurées. Pour y parvenir (ce que je t'ai dit et t'a également dit jack), il te faut ouvrir "For random" et que les structures traitées soient de longueur connue (donc chaque élément de longueur également connue). Ouvre donc "For Random" et tu comprendras : refus d'un champ string de longueur non fixe.

Alors ? Si c'est pour finir comme tu "finis par finir", autant travaillet avec un fichçier texte "plat" !
Voilà ! Je ne suis pas clair ? Peut-être, ami, peut-être ...
Bonne chance.
________________________
Réponse exacte ? => "REPONSE ACCEPTEE" pour faciliter les recherches.
Pas d'aide en ligne installée ? => ne comptez pas sur moi pour simplement vous dire ce qu'elle contient. Je n'interviendrai qu'en cas de nécessité de développ
0
inteloide2 Messages postés 16 Date d'inscription jeudi 30 décembre 2010 Statut Membre Dernière intervention 1 septembre 2012
31 août 2012 à 07:43
ok, je vient de capter. Puisque que le "dim" dans un autre type personnalisé ne marche pas, J'ai essayé aussi ça:
Dim autre As voila
autre = usons_toto
MsgBox autre.el2

Sans succès non plus... à "autre" de type "voilà" on ne peut pas une varaible de type "toto".

J'essaye pour le For Random et je vous dit quoi.
0
inteloide2 Messages postés 16 Date d'inscription jeudi 30 décembre 2010 Statut Membre Dernière intervention 1 septembre 2012
1 sept. 2012 à 10:40
Bon, je viens d'essayer pour le "for random" ça marche aussi avec l'avantage de pouvoir spécifier le numéro de l'enregistrement à lire.

Pas de soucis non plus pour mettre des données de longeur variable. ça marche aussi (faut pas mettre de clause "len"). Par contre en faisant ça plus possible de remplacer un enregistrement par un autre comme vous l'avez tous dit.

Mais bon, pour mon utilisation, je charge tout en mémoire puis je traite les données en mémoire (ajout/suppr/modification) et j'enregistre tout après.
Le seul inconvénient de ça c'est si excel (ou le pc) plante lors de la ré-écriture des données...là on perd tout, alors qu'avec des variables de longeur fixe, on perd que la données modifiée.
Mais encore une fois pour mon utilisation mettre des variables de longeur fixe m'oblige à beaucoup de contraintes...(limiter longeur champs de saisie, taille du fichier peut être plus grande que nécessaire) Et pus si je veut supprimer des données ça laisse des trous...donc ré-écriture du fichier à un moment donné.

arff, pourquoi n'y a t il pas de solution miracle ???

Un grand merci pour cette réflexion.
0
Rejoignez-nous