Search code examples
c++winapidatetimepicker

How to handle notification from date picker control


I can construct and show a date picker control. I can change the date displayed in the control by clicking on it.

image

The problem is that I cannot get the notification when the date is changed.

Here is the complete, minimal code for an application showing the problem. The intention to for the console to show a message when the notification is received, either in the message loop or in the window message handler.

#include <windows.h>
#include <iostream>
#include "commctrl.h"

// Add common controls V6 to manifest
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

// Message handler
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
        EndPaint(hwnd, &ps);
    }
    return 0;

    case DTN_DATETIMECHANGE:
        std::cout << " date change\n";
        return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

HWND ConstructMainWindow()
{
    // Register the window class.
    const char CLASS_NAME[] = "Sample Window Class";

    WNDCLASSA wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = NULL;
    wc.lpszClassName = CLASS_NAME;

    RegisterClassA(&wc);

    // Create the window.

    HWND hwnd = CreateWindowExA(
        0,                          // Optional window styles.
        CLASS_NAME,                 // Window class
        "Date Picker Test", // Window text
        WS_OVERLAPPEDWINDOW,        // Window style

        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

        NULL, // Parent window
        NULL, NULL, NULL);
    if (hwnd == NULL)
    {
        std::cout << "main window failed\n";
        exit(1);
    }

    return hwnd;
}

void ConstructDatePicker(HWND main)
{
    // initialize date picker control
    INITCOMMONCONTROLSEX icex;
    icex.dwSize = sizeof(icex);
    icex.dwICC = ICC_DATE_CLASSES;
    if (!InitCommonControlsEx(&icex))
        std::cout << "InitCommonControlsEx failed\n";

    // send messages to WindowProc
    WNDCLASSA wc = {};
    wc.lpfnWndProc = &WindowProc;
    wc.hInstance = NULL;
    wc.lpszClassName = "DateTime";
    RegisterClassA(&wc);

    CreateWindowExA(0,
                    DATETIMEPICK_CLASSA,
                    "DateTime",
                    WS_BORDER | WS_CHILD | WS_VISIBLE | DTS_SHOWNONE,
                    30, 30, 200, 30,
                    main,
                    NULL,
                    NULL,
                    NULL);
}

