Putting a Month Calendar Control on the MDI Client

Environment: MFC/C++

This technique has been used in Windows NT 4.0, Windows XP and Windows 95, and built in Visual Studio 6.0 and Visual Studio .NET. It is written in C++ using MFC.

There are plenty of articles about subclassing the MDI client window and drawing on it, but I wanted to put a control there -- specifically the Month Calendar control, using MFC. Adding the control was easy. Just create it in PreSubclassWindow(), size it in OnSize(), and destroy it in UnsubclassWindow(). Here's how:

in MDIClient.h

class CMDICalendar : public CWnd
{
public:
  CMDICalendar();
  virtual ~CMDICalendar();
  HWND UnsubclassWindow();

protected:
  DECLARE_MESSAGE_MAP()
  virtual void PreSubclassWindow();
  CMonthCalCtrl m_ctrlCalendar;
  afx_msg void OnSize(UINT nType, int cx, int cy);
};

in MDIClient.cpp

#define ID_CALENDAR    4500

CMDICalendar::CMDICalendar()
{
}

CMDICalendar::~CMDICalendar()
{
}

BEGIN_MESSAGE_MAP(CMDICalendar, CWnd)
  ON_WM_SIZE()
END_MESSAGE_MAP()

void CMDICalendar::PreSubclassWindow()
{
  RECT rc;
  GetClientRect(&rc);
  VERIFY(m_ctrlCalendar.Create(WS_CHILD | WS_VISIBLE | 
                               WS_CLIPSIBLINGS |
                               MCS_DAYSTATE | MCS_NOTODAYCIRCLE,
                               rc, this, ID_CALENDAR));
  DWORD colour = ::GetSysColor(COLOR_APPWORKSPACE);
  m_ctrlCalendar.SetColor(MCSC_BACKGROUND, colour);
  m_ctrlCalendar.SetColor(MCSC_MONTHBK, colour);
  CWnd::PreSubclassWindow();
}

void CMDICalendar::OnSize(UINT nType, int cx, int cy)
{
  CWnd::OnSize(nType, cx, cy);
  m_ctrlCalendar.MoveWindow(0, 0, cx, cy, FALSE);
}

HWND CMDICalendar::UnsubclassWindow()
{
  VERIFY(m_ctrlCalendar.DestroyWindow());
  return CWnd::UnsubclassWindow();
}

The calendar control is created in PreSubclassWindow() and destroyed in UnsubclassWindow(), which is an override, rather than a virtual function. This creation and destruction are necessary because we subclass the MDI client to show the Month Calendar control and unsubclass to restore the client to normal from a menu command. The background colour of the control is set to the same colour as the client. Refer to the calendar 1 program in the Downloads section.

This is quite easy, and it works -- as long as you use the WS_CLIPSIBLINGS style for the control. But there are three major problems:

  1. When an MDI child window is minimized, it disappears.
  2. When the 'Next' command is used in the child's system menu, or Ctrl+Tab is pressed, the child window disappears.
  3. When the Month Calendar control is clicked with the mouse, the child windows disappear.

