FUKU様
ありがとうございます。
現在いる場所にVista環境がないため、帰宅してから確認してみたいと思います。
なんとなく、FUKU様の助言通りで直りそうな気がしてきました(^-^)
PATIO様
基本的にそういう作りです。
ただ、すでにできあがっているコードが、ファイルコピー処理中に、ウィンドウ(このウ
ィンドウがViewと通信してしまっている)生成を伴う処理をいくつか含んでおり、この段
階でそれらを取り除くには、大きな工数がかかるということです。
今、その作業を、行っております。
私自身、一から設計し直すなら、PATIO様のおっしゃるとおりの設計にするのは毎
違いないです。
また、何か分かりましたら報告します。
FUKU様
いただいたマニフェストで試してみましたが、残念ながら駄目でした。
PATIO様
マルチスレッド化において、問題となっている部分が、昨日まで思っていた部分
とは違うようです。
VC++2005 SP1を使用して、MFCプログラミングをしていますが、
プログレスバーの横に、スタティックテキストを置き、「50%」のように表示し
ていますが、スタティックテキストにCString型のDDX変数を割り当てて、
UpdateData(FALSE);
で、毎回、表示値を更新しています。このUpdateData(FALSE)の際に、
CDataExchangeクラスのコンストラクタで、
ASSERT_VALID(pDlgWnd);
に失敗しているようです(wincore.cpp 3977行目)。
マルチスレッドプログラミングにおけるタブーがよく分かっていないのですが、
ワーカースレッド内から、別スレッドで作成されたウィンドウオブジェクト(MFC
のCHandleMapに登録されたもの)にアクセスしてはいけないのでしょうか?
別スレッドで作成されたウィンドウにアクセスするときは、ユーザー定義メッセ
ージを使用するといいと書かれた記事も目に入りました。
問題を切り分けるために、問題のアプリケーションのプログレスバーを含むダイ
アログだけを切り出し、新規ダイアログアプリに貼り付けてみました。
結果はそれでも駄目でした。
さらに、切り分けるために、さらにダイアログの機能を削ってみようと思います。
参考のため、現状、ダイアログの機能を列挙しておきます。
・処理中を示すために、風車が常に回っている
(WM_TIMERで1秒毎にCStaticに2種類のアイコンを交互に張り直しているだけ)
・プログレスバーは2本張りついている
(1本は全フォルダでの%表示、もう一本はフォルダ内での%表示)
・それぞれのプログレスバーには、横にスタティックテキストがある
(プログレスバーの値を「50%」の用に表示)
・タイトルバーにはコピー中のフォルダ名が表示されている
よろしくお願いいたします。
> マルチスレッドプログラミングにおけるタブーがよく分かっていないのですが、
> ワーカースレッド内から、別スレッドで作成されたウィンドウオブジェクト(MFC
> のCHandleMapに登録されたもの)にアクセスしてはいけないのでしょうか?
僕の考えです。
間違っているところはフォロー願います。
僕の考えでは、
「ウインドウハンドル(HWND)を扱う処理をワ-カスレッドで行わない。」
ということですね。
どのようなタイミングか分らないですが、「HWND = NULL」になる時があります。
そのときにワ-カスレッドで処理を行うと例外が発生してアプリが異常終了します。
但し、親スレッドが「CView、CDialog」関連でコントロールのウインドウハンドル
(HWND)がいつでも取得できる場合は別です。
それでも、「HWND = NULL」の時処理しないようにするぐらいの考慮は必要だと思いま
す。
あと、「いつでも終了できるようにしておく。」
ことぐらいですね。
「ユーザー インターフェイス スレッド」は、終了処理の関係でシングルスレッドに
とどめておいたほうがいいと思います。
中間報告です。
問題の再現する最低条件を追求調査しました。
CProgressCtrl::SetPos(0)
CProgressCtrl::SetPos(5)
CProgressCtrl::SetPos(10)
:
CProgressCtrl::SetPos(100)
を1回通り行うだけの場合、
視覚テーマをVistaスタイル(緑色の立体プログレスバー)にしても問題は再現し
ません。ただし、連続して複数回行うと、0~15ぐらいを往復します。
(例) プログレスバーが m_CtlProgress に Attach()してあるとして、
for(int nIdx=0; nIdx<=100; nIdx+=5)
{
::Sleep(5);
m_CtlProgress.SetPos(nIdx);
}
は正常動作(0~100まで表示)します。
しかし、
for(int nLoop=0; nLoop<10; nLoop++)
{
for(int nIdx=0; nIdx<=100; nIdx+=5)
{
::Sleep(5);
m_CtlProgress.SetPos(nIdx);
}
}
は、0~15ぐらいを往復します。
そして、最後のループ完了後、100になります。
どうやら、描画が追いついていないという線が非常に濃くなりました。
前回の調査結果により、WM_PAINTによる描画が破棄されている可能性が高いと
判断し、以下のキーワードでネット検索を行いました。
「Vista 描画 破棄」
その結果、ついに核心に迫る、検索結果にたどり着きました。
> [DWM]ディスプレイのリフレッシュレートを超える画面更新は破棄されるよう
> になった (ティアリング回避のため).
詳細は分かりませんが、Vista(Aero)ではダブルバッファリング(オフスクリーン
描画?)が標準のようです。
おそらく、WM_PAINTを高速に投げると、オフスクリーンに描画されるだけで、実
画面への転送はディスプレイのリフレッシュレートで行われるということかな?
と理解しました。
マルチスレッド化の方もコーディングが完了しましたが、やはり効果はありませ
んでした。どうやら、今回の件とは関係なさそうです。
こうなってくると解決方法は、いかの2つぐらいしか思いつきません。
1) 高速描画をやめる
WM_PAINTを高速に投げ続ける、つまりCProgressCtrl::SetPos()を呼びまくる
と描画落ちすると思われる。
あまりに高速になるときは、CProgressCtrl::SetPos()を呼ぶ回数を減らす。
ただし、0% と 100% が表示されないと、不自然さを感じるので、呼ぶ回数を
減 らした結果、SetPos()の値を絞り込む際に 0% と 100% mには優先権を持
たせる。
2) プログレスバーだけVistaスタイルをやめる
Vistaスタイルのマニフェストがあたっていても、以下のAPIを使用して、特定
のコントロールだけVistaスタイルを外すことができるので、プログレスバー
だけVistaスタイルを外してしまう。
他にも良い方法を思いつく方はいらっしゃいますでしょうか?
よろしくお願いいたします。
VistaスタイルをやめるAPIを載せるのを忘れました。
SetWindowTheme()
です。
よろしくお願いいたします。
このスレを文字列検索したけど、出ていないようなので一応書いとく。
「UpdateWindow() で強制的にプログレスバーの描画を更新してもダメ?」
> 本格的なマルチスレッドプログラミングは初めてで
とあるので念のため書いとくと、基本的にウィンドウが所属するスレッド以外から
ウィンドウ操作をするのは×だよ。
タイミングによっては処理の衝突が起こってデッドロックに陥ったり等問題がでる。
gak様
UpdateWindow(), RedrawWindow() とも駄目でした。
> とあるので念のため書いとくと、基本的にウィンドウが所属するスレッド以外から
> ウィンドウ操作をするのは×だよ。
自分でもそういう結論に達していたのですが、太鼓判を押していただいたおかげで、
自信が持てました。
ありがとうございます。
ちょっと?な部分も残っていますが、これにて解決としたいと思います。
今更な感じですが当方でも同じ現象を確認しました。
環境はVista(32bit) VS2005 SP1 MFCです (ダイアログアプリで実験)
この現象の発生条件を整理すると、
・実行ファイルにVistaスタイルのマニフェストを組み込んでいる
・短時間に連続してSetPos()を呼んでいる
の2点のようです。
UpdateWindow(),RedrawWindow(),CWinThread::PumpMessage()等
色々試してみましたが駄目みたいですね。
個人的な憶測ですが、VistaスタイルのSetPos()は、
「指定された位置までのバーを描画する」ではなく
「指定された位置へのバー移動アニメーションを開始する」
に変更されているのではないでしょうか?
例えば、範囲を0-100にして m_Progress.SetPos(100) した場合
XPでは一瞬でバーが(100%の状態に)描画されるのに対して、
Vistaの場合は0.5秒くらい掛けてバーが増えていくのが分かります。
(びよぉ~んって感じですかね)
とりあえず、XPと同じ視覚テーマなら回避できるようですが...
FUKU様
解決後に話が続いちゃってますが(^^;)
> 例えば、範囲を0-100にして m_Progress.SetPos(100) した場合
> XPでは一瞬でバーが(100%の状態に)描画されるのに対して、
> Vistaの場合は0.5秒くらい掛けてバーが増えていくのが分かります。
> (びよぉ~んって感じですかね)
もしかして、プログレスバーの目盛りが戻ることを想定していない?
まさかね。
今回の私のように、
ダブルプログレスバーだと、主バーは目盛りが戻ることはありませんが、
副バーは戻らざるを得ないのですが...
まぁ、根本治療(プログレスバー自体の修正)は無理そうですので、対症
療法で行くことにします。