当用户取消时,如何优雅地从嵌套子例程的中间退出?

时间:2009-02-25 18:28:23

标签: vb6

(我正在使用VB6,但我想这会出现在大多数其他语言中。)

我有一个GUI按钮,调用一个需要一两分钟才能完成的例程。我希望不耐烦的用户能够第二次点击按钮,让它在任何时候都能优雅地退出例程。

我使用了一个静态变量来使其工作得很好(参见下面的代码),但是我正在清理项目,我想将For / Next循环放入它自己的函数中,因为它需要在几个不同的地方在项目中。

但这样做会破坏嵌入在for / next中的静态标志,所以我需要进行一些更改。在我做一些公共(全局)变量的事情之前,我想我会问其他人(更聪明,也许实际上是CS受过教育的)人们在面对这个问题时所做的事情。

所以基本上我的问题是如何复制这个:

Private Sub DoSomething_Click()

  Static ExitThisSub As Boolean ' Needed for graceful exit

  If DoSomething.Caption = "Click To Stop Doing Something" Then
    ExitThisSub = False ' this is the first time we've entered this sub
  Else ' We've re-entered this routine (user clicked on button to stop it)
    ExitThisSub = True ' Set this so we'll see it when we exit this re-entry
    Exit Sub '
  End If


  DoSomething.Caption = "Click To Stop Doing Something"

  For i = 0 To ReallyBigNumber
    Call DoingSomethingSomewhatTimeConsuming
    If ExitThisSub = True Then GoTo ExitThisSubNow
    DoEvents
  Next

  ' The next line was missing from my original example,
  ' prompting appropriate comments
  DoSomething.Caption = "Click To Do Something"

  Exit Sub

ExitThisSubNow:

  ExitThisSub = False ' clear this so we can reenter later
  DoSomething.Caption = "Click To Do Something"

End Sub

当我将for / next循环移动到它自己的函数时?

我想我会将ExitThisSub更改为公共变量QuitDoingSoManyLongCalculations,它将以相同的方式退出new for / next sub,然后退出DoSomething_Click。

但是当我使用全局变量时,我总觉得自己是业余爱好者(我是) - 有更优雅的解决方案吗?

6 个答案:

答案 0 :(得分:3)

您可以将表单中模块级别的变量声明为private。 这不是全局变量而是模块级变量。 然后你可以将它传递给你创建的函数并在函数中检查它。

但要小心DoEvents。它基本上意味着允许Windows消息循环处理消息。这意味着用户不仅可以再次单击您的按钮,还可以关闭表单并执行其他操作。所以当你在这个循环中时,你需要设置一个模块级变量,因为你需要在表单的QueryUnload和任何事件处理程序中检查它。

您还可以使用控件本身的Tag属性来存储排序标志。但我不认为那更优雅。

我也更喜欢使用两个不同的按钮。只需隐藏一个并显示另一个。这样,取消代码和运行代码在不同的事件处理程序中分开。

为了扩展我的答案,这里有一些处理卸载方面的示例代码。 如果您通过x停止,它会提示您。如果你通过任务管理器杀死它,它会优雅地死掉。

Option Explicit

Private Enum StopFlag
   NotSet = 0
   StopNow = 1
   StopExit = 2
End Enum

Private m_lngStopFlag As StopFlag
Private m_blnProcessing As Boolean

Private Sub cmdGo_Click()

   Dim lngIndex As Long
   Dim strTemp As String

   m_lngStopFlag = StopFlag.NotSet
   m_blnProcessing = True

   cmdStop.Visible = True
   cmdGo.Visible = False

   For lngIndex = 1 To 99999999

      ' check stop flag
      Select Case m_lngStopFlag

         Case StopFlag.StopNow

            MsgBox "Stopping - Last Number Was " & strTemp
            Exit For

         Case StopFlag.StopExit

            m_blnProcessing = False
            End

      End Select

      ' do your processing
      strTemp = CStr(lngIndex)

      ' let message loop process messages
      DoEvents

   Next lngIndex

   m_lngStopFlag = StopFlag.NotSet
   m_blnProcessing = False
   cmdGo.Visible = True
   cmdStop.Visible = False

End Sub

Private Sub cmdStop_Click()

   m_lngStopFlag = StopFlag.StopNow

End Sub

Private Sub Form_Load()

   m_blnProcessing = False

End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)

   Select Case UnloadMode

      Case vbFormControlMenu, vbFormCode

         If m_blnProcessing Then

            Cancel = True

            If MsgBox("Unload Attempted - Cancel Running Process?", vbOKCancel + vbDefaultButton1 + vbQuestion, "Test") = vbOK Then

               m_lngStopFlag = StopFlag.StopExit

            End If

         End If

      Case Else

         m_lngStopFlag = StopFlag.StopExit
         Cancel = True

   End Select

End Sub

答案 1 :(得分:2)

您需要某种共享变量,以便您的for循环和按钮可以进行通信。我将for循环(及其相关代码)放在命令对象中。我的VB很生疏,但我认为你可以使用自己的'全局'变量和函数声明模块。您可以将所有代码移动到模块中,只需像现在一样检查全局变量。

我对你发布的代码示例的主要关注与用户取消无关,而是与其他一切有关:你通过阅读按钮文本而不是反过来来检查你的运行状态(设置按钮文本)因为运行状态,应该存储在一个变量中);你使用GOTO来退出for循环而不是中断(VB有中断吗?)并且你把你的清理代码放在正常流程之外,而在我看来无论用户是否取消它都可以运行。

答案 2 :(得分:2)

一种可能的替代方法是将繁重的工作函数卸载到新的Thread。然后,如果用户想要取消,您可以直接杀死该线程,或者您可以向该线程发送消息。

通过按钮名称进行切换,正如您在上面所做的那样,这是一个非常常见的技巧,如果您只有几个按钮状态,那么非常安全。

答案 3 :(得分:2)

我总是使用像bUserPressedCancel这样的全局布尔变量,以及循环中的DoEvents。优雅,有气味,有效。

我同意Shiny先生的观点,即对标题的价值进行测试并不是一个好主意。如果更改设计器中按钮上的文本,则会破坏代码。最好不要依赖文本的措辞来使代码工作。

答案 4 :(得分:0)

一直工作,直到您需要进行本地化或任何其他需要独立于逻辑更改UI的内容。我会使用Tag属性或私有模块级变量。然后,您可以独立于逻辑更改标题。

答案 5 :(得分:0)

如果是我,我会使用2个按钮 - 一个用于GO,一个用于停止。单击“开始”时,“STOP”按钮可见。 STOP的Click事件只是隐藏自己 - 就是这样。

然后您的循环可以简单地检查STOP按钮是否仍然可见。如果不是,那就意味着它被点击了你应该突破。

您的控件是具有表单范围的静态对象...