Click here to Skip to main content
15,886,576 members
Articles / Desktop Programming / MFC

Editable Date Picker

Rate me:
Please Sign up or sign in to vote.
4.78/5 (24 votes)
24 Jan 2008CPOL6 min read 331.8K   9.6K   92  
Fully editable date picker control based on CDateTimeCtrl
////////////////////////////////////////////////////////////////////////////
// File:	DateTimeEditCtrl.h
// Version:	3.7
// Created:	21-Jan-2008
//
// Author:	Paul S. Vickery
// E-mail:	paul@vickeryhome.freeserve.co.uk
//
// Class to provide an editable Date picker control. Based on CDateTimeCtrl.
//
// You are free to use or modify this code, with no restrictions, other than
// you continue to acknowledge me as the original author in this source code,
// or any code derived from it.
//
// If you use this code, or use it as a base for your own code, it would be 
// nice to hear from you simply so I know it's not been a waste of time!
//
// Copyright (c) 2003-2008 Paul S. Vickery
//
////////////////////////////////////////////////////////////////////////////
// Version History:
//
// Version 3.7 - 21-Jan-2008
// =========================
//
// - updated to work correctly on Vista
//
// Version 3.6.1 - 20-Apr-2007
// ===========================
//
// - fixed missing and incorrect NM_KILLFOCUS notifications
// - changed to compile correctly in VC7 (VS2003) and VC8 (VS2005)
// - added fix to control being shown if hidden in initialising of parent
//
// Version 3.6 - 22-Dec-2005
// =========================
//
// - added 'read-only' state, to allow a semi-disabled state
// - updated to recognise XP theme changes, and re-draw accordingly
// - fixed bug where calendar could be shown in non-editable mode on F4, and 
//   date could be changed by using up/down keys
// - added support for a 'disabled' image to be used when the control is 
//   disabled or read-only
// - fixed bug where creating control with ES_READONLY style failed
// - nested button/edit/calendar/xp-style classes so they don't appear in 
//   the top level of the ClassView
//
// Version 3.5 - 04-Apr-2005
// =========================
// - updated to support XP-styling
// - changed to grey-out edit if disabled (suggested by Hans-Georg Ulrich)
// - added fix to enable compilation with VS.NET (thanks to Mark Harmon)
//
// Version 3.4 - 09-Jul-2004
// =========================
// - Fixed bug where dismissing the month menu in the calendar control by 
//   clicking outside of the calendar area then prevents the calendar being 
//   dismissed without first clicking back on the calendar control.
//   (Reported by winkle.)
// - Fixed assertion failures on re-creation of the control when two or more 
//   instances of the control are on a dialog. (Reported by pimOOsse and Rick 
//   Austin.)
//
// Version 3.3 - 09-Jul-2003
// =========================
// Updated to support Unicode.
//
// Version 3.2 - 15-Apr-2003
// =========================
// - Extended control to accept the DTM_SETRANGE and DTM_GETRANGE messages.
// - Fixed incorrect implementation of DTS_RIGHTALIGN.
// - Added facility to use a bitmap for the button instead of a down arrow
// - WM_NOTIFY notifications now passed to parent window from date control's 
//   children
// - Fixed bug where clicking on current (circled) day when edit control is 
//   empty closed the calendar did not put the selected day in the edit control
//
// Version 3.1 - 20-Jan-2003
// =========================
// - Fixed bug where, if new date text is set in the DTN_DATETIMECHANGE handler 
//   (for instance to constrain the date within some boundary), and the user 
//   selects a date which is 'out-of-bounds', and the calendar closes, the 
//   selected date is copied to the edit control, overwriting the 'fixed' date.
// 
// Version 3 - 17-Jan-2003
// =======================
// - Fixed bug where a call to Get/SetWindowText, SetFont, or any function 
//   which resulted in a sending of a DTM_XXX message caused an assertion the 
//   first time round, if called from the OnInitDialog of a parent dialog, or 
//   equivalent.
// 
// Version 2 - 07-Jan-2003
// =======================
// - Edit control notifications (eg EN_CHANGE) now get sent to date control's 
//   parent window
// - Added features as suggested by Marc Clifton on CodeProject:
//   - Added function to validate a date string (IsValidDate)
//   - Added function to only allow valid chars to be typed into the edit control.
//     Also a function to set the valid characters. The default valid characters 
//     are 0-9 and the current user's locale's date separator. If the user changes 
//     the locale settings then the control will automatically pick this up and 
//     use the new separator.
//   - Added ability to use up/down keys to edit portions of the date text
// 
// Version 1 - 03-Jan-2003
// =======================
// Initial version
// 
////////////////////////////////////////////////////////////////////////////
// PLEASE LEAVE THIS HEADER INTACT
////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_DATETIMEEDITCTRL_H__737F6EBA_CC75_11D6_8634_0000B48746CF__INCLUDED_)
#define AFX_DATETIMEEDITCTRL_H__737F6EBA_CC75_11D6_8634_0000B48746CF__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "afxdtctl.h" // only needed for date/time controls

