2
votes

Comment faire attendre le code VBA jusqu'à ce que le formulaire utilisateur vbModeless soit fermé

Je voudrais utiliser un formulaire utilisateur informel pour que l'utilisateur puisse naviguer dans la feuille Excel avant de répondre à la question sur le formulaire utilisateur. Je dois mettre le code en pause ou en boucle jusqu'à ce que le formulaire utilisateur soit fermé (masqué ou déchargé).

Problème similaire à celui-ci: Comment puis-je attendre qu'un code spécifique s'exécute lorsque le formulaire est fermé et défini sur vbModeless? mais la solution ici ne fonctionne pas pour mon application; Mon formulaire utilisateur est ouvert au milieu d'un long sous-programme qui doit terminer son exécution après la fermeture du formulaire utilisateur.

Dim popupActive as Boolean

popupActive = True
StartingSINT_Popup.Show vbModeless 'Open userform

'have VBA code wait until userform is closed
wait until popupActive = False 'set to false with OK button on userform

'continue code with info input inside StartingSINT_Popup userform


0 commentaires

3 Réponses :


2
votes

Je n'ai pas créé la fonction suivante, mais je l'utilise depuis longtemps et cela fonctionne.

Do While IsLoaded("StartingSINT_Popup")
    Debug.Print Time; " StartingSINT_Popup Is Loaded!"
Loop

Vous devrez coder en dur le nom de la chaîne et ne pas utiliser le .Name du formulaire car le formulaire n'est peut-être pas encore chargé et ne contient pas cette propriété.

Voici un petit extrait de la façon dont vous pouvez utiliser cette fonction:

Private Function IsLoaded(ByVal formName As String) As Boolean
    Dim frm As Object
    For Each frm In VBA.UserForms
        If frm.Name = formName Then
            IsLoaded = True
            Exit Function
        End If
    Next frm
    IsLoaded = False
End Function


4 commentaires

Cela plante VBA parce que (je pense) que la boucle while s'exécute si vite et qu'elle est toujours chargée.


Vous devrez probablement ajouter une ligne DoEvents dans la boucle et / ou un minuteur qui attend pour appeler la fonction.


@Nick FWIW En - attendre comme ça, c'est vraiment, vraiment étirer le paradigme procédural bien au-delà de ce qui est acceptable IMO.


@Nick - Pour info, je recommande de lire la rubrique associée détruire correctement une instance de formulaire utilisateur non modale basé sur la vue d'ensemble exceptionnelle de Mathieu "UserForm1.Show?"



5
votes

Mon formulaire utilisateur est ouvert au milieu d'un long sous-programme qui doit finir de s'exécuter après la fermeture du formulaire utilisateur.

Votre procédure fait trop de choses et doit être décomposée en procédures plus petites et plus spécialisées.

La bonne façon de procéder est de changer le paradigme de procédural à event-driven

Au lieu d'afficher l'instance par défaut du formulaire comme ceci:

Private presenter As PopupPresenter

Public Sub DoStuff()
    Set presenter = New PopupPresenter

    'do stuff...

    presenter.Show
    presenter.Foo = "some data"

    'rest of the code in this scope will run immediately AND THIS IS FINE

End Sub
Avoir un module de classe qui contient une instance WithEvent de celui-ci:
Private WithEvents popup As StartingSINT_Popup
Public Foo As String

Private Sub Class_Initialize()
    Set popup = New StartingSINT_Popup
End Sub

Public Sub Show()
    popup.Show vbModeless
End Sub

Private Sub popup_Closed()
    ' code to run when the form is closed
    MsgBox Foo
End Sub

Dans le code-behind du formulaire, déclarez un Closed événement:

Private presenter As PopupPresenter

Public Sub DoStuff()
    Set presenter = New PopupPresenter

    'do stuff...

    presenter.Show

    'rest of the code in this scope will run immediately AND THIS IS FINE

End Sub

Et puis le déclencher dans le gestionnaire QueryClose :

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = 0 Then 'controlbox was clicked (the "red X button")
        Cancel = True 'would otherwise destroy the form instance
        Me.Hide 'always hide, never unload
    End If
    RaiseEvent Closed
