Double-click in MDI Client

It’s hard to believe that handling mouse double-clicks in MDI Client windows can be super useful, but I found this problem in a discussion forum. I presumed that it was not only for fun, so I tried to resolve it. First, I subclassed the MDI Client window in a CWnd-derived class. That is possible for a standard MFC project in this way:

class CMainFrame : public CMDIFrameWnd
{
    // ...
    // Attributes
private:
    CMDIClientWnd m_wndMDIClient;
    // ...
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    // NOTE: m_hWndMDIClient is a public member of CMDIFrameWnd
    // keeping the MDI Client window handle
    m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
    // ...
    return 0;
}

But no success: WM_LBUTTONDBLCLK message handler is not called in CMDIClientWnd class. Then I tried to catch it in overridden PreTranslateMessage or even in a WH_MOUSE hook with the same result. Finally, I had a look using Spy++ tool. Bingo! The MDI Client (of predefined window class named “MdiClient“) has no CS_DBLCLKS style so it does not deal with mouse double-clicks.

MDIClient
MDIClient

All wat he have to do is to create the The MDI Client window with our own registered window class. This is possible by overriding CMDIFrameWnd::CreateClient.

Create a MDI client window which belongs to a window class having CS_DBLCLKS style

BOOL CMainFrame::CreateClient(LPCREATESTRUCT lpCreateStruct, CMenu* pWindowMenu)
{
    // NOTE: do not call CMDIFrameWnd::CreateClient

    // get MDiCLient predefined window class info
    WNDCLASS wndClass = { 0 };
    if (!::GetClassInfo(AfxGetInstanceHandle(), _T("MdiClient"), &wndClass))
        return FALSE;

    // put our own window class name in WNDCLASS structure
    wndClass.lpszClassName = OUR_OWN_CLASS_NAME;
    // set CS_DBLCLKS flag in class style
    wndClass.style |= CS_DBLCLKS;
    // register window class
    AfxRegisterClass(&wndClass);

    ////////////////////////////////////////////////////////////////////////
    // Next is copy-pasted from MFC framework implementation, 
    // except that use our own registered window class
 
    // ...
    // ...

    // Create MDICLIENT control with special IDC
    if ((m_hWndMDIClient = CreateWindowEx(dwExStyle,
        /*_T("mdiclient")*/ OUR_OWN_CLASS_NAME, // use our own window class
        NULL,
        dwStyle, 0, 0, 0, 0, m_hWnd, (HMENU)AFX_IDW_PANE_FIRST,
        AfxGetInstanceHandle(), (LPVOID)&ccs)) == NULL)
    {
        TRACE(traceAppMsg, 0, 
            _T("Warning: CMDIFrameWnd::OnCreateClient: failed to create MDICLIENT.")
            _T(" GetLastError returns 0x%8.8X\n"), ::GetLastError());
        return FALSE;
    }
    // Move it to the top of z-order
    ::BringWindowToTop(m_hWndMDIClient);

    return TRUE;
}

Handle MDI Client double-clicks in standard MFC applications

Once we have created the MDI Client window and subclassed it as shown above, can simply handle (for example) WM_LBUTTONDBLCLK in our CWnd-derived class.

BEGIN_MESSAGE_MAP(CMDIClientWnd, CWnd)
    ON_WM_LBUTTONDBLCLK()
END_MESSAGE_MAP()

// CMDIClientWnd message handlers

void CMDIClientWnd::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    // Do something cool!
    // ...
    CWnd::OnLButtonDblClk(nFlags, point);
}

Handle MDI Client double-clicks in Visual Studio or Office-style MFC applications

CMDIFrameWndEx uses its own CWnd-derived class for the MDI Client window, so we cannot use another one in  main frame class. However, this is not so big problem because we can now catch WM_LBUTTONDBLCLK in overridden PreTranslateMessage method.

BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
    if ((pMsg->message == WM_LBUTTONDBLCLK) && (pMsg->hwnd == m_hWndMDIClient))
    {
        // do something cool!
    }
    return CMDIFrameWndEx::PreTranslateMessage(pMsg);
}

Or, you can catch it in a WH_MOUSE hook, if think that can be more exciting. 🙂

class CMainFrame : public CMDIFrameWndEx
{
    static HHOOK m_hHook;
    static HWND m_hWndClient;
    static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam);
// ...
};

    // ...

    DWORD dwThreadID = ::GetCurrentThreadId();
    m_hHook = ::SetWindowsHookEx(WH_MOUSE, &CMainFrame::MouseProc, NULL, dwThreadID);

         // ...

LRESULT CALLBACK CMainFrame::MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode < 0) 
        return ::CallNextHookEx(m_hHook, nCode, wParam, lParam); 

    UINT nMsg = (UINT)wParam; 
    MOUSEHOOKSTRUCT* pMHS = (MOUSEHOOKSTRUCT*)lParam; 
    if (m_hWndClient && pMHS && (WM_LBUTTONDBLCLK == nMsg) && (pMHS->hwnd == m_hWndClient))
    {
        // do something coo!
    }
    return ::CallNextHookEx(m_hHook, nCode, wParam, lParam);
}
    // ...

    if (NULL != m_hHook)
    {
        ::UnhookWindowsHookEx(m_hHook);
        m_hHook = NULL;
    }

Note that creating the MDI Client window in overridden CMDIFrameWnd::CreateClient cand be done in all cases in the same way.

Demo solution

Download: MDI client double-click samples.zip (1265 downloads)
The sample Visual Studio solution contains two projects: one is a standard MDI application, and the other one is Office-style. Just enjoy of MDI Client double-clicks! 🙂

Resources and related articles

1 thought on “Double-click in MDI Client”

Leave a Comment