XL 2010 Création dynamique de Controles et du code event associé

CC76

XLDnaute Nouveau
Bonjour
J'essaie de créer dynamiquement sur une forme un bouton (ça, ça marche), et le code de l'événement Click associé, et ça ça ne marche pas.
J'ai une erreur sur l'utilisation de CreateEventProc. ( 'Erreur d'exécution '57017' Gestionnaire d'événements non valide).
Quelqu'un peut-il m'expliquer pourquoi ça ne marche pas
Grand merci d'avance

Voilà le code complet de la proc:
Public Sub AjouteCtrl()
Dim CtrlName As String, MyCode As String, CodeMacro As String, TextLoc As Long
'Tentative de créer un bouton TestButton sur la form "FormJeu" et de
'créer la Sub TestButton_Click
CtrlName = "TestButton"
CodeMacro = " msgbox coucou"
'Création du bouton - OK, ça marche
With FormJeu
.Controls.Add "Forms.CommandButton.1", CtrlName
With .Controls(CtrlName)
.Caption = CtrlName
.Visible = True
.Locked = False
End With
End With
'Création de la sub associée à l'événement Click - ça ne marche pas -
With Application.VBE.ActiveVBProject.VBComponents("FormJeu").CodeModule
TextLoc = .CreateEventProc("Click", CtrlName) 'Erreur d'exécution '57017' Gestionnaire d'événements non valide
.InsertLines TextLoc + 1, CodeMacro
End With
'Alors que le code suivant fonctionne (presque)
MyCode = "Private Sub TestButton_Click()" & vbCrLf & CodeMacro & vbCrLf & "End Sub"

With Application.VBE.ActiveVBProject.VBComponents("FormJeu")
.CodeModule.AddFromString MyCode 'ça, ça marche - La proc est ajoutée, mais le bouton ne réagit pas quand on click dessus
End With
FormJeu.Show
End Sub
 
Solution
La collection Cln sert à conserver en mémoire les exemplaires de SupportImage créés.
Une Public Sub dans un module de classe, ça s'appelle une méthode, pour qu'on se souvienne qu'elle ne peut être invoquée sans être qualifiée de l'objet qui la possède.
La méthode Init, donc, sert simplement à initialiser les objets propre à l'exemplaire, l'objet parent pour pouvoir invoquer celle de ses méthodes qui prendra en charge le clic, et l'image supportée.
Avoir renommé MaFormeVirtuelle le SupportsImages, je veux bien, mais ça ne désigne plus du tout ce que ça représente, à savoir une collection d'éléments SupportImage.
Et MéthodeRéservéeÀSupportImage avait le mérite d'indiquer qu'elle ne devait pas être employée par le programmeur d'application...

CC76

XLDnaute Nouveau
Bonjour @CC76
Pour résumer le principe: Il est tout à fait possible de rendre opérationnels des CommandButton créés dynamiquement dans un UserForm chargé, mais seulement par du code qui a déjà été prévu pour cela, et dont la résidence normale est un module de classe. Je pourrais vous indiquer la meilleure façon de l'écrire si vous m'en disiez davantage sur ce que vous voudriez. Dans l'UserForm, vous auriez une procédure unique pour tous les CommandButton créés dynamiquement, qui s'exécuterait quand on clique sur l'un d'eux. À vous de me dire quel(s) argument(s) vous aimeriez qu'elle transmette pour vous permettre de savoir ce qu'il y a lieu d'y exécuter, selon celui qui aura été actionné.
Le mieux ce serait même de joindre un fichier montrant d'avantage le contexte.
Bonjour,
Merci pour votre proposition. J'écris du VB sous excel depuis des années (quelques milliers de lignes de code) mais en utilisant des forms et des modules standard. Je n'ai jamais utilisé de module de classe, et je ne sais pas à quoi cela sert, mais je ne demande pas mieux que d'apprendre. D'ailleurs, ce que je cherche à faire est d'abord de m'amuser, et ensuite d'étendre mes connaissances en VB. Donc, je suis preneur, et je vais essayer dès maintenant de trouver des infos sur les modules de classe, et leur utilisation.
En ce qui concerne le contexte, c'est très simple. Je veux créer dynamiquement, sur une forme, une centaine d'objets (qui ne seront pas des boutons, mais plutôt des frames capables "d'accueillir" une image (donc soit des contrôles image ou des contrôles label ). J'ai besoin que ces contrôles soient clickables, et pouvoir récupérer le nom du contrôle qui a déclenché le click.
Voilà, c'est simple. La solution proposée par Patricktoulon fonctionne parfaitement, même si j'ai un peu peur des performances (comme je lui ai indiqué). Donc si on peut faire autrement, pourquoi pas ? Je garderai la solution qui me paraitra la plus efficace.
Merci d'avance
 

patricktoulon

XLDnaute Barbatruc
re

de 100 boutons, j'étais un peu inquiet pour les performances du fait de la création d'une centaine d'instances de la forme initiale.

ne prends pas pour argent comptant ce que crois @Dranreb tu ne crée pas 100 instances de la forme
tu crée 100 instance du module classe "FormJeu" qui est le MODULE!!! du userform PAS L OBJECT USERFORM
POUR CREER 100 INSTANCES DE L OBJECT USERFORM IL FAUDRAIT LEUR FAIRE UN SHOW POUR QU EN INTERNE VBA créee et AFFICHE LE USERFORM

UN MODULE USERFORM N EST QU UN MODULE (CLASSE )A LA BASE
pour t'en convaincre fait ce test

un fichier vierge
un userform nommé "userform1" et un module

dans le userform colle
Code:
public function quoi()
quoi="chocolat"
end function

maintenant dans le module tu met
Code:
sub test()
msgbox "j'aime le " & UserForm1.quoi
end sub
a aucun moment l’instance d'un handle !!!!d'une fenêtre userform sera créée
;)

 

Dranreb

XLDnaute Barbatruc
Je suppose que c'est parce que l'objet de nom CtrlName n'existe pas dans le VBComponent de l'UserForm.
Forcément puisqu'il était censé être créé à l'exécution, ce qui est beaucoup trop tard. Voir mes explications au poste #10.
La 1ère solution de @patricktoulon revienait bien à créer autant d'exemplaire de l'UserForm, avec leurs propres collections Controls, et autres sans doute, qu'il y a de CommandButton à traiter dans celui affiché. Seule la programmation du module de l'UserForm restait commune à tous ces exemplaires.
 
Dernière édition:

Dranreb

XLDnaute Barbatruc
Je n'ai jamais utilisé de module de classe, et je ne sais pas à quoi cela sert, mais je ne demande pas mieux que d'apprendre
Un module de classe sert à définir un type d'objet personnalisé, avec ses propriétés, méthodes et évènements. Les exemplaires qui en sont ensuite créés à l'aide du mot clé New ne contiennent que des données. On dit parfois ainsi que les données sont propre à l'exemplaire tandis que les méthodes sont propre au type. Pour les propriétés, leurs noms sont propres au type mais leurs valeurs sont propres à l'exemplaire. Mais toutes les données sauf les variables Static sont propres à l'exemplaire, y compris le contenu des variables Private du module de classe.
 
Dernière édition:

patricktoulon

XLDnaute Barbatruc
re
La solution de @patricktoulon revient bien à créer autant d'exemplaire de l'UserForm, avec leurs propres collections Controls, et autres sans doute, qu'il y a de CommandButton à traiter dans celui affiché. Seule la programmation du module de l'UserForm reste commune à tous ces exemplaires.
dis comme ça c'est faux car c'est mal interprété

d'ailleurs un module classe control c'est
La solution de @Dranreb revient bien à créer autant d'exemplaire du module classe, avec leurs propres collections Controls, et autres sans doute, qu'il y a de CommandButton à traiter dans celui affiché. Seule la programmation du module de la classe reste commune à tous ces exemplaires.
car les object mappés dans la classes sont mappés avec "set" qui instancie un object , de la même manière que je le fait dans le userform OU!! que l'userform le fait en interne lorsque les objects sont ajoutés mano mano dans VBE
:rolleyes:

je répète ne pas confondre userform1 qui est un module et userform1 qui a un contexte(handle) a partir du moment ou on appelle la fonction show dans le module classe(userform1) ou dans un autre module

d'ailleurs nous retrouvons les fonctions de bases que l'on a dans un module classe
initialise,terminate,etc...etc....

d'ailleurs essayez donc d'ajouter un object userform ( ca n'existe pas !!!)on ajoute un vbcomponents(type 3)
et on l'ajoute a la collection vba pour le manipuler comme un userform ,sans ça ;ça ne fonctionne pas


et puisque ca rentre toujours pas
voila une démo qui est plus que parlante
ajouter dans un projet vierge
1 userform1
2 userform2
1 module


dans le module!!!
VB:
Sub test()
    Dim forme, I&, B
    With UserForm1
        For I = 1 To 3
            Set B = .Controls.Add("Forms.CommandButton.1")
            B.Name = "bouton" & I
            B.Caption = B.Name
            B.Top = (20 * (I - 1)) + (10 * I)
        Next
    .Show 0
    End With
End Sub

dans le userform1!!!
VB:
Dim cls() As New UserForm2

Private Sub UserForm_Activate()
    For Each ctrl In Me.Controls
        If ctrl.Name Like "bouton*" Then I = I + 1
        ReDim Preserve cls(1 To I): Set cls(I).bouton = ctrl
        MsgBox "prise en charge du bouton""" & ctrl.Name & """"
        Next
End Sub

et enfin dans le userform2
VB:
Public WithEvents bouton As MSForms.CommandButton

Private Sub bouton_Click()
MsgBox "vous avez cliqué sur le bouton """ & bouton.Name & """"
End Sub
et voila vos boutons sont mappés dans le userform2 et on ne vois pas de userform2 à l’écran
les boutons cliqués dans le userform1 vont répondre

d'ailleurs pour avoir testé sauf oublie de ma part , je pense que l'on peut tout mapper dans un usf
d'ailleurs dans une de mes ressources je map l'event right clik des feuilles excel pour l'auto positionnement de l'usf sur la cellule cliqué
 

Pièces jointes

  • truc rigolo.xlsm
    15.3 KB · Affichages: 5
Dernière édition:

CC76

XLDnaute Nouveau
Bonjour @CC76
Pour résumer le principe: Il est tout à fait possible de rendre opérationnels des CommandButton créés dynamiquement dans un UserForm chargé, mais seulement par du code qui a déjà été prévu pour cela, et dont la résidence normale est un module de classe. Je pourrais vous indiquer la meilleure façon de l'écrire si vous m'en disiez davantage sur ce que vous voudriez. Dans l'UserForm, vous auriez une procédure unique pour tous les CommandButton créés dynamiquement, qui s'exécuterait quand on clique sur l'un d'eux. À vous de me dire quel(s) argument(s) vous aimeriez qu'elle transmette pour vous permettre de savoir ce qu'il y a lieu d'y exécuter, selon celui qui aura été actionné.
Le mieux ce serait même de joindre un fichier montrant d'avantage le contexte.
Merci pour cette proposition, mais je ne connais pas les modules de classe, je ne m'en suis jamais servi. Mais je ne demande pas mieux que d'apprendre... Ce dont j'ai besoin est très simple : Je veux créer dynamiquement une centaine d'objets clickables sur une forme (des objets pouvant héberger une image, donc plutôt des contrôles "image" ou "label"). Et quand l'utilisateur clique dessus, je veux pouvoir récupérer le nom de l'objet.
C'est tout ....
Merci d'avance
 

Dranreb

XLDnaute Barbatruc
@patricktoulon Par définition un exemplaire d'objet n'est pas un module, c'est seulement le jeu de données des valeurs qui lui sont propres, mais celles de tout ce qui y est prévu, même si ça ne sert pas.
@CC76 Le récupérer où ? Joignez donc un fichier, qu'on ait une base pour vous proposer quelque chose. En attendant j'ai un module de classe prévu pour supporter une image, mais il est probablement plus compliqué que ne le nécessite vos besoins. Il s'appelle CAssoImg. Le voici quand même :
VB:
Option Explicit
Implements CAsso
Private Parent As ControlsAssociés, WithEvents Img As MSForms.Image, Index As Long, _
   Colonne As Long, Dossier As String, UsMode, RéfFic As String, Conteneur As Object
Private Sub CAsso_Init(ByVal Lui As ControlsAssociés, ByVal Ctl As MSForms.IControl, ByVal Idx As Long, _
   ByVal Col As Long, ByVal Fmt As String, ByVal Mode As Variant)
   Dim ImOBn As MSForms.OptionButton, CAssoOBn As CAssoOBn
   Set Parent = Lui: Set Img = Ctl: Index = Idx: Colonne = Col: UsMode = Mode
   Dossier = Fmt
   Set Conteneur = Ctl.Parent
   End Sub
Private Function CAsso_Ctl() As MSForms.IControl
   Set CAsso_Ctl = Img
   End Function
Private Function CAsso_Index() As Long
   CAsso_Index = Index
   End Function
Private Function CAsso_Col() As Long
   CAsso_Col = Colonne
   End Function
Private Function CAsso_Format() As String
   CAsso_Format = Dossier
   End Function
Private Function CAsso_Mode() As Variant
   CAsso_Mode = UsMode
   End Function
Private Property Let CAsso_Valeur(ByVal RHS As Variant)
   On Error Resume Next
   If Dossier <> "" And RHS <> "" Then
      Img.Picture = LoadPicture(Dossier & "\" & RHS)
   Else
      Img.Picture = LoadPicture(RHS)
      End If
   If Err = 0 Then RéfFic = RHS Else MsgBox RHS & vbLf & Err.Description, vbExclamation, "Image"
   Conteneur.Repaint
   End Property
Private Property Get CAsso_Valeur() As Variant
   CAsso_Valeur = RéfFic
   End Property
Private Sub Img_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
   Dim GOFn
   If Button = 1 Then
      If Dossier <> "" Then ChDrive Dossier: ChDir Dossier
      GOFn = Application.GetOpenFilename("Image,*.jpg;*.gif;*.bmp")
      If VarType(GOFn) <> vbString Then Exit Sub
      If Dossier <> "" Then If Left$(GOFn, Len(Dossier) + 1) <> Dossier & "\" Then MsgBox _
         "Image non rattachée au dossier :" & vbLf & Dossier, vbCritical, "Image" _
         Else CAsso_Valeur = Mid$(GOFn, Len(Dossier) + 2) Else CAsso_Valeur = GOFn
   ElseIf Button = 2 Then
      If CAsso_Valeur = "" Then Exit Sub
      If MsgBox("Voulez-vous effacer cette image ?", vbYesNo, "Clic droit") = vbNo Then Exit Sub
      CAsso_Valeur = ""
      End If
   Parent.CAM_Change Me
   End Sub
Le bout de code qui en crée un exemplaire est dans la méthode Add d'un autre module de classe ControlsAssociés :
Code:
Public Sub Add(ByVal Ctl As MSForms.Control, Optional ByVal Colonne As Variant = -1, _
   Optional ByVal Format As String = "", Optional ByVal Mode = Empty)
' — Arguments :
'  Ctl :  Le contrôle à ajouter.
'  Colonne :  La colonne dans le tableau.
'  Format :  Le format de conversion de la valeur en le texte affiché dans le contrôle.
'  Mode :  Ce que vous aimeriez retrouver en CAM.Mode pour orienter un traitement d'évènement.
   Dim I As Long
   CorrigerColonne Colonne, "Add Me." & Ctl.Name
   I = Me.Count + 1: ReDim Preserve TCAssos(1 To I) As CAsso
   Select Case True
      Case TypeOf Ctl Is MSForms.TextBox:    Set TCAssos(I) = New CAssoTBx
      Case TypeOf Ctl Is MSForms.ComboBox:   Set TCAssos(I) = New CAssoCBx
      Case TypeOf Ctl Is MSForms.OptionButton: MsgBox "Contrôle de type ""OptionButton"" non supporté," _
         & vbLf & "mais un ""Frame"" en contenenant serait accepté.", _
         vbCritical, "ControlsAssociés Add Me." & Ctl.Name: End
      Case TypeOf Ctl Is MSForms.CheckBox:   Set TCAssos(I) = New CassoCkx
      Case TypeOf Ctl Is MSForms.Frame:      Set TCAssos(I) = New CAssoFrm
      Case TypeOf Ctl Is MSForms.Image:      Set TCAssos(I) = New CAssoImg
      Case Else: MsgBox "Contrôle de type """ & TypeName(Ctl) & """ non supporté actuellement", _
         vbCritical, "ControlsAssociés Add Me." & Ctl.Name: End: End Select
   TCAssos(I).Init Me, Ctl, I, Colonne, Format, Mode
   End Sub
 

CC76

XLDnaute Nouveau
Merci,
J'ai fini, avec quelques difficultés je l'avoue, de comprendre votre code, et j'ai pas mal de questions. Trop pour les poser à cet endroit, d'autant plus qu'on s'éloigne de la question initiale. Comment puis-je vous les communiquer ?
Merci encore
 

CC76

XLDnaute Nouveau
Merci,
J'ai fini, avec quelques difficultés je l'avoue, de comprendre votre code, et j'ai pas mal de questions. Trop pour les poser à cet endroit, d'autant plus qu'on s'éloigne de la question initiale. Comment puis-je vous les communiquer ?
Merci encore

Non, ça ne peut pas être trop, parce qu'il n'y a pas tant de choses à savoir sur le principe des objets VBA
Posez vos question ici, l'une après l'autre.
Ok, voir l'onglet Questions du fichier ci-joint
Cordialement
 

Pièces jointes

  • ExemplesControles.xlsm
    31.7 KB · Affichages: 7

Dranreb

XLDnaute Barbatruc
La collection Cln sert à conserver en mémoire les exemplaires de SupportImage créés.
Une Public Sub dans un module de classe, ça s'appelle une méthode, pour qu'on se souvienne qu'elle ne peut être invoquée sans être qualifiée de l'objet qui la possède.
La méthode Init, donc, sert simplement à initialiser les objets propre à l'exemplaire, l'objet parent pour pouvoir invoquer celle de ses méthodes qui prendra en charge le clic, et l'image supportée.
Avoir renommé MaFormeVirtuelle le SupportsImages, je veux bien, mais ça ne désigne plus du tout ce que ça représente, à savoir une collection d'éléments SupportImage.
Et MéthodeRéservéeÀSupportImage avait le mérite d'indiquer qu'elle ne devait pas être employée par le programmeur d'application qui l'utilise, puis qu'elle était à usage exclusif du module de classe SupportImage. (Je considère un peu qu'on n'est plus tout à fait programmeur d'application lorsqu'on se met à concevoir un module de classe car c'est à un niveau d'abstraction au dessus)
Sans entrer dans les détails, puisque vous parlez de mécanisme, une variable objet n'est qu'un pointeur, c'est à dire qu'il contient simplement l'adresse d'un de ses exemplaires, et l'instruction Set permet de l'initialiser.
 
Dernière édition:

Discussions similaires

F
Réponses
7
Affichages
4 K
E

Statistiques des forums

Discussions
314 635
Messages
2 111 452
Membres
111 144
dernier inscrit
shura_77