Here are the solutions:

  1. Reduce the size of the Month Calendar control to allow one line of icons at the bottom of the client window. The control background is the same colour as the client, so you can't see the join. Save the height of the icon in a member variable and use it like so:
    CMDICalendar::CMDICalendar()
    : m_cyIcon(0)
    {
    }
    
    void CMDICalendar::PreSubclassWindow()
    {
      m_cyIcon = ::GetSystemMetrics(SM_CYMINIMIZED);
      RECT rc;
      GetClientRect(&rc);
      rc.bottom -= m_cyIcon;
      VERIFY(m_ctrlCalendar.Create(WS_CHILD | WS_VISIBLE | 
                                   WS_CLIPSIBLINGS |
                                   MCS_DAYSTATE | MCS_NOTODAYCIRCLE,
                                   rc, this, ID_CALENDAR));
      DWORD colour = ::GetSysColor(COLOR_APPWORKSPACE);
      m_ctrlCalendar.SetColor(MCSC_BACKGROUND, colour);
      m_ctrlCalendar.SetColor(MCSC_MONTHBK, colour);
      CWnd::PreSubclassWindow();
    }
    
    void CMDICalendar::OnSize(UINT nType, int cx, int cy)
    {
      CWnd::OnSize(nType, cx, cy);
      m_ctrlCalendar.MoveWindow(0, 0, cx, cy - m_cyIcon, FALSE);
      ArrangeIconicWindows();
    }
    

    The call to ArrangeIconicWindows() keeps the icons in place as the window is sized. Of course, this only allows one row of icons, but that's enough for most users.

  2. Trapping WM_MDISETMENU and sending the Month Calendar control to the bottom of the Z-order stops the child windows from disappearing. Here's how:
    BEGIN_MESSAGE_MAP(CMDICalendar, CWnd)
      ON_WM_SIZE()
      ON_MESSAGE(WM_MDISETMENU, OnMDISetMenu)
    END_MESSAGE_MAP()
    
    LRESULT CMDICalendar::OnMDISetMenu(WPARAM wParam, LPARAM lParam)
    {
      m_ctrlCalendar.SetWindowPos(&wndBottom, 0, 0, 0, 0,
      SWP_NOACTIVATE | SWP_NOSIZE |
      SWP_NOMOVE | SWP_NOSENDCHANGING);
      return Default();
    }
  3. Trapping NM_RELEASEDCAPTURE in CMDICalendar allows a mouse click to bring the Month Calendar control to the fore as before, but releasing the mouse puts it behind all the MDI child windows again. This is a method of consulting the calendar, and then switching back to the child windows, assuming the child windows aren't maximized, of course.

    in MDIClient.cpp

    BEGIN_MESSAGE_MAP(CMDICalendar, CWnd)
      ON_WM_SIZE()
      ON_MESSAGE(WM_MDISETMENU, OnMDISetMenu)
      ON_NOTIFY(NM_RELEASEDCAPTURE, ID_CALENDAR, OnReleasedCapture)
    END_MESSAGE_MAP()
    
    void CMDICalendar::OnReleasedCapture(NMHDR* pNMHDR, LRESULT* pResult)
    {
      m_ctrlCalendar.SetWindowPos(&wndBottom, 0, 0, 0, 0,
      SWP_NOACTIVATE | SWP_NOSIZE |
      SWP_NOMOVE | SWP_NOSENDCHANGING);
    }
    

This sends the Month Calendar control to the bottom, as seen in the Calendar 2 program in the Downloads section.

Further to Point 2 above, this only works if a WM_MDISETMENU command is actually sent to the client window. In one of my applications (a database application), there in no document, and therefore no CMultiDocTemplate because each child window represents a different table of the database. The database connection is kept in a variable in the CWinApp derived class and there is only one menu, whatever is displayed. As a result, no WM_MDISETMENU is sent, and it has to be manufactured like so:

void CChildFrame::OnMDIActivate(BOOL bActivate, 
                                CWnd* pActivateWnd, 
                                CWnd* pDeactivateWnd)
{
  CMDIChildWnd::OnMDIActivate( bActivate, 
                               pActivateWnd, 
                               pDeactivateWnd);
  ::SendMessage(GetMDIFrame()->m_hWndMDIClient, 
                WM_MDISETMENU, 
                0, 0);
  if(!bActivate)
        RedrawWindow(NULL, NULL,
                     RDW_INVALIDATE | RDW_FRAME | RDW_NOCHILDREN);
}

When WM_MDISETMENU is sent with a WPARAM and LPARAM of zero, the result is that the menu is refreshed, but not changed. The RedrawWindow(...) is necessary; otherwise, not all the deactivated window frames are repainted.

Downloads

Download source -- 25 Kb
Download source -- 25 Kb


Comments

  • Very good idea!

    Posted by Legacy on 06/12/2002 12:00am

    Originally posted by: Willi

    Thank you for source code.

    A little bug.
    You create Calendar Control with MCS_DAYSTATE style.
    But don't handle MCN_GETDAYSTATE notification.
    As a result we have random days numbers with bold font.

    Reply
  • did the lakers win?

    Posted by Legacy on 06/11/2002 12:00am

    Originally posted by: walter

    i missed the end of the game. Anyone?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Live Event Date: November 13, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT APIs can be a great source of competitive advantage. The practice of exposing backend services as APIs has become pervasive, however their use varies widely across companies and industries. Some companies leverage APIs to create internal, operational and development efficiencies, while others use them to drive ancillary revenue channels. Many companies successfully support both public and private programs from the same API by varying levels …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds