ちょっと細かい話です。
事の経緯
DoModalって普通はユーザに回答を促すダイアログを出したいときに使うので、表題のようなことは起こり得ないんでは…と思われるかもしれませんが、こういう状況は例えば…
「ファイルのコピー中の進捗表示」
などの、進捗表示系ダイアログを作るときに往々にして起こり得ます。
だいたい、進捗表示をやらせる場合は、
- メイン(UI)スレッドに進捗ダイアログ表示
- ワーカースレッドで実作業
という形にほぼ落ち着くかと思います。で、ワーカー側からユーザ定義ウィンドウメッセージをダイアログに送って、表示の更新を行うのがセオリーですね。
で、「ワーカーが終わったらダイアログを自動的に閉じたい」という欲求も当然でてきます。普通に組むとそれなりに動いてくれるわけですが、ここで問題があって、ワーカーの処理が早く終わりすぎた等、ダイアログが表示し終わる前にEndDialog等で終わらせるとASSERTが出てしまう…ということです。
ASSERTはデバッグの時しかでないので無視してもいいといえばいいんですが、それでは気持ちが悪いのでなんとかしてみましょうということです。
モードレスダイアログならこういった状況は起きにくいんですが(HIDEで隠してあとで破棄すればよいだけの話)、モードレスの場合はメインウィンドウが触れたりしますよね。それは困るのでモーダルで…と言うことです。
解決方法
色々やってみましたが、結局
- OnInitDialog でタイマー起動
- タイマー関数で閉じる要求のフラグを見てここからEndDialog発行
- ユーザメッセージからのダイアログ閉じる要求は、直接EndDialogを呼び出すのではなく、閉じる要求フラグを立てる
という方法に落ち着きました。多少泥臭いですが仕方なしです。
「メッセージを送るんじゃなくて直接閉じれないの?」と思われがちですが、MFC(かな)は「スレッドが違うもの同士のダイアログは触れてはならない」という面倒くさい鉄の掟があるのでメッセージで…ということでした。
コードにするとこんな感じです。
# define USER_PROGRESS WM_USER + 0x10
// 進捗状況ダイアログを閉じたい
void WorkerThread::CloseWindow(CWnd *pWnd)
{
pWnd->SendMessage(USER_PROGRESS,0,1);
}
CxxxDialog::CxxxDialog(hogehoge) : hogehoge
{
m_close = false;
}
BEGIN_MESSAGE_MAP(CxxxDialog, CDialog)
...
ON_MESSAGE(USER_PROGRESS,&CxxxDialog::OnUserProgress)
ON_WM_TIMER()
END_MESSAGE_MAP()
BOOL CxxxDialog::OnInitDialog()
{
// iroiro
m_TimerID = SetTimer(100,100,NULL);
// iroiro
}
LRESULT CxxxDialog::OnUserProgress(WPARAM wParam, LPARAM lParam)
{
// iroiro
// 仮に、lparamが1の時は閉じる要求があったことにする
if(lParam == 1){
m_close = true;
return 0;
}
// iroiro
}
void CxxxDialog::OnTimer(UINT nIDEvent)
{
// クローズ指示が来た場合、タイマーによってEnddialogを使うことで安全に閉じる
if(m_close){
KillTimer(m_TimerID);
EndDialog(0);
}
}