none
はじめて Window が描画されるときの背景色 RRS feed

  • 質問

  • Win7 pro x64
    VC2010 Express

    お世話になっております。
    通常ウィンドウの背景色は
    WNDCLASS構造体の hbrBackground で指定します。
    ここで指定された色は、WM_ERASEBKGND でデフォルトウィンドウプロシージャにより描画されます。

    しかし、はじめてウィンドウが描画されるのは WM_ERASEBKGND が飛んでくる よりも前です。
    このとき、ウィンドウの背景色は hbrBackground で指定した色に関わらず、白くなります。

    このためウィンドウがはじめて描画されるとき一瞬白くなってしまします。
    ダイアログボックスなどでは一瞬白くなるのはみたことがありませんので、これを防ぐ方法はきっとある思いますが、いろいろ調べてもわかりませんでした。

    適切なやりかたをご存知でしたらご教授ください。

    2012年6月22日 16:16

回答

  • 閉じるといってるのに返信いれるのもなんなのですが、

    しかし描画すべきものをすべてDCにかきこんだあと初めて表示するような処理がOSレベルでできないというのはちょっと納得できないんですよね。
    DirectXとかOpenGLを使えばやれそうなきもします。

    DirectX 等でもできるでしょうが、そういうことが行いたいなら Layered Window でもできなくはないと思います。
    ちょっと古い内容ですが、レイヤードウィンドウ あたりが参考になると思います。



    フレームが描画されているのに、クライアント領域だけ何も描画されないというのは問題かもしれません。

    昔はアプリ無応答で同様状態によくなって、画面崩れで印象がよくなかったんですけどね・・・。

    ----------------------------------------------------------------------------------------------

    「DC に書込んだ後に表示」ってことだったので的外れだった気がしてきました、無駄な書込みごめんなさい。

    • 編集済み kyano30 2012年6月30日 3:40 追記
    • 回答としてマーク サウロ 2012年7月6日 15:08
    2012年6月30日 2:59

