none
ウィンドウの背景色を変更する CMDIFrameWndExの場合 RRS feed

回答

  • kyano30さんの指摘どおりですね。
    m_bDoSubclassがTRUEならm_wndClientAreaがサブクラス化しちゃってますね。

    1.ご希望のエリアはクラス名"mdiclient"で、HWND = CMDIFrameWnd::m_hWndMDIClientです。
      (フレームのクライアントエリアではありません。)
    2.virtual BOOL CMDIFrameWnd::CreateClient()で作成されています。
    3.しかし、オーバーライドされたCMDIFrameWndEx::OnCreateClient()で
     CMDIClientAreaWndにサブクラス化 されています。メンバ名はm_wndClientArea。
     この実装が工夫の余地をなくしてしまっていると考えます。
     (レドモンドの連中はもう少し気を利かせるべきだったと感じます・・・ポインタで実装するとか)。
    4.従って、再度CWnd::SubclassWindow()でサブクラス化することができません。

    解決方法もkyano30さんの指摘どおりだと考えられます。
    むりやりやるとするとフレームのOnCreate()で、

    // WNDPROC m_OldProcはメンバ
    static  MainFrame *ThisFrame;
    int MainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct){
        if (CMDIFrameWndEx::OnCreate(lpCreateStruct) == -1) return -1;
        ThisFrame = this;
        m_OldProc = ( WNDPROC)GetWindowLongPtr( m_hWndMDIClient, GWLP_WNDPROC);
        SetWindowLongPtr( ex_hWnd, GWLP_WNDPROC, ( LONG_PTR)WndProc);
    }
    LRESULT CALLBACK
    WndProc( HWND ex_hWnd, UINT ex_Msg, WPARAM ex_Wpar, LPARAM ex_Lpar)
    {
        return ThisFrame->CallBack( ex_hWnd, ex_Msg, ex_Wpar, ex_Lpar);
    }
    LRESULT CALLBACK
    MainFrame::CallBack( HWND ex_hWnd, UINT ex_Msg, WPARAM ex_Wpar, LPARAM ex_Lpar)
    {
        if( WM_ERASEBKGND == ex_Msg){
            RECT    rc;
            HDC      hDC  = ( HDC)ex_Wpar;
            ::GetClientRect( ex_hWnd, &rc);
            ::FillRect( ex_hDC, &rc, ( HBRUSH)ご希望の背景ブラシ);
            return TRUE;
        }
        else{
            return m_OldProc( ex_hWnd, ex_Msg, ex_Wpar, ex_Lpar);
        }
    }

    てな、感じ。ソラで書いてるので、エラーなどはご容赦ください。

    • 回答としてマーク matsu-h 2012年10月20日 7:30
    2012年10月18日 1:38
  • サブクラス化を間違えてますね。

    ThisFrame = this;

    m_OldProc = ( WNDPROC)GetWindowLongPtr( m_hWndMDIClient, GWLP_WNDPROC);
    SetWindowLongPtr( m_hWnd, GWLP_WNDPROC, ( LONG_PTR)WndProc);

    サブクラス化する対象のウィンドウプロシジャーを差替えるのですから、WndProc を呼んでもらうウィンドウハンドルを良く考えましょう。

    ----------------------------------------------------------------------------------------------------
    書かないでおこうと思っていたのですが、気になってしょうがないのでもう一点。

    リソースリークにも注意しましょう。

    • 編集済み kyano30 2012年10月19日 18:12 追記
    • 回答としてマーク matsu-h 2012年10月20日 7:30
    • 回答としてマークされていない matsu-h 2012年10月20日 7:30
    • 回答としてマーク matsu-h 2012年10月20日 7:31
    2012年10月19日 17:06

