none
モダルとモードレス ダイアログの違い RRS feed

  • 質問

  • DoModal()で呼び出してきちんと動作しているダイアログに時々ステータスを表示できる機能をつけるためにモードレスにしようと、Create()を使うように変更しましたが、ボタン(キャンセル)が実行されなくなってしまいました。具体的にはオーバーライドしたOnCancel()が呼び出されません。 親側からDestroyWindow()でクローズはできますが。モーダルー>モードレスの変更で、なにか処理を追加する必要があるのでしょうか。 インターネットでも検索してみましたが、特に追加の処理が必要という記事も見つかりませんでした。 環境はVS2005 C++(SP1)でMFCベースです。

     

     

    2007年2月14日 3:05

すべての返信

  • VC6の説明ですが、ほとんど同じですので参考にどうぞ。
    http://athomejp.com/goldfish/mfc/dialog/modeless.asp

    DoModalと違ってポインタで管理する(すべき)のが大きな違いでしょうかね。
    2007年2月14日 3:59
  • その後、いくつか思いつくことと、得た情報を試してみたのですが、だめでした。 

    ステータス表示のモードレスダイアローグを別スレッドにする必要があるのではないかという指摘があり、現在検討中ですが、別スレッドにすると変更が大きいので現在保留で別の作業を行っています。

     

    2007年2月16日 1:31
  • 具体的にどんな変更を行ったのかわからなければ、レスのつけようが無いです。
    一つ言うなら、VS2005でネイティブC++でMFCを使った状況では別に問題なく普通にできました。
    イベントハンドラも呼ばれていますし、特に今までと違いは無い様に感じます。
    もっと有効なレスポンスがほしいと言う話でしたら、もっと詳細な情報を提示する必要があると思います。

     

    2007年2月19日 7:15
  • 遅くなりましたが、レスをいただいた方もいるので、結果を書かせていただきます。

    やはりモードレス・ダイアログでは同じスレッド内で処理を続けながら、ボタンのイベントを受け取ることはできませんでした。

    やりたかったことは、長くかかる処理の状態を表示させて、場合によってはユーザーからキャンセルできるようにする、ということでした。

    コードにすると長くなるので、以下に実際に動作したフローだけを書きます。

    ===== メイン ====

    1.モードレス・ダイアローグ表示

    2.別スレッドを起動

    3.Windowsにコントロールを返す

    ===== モードレス・ダイアログ ====

    メッセージループで

    1.キャンセル・ボタンが押されたら、別スレッドにCEVentを使って通知

    2.別スレッドから表示更新メッセージがきたら、表示更新

    3.別スレッドからWM_CLOSEがきたらクローズ

    ===== 別スレッド =====

    1.CEventが来たときは、処理を中断、スレッド停止

    2.処理をしながら途中経過をモードレス・ダイアログに渡す

    3.モードレス・ダイアログに表示更新メッセージを送る

      (別スレッドからダイアログのDDXを呼び出すのは許されてないようです)

    4.処理が完了したら、ダイアログにWM_CLOSEを送り、スレッド停止

     

    2007年2月23日 6:43
  • モードレスダイアログでワーカースレッドを使わないで実装するのであれば、以下のようなコードで実現できませんか?
    あくまでもサンプルですので、必要なところは適宜追加してください。

    -----------------------------------------------------------
    // ---- ダイアログ ----
    class CXXXDlg : public CDialog
    {
    public:
      // 途中省略...

      BOOL IsCancel();

    protected:
      BOOL m_bCancel;
    };

    CXXXDlg::CXXXDlg(CWnd* pParent /*=NULL*/)
     : CDialog(CXXXDlg::IDD, pParent)
     , m_bCancel(FALSE)
    {
    }

    void CXXXDlg::OnCancel()
    {
      m_bCancel = TRUE;
    }

    BOOL CXXXDlg::IsCancel()
    {
      MSG msg;
      while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
      {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
      }

      return m_bCancel;
    }


    // ---- 処理側 ----

    BOOL CXXX::Hoge()
    {
      BOOL bCancel = FALSE;

      CXXXDlg dlg;
      dlg.Create(...);
      dlg.ShowWindow(SW_SHOW);
      dlg.UpdateWindow();

      for (...)
      {
        // 重い処理...

        bCancel = dlg.IsCancel();
        if (bCancel)
          break;
      }

      dlg.DestroyWindow();

      return bCancel;
    }
    -----------------------------------------------------------

    ワーカースレッドを使って処理させるのであれば、モーダルダイアログ内でスレッド処理をおこなうのが簡単かと思いますが。

    2007年2月23日 10:41
  • なるほどそういう方法もあるのですね、今日はもう試せませんので、来週試してみて、結果を報告したいと思います。
    2007年2月23日 14:41
  • 教えていただいた方法でうまくいきました。 ただし、やはり”重い処理”の部分が実際に重いと、キャンセルの効きが悪くなるので、なるべく処理を細分化して、IsCancelを頻繁に呼び出してやるようにしました。 コードはマルチスレッドより簡略なので、これで行こうと思います。
    2007年2月26日 14:28
  • 既に決着しているようですが。

    そもそも、スレッド跨ぎでCWndのクラスオブジェクトをアクセスしてはいけないと言う記述が
    MSDNにあったと思います。理由に関しても確か記述されていたはずです。
    OSの製造元がしないでくれと書いているわけですから、現状動いていてもするべきでは無いと思います。
    今後の実装に関しても考慮の上でしないでくれと書いている可能性もありますし、
    無保証の方法を採用するのはよく無いと思います。

    モーダルとモードレスの違いと言うよりもマルチスレッドの組み方の問題で
    言われている問題が起きているような気もします。

    2007年2月27日 2:59
  • 補足ですが、モーダルダイアログと違い、モードレスダイアログでは親ウィンドウが無効化されませんので、もしキャンセル用のダイアログが表示されている時に親ウィンドウのコントロールやメニューなどがクリックされると、IsCancel メソッドが呼び出された時に親ウィンドウのメッセージ処理もおこなわれてしまいますので注意してください。

    キャンセル用のモードレスダイアログを表示する時に、処理されては困る親ウィンドウのコントロールは無効化するなどしておいたほうがよいでしょう。

     

     

    2007年2月27日 3:00
  • PATIOさん

    別スレッドからCWnd系のクラスへは、直接メンバーのアクセスは行っていません。 MSDNに書いてあるようにPostMrssage/SendMessageでやりとりをしています。 実際に直接アクセスすると、実行時に例外を起こしてしまいました。 CObject(親ウインドウとは別にユーティリティを集めたクラス)と別スレッド間ではメンバーのアクセスを行っています。 アクセス時はCriticalSectionで括って問題が起きないようにしています。 

    chackさん

    親ウインドウのコントロールは念のため禁止処理を追加しておきました。

    2007年2月28日 11:59
  • ちゃちゃ入れになってしまうかもしれませんが・・・

     chack - Akira Inoue さんからの引用

    BOOL CXXXDlg::IsCancel()
    {
      MSG msg;
      while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
      {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
      }

      return m_bCancel;
    }

    は、PumpMessageを使うようにしないと、CWnd::PreTranslateMessage等の処理が行われません。



    BOOL CXXXDlg::IsCancel()
    {
      MSG msg;
      while (!m_bCancel && ::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE))
      {
          if (!AfxGetThread()->PumpMessage()) {
              // WM_QUIT を受けたときの処理
              m_bCancel = TRUE;
          }
      }
      return m_bCancel;
    }

     

    こんな感じかな。

    2007年3月1日 5:41
  • そうですね、確かにこの場合 MFC ですから CWinThread::PumpMessage を使ったほうがいいですね。
    補足ありがとうございます。

    ちなみに、AfxPumpMessage() を使ってもいいでしょうね(アンドキュメンテッドですが・・・)。

    2007年3月2日 9:28
  •  ゴマちゃん さんからの引用

    PATIOさん

    別スレッドからCWnd系のクラスへは、直接メンバーのアクセスは行っていません。 MSDNに書いてあるようにPostMrssage/SendMessageでやりとりをしています。 実際に直接アクセスすると、実行時に例外を起こしてしまいました。 CObject(親ウインドウとは別にユーティリティを集めたクラス)と別スレッド間ではメンバーのアクセスを行っています。 アクセス時はCriticalSectionで括って問題が起きないようにしています。 

    えーと、メンバのアクセスは行っていないと言うのはメンバー関数の呼び出しも行っていないでしょうか?
    スレッド間ではHWNDを引き渡してWin32APIの::PostMessageや::SendMessageを使われているという話なら
    問題ないと思います。(MSDNにもそう書いてありましたし)
    もしかしてCWnd系クラスのポインタをHWNDを引き渡す為のコンテナ代わり使っているんでしょうか。

    2007年3月6日 2:36
  •  FC-Shiro さんからの引用



    BOOL CXXXDlg::IsCancel()
    {
      MSG msg;
      while (!m_bCancel && ::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE))
      {
          if (!AfxGetThread()->PumpMessage()) {
              // WM_QUIT を受けたときの処理
              m_bCancel = TRUE;
          }
      }
      return m_bCancel;
    }

     

    FC-Shiroさん、ありがとうございます。 ご指摘どおり、直しておきました。

     PATIO さんからの引用

    もしかしてCWnd系クラスのポインタをHWNDを引き渡す為のコンテナ代わり使っているんでしょうか。

    CWnd系ではなくてCObject系のポインタをモードレスダイアログのHWNDなどをを引き渡すために使っています。

    2007年3月6日 14:30