none
MFC MDI アプリケーションで子フレームをアイコン化した際に位置がずれる RRS feed

  • 質問

  • お世話になります。

    MFC MDI アプリケーションに関する質問です。

    複数のドキュメントを開き、それらを全てアイコン化して、一つのドキュメントを最大化表示した後に、再びアイコン化すると最大化する前よりも下にずれた位置にアイコン化されます。クラシックスタイル、XP スタイル共に同じ現象が起きます。

    回避方法をご存知の方、よろしくお願いします。
    2009年8月6日 7:03

回答

  • 手元にあった、VS2005のMFCのソースでは、
    CMDIChildWnd::UpdateClientEdge()で変更しているようです。
    • 回答としてマーク ミッヒー 2009年10月29日 7:47
    • 回答としてマークされていない ミッヒー 2009年10月29日 10:44
    • 回答としてマーク ミッヒー 2009年11月1日 6:59
    2009年10月29日 1:03
  • 内容に対するレスでなくてすいません。

    このスレッドのウォッチングはしているのですが、
    何分、MDIは殆ど組んだ事が無いので話に参加で来ていないのが実情です。
    SDIなら結構作成した事があるんですけれど、不思議とMDIはないです。
    MDIは複雑になりそうなので必要がない限り採用する気が無かったと言うのが正直な所。

    ただ、技術的にMFCのフレームワークにそういう不具合があると言う事は知識としては必要だと思うので
    このスレッドその物は後続の人間の役に立つと思いますよ。
    こう言う情報はなかなか出てこないので、顛末まで書いていただけるととても助かります。
    参考にさせていただいています。

    なんか、たんなる応援みたいになってますが、見ているよと言うことを書きたかったので。

    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 回答としてマーク ミッヒー 2009年11月1日 7:01
    2009年10月29日 2:39
  • ソースを見る限りでは、結局、MDI子ウィンドウ内のViewにWS_EX_CLIENTEDGEが
    設定されていなければ、CMDIChildWnd::UpdateClientEdge()でMDIクライアントを
    あれこれされることはないようです。

    ただし、これはCFrameWnd::CreateView()でViewを作成している一般的な場合で、
    MDI子ウィンドウにWS_EX_CLIENTEDGEが設定されているようだと、結局だめです。

    BOOL CHogeView::PreCreateWindow(CREATESTRUCT& cs)
    {
      BOOL result = CView::PreCreateWindow(cs);
      cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
      return result;
    }
    

    CMDIChildWnd::UpdateClientEdge()の他に、
    CFrameWnd::CreateView()やCFrameWnd::PreCreateWindow()なども
    見てみるとお分かりいただけると思います。

    ただ、そもそも、CViewにWS_EX_CLIENTEDGEがなくてもいいのかどうかは
    実装する人の事情によりますので、これを外せないなら別な方法を取るしかないです。
    • 回答としてマーク ミッヒー 2009年10月29日 7:47
    • 回答としてマークされていない ミッヒー 2009年10月29日 10:44
    • 回答としてマーク ミッヒー 2009年11月1日 6:59
    2009年10月29日 6:08
  • WinMe で動かそうとしたのですが、プロジェクトを WinMe で動くように設定が出来ていなかったらしく、起動しませんでした。

    Visual Studio 2005 で MFC MDI アプリケーションとして雛形を作成し、それだけをビルドしたものも動かなかったので、プロジェクトの設定がなされていない、というだけだと思います。まだ Visual C++ 6.0 をうろつく事が多いもので・・・。

    そんな理由から WinMe での動作確認はまたの機会となってしまいました。

    その機会がいつになるのか判らないので、WinMe と欲張って Win2000 での検証は皆さんにお願いできたら・・・等と都合のいい事を思いつつ。


    それよりも、もう一度皆さんにお詫びをしなければなりません。

    コピペの際に間違っており、以前示したコードはコンパイルエラーとなります。また、無駄なコードもありました。
    それと、

    #ifndef _AFX_NO_OLE_SUPPORT
        if (m_pNotifyHook != NULL && m_pNotifyHook->OnDocActivate(bActivate))
            bHooked = TRUE;
    #endif

    部分の対応方法と CChildFrame クラスをドキュメントタイプ分準備すればいいのか、の検証が終わったので、改めて MFC MDI アプリケーション、MFC アプリケーション ウィザードで作成された雛形のどの部分をどうしたのか、今回はもう少し判りやすい様に書いてみたつもりです・・・。


    ChildFrm.h

    class CChildFrame : publc CMDIChildWnd
    {

    // 操作
    protected:
        BOOL UpdateClientEdge(LPRECT lpRect = NULL);

    // 生成された、メッセージ割り当て関数
    protected:
        afx_msg void OnWindowPosChanging(WINDOWPOS* lpWndPos);
        afx_msg void OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd);

    };


    ChildFrm.cpp

    // ChildFrm.cpp : CChildFrame クラスの実装
    //
    #include "stdafx.h"
    #include "MDI.h"    // CWinApp 派生クラス

    #include "MDIDoc.h"     // CDocument 派生クラス
    #include "MDIView.h"    // CView 派生クラス
    #include "ChildFrm.h"    // CMDIChildWnd 派生クラス

    #include <afxole.h>    // _AFX_NO_OLE_SUPPORT 関係
    #include "C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\src\mfc\oleimpl2.h" // Visual Studio でパスが設定されていないので、絶対パスを設定しました。もちろん、皆さんの環境に合わせて変えて下さい。

    BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
        ON_WM_WINDOWPOSCHANGING()
        ON_WM_MDIACTIVATE()
    END_MESSAGE_MAP()

    BOOL CChildFrame::UpdateClientEdge(LPRECT lpRect /* = lpRect */)
    {
        CWnd* pChild = GetWindow(GW_CHILD);

        if (WS_MAXIMIZE & GetStyle())
        {
            pChild->ModifyStyleEx(WS_EX_CLIENTEDGE, 0L);
        }
        else
        {
            pChild->ModifyStyleEx(0L, WS_EX_CLIENTEDGE);
        }

        return TRUE;
    }

    // CChildFrame メッセージ ハンドラ

    void CChildFrame::OnWindowPosChanging(WINDOWPOS* lpWndPos)
    {
        UpdateClientEdge();

        CFrameWnd::OnWindowPosChanging(lpWndPos);
    }

    void CChildFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
    {
        m_bPseudoInactive = FALSE;  // must be happening for real

        // make sure MDI client window has correct client edge
        UpdateClientEdge();

        // send deactivate notification to active view
        CMDIView* pActiveView = (CMDIView*) GetActiveView();
        if (!bActivate && pActiveView != NULL)
            pActiveView->OnActivateView(FALSE, pActiveView, pActiveView);

        // allow hook to short circuit normal activation
        BOOL bHooked = FALSE;

    #ifndef _AFX_NO_OLE_SUPPORT
        if (m_pNotifyHook != NULL && m_pNotifyHook->OnDocActivate(bActivate))
            bHooked = TRUE;
    #endif

        // update titles (don't AddToTitle if deactivate last)
        if (!bHooked)
            OnUpdateFrameTitle(bActivate || (pActivateWnd != NULL));

        // re-activate the appropriate view
        if (bActivate)
        {
            if (pActiveView != NULL && GetMDIFrame() == GetActiveWindow())
                pActiveView->OnActivateView(TRUE, pActiveView, pActiveView);
        }

        // update menus
        if (!bHooked)
        {
            OnUpdateFrameMenu(bActivate, pActivateWnd, NULL);
            GetMDIFrame()->DrawMenuBar();
        }
    }


    MDIView.h

    class CMDIView : public CView
    {

        friend class CChildFrame;
    };


    以上です。

    前回は CChildFrame::UpdateClientEdge の引数が LPRECT* lpRect となっており、ヘッダーと異なっていました。
    また、UpdateClientEdge 関数に戻り値が設定されていない為、コンパイルエラーが起きる様になっていました。
    そして、friend 宣言は継承されないという部分を、基本クラスの protected 関数にアクセス出来ない、と勘違いしていました。

    再びお詫びします。ご迷惑をお掛けしました。

    なお、

    #ifndef _AFX_NO_OLE_SUPPORT
        if (m_pNotifyHook != NULL && m_pNotifyHook->OnDocActivate(bActivate))
            bHooked = TRUE;
    #endif

    部分は、おそらく今で言う ActiveX 関係でしょうから、アプリケーション内部で ActiveX を使った場合、これだけのコードでいいのか、また、ActiveX を使わない場合、コメントアウトしていいのか ( #include 部分が前回のものに戻せる )、判断出来ませんでした。

    もう一つの マルチタイプな場合ですが、Document、View、Frame の 3 セットを用意すれば上手く動きました。


    一応、整理の意味で書いておきますが、ここで示させて頂いた対処方法は MDI クライアント側の WS_EX_CLIENTEDGE スタイルを常に有効とし、CView の最大化時には CView 側の WS_EX_CLIENTEDGE スタイルを解除する、という従来の MFC とは逆の方法です。

    他の対処方法をご存知の方がおられましたら、ぜひとも投稿して頂きたいと思います。

    # というか、海の向こうでは問題になっていなかったんでしょうか・・・。


    追記

    WinMe と Win2000 での動作確認をしてみたかったのは、Windows XP でテーマが導入された際、DefMDIChildProc が修正され、今までの MFC が行っていたタイミングでは上手く行かなかったのではないか、というだけです。Windows 7 も発売された以上、MFC も API のどちらも修正不可能になっている感じがしますね・・・。

    さらに追記

    今回のアイコンの位置がずれる現象への対処はこれで一段落とするつもりです。
    Atsushi777 さんが書いておられるように、PreCreateWindow のタイミングで CView 派生クラスから WS_EX_CLIENTEDGE スタイルを無くせばいいはずですから。

    とは言え、今回の事で MFC の MDI 部分をうろついたお陰で、色々な事が判りましたので、もっと簡単な修正方法があれば、新しいスレッドとして投稿させて頂きたいと思います。

    とりあえず、Atsushi777 さんの WS_EX_CLIENTEDGE スタイルを外す案と、CView::OnNcCalcSize でクライアントエッジ分を取り除く処理と、CView::OnNcPaint でクライアントエッジを描画すれば、回避出来る様な気もしますが。

    • 編集済み ミッヒー 2009年11月1日 6:58 追記部分に追記
    • 回答としてマーク ミッヒー 2009年11月1日 6:59
    2009年11月1日 6:41

すべての返信

  • 追記です。

    念の為、http://support.microsoft.com/kb/926152/ja を参考にテーマそのものを無効化して、再起動しましたが、やはり同じ現象が発生します。

    なお、XP スタイルと書いたので皆さんお気付きかとは思いますが、Windows XP SP 3 での話です。
    2009年8月6日 8:19
  • あー。至極単純な話でした。

    CChildFrame の OnWindowPosChanging または OnWindowPosChanged をオーバーライドすれば修正は可能のようです。
    精神的に疲れたので、まだ実装はしていませんが・・・。

     # 投稿自体を削除しようかとも思いましたが、後の人の為に ( ? ) 、一応残しておきます・・・。
    • 回答としてマーク ミッヒー 2009年8月6日 12:09
    • 回答としてマークされていない ミッヒー 2009年8月6日 12:14
    2009年8月6日 12:07
  • とりあえず、暫定的な回避方法です。
    void CChildFrame::OnWindowPosChanging(WINDOWPOS* lpwndpos)
    {
        CMDIChildWnd::OnWindowPosChanging(lpwndpos);
    
        // TODO: ここにメッセージ ハンドラ コードを追加します。
        if (!IsIconic())	return;
    
        CMDIFrameWnd*	pFrameWnd	= DYNAMIC_DOWNCAST(CMDIFrameWnd, GetTopLevelFrame());
    
        ASSERT(NULL != pFrameWnd);
    
        for (CWnd* pWnd = pFrameWnd->GetTopWindow(); NULL != pWnd; pWnd = pWnd->GetNextWindow())
        {
            if (pFrameWnd->m_hWndMDIClient == pWnd->GetSafeHwnd())
            {
                CRect	rcLeftOver;
    
                pWnd->GetClientRect(&rcLeftOver);
    
                if (rcLeftOver.bottom - lpwndpos->cy != lpwndpos->y)
                {
                    lpwndpos->y	= rcLeftOver.bottom - lpwndpos->cy;
                }
    break; } } }
    暫定的な方法なので、アイコン化されたウィンドウが複数行になるととたんに破綻します・・・。
    また、アイコン化の y 位置を固定している為、アイコンを自由に動かせませんが・・・。

     # 時間が取れたら、ライブラリを使わない MDI アプリを作成して見ようかと思います。何だか、Windows のバグの様な気も微かにしますので・・・。

    自分の投稿に自分で回答としてマークするのも何だかイヤーな感じがしますが、とりあえずスレッドを閉じておきます。
    • 回答としてマーク ミッヒー 2009年8月7日 7:30
    • 編集済み ミッヒー 2009年8月7日 7:47
    • 回答としてマークされていない ミッヒー 2009年10月24日 8:04
    2009年8月7日 7:30
  • 前回のあまりにひどい暫定的な回避方法を直すべく、VC++ 2008 で Win32 MDI アプリケーションを作成しています。とは言え、ドキュメントはありませんが・・・。

    MFC の MDI アプリケーションを spy++ で見た限り、メインフレームは WS_EX_WINDOWEDGE、クライアント領域は WS_EX_CLIENTEDGE、子フレームは WS_EX_WINDOWEDGE、ビュークラスは WS_EX_CLIENTEDGE となっており、子フレームの最大化時に限り、クライアント領域の WS_EX_CLIENTEDGE 拡張スタイルが解除されるようでした。

    そこで、Win32 MDI アプリ内でも子フレームの最大化時に

         ::SetWindowLong(hMDIClient, GWL_EXSTYLE, WS_EX_WINDOWEDGE);
         ::SetWindowPos(hMDIClient, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

    の 2 文を追加し、最大化時のクライアントエッジが 2 重に表示されるという現象は回避できました。
    この部分は子フレームの WM_SIZE ハンドラ内で実装しました。

    問題はその次の、最大表示されていた子フレームをアイコン化した場合です。

    ::DefMDIChildProc を呼び出す前にクライアント領域の WS_EX_CLIENTEDGE スタイルを再設定しないとアイコン化される場所がおかしくなると思い、

         ::SetWindowLong(hMDIClient, GWL_EXSTYLE, WS_EX_CLIENTEDGE);
         ::SetWindowPos(hMDIClient, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

    としてクライアント領域の WS_EX_CLIENTEDGE スタイルを復元してから、::DefMDIChildProc を呼び出しましたが、本題の MFC と同様にアイコンが下にずれてしまいました。

    WS_EX_CLIENTEDGE スタイルを復元する際に ::SetWindowPos を呼び出さなければ、子フレームはちゃんとした場所にアイコン化されますが、今度はクライアント領域の WS_EX_CLIENTEDGE スタイルが適応されません。

    クライアント領域の ::SetWindowPos を呼び出す際に ::GetClientRect を呼び出し、::GetSystemMetrics で取得した SM_CXEDGE 等を用いてクライアント領域自体を WS_EX_CLIENTEDGE 分小さくした所、アイコンは更に下にずれました。

    Win32 で MDI アプリを開発した経験がない為、おかしな事をしているのでは、という気がしますが、修正方法をご存知の方、よろしくお願いします。


    追記

    WindowsXP SP3 と Visual Studio 2008 を使用しています。
    • 編集済み ミッヒー 2009年10月24日 9:07 開発環境追記
    2009年10月24日 8:30
  • 一体何人の方がこの情報を必要としているのか、空しい一人相撲をとっているだけではないのか、等と考えつつ、でも、未だに問題解決には至っていません。

    ただ、問題の本質は判って来ました。

    MFC で MDI アプリケーションの雛形を作成すると、MDI クライアントが WS_EX_CLIENTEDGE を持ち、MDI 子フレーム内のウィンドウも WS_EX_CLIENTEDGE を持ちます。

    MDI クライアントと MDI 子フレーム内のウィンドウの 2 つが WS_EX_CLIENTEDGE を持つ為、単純に MDI 子フレームを最大化表示すると、MDI クライアントと MDI 子フレーム内のウィンドウで 2 重の WS_EX_CLIENTEDGE が表示されてしまいます。

    その為、MFC は MDI 子フレームの最大化のタイミングで MDI クライアント側の WS_EX_CLIENTEDGE を無くします。

    そして最大表示されている MDI 子フレームが無くなった時点で、MDI クライアントの WS_EX_CLIENTEDGE を復元しているようです。

    しかし、最大表示されている MDI 子フレームがいきなり最小化され、他に最大表示されている MDI 子フレームが無い場合、DefMDIChildProc ( Win32 の場合です。MFC では他のデフォルトプロシジャーが使われています。) は MDI クライアントに WS_EX_CLIENTEDGE スタイルが無いものとして、MDI 子フレームのアイコン化場所を設定するようです。そして、その後に MDI クライアントに再設定される WS_EX_CLIENTEDGE により MDI クライアント自体がエッジ分縮小し、上エッジに押される形で MDI 子フレームのアイコンが下にずれるようです。

    業務で MFC / Win32 を問わず MDI アプリケーションを作成した経験は無かったと記憶しているので、この問題がいつから発生しているのかは判りません。しかし、この書き込み自体そうですが、皆様のお叱りを受ける覚悟で言えば、Windows XP でテーマが導入されてからではないか、と思います。

    そこで、Windows XP 以降と思しき Microsoft 社の製品で上記の WS_EX_CLIENTEDGE を使っているアプリケーションを探してみた所、Visual Studio 2005 が見付かりました。

    ただし、WS_EX_CLIENTEDGE スタイルの扱いは逆になっていました。

    従来の、そして Visual Studio 2008 の MFC MDI アプリケーションが行っている様に、子フレームの最大化に合わせて、MDI クライアントの WS_EX_CLIENTEDGE を無くすのではなく、子フレーム内のウィンドウの WS_EX_CLIENTEDGE スタイルが無くなっていました。

    という訳で、今は既存の MFC が、どのタイミングで、どんな方法を使って ( まあ、CWnd::ModifyStyleEx か ::SetWindowLong でしょうが )、MDI クライアントの WS_EX_CLIENTEDGE スタイルを無しにしているのかを調べています。


    長文で愚痴が大半になりましたが、MFC がどのタイミングで、どういった方法を用いて MDI クライアントのスタイルを変更しているのか、お判りになる方がおられれば、或いはヒント的なものでも構いませんので、ご存知の方、お力をお貸し下さい。

    • 編集済み ミッヒー 2009年10月29日 0:25 誤記修正
    2009年10月29日 0:22
  • 手元にあった、VS2005のMFCのソースでは、
    CMDIChildWnd::UpdateClientEdge()で変更しているようです。
    • 回答としてマーク ミッヒー 2009年10月29日 7:47
    • 回答としてマークされていない ミッヒー 2009年10月29日 10:44
    • 回答としてマーク ミッヒー 2009年11月1日 6:59
    2009年10月29日 1:03
  • Atsushi777 さん、いつもお世話になっています。
    前回の UNICODE とマルチメディアファイルに関して の時に同様に半ばパニくっている状況で、誰かから回答があると非常に元気付けられます。今回もありがとうございます。

    それで、CMDIChildWnd::UpdateClientEdge ですが、CMDIChildWnd クラスの非 virtial な protected メンバ関数として定義されていますよね・・・。

    ただ、幸いな事に、呼び出されるのが OnWindowPosChanging、OnDestory、OnMDIActivate というメッセージハンドラのみになっているようで、そちらで対応出来そうです。

    Atsushi777 さんが MFC MDI アプリケーションを必要とされているのかはわかりませんが、しっかりとした回避方法が判り次第、回答させて頂きます。

    なお、ほとんど愚痴の書き込みについて、解答下さった事について、改めて感謝申し上げます。
    ありがとうございました。
    2009年10月29日 1:31
  • 内容に対するレスでなくてすいません。

    このスレッドのウォッチングはしているのですが、
    何分、MDIは殆ど組んだ事が無いので話に参加で来ていないのが実情です。
    SDIなら結構作成した事があるんですけれど、不思議とMDIはないです。
    MDIは複雑になりそうなので必要がない限り採用する気が無かったと言うのが正直な所。

    ただ、技術的にMFCのフレームワークにそういう不具合があると言う事は知識としては必要だと思うので
    このスレッドその物は後続の人間の役に立つと思いますよ。
    こう言う情報はなかなか出てこないので、顛末まで書いていただけるととても助かります。
    参考にさせていただいています。

    なんか、たんなる応援みたいになってますが、見ているよと言うことを書きたかったので。

    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    • 回答としてマーク ミッヒー 2009年11月1日 7:01
    2009年10月29日 2:39
  • PATIO さん、ありがとうございます。非常に励まされます。

    MFC に関する質問をしても、あまり返ってくる事が少なかったので、そもそも皆、MFC 使ってないんじゃね?等という弱気な面もあったのですが、未だに前線で MFC は使われているのですね。安心しました。

    フォーラムの主旨から大きく逸脱しているとは思いますが、個人的には PATIO さんの応援的な書き込み、嬉しいです。

    ありがとうございました。

    2009年10月29日 2:48
  • ソースを見る限りでは、結局、MDI子ウィンドウ内のViewにWS_EX_CLIENTEDGEが
    設定されていなければ、CMDIChildWnd::UpdateClientEdge()でMDIクライアントを
    あれこれされることはないようです。

    ただし、これはCFrameWnd::CreateView()でViewを作成している一般的な場合で、
    MDI子ウィンドウにWS_EX_CLIENTEDGEが設定されているようだと、結局だめです。

    BOOL CHogeView::PreCreateWindow(CREATESTRUCT& cs)
    {
      BOOL result = CView::PreCreateWindow(cs);
      cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
      return result;
    }
    

    CMDIChildWnd::UpdateClientEdge()の他に、
    CFrameWnd::CreateView()やCFrameWnd::PreCreateWindow()なども
    見てみるとお分かりいただけると思います。

    ただ、そもそも、CViewにWS_EX_CLIENTEDGEがなくてもいいのかどうかは
    実装する人の事情によりますので、これを外せないなら別な方法を取るしかないです。
    • 回答としてマーク ミッヒー 2009年10月29日 7:47
    • 回答としてマークされていない ミッヒー 2009年10月29日 10:44
    • 回答としてマーク ミッヒー 2009年11月1日 6:59
    2009年10月29日 6:08
  • Atsushi777 さん、MFC ソースまで見て下さったようで、感謝しております。

    確かに仰るとおり、MFC は WS_EX_CLIENTEDGE しか確認していないようですので、WS_BORDER 等の通常スタイルで何とかしようとも思いました。また、エッジは無しにして、CChildFrame 側でエッジを描画し、CView::OnNcCalcSize 子フレーム内のウィンドウ位置を整えようかとも思いました。

    ただ、WS_EX_CLIENTEDGE は MFC MDI アプリケーションのデフォルト値です。特別な操作もせず、普通に作った雛形がバグを持っているのが納得できず、今回の質問をさせて頂きました。

    色々な点で、こんなコード書いていいのだろうか、という部分はありますが、もう少しで解決する所まで来ました。

    無駄な事と思われるかもしれませんが、ここまで来れたのも Atsushi777 さんと PATIO さんのお陰です。
    心より感謝申し上げます。
    • 編集済み ミッヒー 2009年10月29日 6:55 誤記訂正
    2009年10月29日 6:47
  • そんなこんなで一応回避策が出来ました。追加した部分のみを書いておきます。元からある部分、または場所を明確にする部分は行の終わりに //// を追加しておきました。

    ChildFrame.h

    // 操作    ////
    protected:
     BOOL UpdateClientEdge(LPRECT* lpRect = NULL);

    // 生成された、メッセージ割り当て関数    ////
    protected:
     afx_msg void OnWindowPosChanging(WINDOWPOS* lpWndPos);
     afx_msg void OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd);


    ChildFrame.cpp

    #include "MDIDoc.h"
    #include "MDIView.h"
    #include "ChildFrm.h"    ////

    BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)    ////
     ON_WM_WINDOWPOSCHANGING()
     ON_WM_MDIACTIVATE()
    END_MESSAGE_MAP()    ////

    BOOL CChildFrame::UpdateClientEdge(LPRECT lpRect)
    {
        CWnd* pChild = GetWindow(GW_CHILD);

        if (WS_MAXIMIZE & GetStyle())
        {
            pChild->ModifyStyleEx(WS_EX_CLIENTEDGE, 0L);
        }
        else
        {
            pChild->ModifyStyleEx(0L, WS_EX_CLIENTEDGE);
        }
    }

    // CChildFrame メッセージ ハンドラ    ////

    void CChildFrame::OnWindowPosChanging(WINDOWPOS* lpWndPos)
    {
        UpdateClientEdge();

        CFrameWnd::OnWindowPosChanging(lpWndPos);
    }

    void CChildFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
    {
        m_bPseudoInactive = FALSE;  // must be happening for real

        // make sure MDI client window has correct client edge
        UpdateClientEdge();

        // send deactivate notification to active view
        CMDIView* pActiveView = (CMDIView*) GetActiveView();
        if (!bActivate && pActiveView != NULL)
        pActiveView->OnActivateView(FALSE, DYNAMIC_DOWNCAST(CView, pActiveView), DYNAMIC_DOWNCAST(CView, pActiveView));

        // allow hook to short circuit normal activation
        BOOL bHooked = FALSE;
    /*
    #ifndef _AFX_NO_OLE_SUPPORT
        if (m_pNotifyHook != NULL && m_pNotifyHook->OnDocActivate(bActivate))
            bHooked = TRUE;
    #endif
    */

        // update titles (don't AddToTitle if deactivate last)
        if (!bHooked)
            OnUpdateFrameTitle(bActivate || (pActivateWnd != NULL));

        // re-activate the appropriate view
        if (bActivate)
        {
            if (pActiveView != NULL && GetMDIFrame() == GetActiveWindow())
                pActiveView->OnActivateView(TRUE, DYNAMIC_DOWNCAST(CView, pActiveView), DYNAMIC_DOWNCAST(CView, pActiveView));
        }

        // update menus
        if (!bHooked)
        {
            OnUpdateFrameMenu(bActivate, pActivateWnd, NULL);
            GetMDIFrame()->DrawMenuBar();
        }
    }


    MDIView.h

    // オーバーライド    ////
    protected:
        virtual void OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
        {
            CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
        }

    // 生成された、メッセージ割り当て関数    ////
    protected:
        DECLARE_MESSAGE_MAP()    ////

        friend class CChildFrame;


    以上で MDI 子ウィンドウが最大化された際にも MDI クライアントが WS_EX_CLIENTEDGE スタイルを保つ事が出来ます。

    ただ、CChildFrame::OnMDIActivate 内の /* と */ で囲った部分、MFC のソースではコメント化されていません。
    #include <afxole.h> 等と試しましたが、未定義の識別子です、のエラーから逃れられませんでした。
    この部分が判る方にフォローして頂ければ幸いです。

    なお、上記ソースをよく見れば判りますが、本来なら呼び出せない CView 定義の OnActivateView を呼び出す為 ( protected 定義。CChildFrame の基本クラス CMDIChildWnd は friend 宣言されている為に呼び出せる ) に、CMDIView クラス内で OnActivateView をオーバーライドし、同時に CChildFrame を friend 宣言しています。これはたまたま CView::OnActivateView が仮想関数だった為に実装出来ました。

    禁じ手に手を出した、綱渡りのプログラムだという感覚はありますが、他にいい方法があれば、( /* */ の部分共々) ご教授下さい。


    かなりインチキなプログラムですが、Atsushi777 さんと PATIO さんには改めて御礼申し上げます。
    お二人がいなければ途中で挫折していた可能性大でしたので。

    ありがとうございました。

    追記

    近々、WinMe に触れる機会がありそうなので、憶測のテーマが追加された WinXP から起こるようになったバグなのか、そしてこの修正したコードは動くのか検証してみたいと思います。

    • 編集済み ミッヒー 2009年10月29日 8:01 誤記訂正
    2009年10月29日 7:46
  • Atsushi777 さんにはすいませんが、一度回答としてマークの設定を解除させて頂きました。

    #ifndef _AFX_NO_OLE_SUPPORT
        if (m_pNotifyHook != NULL && m_pNotifyHook->OnDocActivate(bActivate))
            bHooked = TRUE;
    #endif

    この部分の対処方法と、WinMe で動かした場合に、この修正版と MFC アプリケーション ウィザードの雛形そのものが、どういった動作を示すのかを見た上で、始めて問題が解決したかが判ると思い直しましたので。

    # Microsoft 社の方に MFC の修正もお願いしたい所ですが、歴史的背景があるので無理でしょうねぇ。
    • 編集済み ミッヒー 2009年10月29日 11:12 変な文章修正
    2009年10月29日 10:57
  • 既にこのスレッドを見た皆様にお詫びです。

    バグが残っていました。

    CChildFrame::OnMDIActivate 内で

    CMDIView* pActiveView = (CMDIView*) GetActiveView();

    として CMDIView クラスの方でも OnActivateView をオーバーライドし、CMDIView クラスで CChildFrame クラスをフレンド指定する事で OnActivateView 関数にアクセス出来るようにしているのですが、この対処方法はあくまでも単一のビュークラスを扱う MDI アプリケーションでしか通用しません。

    アプリケーションクラスの InitInstance でドキュメントテンプレートを複数定義し、Visual Studio の様な マルチタイプ マルチドキュメントとでもいうべきアプリケーションを作成した場合には、この方法ではコンパイル出来ません。

    アプリケーションクラス内でのドキュメントテンプレートの登録時 ( InitInstance 内 ) に

    CMultiDocTemplate* pDocTemplate;
    pDocTemplate = new CMultiDocTemplate(IDR_MDITYPE,
        RUNTIME_CLASS(CMDIDoc),
        RUNTIME_CLASS(CChildFrame), // カスタム MDI 子フレーム
        RUNTIME_CLASS(CMDIView));
    if (!pDocTemplate)
        return FALSE;
    AddDocTemplate(pDocTemplate);

    以上の様なコードが存在しますが、他のドキュメントタイプにも対応させるべく、

    pDocTemplate = new CMultiDocTemplate(IDR_MDITYPE2,
        RUNTIME_CLASS(CMDIDoc2),
        RUNTIME_CLASS(CChildFrame), // カスタム MDI 子フレーム
        RUNTIME_CLASS(CMDIView2));
    if (!pDocTemplate)
        return FALSE;
    AddDocTemplate(pDocTemplate);

    等を追加した場合、CChildFrame は変わりませんから、

    CChildFrame::OnMDIActivate 内の

    CMDIView* pActiveView = (CMDIView*) GetActiveView();

    部分のキャスト対象が判らなくなってしまいます。

    CChildFrame クラスもドキュメントタイプ分準備すればいいのかは、まだ未検証ですが、取り急ぎご報告しておきます。


    追記

    改めて見直して気付いたのですが、CChildFrame::OnMDIActivate 内での

    pActiveView->OnActivateView(FALSE, DYNAMIC_DOWNCAST(CView, pActiveView), DYNAMIC_DOWNCAST(CView, pActiveView));

    この部分は

    pActiveView->OnActivateView(FALSE, pActiveView, pActiveView);

    こうでした。そもそも、基本クラスにキャストするのに DYNAMIC_DOWNCAST を使っている事自体が間違っていました。DYNAMIC_UPCAST ( そんな命令は存在しません。念の為。 ) とでもいうべき処理ですから・・・。
    2009年10月31日 3:14
  • WinMe で動かそうとしたのですが、プロジェクトを WinMe で動くように設定が出来ていなかったらしく、起動しませんでした。

    Visual Studio 2005 で MFC MDI アプリケーションとして雛形を作成し、それだけをビルドしたものも動かなかったので、プロジェクトの設定がなされていない、というだけだと思います。まだ Visual C++ 6.0 をうろつく事が多いもので・・・。

    そんな理由から WinMe での動作確認はまたの機会となってしまいました。

    その機会がいつになるのか判らないので、WinMe と欲張って Win2000 での検証は皆さんにお願いできたら・・・等と都合のいい事を思いつつ。


    それよりも、もう一度皆さんにお詫びをしなければなりません。

    コピペの際に間違っており、以前示したコードはコンパイルエラーとなります。また、無駄なコードもありました。
    それと、

    #ifndef _AFX_NO_OLE_SUPPORT
        if (m_pNotifyHook != NULL && m_pNotifyHook->OnDocActivate(bActivate))
            bHooked = TRUE;
    #endif

    部分の対応方法と CChildFrame クラスをドキュメントタイプ分準備すればいいのか、の検証が終わったので、改めて MFC MDI アプリケーション、MFC アプリケーション ウィザードで作成された雛形のどの部分をどうしたのか、今回はもう少し判りやすい様に書いてみたつもりです・・・。


    ChildFrm.h

    class CChildFrame : publc CMDIChildWnd
    {

    // 操作
    protected:
        BOOL UpdateClientEdge(LPRECT lpRect = NULL);

    // 生成された、メッセージ割り当て関数
    protected:
        afx_msg void OnWindowPosChanging(WINDOWPOS* lpWndPos);
        afx_msg void OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd);

    };


    ChildFrm.cpp

    // ChildFrm.cpp : CChildFrame クラスの実装
    //
    #include "stdafx.h"
    #include "MDI.h"    // CWinApp 派生クラス

    #include "MDIDoc.h"     // CDocument 派生クラス
    #include "MDIView.h"    // CView 派生クラス
    #include "ChildFrm.h"    // CMDIChildWnd 派生クラス

    #include <afxole.h>    // _AFX_NO_OLE_SUPPORT 関係
    #include "C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\src\mfc\oleimpl2.h" // Visual Studio でパスが設定されていないので、絶対パスを設定しました。もちろん、皆さんの環境に合わせて変えて下さい。

    BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
        ON_WM_WINDOWPOSCHANGING()
        ON_WM_MDIACTIVATE()
    END_MESSAGE_MAP()

    BOOL CChildFrame::UpdateClientEdge(LPRECT lpRect /* = lpRect */)
    {
        CWnd* pChild = GetWindow(GW_CHILD);

        if (WS_MAXIMIZE & GetStyle())
        {
            pChild->ModifyStyleEx(WS_EX_CLIENTEDGE, 0L);
        }
        else
        {
            pChild->ModifyStyleEx(0L, WS_EX_CLIENTEDGE);
        }

        return TRUE;
    }

    // CChildFrame メッセージ ハンドラ

    void CChildFrame::OnWindowPosChanging(WINDOWPOS* lpWndPos)
    {
        UpdateClientEdge();

        CFrameWnd::OnWindowPosChanging(lpWndPos);
    }

    void CChildFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
    {
        m_bPseudoInactive = FALSE;  // must be happening for real

        // make sure MDI client window has correct client edge
        UpdateClientEdge();

        // send deactivate notification to active view
        CMDIView* pActiveView = (CMDIView*) GetActiveView();
        if (!bActivate && pActiveView != NULL)
            pActiveView->OnActivateView(FALSE, pActiveView, pActiveView);

        // allow hook to short circuit normal activation
        BOOL bHooked = FALSE;

    #ifndef _AFX_NO_OLE_SUPPORT
        if (m_pNotifyHook != NULL && m_pNotifyHook->OnDocActivate(bActivate))
            bHooked = TRUE;
    #endif

        // update titles (don't AddToTitle if deactivate last)
        if (!bHooked)
            OnUpdateFrameTitle(bActivate || (pActivateWnd != NULL));

        // re-activate the appropriate view
        if (bActivate)
        {
            if (pActiveView != NULL && GetMDIFrame() == GetActiveWindow())
                pActiveView->OnActivateView(TRUE, pActiveView, pActiveView);
        }

        // update menus
        if (!bHooked)
        {
            OnUpdateFrameMenu(bActivate, pActivateWnd, NULL);
            GetMDIFrame()->DrawMenuBar();
        }
    }


    MDIView.h

    class CMDIView : public CView
    {

        friend class CChildFrame;
    };


    以上です。

    前回は CChildFrame::UpdateClientEdge の引数が LPRECT* lpRect となっており、ヘッダーと異なっていました。
    また、UpdateClientEdge 関数に戻り値が設定されていない為、コンパイルエラーが起きる様になっていました。
    そして、friend 宣言は継承されないという部分を、基本クラスの protected 関数にアクセス出来ない、と勘違いしていました。

    再びお詫びします。ご迷惑をお掛けしました。

    なお、

    #ifndef _AFX_NO_OLE_SUPPORT
        if (m_pNotifyHook != NULL && m_pNotifyHook->OnDocActivate(bActivate))
            bHooked = TRUE;
    #endif

    部分は、おそらく今で言う ActiveX 関係でしょうから、アプリケーション内部で ActiveX を使った場合、これだけのコードでいいのか、また、ActiveX を使わない場合、コメントアウトしていいのか ( #include 部分が前回のものに戻せる )、判断出来ませんでした。

    もう一つの マルチタイプな場合ですが、Document、View、Frame の 3 セットを用意すれば上手く動きました。


    一応、整理の意味で書いておきますが、ここで示させて頂いた対処方法は MDI クライアント側の WS_EX_CLIENTEDGE スタイルを常に有効とし、CView の最大化時には CView 側の WS_EX_CLIENTEDGE スタイルを解除する、という従来の MFC とは逆の方法です。

    他の対処方法をご存知の方がおられましたら、ぜひとも投稿して頂きたいと思います。

    # というか、海の向こうでは問題になっていなかったんでしょうか・・・。


    追記

    WinMe と Win2000 での動作確認をしてみたかったのは、Windows XP でテーマが導入された際、DefMDIChildProc が修正され、今までの MFC が行っていたタイミングでは上手く行かなかったのではないか、というだけです。Windows 7 も発売された以上、MFC も API のどちらも修正不可能になっている感じがしますね・・・。

    さらに追記

    今回のアイコンの位置がずれる現象への対処はこれで一段落とするつもりです。
    Atsushi777 さんが書いておられるように、PreCreateWindow のタイミングで CView 派生クラスから WS_EX_CLIENTEDGE スタイルを無くせばいいはずですから。

    とは言え、今回の事で MFC の MDI 部分をうろついたお陰で、色々な事が判りましたので、もっと簡単な修正方法があれば、新しいスレッドとして投稿させて頂きたいと思います。

    とりあえず、Atsushi777 さんの WS_EX_CLIENTEDGE スタイルを外す案と、CView::OnNcCalcSize でクライアントエッジ分を取り除く処理と、CView::OnNcPaint でクライアントエッジを描画すれば、回避出来る様な気もしますが。

    • 編集済み ミッヒー 2009年11月1日 6:58 追記部分に追記
    • 回答としてマーク ミッヒー 2009年11月1日 6:59
    2009年11月1日 6:41