Search code examples
c++buildervclc++builder-xe3

Use TMonthCalendar for months and years only


My point is I want to keep the calendar always on the months view, like this:

Expected TMonthCalendar view:

image

So that when I click in a month, instead of showing the days of the month, it stays in this screen and call the event.


Solution

  • Prior to Vista, the underlying Win32 MonthCal control that TMonthCalendar wraps has no concept of views at all, so you can't do what you are asking for in XP and earlier, unless you find a 3rd party calendar that supports what you want on those Windows versions.

    However, in Vista and later, the underlying MonthCal control is view-aware (but TMonthCalendar itself is not). You can manually send a MCM_SETCURRENTVIEW message to the TMonthCalendar's HWND to set its initial view to MCMV_YEAR, and subclass its WindowProc property to intercept CN_NOTIFY messages (the VCL's wrapper for WM_NOTIFY) looking for the MCN_VIEWCHANGE notification when the user changes the active view. You can't lock the control to a specific view, but you can react to when the user changes the active view from the Year view to the Month view, and then you can reset the calendar back to the Year view if needed.

    For example:

    class TMyForm : public TForm
    {
    __published:
        TMonthCalendar *MonthCalendar1;
        ...
    private:
        TWndMethod PrevMonthCalWndProc;
        void __fastcall MonthCalWndProc(TMessage &Message);
        ...
    public:
        __fastcall TMyForm(TComponent *Owner)
        ...
    };
    

    #include "MyForm.h"
    #include <Commctrl.h>
    
    #ifndef MCM_SETCURRENTVIEW
    
    #define MCMV_MONTH      0
    #define MCMV_YEAR       1
    
    #define MCM_SETCURRENTVIEW (MCM_FIRST + 32)
    #define MCN_VIEWCHANGE     (MCN_FIRST - 4) // -750
    
    typedef struct tagNMVIEWCHANGE
    {
        NMHDR           nmhdr;
        DWORD           dwOldView;
        DWORD           dwNewView;
    } NMVIEWCHANGE, *LPNMVIEWCHANGE;
    
    #endif
    
    __fastcall TMyForm(TComponent *Owner)
        : TForm(Owner)
    {
        if (Win32MajorVersion >= 6)
        {
            SendMessage(MonthCalendar1->Handle, MCM_SETCURRENTVIEW, 0, MCMV_YEAR);
            PrevMonthCalWndProc = MonthCalendar1->WindowProc;
            MonthCalendar1->WindowProc = MonthCalWndProc;
        }
    }
    
    void __fastcall TMyForm::MonthCalWndProc(TMessage &Message)
    {
        PrevMonthCalWndProc(Message);
        if (Message.Msg == CN_NOTIFY)
        {
            if (reinterpret_cast<NMHDR*>(Message.LParam)->code == MCN_VIEWCHANGE)
            {
                LPNMVIEWCHANGE lpNMViewChange = static_cast<LPNMVIEWCHANGE>(Message.LParam);
                if ((lpNMViewChange->dwOldView == MCMV_YEAR) && (lpNMViewChange->dwNewView == MCMV_MONTH))
                {
                    // do something ...
                    SendMessage(MonthCalendar1->Handle, MCM_SETCURRENTVIEW, 0, MCMV_YEAR);
                }
            }
        }
    }
    

    If you are using C++Builder 10.1 Berlin or later, look at the newer TCalendarView and TCalendarPicker components. They both have a DisplayMode property that you can set to TDisplayMode::dmYear for the current view, and an On(Calendar)ChangeView event to react to view changes by the user.