none
CS_SAVEBITSクラススタイルの使用方法 RRS feed

  • 質問

  • Visual C++に限った話でもないのですが、VCでアプリを書いているのでここで質問させてください。

    CS_SAVEBITSクラススタイルというのがありますが、このスタイルの意味は以下のような理解で間違っていないでしょうか?
    (特にウィンドウの前後関係と、どちらのウィンドウにCS_SAVEBITSスタイルを与えるか?、が英語のMSDNドキュメントからは100%汲み取れないです)

    私の理解:
    2つの重なり合うウィンドウがあるとします。このウィンドウは親子関係であるか、兄弟関係であるかは関係なく、単純に片方がもう片方に重なっているとします。
    重なるウィンドウのうち、手前にある方を仮にwndTopとします。奥にある方をwndBottomとします。
    wndTopのウィンドウクラスとしてCS_SAVEBITSスタイルが設定されているとします。
    ここで、wndTopウィンドウをShowWindow(SW_HIDE)したとき、wndBottomは再描画する必要があるが、wndTopに設定されたCS_SAVEBITSスタイルのおかげでwndBottomのWM_PAINTハンドラは呼ばれずに、saveされたビットマップから再描画される(あるいは、システムはWM_PAINTを呼ばずに済むように最大限“努力”してくれる)
    --ここまで--

    自分自身なんだかおかしいような気がします。CS_SAVEBITSを設定しビットマップを保存するように指定するウィンドウとしては再描画を行う方(つまり、下にあるwndBottom側)である方が理解しやすいのですが、しかしMSDNによれば「CS_SAVEBITSはメニューやダイアログのように小さくて一時的に表示するウィンドウのときに有用」というようなくだりがありますので、このクラススタイルはむしろ手前に表示されるウィンドウ(=wndTop)に設定されるべきだと考えました。

    簡単なテストプログラムを作って試してもみました。
    親子関係にあるwndTopとwndBottomを作って、wndTop側をタイマーでShowWindow(SW_SHOWNA or SW_HIDE)させてみて、wndBottom側のWM_PAINTが呼ばれるかどうかをTRACE()出力で確認する、というものです。
    私の理解が正しければ、CS_SAVEBITSクラススタイルはwndTop側のみに与えるべきですが、念のためwndBottom側にも両方に与えてみました。

    結果は、wndTopがSW_HIDEされると、wndBottomのWM_PAINTが呼ばれていました。保存されたビットマップからシステムが自動的に再描画してくれませんでした。
    CS_SAVEBITSはあまり有効に機能しないのでしょうか?それとも、何か他に必要条件があるのでしょうか?
    ちなみに、このテストプログラムはXP(x86)と、Win7RTM(x64)で試してみたのですが、やはりWM_PAINTは呼ばれていてCS_SAVEBITSが機能しているようには見えませんでした。

    念のため、テストプログラムの一部を書いておきます(MFCで書いています)。よろしくお願いいたします。

    ウィンドウのクラス定義
    class WndTop : public CWnd { afx_msg void OnPaint(...); }
    class WndBottom : public CWnd { afx_msg void OnPaint(...); }

    void WndTop::OnPaint()
    {
        CPaintDC dc(this);
        TRACE("WndTop::OnPaint() is called\n");
        ~ 適当な描画処理 ~
    }
    void WndBottom::OnPaint()
    {
        CPaintDC dc(this);
        TRACE("WndBottom::OnPaint() is called\n");
        ~ 適当な描画処理 ~
    }

    ウィンドウのクラスインスタンス
    WndTop m_wndTop;
    WndBottom m_wndBottom;

    ウィンドウの生成処理
    CString clsBottom = AfxRegisterWndClass(CS_VREDRAW|CS_HREDRAW|CS_DBLCLK);
    VERIFY(m_wndBottom.Create(clsBottom, "", WS_CHILD|WS_VISIBLE, rectBottom, 適当な親ウィンドウ, ...));

    CString clsTop = AfxRegisterWndClass(CS_VREDRAW|CS_HREDRAW|CS_DBLCLK|CS_SAVEBITS);
    VERIFY(m_wndTop.Create(clsTop, "", WS_CHILD|WS_VISIBLE, rectTop, &m_wndBottom, ...));  //<--親ウィンドウとしてm_wndBottomを指定

    適当なウィンドウのWM_TIMERハンドラ内で・・・
    BOOL now_showing = ::IsWindowVisible(m_wndTop);
    m_wndTop.ShowWindow(now_showing ? SW_HIDE : SW_SHOWNA);        // <-- wndTopウィンドウ表示を1秒ごとにトグル
    2009年9月29日 1:21

すべての返信

  • 確かに m_wndBottom に WM_PAINT が飛んできますね。
    WS_CHILD の場合は、この例の「適当な親ウィンドウ」あるいは Window Manager が m_wndBottom に対して、「おい、お前んとこの子ウィンドウ(m_wndTop)の状態が変わったゾ」といった具合に(CS_SAVEBITS の有無に関わらず)WM_PAINT を発行してしまうのではないかと予想します。

    試しに WS_POPUP で実験すると、思った通りの動作になりました。
    もともとポップアップメニューやダイアログで使われることを想定したものなので、WS_CHILD で動かないのは仕方がないような気もしますが、この点については私の勝手な解釈なので全く自信はありません。

    以下、お試しコード。

    CRect rectBottom(0, 0, 100, 100);
    CString clsBottom = AfxRegisterWndClass(CS_VREDRAW|CS_HREDRAW|CS_DBLCLKS);
    VERIFY(m_wndBottom.CreateEx(0, clsBottom, _T(""), WS_POPUP|WS_VISIBLE, rectBottom, this, 0));

    CRect rectTop(50, 50, 150, 150);
    CString clsTop = AfxRegisterWndClass(CS_VREDRAW|CS_HREDRAW|CS_DBLCLKS|CS_SAVEBITS);
    VERIFY(m_wndTop.CreateEx(0, clsTop, _T(""), WS_POPUP, rectTop, this, 0));
    2009年9月29日 5:35
  • zakioさん、ありがとうございます。

    確かにWS_POPUPにするとWM_PAINTが来ずにwndBottomの表示内容が再表示されますね。それならそうとMSDNライブラリに書いておいてくれたらよかったのに・・・。と思いますが、WS_CHILDでも何か他の条件が揃えば使えるのでしょうかね~。

    CS_SAVEBITSを指定すればいつでもどんな条件でも機能するとは思っていない(GDIリソースやビットマップを保持するメモリの制限もあるでしょうし・・・)のですが、とりあえずどのくらいの大きさのビットマップなら保持してくれるかも調べてみました。
    こちらで試したところ、128x128程度のビットマップならCS_SAVEBITSで再表示してくれましたが、これより多少大きくなると(135x135とか)WM_PAINTで再描画するよう要求されました。128x128というのはメニューウィンドウを表示するにしてもちょっと小さすぎるのではないでしょうかね?このサイズは環境にもよるでしょうからあまり当てには出来ませんし、最初からダブルバッファでビットマップを保持しておいてそこからBitBltするような仕組みでWM_PAINTを処理する方が確実ではないかと思いました。

    CS_SAVEBITSがいま着手している件に使えるかどうかは微妙ですが、このフラグの使い方は大変勉強になりました。ありがとうございました。
    2009年9月30日 3:57