【问题标题】:Userform resilient to Unload call用户表单对卸载调用具有弹性
【发布时间】:2020-08-25 23:39:02
【问题描述】:

在显示另一个之前尝试Unload Useforms,我遇到了一个奇怪的问题。
我的工作表是由大师UserForm组成的,叫UFMainMenu,可以调用别人。当主控调用用户窗体时,它会在主用户窗体代码中使用类似这样的内容进行隐藏:

Private Sub BtnSupprimerValeur_Click()
    Me.Hide
    UFSupInstanceParametre.Show
End Sub

这按预期工作,并打开UFSupInstanceParametre
UFSupInstanceParametre 内部,如果用户关闭它,代码Unload UFSupInstanceParametre 并启动我的主人UserForm

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = vbFormControlMenu Then
        Unload Me
        Call mainMenu
    End If
End Sub

mainMenu 就是这个简单的子菜单:

Sub mainMenu()
    Application.ScreenUpdating = True
    
    Dim objLoop As Object
    'The following loop is just for me to see if previous userform was indeed unloaded, and if not it unloads it again
    For Each objLoop In VBA.UserForms
        If objLoop.Name <> "UFMainMenu" Then
            Unload objLoop
        End If
    Next objLoop
    
    UFMainMenu.Show
    
End Sub

问题是:
-UFSupInstanceParametre 中的Unload Me 并没有真正卸载UFSupInstanceParametre,因为它仍然出现在屏幕上。
- 在 mainMenu 的 For 循环内,当逐步运行时,我看到 UFSupInstanceParametre 没有被卸载,我确认代码然后运行 ​​Unload objLoop 行,无论如何都应该卸载它。它没有,UFSupInstanceParametre 仍在屏幕上。为了 100% 确定它没有被卸载,我尝试第二次运行 For 循环,它再次发现 UFSupInstanceParametre 仍然加载,并再次尝试使用 Unload objLoop 行卸载它,但没有成功。 -代码不会抛出错误,它只是不会卸载任何用户表单。 - 代码尝试在 mainMenu 中卸载其他用户表单后按预期启动主用户表单,但另一个用户表单仍然可见。

我不得不承认我对这个有点迷失了,我知道我只缺少一个关于 UserForms 的简单属性,但我找不到它。

有什么想法吗?

【问题讨论】:

  • 您真的需要卸载表单吗?您是否了解后果(破坏对象,立即重新创建的默认实例的问题......)?尝试将表单设置为隐藏(Me.HideobjLoop.Hide)。并阅读rubberduckvba.wordpress.com/2017/10/25/userform1-show
  • 我需要卸载它,是的,因为它们需要使用更新的数据和条件重新初始化,所以只需隐藏它并再次显示它就会让它看起来回到以前的状态,我不能这样。我可以使用隐藏和显示,并在取消隐藏时将用户窗体重置为其原始状态,但在我的情况下,卸载它并从头开始会更有效
  • 我必须补充一点,从主用户表单启动的每个用户表单并不打算一次又一次地启动,但仅在某些情况下用户可能想要重新执行操作。我对将它们保存在内存中没有太大兴趣
  • Unload Me 总是一个可怕的想法——你在其代码仍在运行时销毁一个对象。你确定你总是看到同一个实例吗?我假设你每次都看到新创建的默认实例。无论如何,为什么不将填写表单的代码移动到一个 sub 并从 Activate 而不是 Initialize.event 调用它?不是在谈论数据由控制器而不是视图控制的 MVC 系统
  • 我会尝试,但是关于 Unload Me,我在看到 For Loop 无法卸载每个 UF 后添加了这些,以检查它是否可以工作。我会选择Activate 事件,即使这意味着添加的代码比我预期的要多,因为它必须让 UF 从其界面中清除一些东西。尽管如此,我还是觉得这样做有点想躲避问题,我真的不明白为什么它不会卸载它们。

标签: excel vba


【解决方案1】:

我无法详细解释您代码的每一个行为,但主要问题在于您通过显示主表单中断了UserForm_QueryClose 中子表单的代码。 VBA 是单线程的,在您显示表单模式的那一刻,例程的代码被中断,显示表单并执行后面的代码(例如激活触发器)。

一旦您的主窗体再次隐藏,子窗体的中断QueryClose-code 将继续。您需要知道的是,每当表单即将被销毁时都会调用 queryClose-Trigger - 如果用户按下系统关闭按钮(红色“X”按钮)或代码中的某处 @ 987654324@ 语句被执行。 (除非你设置了参数Cancel = True)。

现在,如果您在 queryClose-Trigger 中发出 Unload-statement 会发生什么?我不太清楚,但运行时似乎知道您已经处于 Unload-process 的中间并忽略了下一次尝试 - 这解释了为什么您永远不会摆脱 mainmenu-code 中的实例.

显示表单vbmodeless 会导致完全不同的行为这一事实很容易解释:显示表单的代码(仍在谈论您的queryClose-例程)继续运行,因为显示表单非模态不会不要打断代码。

那么如何解决这个烂摊子:

您需要实现的是一种显示/隐藏表单的控制器实例。不要让表格变得“聪明”,摧毁自己或知道接下来要显示哪种表格不是他们的工作——这是控制器的工作。控制器的工作也是决定是否销毁任何表单对象。

一个快速的解决方案是让主窗体成为控制器。这肯定不是最佳做法,但很容易实施。
在主窗体中,您将代码显示为这样的子窗体(由按钮或任何其他事件触发)

Sub showChildform()
    Me.Hide
    UFMainMenu.Show
    Me.Show
End Sub

就是这样。

UFMainMenu中,可以去掉UserForm_QueryClose的完整代码。现在您唯一需要记住的是如何关闭您的子表单。如果您只有系统关闭按钮,则无需执行任何操作 - 请记住,按下该按钮会破坏表单。但是,如果您添加一个按钮,如取消、关闭、确定或其他,您需要按代码“离开”表单。选项为UnloadHideUnload 是,如前所述,非常糟糕的做法,在按钮的触发代码中使用语句 Me.Hide

但这会让您处于表单可以保留两种不同状态的情况:隐藏或销毁。如果要保持一致,将以下代码放入UserForm_QueryClose-Trigger,当系统关闭按钮按下时,它会隐藏表单但取消销毁。为此,它检查参数CloseMode:如果它的值为vbFormControlMenu,则用户按下关闭(有other cases 调用触发器)

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = vbFormControlMenu Then
        Cancel = True
        Me.Hide
    End If
End Sub 

现在,为什么表单会自我毁灭被认为是不好的做法?简短的回答:这不是表单(或任何其他对象)的任务。它会使引用无效,并且调用代码不会知道。如果表单被按下 OK 或 Cancel 按钮,调用代码无法检查在表单上所做的任何事情。我可以更详细地解释这一点,但上面提到的 Rubberduck 文章已经很好地解释了。

【讨论】:

  • 我明白了。感谢您的解释,现在有点道理。太糟糕了 VBA 只是单线程的,我猜是因为它是一种旧语言,在他们设计它的时候可能没有用。
猜你喜欢
  • 2020-02-11
  • 2011-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多