#define DTCEM_DESTROY_CALENDAR	(WM_USER+1)
#define DTCEM_RECREATE		(WM_USER+2)

#ifndef _UXTHEME_H_
// to avoid reliance on uxtheme.h, and uxtheme.lib, we'll load 
// the theme functions, but only once
typedef HANDLE HTHEME;
#endif // _UXTHEME_H_

typedef HTHEME(_stdcall *PFNOPENTHEMEDATA)(HWND, LPWSTR);
typedef HRESULT(_stdcall *PFNCLOSETHEMEDATA)(HTHEME);
typedef HRESULT(__stdcall *PFNDRAWTHEMEBACKGROUND)(HTHEME hTheme, HDC hdc, 
	int iPartId, int iStateId, const RECT *pRect,  const RECT *pClipRect);
typedef BOOL(__stdcall *PFNISAPPTHEMED)(void);
typedef HRESULT (__stdcall *PFNGETTHEMEBACKGROUNDCONTENTRECT)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pBoundingRect, RECT *pContentRect);

#include <shlwapi.h>  // for DllGetVersion definitions

/////////////////////////////////////////////////////////////////////////////
// CDateTimeEditCtrl window

class CDateTimeEditCtrl : public CDateTimeCtrl
{
  DECLARE_DYNAMIC(CDateTimeEditCtrl)

  class CDateTimeEditXPStyle
  {
    // comctl32.dll, and some other DLLs, export a DllGetVersion method 
    // so we use that to see which version of comctl32.dll is currently loaded
    static DWORD GetComCtl32Version()
    {
      HINSTANCE hInstDll = LoadLibrary(_T("COMCTL32.DLL"));
      if (hInstDll == NULL)
	return 0;
      DLLGETVERSIONPROC pfnDllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(hInstDll, "DllGetVersion");
      if (pfnDllGetVersion == NULL)
	return 0;
      DLLVERSIONINFO dvi = { sizeof(dvi) };
      pfnDllGetVersion(&dvi);
      DWORD dwMajor = dvi.dwMajorVersion;
      FreeLibrary(hInstDll);
      return dwMajor;
    }
  public:
    CDateTimeEditXPStyle()
    {
      m_nIsAppXPStyled = -1;
      m_hThemeDLL = LoadLibrary(_T("uxtheme.dll"));
      if (m_hThemeDLL != NULL)
      {
	m_pfnOpenThemeData = (PFNOPENTHEMEDATA)GetProcAddress(m_hThemeDLL, "OpenThemeData");
	m_pfnCloseThemeData = (PFNCLOSETHEMEDATA)GetProcAddress(m_hThemeDLL, "CloseThemeData");
	m_pfnDrawThemeBackground = (PFNDRAWTHEMEBACKGROUND)GetProcAddress(m_hThemeDLL, "DrawThemeBackground");
	m_pfnIsAppThemed = (PFNISAPPTHEMED)GetProcAddress(m_hThemeDLL, "IsAppThemed");
	m_pfnGetThemeBackgroundContentRect = (PFNGETTHEMEBACKGROUNDCONTENTRECT)GetProcAddress(m_hThemeDLL, "GetThemeBackgroundContentRect");
      }
    }
    ~CDateTimeEditXPStyle()
      { if (m_hThemeDLL != NULL) FreeLibrary(m_hThemeDLL); }
    HTHEME OpenThemeData(HWND hwnd, LPWSTR pszClassList)
      { return (m_pfnOpenThemeData != NULL) ? m_pfnOpenThemeData(hwnd, pszClassList) : NULL; }
    HRESULT CloseThemeData(HTHEME hTheme)
      { return (m_pfnCloseThemeData != NULL) ? m_pfnCloseThemeData(hTheme) : E_FAIL; }
    HRESULT DrawThemeBackground(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, const RECT *pClipRect = NULL)
      { return (m_pfnDrawThemeBackground != NULL) ? m_pfnDrawThemeBackground(hTheme, hdc, iPartId, iStateId, pRect, pClipRect) : E_FAIL; }
    HRESULT GetThemeBackgroundContentRect(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pBoundingRect, RECT *pContentRect)
      { return (m_pfnGetThemeBackgroundContentRect != NULL) ? m_pfnGetThemeBackgroundContentRect(hTheme, hdc, iPartId, iStateId, pBoundingRect, pContentRect) : E_FAIL; }
    BOOL IsAppThemed(void)
      { return (m_pfnIsAppThemed != NULL) ? m_pfnIsAppThemed() : FALSE; }
    BOOL IsAppXPStyled()
    {
      if (m_nIsAppXPStyled == -1)
	// IsAppThemed returns TRUE even if the app has no manifest file 
	// The only way to really check is to test the major version of comctl32.dll
	m_nIsAppXPStyled = (IsAppThemed() && GetComCtl32Version() >= 6) ? 1 : 0;
      return (m_nIsAppXPStyled > 0);
    }
    void Reset() { m_nIsAppXPStyled = -1; }
  protected:
    HINSTANCE m_hThemeDLL;  // DLL handle for XP styling library
    PFNOPENTHEMEDATA m_pfnOpenThemeData;
    PFNCLOSETHEMEDATA m_pfnCloseThemeData;
    PFNDRAWTHEMEBACKGROUND m_pfnDrawThemeBackground;
    PFNISAPPTHEMED m_pfnIsAppThemed;
    PFNGETTHEMEBACKGROUNDCONTENTRECT m_pfnGetThemeBackgroundContentRect;
    int m_nIsAppXPStyled;
  };

