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.
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 (21)
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
- Codeguru thread: Handle WM_LBUTTONDBLCLK on CMainFrame
- Codexpert blog: Custom Paint in MDI Client