2009-02-25 4 views
0

(私はVB6を使用していますが、これは他のほとんどの言語で表示されると思います)ユーザーがキャンセルしたときにネストされたサブルーチンの途中から正常に終了するにはどうすればいいですか?

GUIボタンが表示され、1〜2分かかることがあります。私は、ちょっとしたユーザーがボタンを2回クリックして、いつでもルーチンから正常に退出できるようにしたいと思います。

私は静的変数を使ってこの作業をかなりうまく行いましたが(以下のコードを参照してください)、私はプロジェクトをクリーンアップしていますが、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ループ:

だから基本的に私の質問は、私はこれを複製行う方法ですか?

私はExitThisSubを新しいfor/next subとDoSomething_Clickを同じ方法で終了するパブリック変数QuitDoingSoManyLongCalculationsに変更すると考えています。

しかし、グローバル変数を使用すると、私はいつもアマチュアのように感じます。より洗練されたソリューションはありますか?

答えて

3

フォームのモジュールレベルで変数をプライベートとして宣言できます。 これはグローバルではなくモジュールレベルの変数です。 それから、作成した関数に関数を渡して関数でチェックすることができます。

DoEventsには注意してください。基本的には、Windowsメッセージループがメッセージを処理できるようにすることを意味します。これは、ユーザーがあなたのボタンを再びクリックできるだけでなく、フォームを閉じて他のことをすることができることを意味します。だから、このループに入っているときは、フォームのQueryUnloadとすべてのイベントハンドラでチェックする必要があるので、モジュールレベルの変数を設定する必要があります。

また、コントロールのタグプロパティを使用して、並べ替えのフラグを格納することもできます。しかし、私はそれをよりエレガントなものとは考えていません。

また、2つの異なるボタンを使用することをお勧めします。一方を隠し、もう一方を表示するだけです。こうすることで、キャンセルコードと実行コードが別々のイベントハンドラで分離されます。

ここで私の答えを拡張するには、アンロードの面を扱うサンプルコードがあります。 ここで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

forループとボタンが通信できるように、何らかの種類の共有変数が必要です。私はforループ(とその関連コード)をコマンドオブジェクトに入れます。私のVBは錆びていますが、私はあなた自身の 'グローバルな'変数と関数でモジュールを宣言できると思います。すべてのコードをモジュールに移動して、今度はグローバル変数をチェックするだけです。

あなたが掲示したコードサンプルの主な懸案事項は、ユーザーによるキャンセルとは関係がありませんが、それ以外のものは何もありません。ボタンのテキストを読む代わりに、テキストは実行状態のため、変数に格納する必要があります)。あなたはGOTOを使用して休憩の代わりにループを終了します(VBはブレークしますか?)そして、ユーザーがキャンセルしたかどうかにかかわらず実行できるように見えるときに、クリーンアップコードを通常のフローの外に置きます。

+0

...フォームのスコープを持つ静的オブジェクトでは、いくつかのリスクを紹介しますが、「正しい」方法でそれを行うと、より多くのコードを追加します。私は、両方の世界のベストは、ボタンのテキストをコード内で定義し、その全体を使っていると思います。それはどこでも変わります。 –

+0

私が気づいているブレイクコマンドはありませんが、あなたは私にそれを見させて、 "Exit For"を使用していなければなりません。 クリーンアップコードは「Exit Sub」の後に来るため、誤って実行することはできません。私はそれがそうしたのは、他の人がそれをやっているのを見たことがあるからです。それが理想的であるかどうかは考えられませんでした。 –

+0

コントロールのtagプロパティを使用することもできます。 –

2

重大な作業を新しいスレッドにオフロードする方法もあります。その後、ユーザーが取り消したい場合は、そのスレッドを直接削除するか、スレッドにメッセージを送信することができます。

上記のように、ボタン名を使って切り替えることはかなり一般的なトリックですが、ボタンの状態が2つしかない場合はかなり安全です。

+0

VB6でのスレッディングは、純粋な計算以外は何もしたくない場合は非常に複雑です。 –

+0

ボタン名の切り替えをサポートしてくれてありがとう! :-) –

2

私はいつもループ内でDoEventsと共にbUserPressedCancelのようなグローバルブール変数を使用してきました。エレガントで落ち着いた雰囲気です。

私はShiny氏に、キャプションの価値に対するテストは素晴らしい考えではないことに同意します。デザイナーのボタンのテキストを変更すると、コードが破損します。あなたのコードが動作するためには、テキストの文言に頼らないことをお勧めします。

0

ローカライズや、ロジックとは独立してUIを変更する必要があるときまで動作します。 Tagプロパティまたはプライベートモジュールレベルの変数を使用します。次に、論理とは別にキャプションを変更することができます。

0

私の場合は、2つのボタンを使用します.1つはGOに、もう1つはSTOPにします。 GOをクリックするとSTOPボタンが表示されます。 STOPのClickイベントは単純に隠れています。それだけです。

ループは、STOPボタンがまだ表示されているかどうかを確認するだけです。そうでなければ、それはクリックされたことを意味します。

あなたのコントロールは、私がボタンのテキストを使用して知っている

関連する問題