トップ回答者
ウィンドウの背景色を変更する CMDIFrameWndExの場合

質問
-
Win7 Pro x32
VC2008
はじめて投稿させていただきます。よろしくお願いいたします。
下記サポートページを参考にウィンドウの背景色を変更しようとしましたが、
http://support.microsoft.com/kb/103786/ja
CMainFrameの基本クラスがCMDIFrameWndExのためか、OnCreate()で追加したm_wndNewClient.SubclassWindow()内でAssertionが発生します。
VisualStudioライクではないアプリの場合(CMDIFrameWnd)は問題は発生せず、背景色は変更できます。
CMDIFrameWndExを使用した場合は背景色を変更できないのでしょうか。
適切なやりかたをご存知でしたらご教授ください。
回答
-
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
-
サブクラス化を間違えてますね。
ThisFrame = this;
m_OldProc = ( WNDPROC)GetWindowLongPtr( m_hWndMDIClient, GWLP_WNDPROC);
SetWindowLongPtr( m_hWnd, GWLP_WNDPROC, ( LONG_PTR)WndProc);サブクラス化する対象のウィンドウプロシジャーを差替えるのですから、WndProc を呼んでもらうウィンドウハンドルを良く考えましょう。
----------------------------------------------------------------------------------------------------
書かないでおこうと思っていたのですが、気になってしょうがないのでもう一点。リソースリークにも注意しましょう。
すべての返信
-
MDIClient領域 だと思うので、VS2012 で試してみたところサブクラス化で Assertion が発生しますね。
詳しく見たわけではありませんが、CWnd::SubclassWindow のヘルプに「この関数が呼び出されるときに、ウィンドウが MFC オブジェクトに結び付けられていないようにしてください。」と記述されているので、 CMDIFrameWndEx が、既に MDIClient をサブクラス化してるからじゃないですかね?
Assertion が発生しても継続させれば背景色は変わるようですが、CMDIFrameWndEx として正常動作しないようで、CMDIFrameWndEx では MDIClient をMFC オブジェクトとして作成するようですので、そちらののサブクラス化が解除されてるように思えます・・・。
MFC を使わず API でサブクラス化しないと CMDIFrameWndEx ではうまく動かないのではないですかね?
-
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
-
仲澤@失業者 様が丁寧な説明をされているので特に書くこともないなと思ってましたが、不可思議なことになってるようなので試してみました。
しかし、こちらの環境 ( Win7 x64 - VS2012 ) だと予定どおりの動作をしています。
仲澤@失業者 様が例示されたコードで問題ないようですがソラ書きですから、意訳的な適用間違いしてるのではないですかね?
----------------------------------------------------------------------------------------------------
しかし MFC の xxxxEx 系のクラスは機能は良いですがカスタマイズしにくい実装で困ったものです・・・。
-
確認いただきありがございました。
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;
}以上、よろしくお願いいたします。
-
サブクラス化を間違えてますね。
ThisFrame = this;
m_OldProc = ( WNDPROC)GetWindowLongPtr( m_hWndMDIClient, GWLP_WNDPROC);
SetWindowLongPtr( m_hWnd, GWLP_WNDPROC, ( LONG_PTR)WndProc);サブクラス化する対象のウィンドウプロシジャーを差替えるのですから、WndProc を呼んでもらうウィンドウハンドルを良く考えましょう。
----------------------------------------------------------------------------------------------------
書かないでおこうと思っていたのですが、気になってしょうがないのでもう一点。リソースリークにも注意しましょう。
-
ご注意いただいた件ですが、デバッグモード終了後、出力ウィンドウにリーク情報はありませんが、
前回、私が投稿したソースではリークが発生する可能性があるのでしょうか。
可能性ではなく投稿ソースではリークしてます。
どの言語でもシステムリソースのリークは表面化しにくいバグの原因になりえますので、例示ソースに含まれてるのが気になっての注意です。
条件的に問題ない場合もありえますが、個人的にバグと考るようにした方がよいと思ってます。
ちなみにデバッグモードで確認できるリーク情報は CRT で取得したメモリリーク情報のみで API で取得するGDIオブジェクト等のシステムリソースのリークを把握しているわけではありません。
Create したものは Delete しないとリークすると考えるのが C 言語系 では普通です。