WTL で MDI フレームとスプリッターを併用する

ちょっとハマったのでメモ。

WTL::CMDI(Frame|Child)WindowImpl (というか WTL::CMDIWindow) の既定の実装では、メニューを切り替えるときに ::GetParent(m_hWndMDIClient) でフレーム ウィンドウのハンドルを取得する様になっている。これだどスプリッターのハンドルが返ってきてしまうのでうまくいかない。そこで、もう一回 GetParent してつじつまを合わせる。ホントは親をたどってトップレベル ウィンドウを見つけたほうがいいんだけど、今はそこまで必要じゃない。

さらに、スプリッターが処理していないからか、子フレームを切り替えたときに非クライアント領域がうまく更新されなくなってしまう。これは子フレーム側で対処する。

そんなハックを加えたクラスを以下のように作って、それを継承するように変更する。

class SplittedMDIWindow : public WTL::CMDIWindow {
 public:
  void SetMDIFrameMenu() {
    HMENU hWindowMenu = GetStandardWindowMenu(m_hMenu);
    MDISetMenu(m_hMenu, hWindowMenu);
    MDIRefreshMenu();
    ::DrawMenuBar(GetMDIFrame());
  }

  HWND GetMDIFrame() const {
    return ::GetParent(::GetParent(m_hWndMDIClient));
  }
};

template<class T>
class SplittedMDIChild : public WTL::CMDIChildWindowImpl<T, SplittedMDIWindow> {
 public:
  BEGIN_MSG_MAP_EX(SplittedMDIChild)
    MSG_WM_MDIACTIVATE(OnMDIActivate)
    CHAIN_MSG_MAP(CMDIChildWindowImpl)
  END_MSG_MAP()

  void OnMDIActivate(CWindow activate, CWindow deactivate) {
    if (activate.IsWindow())
      activate.SendMessage(WM_NCACTIVATE, FALSE, 0);

    if (deactivate.IsWindow())
      deactivate.SendMessage(WM_NCACTIVATE, TRUE, 0);

    SetMsgHandled(FALSE);
  }
};

template<class T>
class SplittedMDIFrame : public WTL::CMDIFrameWindowImpl<T, SplittedMDIWindow> {
};

あとは親フレームの WM_CREATE に対するハンドラーのなかで MDI クライアントを作って、スプリッターを SetParent してやればいい。CreateMDIClient が勝手に m_hWndClient を変更するので、スプリッターより先に MDI クライアントを作ったほうが吉。

int MainFrame::OnCreate(LPCREATESTRUCT create_struct) {
  CreateMDIClient();

  m_hWndClient = splitter_.Create(m_hWnd, rcDefault, NULL,
                                  WS_CHILD |
                                  WS_VISIBLE |
                                  WS_CLIPCHILDREN |
                                  WS_CLIPSIBLINGS);
  splitter_.SetSplitterPosPct(20);

  ::SetParent(m_hWndMDIClient, splitter_);
  splitter_.SetSplitterPane(SPLIT_PANE_RIGHT, m_hWndMDIClient);

  CMessageLoop* messag_loop = _Module.GetMessageLoop();
  messag_loop->AddMessageFilter(this);

  return 0;
}