int main()
{
    HWND hwnd = ConstructMainWindow();

    ConstructDatePicker(hwnd);

    ShowWindow(hwnd, 1);

    // Run the message loop.

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        if( msg.message == DTN_DATETIMECHANGE)
            std::cout << "message " << msg.message << "\n";
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

After fix from Remy Lebeau

case WM_NOTIFY:
{
    NMHDR *pnmhdr = reinterpret_cast<NMHDR *>(lParam);
    if (pnmhdr->code == DTN_DATETIMECHANGE)
    {

         LPNMDATETIMECHANGE lpChange = (LPNMDATETIMECHANGE)(lParam);
        std::cout << " date change to " <<
            lpChange->st.wYear <<"/" <<
            lpChange->st.wMonth <<"/" <<
            lpChange->st.wDay <<" "
            << lpChange->dwFlags << "\n";
    }
}

Output is

 date change to 2022/12/1 0
 date change to 2022/12/1 0

Solution

  • DO NOT call RegisterClass() for system-provided classes. They are already registered by the OS. You are not supposed to register a custom WindowProc for system classes. If you want to hook a system-provided UI control, use SetWindowLongPtr(GWLP_WNDPROC) or SetWindowSubclass() instead. See Subclassing Controls on MSDN.

    However, that being said, there is no need to do that in this case, because the DTN_DATETIMECHANGE notification is sent to the DTP's parent window (ie, your main window), which you already have a WindowProc registered for. The reason you are not seeing the notification is because you are catching it incorrectly. DTN_DATETIMECHANGE (-759) is not the actual message code, so your switch never jumps to that case. The notification is actually wrapped inside of a WM_NOTIFY message, which you would have known if you had read the documentation:

    https://learn.microsoft.com/en-us/windows/win32/controls/dtn-datetimechange

    Sent by a date and time picker (DTP) control whenever a change occurs. This notification code is sent in the form of a WM_NOTIFY message.

    https://learn.microsoft.com/en-us/windows/win32/controls/wm-notify

    Sent by a common control to its parent window when an event has occurred or the control requires some information.

    So, try this instead:

    #include <windows.h>
    #include <iostream>
    #include "commctrl.h"
    
    // Add common controls V6 to manifest
    #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
    
    bool dateValid = false;
    SYSTEMTIME stDate;
    
    bool dateIsSame(const SYSTEMTIME &date1, const SYSTEMTIME &date2)
    {
        return (
            date1.wYear == date2.wYear &&
            date1.wMonth == date2.wMonth &&
            date1.wDay == date2.wDay
        );
    }
    
    // Message handler
    LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            case WM_CREATE:
            {
                HWND dtp = CreateWindowExA(0,
                        DATETIMEPICK_CLASSA,
                        "DateTime",
                        WS_BORDER | WS_CHILD | WS_VISIBLE | DTS_SHOWNONE,
                        30, 30, 200, 30,
                        hwnd,
                        NULL,
                        NULL,
                        NULL);
                dateValid = (DateTime_GetSystemtime(dtp, &stDate) == GDT_VALID);
            }
            return 0;
    
            case WM_DESTROY:
                PostQuitMessage(0);
                return 0;
    
            case WM_PAINT:
            {
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hwnd, &ps);
                FillRect(hdc, &ps.rcPaint, reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1));
                EndPaint(hwnd, &ps);
            }
            return 0;
    
            case WM_NOTIFY:
            {
                NMHDR *pnmhdr = reinterpret_cast<NMHDR*>(lParam);
                if (pnmhdr->code == DTN_DATETIMECHANGE)
                {
                    LPNMDATETIMECHANGE lpChange = reinterpret_cast<LPNMDATETIMECHANGE>(lParam);
                    if (lpChange->dwFlags & GDT_NONE)
                    {
                        if (dateValid)
                            std::cout << " date change to none\n";
                        dateValid = false;
                    }
                    else
                    {
                        if (!dateValid || !dateIsSame(stDate, lpChange->st))
                        {
                            stDate = lpChange->st;
                            std::cout << " date change to " <<
                                stDate.wYear << "/" <<
                                stDate.wMonth << "/" <<
                                stDate.wDay << "\n";
                        }
                        dateValid = true;
                    }
                }
            }
            return 0;
        }
    
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    
    HWND ConstructMainWindow()
    {
        // Register the window class.
        const char CLASS_NAME[] = "Sample Window Class";
    
        WNDCLASSA wc = {};
        wc.lpfnWndProc = WindowProc;
        wc.lpszClassName = CLASS_NAME;
    
        if (RegisterClassA(&wc) == NULL)
        {
            std::cout << "main window register class failed\n";
            exit(1);
        }
    
        // Create the window.
    
        HWND hwnd = CreateWindowExA(
            0,                        // Optional window styles.
            CLASS_NAME,              // Window class
            "Date Picker Test", // Window text
            WS_OVERLAPPEDWINDOW,        // Window style
    
            // Size and position
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    
            NULL, // Parent window
            NULL, NULL, NULL);
        if (hwnd == NULL)
        {
            std::cout << "main window failed\n";
            exit(1);
        }
    
        return hwnd;
    }
    
    int main()
    {
        // initialize date picker control
        INITCOMMONCONTROLSEX icex;
        icex.dwSize = sizeof(icex);
        icex.dwICC = ICC_DATE_CLASSES;
        if (!InitCommonControlsEx(&icex))
        {
            std::cout << "InitCommonControlsEx failed\n";
            return 1;
        }
    
        HWND hwnd = ConstructMainWindow();
    
        ShowWindow(hwnd, 1);
    
        // Run the message loop.
    
        MSG msg = {};
        while (GetMessage(&msg, NULL, 0, 0) > 0)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        return 0;
    }