すべての返信

  • え~と、まず前提を確認しなければなりません。

    通常のつくりの場合、CMDIFrameWndExの「クライアント領域」は
    子ウインドウで埋め尽くされているため、「1dotも表示されない」のが普通です。
    表示されない部分の色を変えることに、意義を感じないのですが、いかがでしょうか。

    ひょっとしてMDIClientのクライアント領域の色を変更しようとしてますでしょうか、
    それとも、個々のViewのクライアント領域でしょうか。

    2012年10月17日 4:32
  • 回答いただきありがとうございます。
    下記の「このエリア」の部分の色(最終的には画像を貼る)を変えたいのですが、
    MDIClient領域なのでしょうか。
    もしそうであれば、CMainFrame::OnCreate()ではなく、ちがうクラスのOnCreate()に追加するとよいのでしょうか。

    MFCアプリケーションウィザードでプロジェクト形式から「VisualStudio」を選択して作成したアプリケーションの場合です。

    よろしくお願いいたします。

    2012年10月17日 6:13
  • MDIClient領域 だと思うので、VS2012 で試してみたところサブクラス化で Assertion が発生しますね。

    詳しく見たわけではありませんが、CWnd::SubclassWindow のヘルプに「この関数が呼び出されるときに、ウィンドウが MFC オブジェクトに結び付けられていないようにしてください。」と記述されているので、 CMDIFrameWndEx  が、既に MDIClient をサブクラス化してるからじゃないですかね?

    Assertion が発生しても継続させれば背景色は変わるようですが、CMDIFrameWndEx として正常動作しないようで、CMDIFrameWndEx では MDIClient をMFC オブジェクトとして作成するようですので、そちらののサブクラス化が解除されてるように思えます・・・。

    MFC を使わず API でサブクラス化しないと CMDIFrameWndEx ではうまく動かないのではないですかね?

    2012年10月17日 16:37
  • kyano30さんの指摘どおりですね。
    m_bDoSubclassがTRUEならm_wndClientAreaがサブクラス化しちゃってますね。

    1.ご希望のエリアはクラス名"mdiclient"で、HWND = CMDIFrameWnd::m_hWndMDIClientです。
      (フレームのクライアントエリアではありません。)
    2.virtual BOOL CMDIFrameWnd::CreateClient()で作成されています。
    3.しかし、オーバーライドされたCMDIFrameWndEx::OnCreateClient()で
     CMDIClientAreaWndにサブクラス化 されています。メンバ名はm_wndClientArea。
     この実装が工夫の余地をなくしてしまっていると考えます。
     (レドモンドの連中はもう少し気を利かせるべきだったと感じます・・・ポインタで実装するとか)。
    4.従って、再度CWnd::SubclassWindow()でサブクラス化することができません。

    解決方法もkyano30さんの指摘どおりだと考えられます。
    むりやりやるとするとフレームのOnCreate()で、

    // WNDPROC m_OldProcはメンバ
    static  MainFrame *ThisFrame;
    int MainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct){
        if (CMDIFrameWndEx::OnCreate(lpCreateStruct) == -1) return -1;
        ThisFrame = this;
        m_OldProc = ( WNDPROC)GetWindowLongPtr( m_hWndMDIClient, GWLP_WNDPROC);
        SetWindowLongPtr( ex_hWnd, GWLP_WNDPROC, ( LONG_PTR)WndProc);
    }
    LRESULT CALLBACK
    WndProc( HWND ex_hWnd, UINT ex_Msg, WPARAM ex_Wpar, LPARAM ex_Lpar)
    {
        return ThisFrame->CallBack( ex_hWnd, ex_Msg, ex_Wpar, ex_Lpar);
    }
    LRESULT CALLBACK
    MainFrame::CallBack( HWND ex_hWnd, UINT ex_Msg, WPARAM ex_Wpar, LPARAM ex_Lpar)
    {
        if( WM_ERASEBKGND == ex_Msg){
            RECT    rc;
            HDC      hDC  = ( HDC)ex_Wpar;
            ::GetClientRect( ex_hWnd, &rc);
            ::FillRect( ex_hDC, &rc, ( HBRUSH)ご希望の背景ブラシ);
            return TRUE;
        }
        else{
            return m_OldProc( ex_hWnd, ex_Msg, ex_Wpar, ex_Lpar);
        }
    }

    てな、感じ。ソラで書いてるので、エラーなどはご容赦ください。

    • 回答としてマーク matsu-h 2012年10月20日 7:30
    2012年10月18日 1:38
  • 詳しい回答ありがとうございます。

    試しましたが、下記変わりませんでした。
    正確には、一度は変わっているのですが、変更が上書きされてしまうようです。

    FillRectの直後にブレークをかけると下記となりますが、(ブラシはRGB(0, 128, 0) )

    続行すると下記に戻ります。

    これから何かわかることがありますでしょうか。

    2012年10月18日 8:44
  • WM_ERASEBKGNDで、FillRectした後、

    return FALSE;  // なぁんもしなかったので処理を継続してねの意。つまり、デフォルトの消去が実行される

    をしてしまっているからではないでしょうか。

    return TRUE;  // ★消去は終わったので、もう処理しないでねの意

    してみてください。

    2012年10月18日 9:27
  • 回答いただきありがとうございます。

    前回いただいたようにFillRectした後は、
    return TRUE;
    を実行しております。

    デバッグモードで少ししてみたところ、WM_PAINTをm_OldProc()で処理しているときに上書きされているようです。
    かと言ってWM_PAINTを何もしないでTRUEで返すようにすると各ペイン、ツールバーなどが再描画されないです。
    添付は最小化から元に戻した状態です。

    上記からウィンドウ内をマウスで移動すると、部分的に再描画がかかった状態です。

    あきらめた方がよさそうでしょうか。

    2012年10月19日 5:44
  • 仲澤@失業者 様が丁寧な説明をされているので特に書くこともないなと思ってましたが、不可思議なことになってるようなので試してみました。

    しかし、こちらの環境 ( Win7 x64 - VS2012 ) だと予定どおりの動作をしています。

    仲澤@失業者 様が例示されたコードで問題ないようですがソラ書きですから、意訳的な適用間違いしてるのではないですかね?

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

    しかし MFC の xxxxEx 系のクラスは機能は良いですがカスタマイズしにくい実装で困ったものです・・・。

    2012年10月19日 11:28
  • 確認いただきありがございました。
    VS2012では問題なくMdiClientの背景色が変更できましたか。

    > 意訳的な適用間違いしてるのではないですかね?

    "MainFrrm.cpp"に追加したコードは下記です。

    // WNDPROC m_OldProcはメンバ
    static  CMainFrame *ThisFrame;                                                             

    LRESULT CALLBACK
    WndProc( HWND ex_hWnd, UINT ex_Msg, WPARAM ex_Wpar, LPARAM ex_Lpar)
    {
        return ThisFrame->CallBack( ex_hWnd, ex_Msg, ex_Wpar, ex_Lpar);
    }


    LRESULT CALLBACK
    CMainFrame::CallBack( HWND ex_hWnd, UINT ex_Msg, WPARAM ex_Wpar, LPARAM ex_Lpar)
    {
    if( WM_ERASEBKGND == ex_Msg){
    RECT rc;
    HDC hDC = ( HDC)ex_Wpar;

    HBRUSH hBkBrush = CreateSolidBrush(RGB(0, 128, 0));

    ::GetClientRect( ex_hWnd, &rc);
    ::FillRect( hDC, &rc, ( HBRUSH)hBkBrush);

    return TRUE;
    }
    else{
    return m_OldProc( ex_hWnd, ex_Msg, ex_Wpar, ex_Lpar);
    }
    }

    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    if (CMDIFrameWndEx::OnCreate(lpCreateStruct) == -1)
    return -1;

    ThisFrame = this;
    m_OldProc = ( WNDPROC)GetWindowLongPtr( m_hWndMDIClient, GWLP_WNDPROC);
    SetWindowLongPtr( m_hWnd, GWLP_WNDPROC, ( LONG_PTR)WndProc);

    // その他生成
    ・・・

    return 0;
    }

    以上、よろしくお願いいたします。

    2012年10月19日 12:59
  • サブクラス化を間違えてますね。

    ThisFrame = this;

    m_OldProc = ( WNDPROC)GetWindowLongPtr( m_hWndMDIClient, GWLP_WNDPROC);
    SetWindowLongPtr( m_hWnd, GWLP_WNDPROC, ( LONG_PTR)WndProc);

    サブクラス化する対象のウィンドウプロシジャーを差替えるのですから、WndProc を呼んでもらうウィンドウハンドルを良く考えましょう。

    ----------------------------------------------------------------------------------------------------
    書かないでおこうと思っていたのですが、気になってしょうがないのでもう一点。

    リソースリークにも注意しましょう。

    • 編集済み kyano30 2012年10月19日 18:12 追記
    • 回答としてマーク matsu-h 2012年10月20日 7:30
    • 回答としてマークされていない matsu-h 2012年10月20日 7:30
    • 回答としてマーク matsu-h 2012年10月20日 7:31
    2012年10月19日 17:06
  • kyano30さん、回答いただきありがとうございます。

    ハンドルを入れ替えて、目的通り動作しました。
    重ねてお礼申し上げます。

    ご注意いただいた件ですが、デバッグモード終了後、出力ウィンドウにリーク情報はありませんが、
    前回、私が投稿したソースではリークが発生する可能性があるのでしょうか。

    以上、よろしくお願いいたします。

    2012年10月20日 1:32
  • ご注意いただいた件ですが、デバッグモード終了後、出力ウィンドウにリーク情報はありませんが、
    前回、私が投稿したソースではリークが発生する可能性があるのでしょうか。

    可能性ではなく投稿ソースではリークしてます。

    どの言語でもシステムリソースのリークは表面化しにくいバグの原因になりえますので、例示ソースに含まれてるのが気になっての注意です。

    条件的に問題ない場合もありえますが、個人的にバグと考るようにした方がよいと思ってます。

    ちなみにデバッグモードで確認できるリーク情報は CRT で取得したメモリリーク情報のみで API で取得するGDIオブジェクト等のシステムリソースのリークを把握しているわけではありません。

    Create したものは Delete しないとリークすると考えるのが C 言語系 では普通です。

    2012年10月20日 7:01
  • Create したものは Delete しないとリークすると考えるのが C 言語系 では普通です。

    kyano30さん、ご忠告いただきありがとうございます。

    久しく、描画関係のコードを書いていませんでしたので、忘れておりました。
    重ねてお礼申し上げます。

    以上です。

    2012年10月20日 7:27