  /////////////////////////////////////////////////////////////////////////////
  // CDateTimeEditCtrlButton window
  class CDateTimeEditCtrlButton : public CButton
  {
  public:
    CDateTimeEditCtrlButton() { m_bRestoreFocus = FALSE; m_pWndLastFocus = NULL; m_bNonEditable = FALSE; }
    void RestoreFocus(BOOL bRestore = TRUE) { m_bRestoreFocus = bRestore; }
    void SetNonEditable(BOOL bNonEditable = TRUE) { m_bNonEditable = bNonEditable; }
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CDateTimeEditCtrlButton)
	  public:
	  virtual BOOL PreTranslateMessage(MSG* pMsg);
	  virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
	  //}}AFX_VIRTUAL

    // Generated message map functions
  protected:
    CWnd* m_pWndLastFocus;
    BOOL m_bRestoreFocus;
    BOOL m_bNonEditable;

    //{{AFX_MSG(CDateTimeEditCtrlButton)
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
    afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
	  afx_msg BOOL OnEraseBkgnd(CDC* pDC);
	  //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
  };

  /////////////////////////////////////////////////////////////////////////////
  // CDateTimeEditCtrlEditCtrl window
  class CDateTimeEditCtrlEditCtrl : public CEdit
  {
  public:
    BOOL GetAllowUpDownKeys();
    void SetAllowUpDownKeys(BOOL bAllow = TRUE);
    CString GetValidChars();
    void SetValidChars(LPCTSTR lpszValidChars = NULL);
    BOOL GetValidCharsOnly();
    void SetValidCharsOnly(BOOL bValidCharsOnly = TRUE);
    CDateTimeEditCtrlEditCtrl();
    void SetNonEditable(BOOL bNonEditable = TRUE) { m_bNonEditable = bNonEditable; }
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CDateTimeEditCtrlEditCtrl)
	  public:
	  virtual BOOL PreTranslateMessage(MSG* pMsg);
	  //}}AFX_VIRTUAL

    // Generated message map functions
  protected:
	  void SendDateTimeChange();
    BOOL m_bNonEditable;
    BOOL m_bValidCharsOnly;
    CString m_sValidChars;
    BOOL m_bAllowUpDownKeys;

	  //{{AFX_MSG(CDateTimeEditCtrlEditCtrl)
	  afx_msg UINT OnGetDlgCode();
	  afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
	  afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor);
	  afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
	  afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
	  afx_msg void OnSettingChange(UINT uFlags, LPCTSTR lpszSection);
	  afx_msg void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp);
	  afx_msg void OnKillFocus(CWnd* pNewWnd);
	  //}}AFX_MSG
	  afx_msg void OnStyleChanged(int nStyleType, LPSTYLESTRUCT lpStyleStruct);
    DECLARE_MESSAGE_MAP()
  };

  /////////////////////////////////////////////////////////////////////////////
  // CDateTimeEditCtrlMonthCalCtrl window

  class CDateTimeEditCtrlMonthCalCtrl : public CMonthCalCtrl
  {
  // Construction
  public:
	  CDateTimeEditCtrlMonthCalCtrl();

  // Attributes
  public:

  // Operations
  public:

  // Overrides
	  // ClassWizard generated virtual function overrides
	  //{{AFX_VIRTUAL(CDateTimeEditCtrlMonthCalCtrl)
	  protected:
	  virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
	  //}}AFX_VIRTUAL

  // Implementation
  public:
	  virtual ~CDateTimeEditCtrlMonthCalCtrl();

	  // Generated message map functions
  protected:
	  DWORD m_nIgnoreNextMessage;

	  //{{AFX_MSG(CDateTimeEditCtrlMonthCalCtrl)
	  //}}AFX_MSG
	  DECLARE_MESSAGE_MAP()
  };

  /////////////////////////////////////////////////////////////////////////////
  // CDateTimeEditCtrlCalendarWnd window

  class CDateTimeEditCtrlCalendarWnd : public CWnd
  {
  // Construction
  public:
    CDateTimeEditCtrlCalendarWnd(CWnd* pComboParent, DWORD dwMCStyle = 0);

  // Attributes
  public:
    CDateTimeEditCtrlMonthCalCtrl* GetMonthCalCtrl() { return m_pCalendar; }

  // Operations
  public:

  // Overrides
	  // ClassWizard generated virtual function overrides
	  //{{AFX_VIRTUAL(CDateTimeEditCtrlCalendarWnd)
	  public:
	  virtual BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext = NULL);
	  virtual BOOL DestroyWindow();
	  protected:
	  virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
	  //}}AFX_VIRTUAL

  // Implementation
  public:
	  virtual ~CDateTimeEditCtrlCalendarWnd();

	  // Generated message map functions
  protected:
	  //{{AFX_MSG(CDateTimeEditCtrlCalendarWnd)
	  afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	  //}}AFX_MSG
  #if _MFC_VER >= 0x0700
	  afx_msg void OnActivateApp(BOOL bActive, DWORD dwThreadID);
  #else
	  afx_msg void OnActivateApp(BOOL bActive, HTASK hTask);
  #endif
	  DECLARE_MESSAGE_MAP()

  private:
	  CDateTimeEditCtrlMonthCalCtrl* m_pCalendar;

	  CWnd* m_pComboParent;

	  DWORD m_dwMCStyle;
  };

