Как изящно выйти из середины вложенной подпрограммы, когда пользователь отменяет? - PullRequest
0 голосов
/ 25 февраля 2009

(я использую VB6, но я думаю, что это встречается в большинстве других языков.)

У меня есть кнопка с графическим интерфейсом, которая вызывает процедуру, которая занимает минуту или две для завершения. Я хочу, чтобы нетерпеливые пользователи могли нажать на кнопку второй раз, чтобы она в любой момент изящно вышла из рутины.

Я использовал статическую переменную, чтобы сделать эту работу довольно хорошо (см. Код ниже), но я очищаю проект и хочу поместить цикл 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, которая будет выходить из новой подпрограммы for / next и затем DoSomething_Click таким же образом.

Но я всегда чувствую себя любителем (которым я являюсь), когда использую глобальные переменные - есть ли более элегантное решение?

Ответы [ 6 ]

3 голосов
/ 25 февраля 2009

Ну, вы можете объявить переменную на уровне модуля в формах как 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
2 голосов
/ 25 февраля 2009

Я всегда использовал глобальную логическую переменную, такую ​​как bUserPressedCancel, вместе с DoEvents внутри цикла. Элегантный, смелый, все работает.

Я согласен с г-ном Шини, что проверка на соответствие значению надписи не является хорошей идеей. Если вы измените текст на кнопке в конструкторе, вы нарушите код. Лучше не полагаться на формулировку текста для вашего кода.

2 голосов
/ 25 февраля 2009

Одна из возможных альтернатив - перенести вашу тяжелую работу в новый поток. Затем вы можете либо напрямую убить этот поток, если пользователь хочет отменить его, либо отправить сообщение в поток.

Переключение с помощью имени кнопки, как вы делаете выше, является довольно распространенным трюком, и довольно безопасным, если у вас есть только несколько состояний кнопки.

2 голосов
/ 25 февраля 2009

Вам нужна какая-то общая переменная, чтобы ваш цикл for и ваша кнопка могли обмениваться данными. Я бы поместил цикл for (и связанный с ним код) в объект команды. Мой VB ржавый, но я думаю, что вы можете объявить Модули с их собственными «глобальными» переменными и функциями. Вы можете переместить весь код в модуль и просто проверить глобальную переменную, как сейчас.

Моя основная проблема с примером кода, который вы разместили, не имеет ничего общего с отменой пользователя, а скорее со всем остальным: вы проверяете свое состояние работы, читая текст кнопки, а не наоборот (установите текст кнопки из-за запущенного состояния, которое должно храниться в переменной); вы используете GOTO для выхода из цикла for вместо перерыва (есть ли в VB разрывы?) и вы помещаете свой код очистки вне нормального потока, когда мне кажется, что он может быть запущен независимо от того, отменил ли пользователь или нет.

0 голосов
/ 28 февраля 2009

Если бы это был я, я бы использовал 2 кнопки - одну для перехода и одну для остановки. Кнопка STOP становится видимой, когда вы нажимаете GO. Событие Click для STOP просто скрывается - вот и все.

Ваша петля может просто проверить, видна ли кнопка СТОП. Если это не так, это означает, что на него нажали, и вы должны выйти.

Ваши элементы управления являются статическими объектами с областью формы ...

0 голосов
/ 28 февраля 2009

Работает до тех пор, пока вам не понадобится локализация или что-либо еще, что требует изменения пользовательского интерфейса независимо от логики. Я бы использовал свойство Tag или частную переменную уровня модуля. Тогда вы можете изменить заголовок независимо от логики.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...