VS2005 MFC XP
・やりたいこと
親ダイアログがいて、ファイル転送を行うが、そこでプログレスバー付きダイアログを
表示したい。
親ダイアログの”送信”ボタン押しで、プログレスバー付きダイアログを表示し、画面
を更新する。
ファイル転送はワーカースレッド。この中でダイアログ表示も行う。
そこで、過去ログのように OnInitDialog() でPostMessage()を投げる
http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200512/05120003.txt
をやってみると確かにダイアログが表示され処理が実行されます。
が、肝心のプログレスバーが描画されていません。送信キャンセルボタンもあるのです
が、表示されません。ダイアログのみの表示になります。
ダイアログは
dlg.Create();
dlg.ShowWindow(SW_SHOW);
として描画しています。
よろしくお願いします。
CDialog::Createなら引数が何かいると思います。
dlg.Create();ではうまくいかないはずですけれど、自作関数ですか?
ならば、その関数の中身も出さないと駄目だと思います。
状態だけを聞くと実際にコントロールを配置したリソースIDが
使われていないのではないかと言う気がします。
あと、ワーカースレッドにしてメインスレッドでプログレスバー付きダイアログを
出すのであれば、モーダルダイアログでも良さそうな気がしますが、
モードレスダイアログを使っているのはなぜでしょう?
モーダルの方が簡単に扱えると思うのですが。
生成は↓のようにやっています。
dlg.Create(::AfxGetMainWnd());
ワーカースレッドで子ダイアログを生成しています。
dlg.Create(::AfxGetMainWnd());
これをモーダルダイアログにした場合
OnInitDialog()でPostMessage()するとダイアログが描画されません。
ただし自動で処理は開始できています。
OnInitDialog()でPostMessage()しないと描画はOKですが、
当然処理は行われません。
モーダルでもモードレスでもどちらでもよいと思っていたのですが、
実際作ってみるとどちらも思ったとおりの動作をさせることができない状態でいま
す。。
今回の場合、リンク先のOnInitDialog() 内でのPostMessageとは
あまり関係ないような気もしますが・・・
>ワーカースレッドで子ダイアログを生成しています。
ダイアログは、別スレッドを作るわけじゃないから
ワーカースレッドと別のスレッドから作るか
ダイアログから、ワーカースレッドをつくるとかじゃないと
「処理」と「インターフェース」を分けることにはならないよ
親―ダイアログ―ワーカー
親―ダイアログ
\ワーカー
このあたりが楽だと思う
その過去ログだと俺には具体的にどう実装して解決したのか俺には分からない。
>OnInitDialog()でPostMessage()するとダイアログが描画されません。
細かい話だけど動作に影響するので念のため、
どのウィンドウからどのウィンドウに?
実際にあなたと同じことを試したわけではないので以下は推測もあります。
ワーカースレッドでダイアログ作ったらダメでしょ。
ワーカースレッドはウィンドウメッセージ処理しないスレッドのことをいう用語だから。
ワーカースレッドにメッセージ処理入れたらそれはGUIスレッドだな。
ワーカースレッドをGUIスレッドにする手もあるが、
それだと何のためにワーカースレッドを作ったんだかってことで駄目だな。
俺なら、プライマリスレッドでモーダルダイアログを出す。
モーダルダイアログならそのモーダルダイアログ閉じるまで
親ダイアログの送信ボタンをまた押してしまうなんてできないから。
それとGUIスレッドだから。
ワーカースレッドからPostMessageでダイアログに進捗や完了を通知する。
ワーカースレッドは終了すると自分を自動でdeleteするタイプか?
それなら完了をPostMessageで通知し忘れない方がいいな。
細かいことはどう実装するか好みとかあるし話が長くなるのでここまで。
過去ログの内容はワーカースレッドにすると言うことではなくて
ファイルの受信処理をInitDialog内で呼んだ為に
InitDialogが終了しきれない状態で処理が動いてしまい、
結果的にダイアログが表示されない状態で処理進んでしまっていた事が
問題なのでワーカースレッドとは直接関係無いです。
wclrp ( 'o')さんも言われていますが、
マルチスレッドとかワーカースレッドについて誤解されているのでは
ないかと思います。
あと、ワーカースレッドは基本的にウインドウメッセージを
処理する部分を持ちませんからこの中でダイアログの表示をするのは
駄目です。これではわざわざワーカースレッドにした意味が有りません。
ウインドウ制御系はメインスレッドに任せて
画面の制御に関係無い処理のみを単純に行う為のものがワーカースレッドです。
既に皆さんも書かれていますが、
構成その物を考え直した方が良いと思います。
wclrp ( 'o')さんが提案されている実装方法を私もとると思います。
追記:
dlg.Create(::AfxGetMainWnd());
これって何処からもってきたんでしょう?
CDialog::CreateとCWnd::Createを見ましたけれど、
この引数では駄目だと思いますけれど。
関数を呼び出す場合、その関数に引き渡す引数の意味とかまで
ちゃんと理解しないと駄目ですよ。
ダイアログにこのダイアログリソースを使ってねと
指示しないとリソースでデザインした画面は出せないのですけれど、
その辺は理解されていますか?
一つ一つ、順序だてて理解していかないと応用が利かないので
自力でプログラムを出来なくなってしまいます。
>>OnInitDialog()でPostMessage()するとダイアログが描画されません。
>細かい話だけど動作に影響するので念のため、
>どのウィンドウからどのウィンドウに?
新規のダイアログから新規のダイアログ自身にです。
>ワーカースレッドにメッセージ処理入れたらそれはGUIスレッドだな。
>ワーカースレッドをGUIスレッドにする手もあるが、
>それだと何のためにワーカースレッドを作ったんだかってことで駄目だな。
そうですね。そんなつくりになっています。
つくりに問題があるのはわかりました。
が、ちょっとこのままでダイアログを正常に描画したいと思っています。
なぜ描画できないのか気になります・・。
>ダイアログにこのダイアログリソースを使ってねと
>指示しないとリソースでデザインした画面は出せないのですけれど、
>その辺は理解されていますか?
上記の通り、できていないと思います。
現状ですが,このような感じで作っています。
CProgressDlg // 新規作成ダイアログクラス プログレスバー、キャンセルボタンが
ある リソースエディタで作成した
メイン(親)のダイアログがいてファイル転送の時に、GUIスレッド(ですよね?)
を起動してその中で CprogressDlg をクリエイトします。(ファイル転送状況をユーザ
ーに見せるだけ)
CProgressDlg cDlg:
cDlg.Create(::AfxGetMainWnd());
この新規スレッドはファイル転送を行い、その転送状況をユーザーに見せるために
CProgressDlgダイアログをクリエイトしています。この機能のみなので、
親 - スレッド(ダイアログをGUIとして使う) というイメージでいるのですが・・
この状態でOnInitDialog() PostMessage()などやってみて最初の発言の状態なのです
が、今の状況では正常に描画できないのでしょうか?
問題は、「OnInitDialog()でPostMessage()している件」とは全く別だと思います。
思うんですが、OnInitDialogの中身をクラスの雛形にあった記述以外全て
コメントアウトしてもダイアログはちゃんと出ないのではないかと思います。
何度も書いていますが、「cDlg.Create(::AfxGetMainWnd());」がそもそも変です。
MSDNでCDialog::CreateやCWnd::Createの確認はしましたか?
CWnd::Createの引数は一体いくつあるでしょう?
それぞれ何を渡さないといけないでしょう?
CDialog::Createは引数のパターンで二種類の関数がありますが、
それぞれ引数はいくつで何を渡さなければならないでしょう?
関数を使う為にはこの部分が理解できていないと先に進めません。
貴方が書いている呼び出しに該当する関数は存在しますか?
もしかしたら間違った引数が間違った解釈をされて動いているかもしれません。
何処からかソースを持ってきているのであれば、
CProgressDlgクラスに新しくCreate関数が作成されていたりしませんか?
作成されているとしたら、その中のコードはどうなっているのでしょう?
> この新規スレッドはファイル転送を行い、その転送状況をユーザーに見せるために
> CProgressDlgダイアログをクリエイトしています。この機能のみなので、
> 親 - スレッド(ダイアログをGUIとして使う) というイメージでいるのですが・・
ワーカースレッドはファイル転送だけを請け負います。
画面の制御はメインスレッドである親画面の流れにあるのが普通の実装です。
ワーカースレッドでの作業状況を表示したいのであれば、
ワーカースレッドからメインスレッドに対してウインドウメッセージやイベントを
使って情報を通知します。
メインスレッドは通知された情報を使って表示を更新します。
ワーカースレッドと表示系の制御は切り離されているべきです。
ですから、既に挙げられている通り、
親画面 -(モーダルで表示)-> プログレスバーのダイアログ <--------+
| |
(ワーカースレッド起動)(イベント等で状況通知)
| |
+->[ワーカースレッド]--+
という構造が妥当だと思います。
サブスレッド側にUIスレッドを使うと話がややこしくなるだけです。
今回の話ならUIスレッドにする必然は無いと思います。
ちなみに今現在の問題に関しては、こういう構造以前の話だと思います。
そもそも、ダイアログの表示がうまくいっていないわけですから。
気になったんですが、ファイル転送処理を行う為にUIスレッドである必要が
あるとかそういう話ではないですよね。
もしそうなら話自体が変わってくると思います。
> が、ちょっとこのままでダイアログを正常に描画したいと思っています。
> なぜ描画できないのか気になります・・。
ここにのみ反応。
> ワーカースレッドで子ダイアログを生成しています。
> dlg.Create(::AfxGetMainWnd());
ウィンドウは、所属するスレッドにメッセージループを用意してメッセージ処理してやら
ないと何もできない。
つまり、初心者さんの場合だとワーカースレッドにメッセージループが必要になる。
> ワーカースレッドにメッセージ処理入れたらそれはGUIスレッドだな。
> ワーカースレッドをGUIスレッドにする手もあるが、
> それだと何のためにワーカースレッドを作ったんだかってことで駄目だな。
ただ、そう実装するならば wclrp ( 'o') さんのこの指摘が引っ掛かってくる
> 俺なら、プライマリスレッドでモーダルダイアログを出す。
自分もこちらを推す…がちょいググっただけでは参考になるページがみつからなかった。
モードレスを使うサンプルばっか。
モーダルでの実装について興味があるならば自分で探してみてください。何処かにはある
でしょう。
ワーカースレッドについてのご指導ありがとうございます。
まだわかっていませんでしたね。。
プログレスバー付きダイアログは
bool CprogressDlg::Create(CWnd *pParent)
{
return CDialog::Create( IDD_PROGRESS,pParent);
}
となっています。
いろいろ試してみたのですが、ダイアログだけでてプログレスバーが描画されないのは
処理が走っているため描画できないのだろうという感じに見えます。
ワーカースレッドを使わずに、プライマリスレッドのみで作成して
何も処理しないループをいれてみていると時間がたてばプログレスバー、ボタンとも描
画します。
dlg.Create();
dlg.Show();
dlg.doAction();
dlg.Destroy();
----doAction()---
while(1){
::Sleep(200);
MSG msg;
while( ::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
を入れて調べました。Sleep(200)だけなのですが、
うまく描画できていないようです。この状況はおかしいでしょうか?
>何も処理しないループをいれてみていると時間がたてばプログレスバー、ボタンとも描
>画します。
これはメッセージを処理できないので普通の状況でしょうか? という質問です。
> プログレスバー付きダイアログは
> bool CprogressDlg::Create(CWnd *pParent)
> {
> return CDialog::Create( IDD_PROGRESS,pParent);
> }
やはり。
これが無いと話にならないのでこれがあるなら話は別です。
>> 何も処理しないループをいれてみていると時間がたてばプログレスバー、
>> ボタンとも描画します。
>
> これはメッセージを処理できないので普通の状況でしょうか? という質問です。
というか、無理やりワーカースレッドにメッセージループを突っ込んで
尚且つ、大元の無限ループの中にSleep文があるんでは、そういう動きになって
るんじゃないかと思います。
本来、あるべき所で無い所にメッセージループが有りますからね。
しかも外側のループ中にSleepがあるのでその間はスレッドが止まってますから。
で、今のやり方に関してはかなり無理やりになっているので
皆さんが進めているやり方に変えたほうが良いと思います。
ウインドウの表示をメインスレッドに持っていって、
プログレスバーつきのダイアログのInitDialogでワーカースレッドを起動する
ワーカースレッドからプログレスバーのダイアログにユーザー定義のウインドウ
メッセージ等で状況通知を行い、メインスレッドのダイアログの方でそれを受けて
表示の更新とか処理が終わった時の処理をするようにします。
ダイアログその物はモーダルでもモードレスでも同じ事です。
モーダルの方が扱いが楽なので皆さんが進めているだけです。
具体的にはメインスレッドの処理開始ボタンのクリックイベントで
プログレスバーのダイアログをモーダルで表示します。
プログレスバーのダイアログのInitDialogでワーカースレッドを起動、
この時にプログレスバーのダイアログのウインドウハンドルをワーカースレッドに
引き渡しておきます。
ワーカースレッドは受け取ったウインドウハンドルを使ってWin32APIのPostMessageで
プログレスバーのダイアログに状況を通知します。
処理の進行状態を通知するユーザー定義メッセージと処理が終了した事を通知する
ユーザー定義メッセージを作成しておくと処理が楽だと思います。
ワーカースレッドが処理が済んだタイミングで処理終了のユーザー定義メッセージを
プログレスバーのダイアログにPostMessageで通知し、
通知を受けたプログレスバーのダイアログ側で処理が終わった時の動作をすれば、
OKだと思います。
モーダルでやるならウインドウを閉じる時はEndDialogを使わないと駄目ですよ。