// Construction
public:
	CDateTimeEditCtrl();

// Attributes
public:

// Operations
public:

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CDateTimeEditCtrl)
	protected:
	virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
	virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
	virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
	virtual void PreSubclassWindow();
	//}}AFX_VIRTUAL

// Implementation
public:
  BOOL SetButtonImageID(UINT nID, UINT nDisabledID = 0);
  UINT GetButtonImageID(UINT* pDisabledID = NULL) const { if (pDisabledID != NULL) *pDisabledID = m_nBtnImageDisabledID; return m_nBtnImageID; }
  void GetButtonImageIDs(UINT& nID, UINT& nDisabledID) const { nID = m_nBtnImageID; nDisabledID = m_nBtnImageDisabledID; }
  BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
  virtual ~CDateTimeEditCtrl();
  CEdit* GetEditControl() const { return m_pEdit; }
  void EnableButton(BOOL bEnable = TRUE);
  void RestoreFocus(BOOL bRestore = TRUE) { if (m_pBtn != NULL) m_pBtn->RestoreFocus(bRestore); }
  void SetNonEditable(BOOL bNonEditable = TRUE);
  BOOL GetNonEditable() { return m_bNonEditable; }
  void SetReadOnly(BOOL bReadOnly = TRUE);
  BOOL GetReadOnly() const { return m_bReadOnly; }
  virtual BOOL IsValidDate(LPCTSTR lpszDate = NULL);
  void SetValidCharsOnly(BOOL bValidCharsOnly = TRUE);
  BOOL GetValidCharsOnly();
  void SetValidChars(LPCTSTR lpszValidChars = NULL);
  CString GetValidChars();
  void SetAllowUpDownKeys(BOOL bAllow = TRUE);
  BOOL GetAllowUpDownKeys();

	// Generated message map functions
