ボタン押下による処理の一時中断/再開の処理

[戻る]
ジョブ 2010/01/05(火) 09:58:53
開発環境
XPSp3 VC++2008 MFC

処理クラス(A)にてモーダレスダイアログを表示し
その後forループを行っております。

モーダレスダイアログには「中断」のボタンが存在し
中断ボタンを押下すると、ボタンは「再開」に変更しforループの処理が一旦止まるよう
な処理を考えております。

現在クラスの構成は
処理クラス(A)とモーダレスダイアログのクラス(B)だけとなっております。

上記内容は実現可能なのでしょうか?

ご教授頂けたら幸いです。宜しくお願いします。

gak 2010/01/05(火) 17:34:36
> 上記内容は実現可能なのでしょうか?
極シンプルな例

A::xxxx() {
    B dlg;
    dlg.モードレス表示on();
    for (; 処理中; ) {
        dlg.「中断」チェック();
        何かしらの処理();
    }
}

class B {
    CEvent event_; // CEvent変数

    B::B() : event_(TRUE, TRUE) {
    }
    void B::「中断」押された() {
        event_.ResetEvent();
    }
    void B::「再開」押された() {
        event_.SetEvent();
    }
    void B::「中断」チェック() {
        for (;;) {
            // 溜まったメッセージ処理
            MSG msg;
            while (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
                ::TranslateMessage(&msg);
                ::DispatchMessage(&msg);
            }
            // 「中断」時は「再開」されるまで待つ
            if (::MsgWaitForMultipleObjects(1, &event_.m_hObject, FALSE,
INFINITE, QS_ALLINPUT) == WAIT_OBJECT_0) {
                // 「中断」中で無いのでloopを抜けて次に進む
                break;
            }
            // 何やらメッセージが発生したようなのでloop最初に戻る
        }
    }
}

ケースにも依るんで蛇足になるかも知れんが一応助言。
実は、この手の処理は「モーダレスダイアログ」では無く「モーダルダイアログ+ワー
カースレッド」で実装した方が良いケースが多い。なので、参考になるページを紹介でき
ればと軽くググッたが、モードレスダイアログを利用している例ばかりでダメだった…
過去スレに少しだけそれっぽい話が見つかったので紹介しとく。
http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200906/09060047.txt
念の為買いとくと、「モーダレス」が完全に間違っていると否定しているワケでは無い
よ。「モーダレス」で運用上問題無いならそれでも良いとは思う。

PATIO 2010/01/06(水) 09:28:24
ダイアログ側をモードレスにするか、モーダルにするかは、
その処理を行っている時に他のウインドウを操作する必要が有るか
と言う事によると思うのでGUIのデザインがどうなっているかが
問題になってくると思います。
処理的にはモーダルにして処理中はその処理の制御以外はさせない方が
楽に制御出来ますけれど、これは要求仕様との兼ね合いもあるでしょうから
一概には言えませんね。

モードレスにするにせよ、モーダルにするにせよ、
いずれの場合でも長い処理を動かす場合はワーカースレッドにして
GUI側が長い処理の影響でメッセージループが止まらないようにする方が
良いと思うのでつくり的には大して変わらないかなと思います。

GUI側のメッセージハンドラ内で長い処理や重い処理を行わないように
するのはイベントドリブン方のシステムのお約束なので
ワーカースレッドを使って制御する方法を模索して見た方が良いと思います。
ワーカースレッドを使った制御であれば、Web上に情報があると思うので
ご自身で探して見てください。

ジョブ 2010/01/06(水) 12:08:15
gak様ご返答ありがとうございます。

中断/再開の実装ができました!
ありがとうございます!

ちなみに途中にちょっと意味の分からなかった事がありましたので
質問させてください。

下記のコーディング方法と、その意味です。
    B::B() : event_(TRUE, TRUE) {
    }

ちなみに私の実装方法はほぼgak様のおっしゃられる通りなのですが、
A::xxxx() {
    B dlg;
    dlg.モードレス表示on();
    for (; 処理中; ) {
        dlg.「中断」チェック();
    dlg.event_.SetEvent();  ←★追加★
        何かしらの処理();
    }
}

B::xxxx.h
    CEvent event_; // CEvent変数

class B {
    コンストラクタ
    B::B(CWnd* pParent /*=NULL*/){
        event_.SetEvent();  ←★追加★
    }
    B::B() : event_(TRUE, TRUE) {}  ←★実装せず★
    void B::「中断」押された() {
        event_.ResetEvent();
    }
    void B::「再開」押された() {
        event_.SetEvent();
    }
    void B::「中断」チェック() {
        for (;;) {
            // 溜まったメッセージ処理
            MSG msg;
            while (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
                ::TranslateMessage(&msg);
                ::DispatchMessage(&msg);
            }
            // 「中断」時は「再開」されるまで待つ
            if (::MsgWaitForMultipleObjects(1, &event_.m_hObject, FALSE,
INFINITE, QS_ALLINPUT) == WAIT_OBJECT_0) {
                // 「中断」中で無いのでloopを抜けて次に進む
                break;
            }
            // 何やらメッセージが発生したようなのでloop最初に戻る
        }
    }
}


です。

よろしければお願いいたします。

ジョブ 2010/01/06(水) 12:11:52
PATIO様ご返答ありがとうございます。

ワーカースレッドですか。
初めて聞きました。

後学の為に勉強しておきたいと思います。
ありがとうございました。

PATIO 2010/01/06(水) 13:10:09
ちなみにですが、個人的にはメッセージループを
あちこちに書くのは気持ち悪いのでメッセージループは
スレッド内にひとつにしてUIが絡まない処理はワーカースレッドに
した方が良いと思っています。
元々のメッセージハンドラのつくりもメッセージループが複数存在することを
想定した作りになっていないと思いますので。

gak 2010/01/06(水) 17:22:30
>  下記のコーディング方法と、その意味です。
>     B::B() : event_(TRUE, TRUE) {
>     }
event_ を初期化してる。「初期化子リスト」等でググれば解説ページが見つかるかと。

http://msdn.microsoft.com/en-us/library/bhf9sxch%28VS.80%29.aspx
標準では CEvent は上記ページで説明されているデフォルト引数で初期化される。
で、例では「bInitiallyOwn」と「bManualReset」を TRUE 指定で初期化してる。
TRUE 指定する事によって生じる違いは↓程度

>     B::B(CWnd* pParent /*=NULL*/){
>         event_.SetEvent();  ←★追加★
>     }
bInitiallyOwn=TRUE だと初期状態がシグナル状態に設定されるので、上記の SetEvent()
が要らなくなる。

>     for (; 処理中; ) {
>         dlg.「中断」チェック();
>     dlg.event_.SetEvent();  ←★追加★
>         何かしらの処理();
>     }
bManualReset=TRUE だと MsgWaitForMultipleObjects() 等を抜ける際に自動で非シグナ
ル状態に設定されないので、上記の SetEvent() が要らなくなる。


まぁ大差無い。

ジョブ 2010/01/08(金) 09:48:33
[[解決]]
gak様、再度ご返答ありがとうございます。

なるほど!
初期化されるので、私の追記分が要らなくなるのですね。
了解しました。
ありがとうございます。

古いスレッドには追加発言できません。