End Sub

Supposons maintenant que vous ayez nommé cette classe PopupPresenter , votre procédure peut maintenant faire ceci:

Public Event Closed()

Gardez le présentateur au niveau du module afin que l'objet ne fonctionne pas t sortir de la portée lorsque DoStuff se termine, et transmettre toutes les variables / valeurs ou indiquer que l'objet présentateur doit faire son travail lorsque le formulaire est fermé. Vous pouvez le faire en exposant des propriétés ou des champs / variables publics (préférez les propriétés, mais c'est un tout autre sujet):

Private WithEvents popup As StartingSINT_Popup

Private Sub Class_Initialize()
    Set popup = New StartingSINT_Popup
End Sub

Public Sub Show()
    popup.Show vbModeless
End Sub

Private Sub popup_Closed()
    ' code to run when the form is closed
End Sub
StartingSINT_Popup.Show vbModeless 'Open userform

6 commentaires

Bien dit. Pourrait devoir remplacer ma fonction!


Comment pouvons-nous déclencher un événement après l'exécution de presenter.Show avec cette approche? Actuellement, UserForm_Activate ne fait rien, cherchant principalement à charger un modèle dans le formulaire


@Jose pas sûr que je suis. Si vous avez une fenêtre contextuelle Private WithEvents As SomeForm , le gestionnaire d'activation serait popup_Activate - vous ne devriez jamais taper les signatures du gestionnaire d'événements à la main ... liste déroulante en haut à gauche, puis sélectionnez l'événement que vous souhaitez gérer dans la liste déroulante en haut à droite. Consultez ce message (et l'article lié) pour plus d'informations sur Model-View-Presenter .


@MathieuGuindon Merci d'avoir fourni cette clarté. Que faire si j'ai du code dans mon UserForm qui définit le dimensionnement du formulaire et charge les valeurs de Model dans ComboBoxes , comment puis-je faire exécuter ce code ? Dois-je déclarer un Public Sub dans le UserForm et appeler le Sub via l'événement popup_Activate ?


J'aurais un Public Property Set Model (ByVal value As Object) et demanderais à la propriété du présentateur d'injecter le modèle là-bas. La propriété a tous les droits pour faire quelque chose comme Set viewModel = value , puis pour appeler une procédure InitializeView avant de retourner, où des choses comme Me.NameBox.Text = viewModel .Name se produit. Pendant ce temps, le gestionnaire NameBox_Change effectue viewModel.Name = Me.NameBox.Text et appelle éventuellement la validation du modèle (qui peut alors déterminer si le bouton OK est activé).


@MathieuGuindon merci !! Cela a du sens! J'apprends toujours MVP et OOP grâce à votre blog RubberDuck! J'étais la même personne qui a commenté votre message CarFactory il y a 2 semaines. Merci pour tout ce que vous faites pour nous!



0
votes

Voici une alternative ...

1. Dans le module [public] d'origine (celui qui appelle userform 1), déclarez une variable booléenne publique.

Public done As Boolean

2. Dans le formulaire utilisateur 1,

a. Attribuez une valeur par défaut à la variable booléenne

b. Appelez Userform 2

c. Faites une boucle while qui ...

  • vérifie cette valeur par défaut
  • inclut la méthode DoEvents (permet à userform 2 de passer malgré la boucle)

Code

    Private Sub submit_Click()

    'Userform submit code
    Dim name As String        
    name = TextBox.Value
    sql = "INSERT INTO table (field) VALUES ('" & name & "')"        
    Call query(sql)

    'IMPORTANT: change Boolean variable to break loop before exiting userform
    done = True

    Unload Me

    End Sub

3. Dans userform 2 , changez la valeur de Boolean pour casser la boucle

Code

    Private Sub event_click()

    done = False

    Dim userform2 As New userform
    userform2.Show Modeless

    'This will loop through until userform2 changes done variable to "True"
    Do While done = False
    DoEvents
    Loop

    'Code after done with userform2
    dataSource.Refresh

    End Sub


0 commentaires