Как изящно выйти из середины вложенной подпрограммы, когда пользователь отменяет?

(Я использую 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.

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


vb6
person Fred Hamilton    schedule 25.02.2009    source источник


Ответы (6)


Ну, вы можете объявить переменную на уровне модуля в формах как частную. Это не глобальная переменная, а переменная уровня модуля. Затем вы можете передать его создаваемой функции и проверить в этой функции.

Но будьте осторожны с 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
person Will Rickards    schedule 25.02.2009

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

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

person Mr. Shiny and New 安宇    schedule 25.02.2009
comment
Я знаю, что использование текста на кнопке сопряжено с определенным риском, но при правильном использовании добавляется больше кода. Я думаю, что лучшее из обоих миров - это определение текста кнопки в коде и его использование повсюду, поэтому, если он изменится; он везде меняется. - person Fred Hamilton; 25.02.2009
comment
Нет команды Break, о которой я знаю, но вы заставили меня поискать ее, и я должен использовать Exit For - спасибо за это. Код очистки идет после Exit Sub, поэтому его нельзя случайно запустить. Я сделал это так, потому что видел, как это делают другие - никогда не задумывался, идеально это было или нет. - person Fred Hamilton; 25.02.2009
comment
Вы также можете использовать свойство tag элемента управления. - person RS Conley; 26.02.2009
comment
Exit For и Exit DO - это два способа выхода VB из цикла. - person RS Conley; 26.02.2009
comment
@Fred: похоже, что в вашем примере этот код очистки должен запускаться после завершения цикла, несмотря ни на что, иначе текст кнопки не будет сброшен. И использование константы для хранения текста кнопки помогает, но я все же считаю, что шаблон в корне неверен. - person Mr. Shiny and New 安宇; 26.02.2009
comment
S: Когда аварийный выход не используется, подпрограмма Exit Sub под оператором Next выйдет из подпрограммы, и код очистки не будет затронут. Вы правы в том, что текст кнопки не сбрасывается при нормальной работе - я забыл включить это выражение, когда писал пример - будет отредактировать, чтобы исправить. - person Fred Hamilton; 27.02.2009

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

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

person Mike    schedule 25.02.2009
comment
Распределение потоков в VB6 очень сложно, если вы хотите делать что-либо, кроме чистых вычислений. - person Dan Byström; 25.02.2009
comment
Спасибо, что поддержали меня при переключении имени кнопки! :-) - person Fred Hamilton; 25.02.2009

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

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

person MikeW    schedule 25.02.2009

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

person RS Conley    schedule 28.02.2009

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

Затем ваш цикл может просто проверить, видна ли еще кнопка STOP. Если это не так, значит, он был нажат, и вам следует вырваться.

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

person Scott Evernden    schedule 28.02.2009