(我正在使用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。
但是当我使用全局变量时,我总觉得自己是业余爱好者(我是) - 有更优雅的解决方案吗?
答案 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按钮是否仍然可见。如果不是,那就意味着它被点击了你应该突破。
您的控件是具有表单范围的静态对象...