すべての返信

  • あてずっぽうですが、システムで設定されているウィンドウの背景色で描画されている気がします。

    システムで設定されているウィンドウの背景色を変えて試されてみてはいかがでしょうか。

    2012年6月22日 21:50
  • ご回答ありがとうございます。
    試してみましたがシステムカラーは関係ないようですね。
    hbrBackground で (HBRUSH)(COLOR_WINODW + 1) などとすれば
    WM_ERASEBKGNDでシステムカラーで描画されますが、やはり初めて描画される際には白くなってしまいます。

    また、システムカラーの変更は他のアプリにも影響を与えてしまうのでこのようなケースの解決策としては不適切と考えられます。

    それから、ボタンも子ウィンドウとして配置しているのですがはじめての描画のときには表示されません。これも修正する方法はないでしょうか。


    • 編集済み サウロ 2012年6月23日 1:09
    2012年6月23日 0:22
  • >しかし、はじめてウィンドウが描画されるのは WM_ERASEBKGND が飛んでくる よりも前です。
    >このとき、ウィンドウの背景色は hbrBackground で指定した色に関わらず、白くなります。

    非表示でウィンドウを作成して、最初の IDLE とかで表示してみるとかはどうでしょう?

    # 試してないので、それで目的が達成できるかどうか分かりませんが。。。

    2012年6月23日 1:15
  • >ダイアログボックスなどでは一瞬白くなるのはみたことがありませんので
    ダイアログボックスのウィンドウプロシージャをサブクラス化して挙動を調べてみました。

    普通につくったウィンドウと同じで WM_ERASEBACKGND がくる前に白で背景が描画されますね。私の勘違いでした。
    ボタンの描画もメインウィンドウが描画されたあとに描画されます。今まで気づかなかったのは描画が非常に高速なためでしょうか。

    渋木さんのおっしゃったことを参考に、WM_DWMNCRENDERINGCHANGED が飛んできたときにShowWindowをはじめて呼び出すように変更したところ、背景の白いウィンドウが描画されるのは防ぐことができるようになりました。
    ただ、このやり方で問題がないのか疑問です。

    もう少しスマートなやり方があればいいのですが、通常のダイアログウィンドウでも白い背景が描画されるところをみると一般的な方法はないのかもしれませんね。
    2012年6月23日 3:53
  • ウィンドウはボタンも含めてすべてプログラムで(リソースではなく)表示しているのでしょうか?

    それであるなら、再描画を禁止させておいて、描画処理が終わったあたりで再描画を許可するという処理を加えればいいと思います。InvalidateRect / ValidateRectだったかな。

    2012年6月23日 3:54
  • >ただ、このやり方で問題がないのか疑問です。

    ShoWindow するトリガーを何にするべきかは検討の余地ありますね。

    >それであるなら、再描画を禁止させておいて、描画処理が終わったあたりで再描画を許可するという処理を加えればいいと思います。InvalidateRect / ValidateRectだったかな。

    WndProc と関係なく背景が塗りつぶされている風なので、効果ありますかねー?

    試してみないと何ともですが。

    2012年6月23日 4:43
  • そーいえば、MFC でもウィンドウは非表示状態で作成して、ひととおりの初期化処理をした後に表示状態にしていたような気がします。

    >非表示でウィンドウを作成して、最初の IDLE とかで表示してみるとかはどうでしょう?

    ひょっとしたら、特にメッセージの待ち合わせとか要らないかもしれませんね。

    2012年6月23日 4:53
  • WM_DWMNCRENDERINGCHANGED が飛んできたときにShowWindow を呼び出してうまくいったように見えたのは勘違いでした。

    ShowWindow( hWnd, SW_SHOWNORMAL );
    とやると、その関数の内部で の中で背景の白いWindowが表示される処理が走り、更に WM_ERASEBACKGND が "Send"Message されるようです。
    つまり ShowWindow を使用している限り背景の白いWindowが表示されるのを防ぐことはできないのかもしれません。

    しかもShowWindowは呼び出されるたびに背景の白いWindowを描画しています。


    >ウィンドウはボタンも含めてすべてプログラムで(リソースではなく)表示しているのでしょうか?
    そうです。
    しかし、再描画というのはクライアント領域のみの話ですので、ちょっと今回の件とはずれてしまいます。


    >>非表示でウィンドウを作成して、最初の IDLE とかで表示してみるとかはどうでしょう?
    >ひょっとしたら、特にメッセージの待ち合わせとか要らないかもしれませんね。
    APIフックとかアブノーマルなことをするしかないのかもしれませんね
    2012年6月23日 5:54
  • >APIフックとかアブノーマルなことをするしかないのかもしれませんね

    WndProc に制御が渡る前の挙動なので、改変不可能かもしれないですねぇ。

    ひょっとしたら、XP 以前だと挙動が違うかもしれませんね。

    2012年6月23日 7:51
  • それであるなら、再描画を禁止させておいて、描画処理が終わったあたりで再描画を許可するという処理を加えればいいと思います。InvalidateRect / ValidateRectだったかな。

    その二つの API よりは LockWindowUpdate のほうが用途的に正しい気もしますが、話的に Window Manager の更新ロックが必要っぽいのでロックが効くのか怪しいところ・・・。

    どうも難しいという結論になりそうですね。

    2012年6月23日 10:17
  • LockWindowUpdate を使ってみましたが、ShowWindowについては特に影響はないようでした。
    やっぱりむずかしいんですね。
    それにしても、Windows の描画処理って結構無駄な処理が走るんですね。
    他のOSのことは知りませんけど。
    2012年6月24日 13:10
  • LockWindowUpdate を使ってみましたが、ShowWindowについては特に影響はないようでした。

    確認作業お疲れ様です、やはり無理ですか。ShowWindow の描画を停止したいなら、デスクトップウィンドウに LockWindowUpdate を行う必要があると思いますけど、やはり Aero デスクトップには効かないのかな・・・。

    API が正常動作してると考えるなら描画処理してない可能性かな?

    それにしても、Windows の描画処理って結構無駄な処理が走るんですね。

    個人的には、この現象が無駄処理とするより、変更する方が無駄処理追加と思いますけどね。

    2012年6月24日 23:33
  • まず、

    1.WM_ERASEBKGNDは、WM_PAINTメッセージで、BeginPaint()が実行されたときに
     発行されるメッセージです。

    これはご存知ですよね。そこで、最初の質問の中にある。

    >しかし、はじめてウィンドウが描画されるのは WM_ERASEBKGND が飛んでくる よりも前です。
    >このとき、ウィンドウの背景色は hbrBackground で指定した色に関わらず、白くなります。

    は、どのような方法で確かめたでしょうか。
    これが、「WM_PAINTメッセージ処理中の、BeginPaint()前でブレークしたときに
    既に白くなっている」のであれば、御主張の通りですが、それ以外は、勘違いの
    可能性があります。
    この前提が崩れると、以後の全ての議論の意味がなくなります。
    もう一度、確認してもらえると良いかもしれません。


    2012年6月25日 0:43
  • >しかし、はじめてウィンドウが描画されるのは WM_ERASEBKGND が飛んでくる よりも前です。

    >このとき、ウィンドウの背景色は hbrBackground で指定した色に関わらず、白くなります。

    は、どのような方法で確かめたでしょうか。

    なるほど 仲澤@失業者 様の言われるとおり、どこの現象を指しているか微妙な質問ですね。


    気になったので環境違いですが Win7 Home x64 の VS2012 のウィザードで生成できる Win32 空ウィンドウで確認してみたところ、ShowWindow の実行でウィンドウ枠が描画された後に WM_ERASEBKGND が投げられてウィンドウの背景を塗り潰していました。

    ※ShowWindow から WndProc に WM_ERASEBKGND のハンドラにブレークポイントを設定して確認しました。

    この WM_ERASEBKGND メッセージの受信前にウィンドウ枠が描画されていて中身が白であることを指しているのだと想像します。

    これが、「WM_PAINTメッセージ処理中の、BeginPaint()前でブレークしたときに既に白くなっている」のであれば、...

    自分も BeginPaint で WM_ERASEBKGND が投げられるて描画されると考えてましたが、どうも違うようですねデバッグで追ってもそういう挙動になっておらず WM_PAINT ハンドラに来る前に WM_ERASEBKGND が届いて BeginPaint() 前には既に指定の背景色で塗り潰されてました。

    挙動を追ってみた限り、ここの挙動変更は難しいでしょうけど、変更する利点が判らないところです・・・・。

    2012年6月25日 6:08
  •  kyano30様、テストご苦労様です。
    今回の件は文面から読み取ると(Expressの使用)、Win32SDKのようです。
    テストされたときのウイザードの設定がそうなっていると良いのですが。
    というのも、MFCの場合は
    1.フレームのクライアント領域はViewで埋められている(場合がほとんど)。
      つまり、フレームのウインドウクラスをいじっても意味がない。
    2.MFCのCViewのウインドウクラスの生成時はバックグラウンドが「白」で固定。
    です。もし、SDKでやっていたら、ごめんなさい。

    ■サウロさんへ
    さて、解決に至るかどうかは保障の限りではありませんが、一般に自分で背景塗りつぶしを
    行いたい場合や、頻繁に背景を変えたい場合は、RegisterClass()に渡す
    WINDCLASS::hbrBackgroundには、「透明なブラシ」を設定するのが、
    常識的段取りとなります。これは、システムが勝手に塗りつぶすのを抑える効果が
    あります。つまり、
      WNDCLASSEX wc;
       :
      wc.hbrBackground = ::GetStockObject( NULL_BRUSH);//NULLではありません
      RegisterClassEx( &wc);
    ですね。
    まず、これを試してみてはどうでしょう。

    ちなみに、 COLOR_WINODW + 1を指定した場合 は、
    「白」になります( +1した値を設定しなければならないと、
    ヘルプに記載されていますよね)。

    2012年6月25日 9:21
  • 仲澤@失業者 様のご指摘もっともですね。

    面倒なのでウィザードで作成しましたが、こちらのテストコードは Win32 SDK でウィンドウ1個のスケルトンが生成されています。


    StockObject の NULL_BRUSH は知らなかったので試してみましたが、 hbrBackground に NULL_BRUSH を指定しても同じ結果になってますね。

    ちなみに最初は COLOR_WINDOW + 2 に変更して WM_ERASEBKGND の背景塗潰し処理で別色(灰色?)に塗り潰されるのをみて動作確認してました。

    今回の NULL_BRUSH だと塗潰しされないでしょうから、動作してるか判別できてないですが hbrBackgroud に0x01900015 が入ってるのでブラシハンドルの取得はできてるようです。


    環境が違うので参考程度に・・・・・・。

    2012年6月25日 9:49
  • >仲澤さん
    おっしゃるとおりWin32SDKです。
    質問の最初にかいておくべきでしたね。

    NULL_BRUSH は以前使ったことがありますが、今回の件ついては関係ないですね。
    というのも hbrBackground は WM_ERASEBKGND がきたときに DefWindowProc が塗りつぶしに使用するものだからです。
    つまり、NULL_BRUSH を指定することと WM_ERASEBKGND でDefWinddowProcを呼ばないことは、ほぼ同じ挙動となります。

    処理の順番については,kyano30さんにも確認していただいていますが

    ShowWindow の呼び出し

    WM_SHOWINDOW
    WM_WINDOWPOSCHANGING
    (初めての描画) ☆
    WM_WINDOWPOSCHANGING
    WM_ACTIVATEAPP
    WM_NCACTIVATE
    WM_GETICON
    WM_GETICON
    WM_GETICON
    WM_ACTIVATE
    WM_IME_SETCONTEXT
    WM_IME_NOTIFY
    WM_SETFOCUS
    WM_NCPAINT
    WM_ERASEBKGND  ☆
    WM_WINDOWPOSCHANGED
    WM_SIZE
    WM_MOVE

    ShowWindow の終了
    UpdateWindowの呼び出し

    WM_PAINT

    UpdateWindowの終了

    というようになっています。
    ブレークポイントを張ったり
    下記のようなコードをかいて確認しました。
    なお、はじめての描画はDefWindowProcの中で行われているわけではありませんでした。

    LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
    {
        trace_message( msg );
        int ret = DefWindowProc( hwnd, msg, wp, lp );
        return ret;
    }

    >WM_ERASEBKGNDは、WM_PAINTメッセージで、BeginPaint()が実行されたときに
    >発行されるメッセージです。

    私もかなり前このような内容のものを読んだ記憶があるのですが、少なくとも現在の私の環境とは異なるようです。


    >kyano30さん

    >個人的には、この現象が無駄処理とするより、変更する方が無駄処理追加と思いますけどね。

    Windowsが無駄に白い背景を描画していることと、「変更が無駄である」ということとは別の次元の話です。

    また、今回の質問は、もし簡単に白背景描画をやめることができるのであれば実装したいという程度のものでした。ダイアログボックスの表示などでは白背景は表示されていないように「みえた」のでおそらく簡便な方法があるのではないかと思ったわけです。実際には勘違いでしたけど。

    とりあえず、もしできたとしても相当難しい作業であることはわかりました。
    描画の抑止は諦めることにします。
    Windows のAPIの挙動がよくわかったのは収穫です。
    皆様、大変参考になる情報をありがとうございました。
    2012年6月26日 17:08
  • >>WM_ERASEBKGNDは、WM_PAINTメッセージで、BeginPaint()が実行されたときに
    >>発行されるメッセージです。

    >私もかなり前このような内容のものを読んだ記憶があるのですが、少なくと
    >も現在の私の環境とは異なるようです。


    MSDN の BeginPaint の項目を読み直してみました。
    書いてある内容は、InvalidateRectの第3引数にTRUEを設定した場合などに,
    BeginPaint から WM_ERASEBKGND が Send されるということであって、
    それ以外の場合に WM_ERASEBKGND はプロシージャにこないという意味ではありませんね。

    実際InvalidateRectを実行して BeginPaint からWM_ERASEBKGND がSendされることを確認しました。
    2012年6月27日 15:16
  • なるほど了解しました。

    1.当該の背景消去(白色)は、ShowWindow()を実行すると必ず発生する。
    2.その呼び出し完了までの期間に、コールバックはWM_PAINTもWM_ERASEBKGNDも
     受け取らないが、背景が白色で塗りつぶされる。

    背景の塗りつぶしがコールバックの管理外に行われている以上、どうしようもないですね。
    従って、対策するとしたら

    3.ShowWindow()を使わない

    という手段をとる必要があるかもしれません。ウインドウを表示するには、
    ::SetWindowPos( hWnd, NULL,0,0,0,0,
       SWP_SHOWWINDOW|SWP_NOZORDER|SWP_NOSIZE
       |SWP_NOSENDCHANGING|SWP_NOREDRAW|SWP_NOMOVE);
    を使ってみてはどうでしょう。フラグの組み合わせはいろいろ試してみる必要があるかもしれません。

    2012年6月28日 1:11
  • 質問としては閉じたものと考えて返信を控えていましたが、

    Windowsが無駄に白い背景を描画していることと、「変更が無駄である」ということとは別の次元の話です。

    この部分で誤解されてそうだと思っていました。気分を害されたなら謝ります。

    また、今回の質問は、もし簡単に白背景描画をやめることができるのであれば実装したいという程度のものでした。ダイアログボックスの表示などでは白背景は表示されていないように「みえた」のでおそらく簡便な方法があるのではないかと思ったわけです。実際には勘違いでしたけど。

    この部分が認識として大きく違う部分だと考えての感想でした。

    こちらとしては「もし簡単に白背景描画をやめることができる」としても、そうすることが「問題のある変更」で「白背景描画」=「無駄な描画ではない」という風に感じたことからの感想です。

    何故そう思ったかというと、

    1.この「ウィンドウフレーム&白背景描画」を行っているのが Window Manager で Window Class の動作ではなさそうである。

    2.Window Manager がウィンドウ描画する場合は、クライアント領域を描画するほうが画面が崩れない。

    ということから「無駄」とは思えないという個人の推測&感想です。

    役割の違う部分に動作変更を行い、それを前提とする実装をするというのも、無駄というか問題がありそうだったので・・・・。

    ※とはいえ Window Manager 関係の API でも指定できそうなものに心当たりはないですけど。


    MSDN の BeginPaint の項目を読み直してみました。
    書いてある内容は、InvalidateRectの第3引数にTRUEを設定した場合などに,
    BeginPaint から WM_ERASEBKGND が Send されるということであって、
    それ以外の場合に WM_ERASEBKGND はプロシージャにこないという意味ではありませんね。

    てっきり Window の初回描画は InvalidateRect の第3引数 TRUE と同じ動作になると思い込んでました。

    思い込みや想像による勘違いになってると、上記の内容も的はずれなんでしょうね・・・・。

    2012年6月28日 2:21
  • >仲澤さん
    WM_PAINTがはじめてとんでくるのはUpdateWindow の中ですが、WM_ERASEBKGNDはShowWindow から処理が戻ってくる前に飛んできます。

    SetWindowPosを使ってみましたが、呼出し後、最初にプロシージャにメッセージが飛んでくる前に描画されてしまいますね。
    しかもShowWindowと違い、WM_ERASEBKGNDは飛んでこないので背景は白いままになってしまいます。

    >kyano30さん
    フレームが描画されているのに、クライアント領域だけ何も描画されないというのは問題かもしれません。
    しかし描画すべきものをすべてDCにかきこんだあと初めて表示するような処理がOSレベルでできないというのはちょっと納得できないんですよね。
    DirectXとかOpenGLを使えばやれそうなきもします。


    質問はこれで閉じさせていただきます。
    ありがとうございました。
    2012年6月29日 16:30
  • 閉じるといってるのに返信いれるのもなんなのですが、

    しかし描画すべきものをすべてDCにかきこんだあと初めて表示するような処理がOSレベルでできないというのはちょっと納得できないんですよね。
    DirectXとかOpenGLを使えばやれそうなきもします。

    DirectX 等でもできるでしょうが、そういうことが行いたいなら Layered Window でもできなくはないと思います。
    ちょっと古い内容ですが、レイヤードウィンドウ あたりが参考になると思います。



    フレームが描画されているのに、クライアント領域だけ何も描画されないというのは問題かもしれません。

    昔はアプリ無応答で同様状態によくなって、画面崩れで印象がよくなかったんですけどね・・・。

    ----------------------------------------------------------------------------------------------

    「DC に書込んだ後に表示」ってことだったので的外れだった気がしてきました、無駄な書込みごめんなさい。

    • 編集済み kyano30 2012年6月30日 3:40 追記
    • 回答としてマーク サウロ 2012年7月6日 15:08
    2012年6月30日 2:59
  • http://blogs.msdn.com/b/oldnewthing/archive/2004/01/23/62123.aspx

    ここに書いてある通り、XPでは、「WM_PAINTに応答がないWindowはウインドウクラスのブラシで塗られる(NULL_BRUSHは白扱い)」という話で、

    だから、十分早くメッセージに応答すれば、背景ブラシで塗られることもない(BREAKしてデバッガで待つとだめ)ってことでは?


    jzkey

    2012年6月30日 13:50
  • jzkey 様

    XP ではないですがテストした時の自分の見解では、ウィンドウクラス側の描画前に常にクライアント領域を白描画していると思っています。

    BREAK 無 で NULL_BRUSH 指定でも白ウィンドウという結果でしたので、NULL_BRUSH は塗潰し処理無しと記憶してますから、その前に白になってないとデスクトップの表示内容のままという結果が予想されますから・・・。

    そのブログの記述のように無応答アプリの上にウィンドウを重ねたりしたら白になっちゃいますが、最小化&最大化でもクライアント領域が真っ白になることにも納得がいきます。

    他にも無応答アプリのクライアント領域は白になるという記述は、どこかでみたのですがサイトが見当たりません。

    もう閉じた質問ですが、自分の返信と関連してそうだったので・・・・。

    2012年6月30日 15:00
  • 目的は知りませんがウィンドウ リダイレクト時に固定の WHITE_BRUSH で fill されることになっている以上、
    有意義ではないですが、DWM を disable にするか、最初から layered にしておくしかないのでは。
    2012年7月1日 12:09
  • HomeCloset 様

    まったく同感なので Layered Window の紹介サイトを返信してみたのですが、質問の内容からいくと DWM を disable にする方が目的に近いことかなと後で思いました。

    自分はそっちの方法はまったく判らないので、知っておられるならヒント程度を教えていただきたいと思ったので返信致しました。

    --------------------------------------------------------------------------------------------------

    自分は質問者じゃないので単なる好奇心です、よって回答マークはつけられません・・・。

    • 編集済み kyano30 2012年7月1日 13:08
    2012年7月1日 13:05
  • CreateWindowEx の第一引数にWS_EX_LAYERED を指定して、SetLayeredWindowAttributes を使用することにより、起動時に一瞬白くなる問題を回避することができるようになりました。

    一応いまつくっているアプリはXP上でも動くようにしたいと思っていますのでDWMでの制御はやめたほうが無難かなと思いました。

    一度質問を閉めましたが、もう少しだけ様子をみて解決としますね。
    ありがとうございました。
    2012年7月1日 14:20