【问题标题】:How to have VBA code wait until vbModeless userform is closed如何让 VBA 代码等到 vbModeless 用户窗体关闭
【发布时间】:2023-04-05 19:20:01
【问题描述】:

我想使用无表单的用户表单,以便用户可以在回答用户表单上的问题之前浏览 Excel 工作表。我需要暂停或循环代码,直到用户窗体关闭(隐藏或卸载)。

与此类似的问题: How can I wait for a specific code to run while when form is closed and is set to vbModeless? 但这里的解决方案不适用于我的应用程序;我的用户表单是在一个长子程序的中间打开的,该子程序需要在用户表单关闭后完成执行。

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

【问题讨论】:

    标签: excel vba userform


    【解决方案1】:

    我的用户窗体是在一个较长的子例程中间打开的,该子例程需要在用户窗体关闭后完成执行。

    您的程序做的事情太多,需要分解成更小、更专业的程序。

    做到这一点的正确方法是将范式从过程转变为事件驱动

    而不是像这样显示表单的默认实例:

    StartingSINT_Popup.Show vbModeless 'Open userform
    

    拥有一个包含 WithEvent 实例的类模块:

    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
    

    在表单的代码隐藏中,声明一个Closed 事件:

    Public Event Closed()
    

    然后在 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
    

    现在假设您将该类命名为 PopupPresenter,您的程序现在可以这样做:

    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
    

    将演示者保持在模块级别,以便在DoStuff 完成时对象不会超出范围,并在表单关闭时传递演示者对象需要完成其工作的任何变量/值或状态。您可以通过公开属性或公共字段/变量来做到这一点(虽然更喜欢属性,但这是另一个话题):

    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
    
    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
    

    【讨论】:

    • 说得好。可能需要替换我的功能!
    • presenter.Show 使用这种方法执行后如何触发事件?目前 UserForm_Activate 什么都不做,主要是将模型加载到表单中
    • 我会有一个Public Property Set Model(ByVal value As Object) 并让演示者属性在那里注入模型。该属性有权执行Set viewModel = value 之类的操作,然后在返回之前调用一些InitializeView 过程,其中会发生Me.NameBox.Text = viewModel.Name 之类的事情。同时NameBox_Change 处理程序执行viewModel.Name = Me.NameBox.Text,并可选地调用模型验证(然后可以确定是否启用了确定按钮)。
    • @MathieuGuindon 谢谢!!这完全有道理!感谢您的 RubberDuck 博客,我仍在学习 MVP 和 OOP!我和两周前评论你的 CarFactory 帖子的人是同一个人。感谢您为我们所做的一切!
    • 我花了一分钟的时间来“概括”这一点,我希望对其进行功能化,以便为多个用户表单使用相同的代码。肯定还在学习,但我意识到这是要走的路。我尝试使用布尔值,这简直是一场噩梦。我有用户表单,调用用户表单,然后我需要多层布尔值......只是感觉不对。这是我的问题,其中包含此答案中的通用代码。谢谢! __> stackoverflow.com/questions/66113380/…
    【解决方案2】:

    下面这个函数不是我自己创作的,但是我用了很久了,还是可以的。

    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
    

    您需要对字符串名称进行硬编码,并且不要使用表单的 .Name 属性,因为该表单可能尚未加载且不包含此属性。

    这里有一个小sn-p,告诉你如何使用这个函数:

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

    【讨论】:

    • 这会使 VBA 崩溃,因为(我认为)while 循环执行得如此之快,并且总是看到它仍在加载。
    • 您可能还需要在循环中添加 DoEvents 行和/或等待调用该函数的计时器。
    • @Nick FWIW While-像这样等待真的,真的将程序范式远远超出了 IMO 可接受的范围。
    • @Nick - 仅供参考,我建议阅读相关主题 destroy a modeless userform instance properly 基于 Mathieu 的出色概述 "UserForm1.Show?"
    【解决方案3】:

    这是另一种选择......

    1.在原始的 [public] 模块(调用 userform 1 的那个)中,声明一个公共布尔变量。

    Public done As Boolean

    2。在用户表单 1 中,

    一个。为布尔变量分配默认值

    b.调用用户表单 2

    c。做一个while循环...

    • 检查该默认值
    • 包括 DoEvents 方法(允许用户窗体 2 推送尽管有循环)

    代码

        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
    

    3.在用户窗体 2 中,将布尔值更改为中断循环

    代码

        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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-14
      • 1970-01-01
      • 2017-11-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多