Windows7、VS2008です。
http://rarara.cafe.coocan.jp/cgi-bin/lng/vc/vclng.cgi?print+201302/13020003.txt
での質問と同じように、
モードレスダイアログで発生している問題を再現させるためのサンプルです
(MFCダイアログベースのプロジェクトをデフォルトのまま新規作成)。
void CTestDlg::OnBnClickedOk()
{
// ユーザがいったん非表示にし、再度表示させる操作
ShowWindow(SW_HIDE);
SetTimer(1, 1000, NULL);
}
void CTestDlg::OnTimer(UINT_PTR nIDEvent)
{
KillTimer(nIDEvent);
ShowWindow(SW_SHOW);
}
void CTestDlg::OnPaint()
{
// このSleep()は残像をわかりやすくするため
Sleep(500);
CString str;
str.Format(_T(%d), m_counter++);
CPaintDC(this).TextOut(0, 0, str);
}
このダイアログを起動すると、画面左上に「0」と表示されます。
その後「OK」と押すといったん非表示になり、1秒後に再表示され、
そのときは「1」と表示されますが、Windows7のAeroが有効になっていると、
ダイアログが再表示された直後に前回の「0」が一瞬が見えます
(その後すぐに消え、0.5秒後に「1」が表示される)。
隠れていたウィンドウを表示するときに
前回の描画内容が表示されることは通常はないはずです。
モードレスダイアログの非表示中にも、
表示するべき内容が変化することが当然あるのですが、
Aeroがなにか余計(?)なことをやっているのでしょうか。
これを防ぐ方法はなにか考えられませんでしょうか。
AeroではGlass表示等を行うために、ウインドウの表示を
キャッシュしているようです。これが一瞬表示されるのでは
ないでしょうか。
やってみるべき事はあまりなくて、
1.ShowWindow(SW_SHOW); の直後に、
InvalidateRect(NULL,TRUE)と
UpdateWindow() をしてみる。
2.ShowWindow(SW_SHOW); の直後に、
RedarwWindow()をRDW_UPDATENOW付きで実行する。
ぐらいですかね、テストのときSleep(500)をコメントするのを
お忘れなく。
(蛇足)
このような問題は、パフォーマンスオプション→視覚効果で
アニメーションなどを有効にしている場合も発生するようです。
こちらの問題は、アニメーション終了時にWM_NCPAINTが送付されない
様にコードされることが原因のようで、本件とは関係ないかもしれません。
あちゃ、言い忘れ。orz.
RedarwWindow()の場合の対象ウインドウは
1.デスクトップ
2.対象DLG
3.上記の両方
を、試してみてください。
仲澤@失業者さん、ありがとうございます。
Invalidate()やRedrawWindow()をいろいろなパターンで呼んでみましたが、
結果は同じでした。やはり残像が出てしまいます。
> AeroではGlass表示等を行うために、ウインドウの表示を
> キャッシュしているようです。これが一瞬表示されるのでは
> ないでしょうか。
まさにこれが原因のような残像問題なのですが、
「キャッシュをクリア」のような仕組みはさすがにないですよね。
非表示になったウィンドウが、再表示時には別の表示状態になっていることがある、
ということがAeroでは想定されていないということになるのでしょうか。
CS_SAVEBITS を外してみるとかどうでしょうか。
forty-fiveさん、ありがとうございます。
OnInitDialog()に以下のような処理を入れたところ、
Spy++で表示されるクラススタイルからは「CS_SAVEBITS」は消えたのですが、
この状態でもやはり同じように残像が出てしまいました。
ULONG_PTR cs = GetClassLongPtr(m_hWnd, GCL_STYLE);
SetClassLongPtr(m_hWnd, GCL_STYLE, cs & ~CS_SAVEBITS);
やはり、だめそうですね。
あきらめたほうが良いかもしれません。
どうしてもなんとかしたい場合の話になりますが、
1.ShowWindow( SW_SHOW)から最初のWM_PAINT受領までの全てのメッセージを
SPY++でログします。
2.ログに現れたメッセージの内、キャッシュが表示される時のメッセージ
を判別します。一つ一つブレークする方法しかないでしょう。
3.そのタイミング、又はそのメッセージ後の合理的なタイミングで
クライアントエリアの再描画を行う。
ことで、回避できるかもしれません。
WM_SHOWWINDOWか、その前後のメッセージではないかと予測できます。
ありがとうございます。
Spy++でいろいろメッセージを追って再描画を入れてみたのですが、
やはり残像は消えないようです。
ちなみになんですが、「同じWindows7+Aeroだけど発生しないよ」
というかたは、やはりいらっしゃいませんでしょうか?
ビデオカードの問題も考えられるかなとも思っていたのですが。
ありがとうございます。
ただ、提示していただいたものは、今回の問題とは関係ないと思われます。
プレビュー表示ではなく、ダイアログの再表示時に残像が出る問題です。
私も色々試してみました。
void CTestDlg::OnBnClickedOk()
{
ShowWindow(SW_MINIMIZE);
ShowWindow(SW_HIDE);
SetTimer(1, 1000, NULL);
}
void CTestDlg::OnTimer(UINT_PTR nIDEvent)
{
KillTimer(nIDEvent);
ShowWindow(SW_RESTORE);
SetActiveWindow();
SetForegroundWindow();
}
これでどうでしょうか。
forty-fiveさん、ありがとうございます。
再表示される際の文字の残像は出なくなりました。
ただ、文字の代わりにダイアログ全体が一瞬黒く表示され、
その直後にダイアログが正しく描画されるようです。
ShowWindow(SW_MINIMIZE)によって、直後にAeroがキャッシュする画像が
真っ黒になったということかもしれません。
もしこの問題がAero環境で必ず発生するもので、
プログラム側で防ぐ方法がないということになれば、
提示していただいた方法を元にいろいろなパターンを試してみて、
一番気にならなそうな再表示方法を探ってみたいと思います。
リージョンを使って消えたように見せかける方法はどうでしょうか。
レイヤードウィンドウでも同じことが出来ると思います。
void CTestDlg::OnBnClickedOk()
{
SetWindowRgn(::CreateRectRgn(0, 0, 0, 0), false);
SetTimer(1, 1000, NULL);
}
void CTestDlg::OnTimer(UINT_PTR nIDEvent)
{
KillTimer(nIDEvent);
SetWindowRgn(NULL, true);
}
forty-fiveさん、引き続きありがとうございます。
残像が出るケースは格段に減りました
(自分の環境ではたまに同じような残像が出ることがあるようですが)。
この方法だと、内部的にはダイアログは表示状態のままなので、
IsWindowVisible()などで判定しているところを改める必要が出てきますが、
有力な回避方法の一つとして検討させていただきます。