protected:
  BOOL DestroyCalendar(BOOL bDiscard = FALSE);
  BOOL CreateCalendar();
  CDateTimeEditCtrlEditCtrl* m_pEdit;
  CDateTimeEditCtrlButton* m_pBtn;
  UINT m_nBtnImageID;
  UINT m_nBtnImageDisabledID;
  BOOL m_bNonEditable;
  BOOL m_bReadOnly;
  BOOL m_bDisabled;
  BOOL m_bRightAlign;

  CSpinButtonCtrl* m_pSpin;

  CDateTimeEditCtrlCalendarWnd* m_pCalWnd;
  CString m_sOrigDate;

  BOOL m_bInCreate;

  COleDateTime* m_pDateMin;
  COleDateTime* m_pDateMax;

  // monthcal properties
  HFONT m_hMCFont;
  COLORREF m_acrMonthCal[6];

	//{{AFX_MSG(CDateTimeEditCtrl)
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	afx_msg void OnEnable(BOOL bEnable);
	afx_msg void OnSetFocus(CWnd* pOldWnd);
	afx_msg void OnSize(UINT nType, int cx, int cy);
	afx_msg void OnCancelMode();
	afx_msg void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp);
	public:
	afx_msg void OnPaint();
	//}}AFX_MSG
	protected:
	afx_msg BOOL OnKillfocus(NMHDR* pNMHDR, LRESULT* pResult);
	afx_msg LONG OnDestroyCalendar(WPARAM wParam, LPARAM lParam);
	afx_msg void OnStyleChanging(int nStyleType, LPSTYLESTRUCT lpStyleStruct);
	afx_msg LONG OnRecreate(WPARAM wParam, LPARAM lParam);
	DECLARE_MESSAGE_MAP()
};

// typedef the nested classes so we can use the plain names
// and so the Class Wizard will still work
typedef CDateTimeEditCtrl::CDateTimeEditCtrlButton CDateTimeEditCtrlButton;
typedef CDateTimeEditCtrl::CDateTimeEditCtrlEditCtrl CDateTimeEditCtrlEditCtrl;
typedef CDateTimeEditCtrl::CDateTimeEditCtrlMonthCalCtrl CDateTimeEditCtrlMonthCalCtrl;
typedef CDateTimeEditCtrl::CDateTimeEditCtrlCalendarWnd CDateTimeEditCtrlCalendarWnd;

/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_DATETIMEEDITCTRL_H__737F6EBA_CC75_11D6_8634_0000B48746CF__INCLUDED_)

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
Originally from an electronics background, I moved into software in 1996, partly as a result of being made redundant, and partly because I was very much enjoying the small amount of coding (in-at-the-deep-end-C) that I had been doing!

I swiftly moved from C to C++, and learned MFC, and then went on to real-time C on Unix. After this I moved to the company for which I currently work, which specialises in Configuration Management software, and currently program mainly in C/C++, for Windows. I have been gradually moving their legacy C code over to use C++ (with STL, MFC, ATL, and WTL). I have pulled in other technologies (Java, C#, VB, COM, SOAP) where appropriate, especially when integrating with third-party products.

In addition to that, I have overseen the technical side of the company website (ASP, VBScript, JavaScript, HTML, CSS), and have also worked closely with colleagues working on other products (Web-based, C#, ASP.NET, SQL, etc).

For developing, I mainly use Visual Studio 2010, along with an in-house-designed editor based on Andrei Stcherbatchenko's syntax parsing classes, and various (mostly freeware) tools. For website design, I use Dreaweaver CS3.

When not developing software, I enjoy listening to and playing music, playing electric and acoustic guitars and mandolin.

Comments and Discussions