0

I ran into a strange problem while using the TreeView Common Control in a Windows application written in C++ and by means of the Windows API (no MFC or other!): The two important notifications TVN_ITEMCHANGED and TVN_ITEMCHANGING (available since Windows Vista) are only sent if Version 6.0 of ComCtl32.dll is loaded (by convincing the linker via a manifest to do so) AND if Unicode character set is employed. Using Multi-Byte character set causes the two notifications mentioned above to disappear. Using Unicode and version 5.82 of ComCtl32.dll yields the same result. I employ Windows 7 x64 and Visual Studio 2010, by the way.

Below, you find a "minimal" (> 180 lines of code :/) working example. Building under Visual Studio 2010 using Unicode character set (Configuration Properties > General > Character Set) makes the program work as expected, but using Multi-Byte character set makes TVN_ITEMCHANGED and TVN_ITEMCHANGING disappear. Other notifications arrive anyway.

Did I overlook something or did I run into a bug in the Common Controls implementation? I sincerely hope it is my former guess, and I very much appreciate your answers and ideas on this matter!

Best regards, D. Feldmann

#include <Windows.h>
#include <CommCtrl.h>

#include <iostream>

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

HINSTANCE g_hInst = 0;
HWND hwndTV_ = 0;

bool setupTreeView(HWND hwnd)
{
    RECT rc = {0};
    GetClientRect(hwnd, &rc);

    DWORD style = WS_CHILD | WS_VISIBLE | WS_OVERLAPPED | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT;
    hwndTV_ = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, 0, style,
        10, 10, (rc.right - rc.left) - 20, (rc.bottom - rc.top) - 20,
        hwnd, (HMENU) 0xDF, g_hInst, 0);

    if (! hwndTV_)
        return false;

    style |= TVS_CHECKBOXES;
    SetWindowLong(hwndTV_, GWL_STYLE, style);

    HIMAGELIST hil = ImageList_Create(24, 24, ILC_COLOR | ILC_COLOR32, 2, 0);
    const int img1 = ImageList_AddIcon(hil, LoadIcon(0, IDI_QUESTION));
    const int img2 = ImageList_AddIcon(hil, LoadIcon(0, IDI_INFORMATION));
    SendMessage(hwndTV_, TVM_SETIMAGELIST, TVSIL_NORMAL, (LPARAM) hil);

    TVINSERTSTRUCT tvis = {0};
    tvis.hParent =  TVI_ROOT;
    tvis.hInsertAfter = TVI_ROOT;
    tvis.item.mask = TVIF_TEXT | TVIF_STATE | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
    tvis.item.cchTextMax = 5;
    tvis.item.pszText = TEXT("root\0");
    tvis.item.state = (2 << 12) | TVIS_EXPANDED;
    tvis.item.stateMask = TVIS_STATEIMAGEMASK | TVIS_EXPANDED;
    tvis.item.iImage = img1;
    tvis.item.iSelectedImage = img2;
    HTREEITEM hRoot = (HTREEITEM) SendMessage(hwndTV_, TVM_INSERTITEM, 0, (LPARAM) &tvis);

    tvis.hParent = hRoot;
    tvis.hInsertAfter = TVI_LAST;
    tvis.item.cchTextMax = 7;
    tvis.item.pszText = TEXT("item 1\0");
    SendMessage(hwndTV_, TVM_INSERTITEM, 0, (LPARAM) &tvis);

    tvis.item.pszText = TEXT("item 2\0");
    SendMessage(hwndTV_, TVM_INSERTITEM, 0, (LPARAM) &tvis);

    return true;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    LRESULT res = 0u;
    bool handled = true;
    switch (msg)
    {
        case WM_CLOSE:
            PostQuitMessage(0);
            break;

        case WM_CREATE:
            setupTreeView(hwnd);
            break;

        case WM_NOTIFY:
        {
            NMHDR* const nmhdr = (NMHDR*)lParam;
            switch (nmhdr->code)
            {
                case NM_CUSTOMDRAW:
                    std::cout << "NM_CUSTOMDRAW\n";
                    break;

                case NM_CLICK:
                    std::cout << "NM_CLICK\n";
                    break;

                case TVN_ITEMCHANGING:
                    std::cout << "!!! TVN_ITEMCHANGING !!!\n";
                    break;

                case TVN_ITEMCHANGED:
                    std::cout << "!!! TVN_ITEMCHANGED !!!\n";
                    break;

                case TVN_SELCHANGED:
                    std::cout << "TVN_SELCHANGED\n";
                    break;

                case TVN_SELCHANGING:
                    std::cout << "TVN_SELCHANGING\n";
                    break;

                case TVN_ITEMEXPANDED:
                    std::cout << "TVN_ITEMEXPANDED\n";
                    break;

                case TVN_ITEMEXPANDING:
                    std::cout << "TVN_ITEMEXPANDING\n";
                    break;

                default:
                    break;
            }   // switch (
            break;
        }

        default:
            handled = false;
            break;
    }   // switch (msg

    if (! handled)
        res = DefWindowProc(hwnd, msg, wParam, lParam);
    return res;
}

int run(HINSTANCE hInst)
{
    g_hInst = hInst;

    WNDCLASSEX wndCls = {0};
    wndCls.cbSize = sizeof(WNDCLASSEX);
    wndCls.cbClsExtra = 0;
    wndCls.cbWndExtra = 0;
    wndCls.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wndCls.hCursor = LoadCursor(0, IDC_HAND);
    wndCls.hIcon = LoadIcon(0, IDI_WINLOGO);
    wndCls.hIconSm = LoadIcon(0, IDI_WINLOGO);
    wndCls.hInstance = g_hInst;
    wndCls.lpfnWndProc = WindowProc;
    wndCls.lpszClassName = TEXT("TestWindowClass");
    wndCls.lpszMenuName = 0;
    wndCls.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    RegisterClassEx(&wndCls);

    INITCOMMONCONTROLSEX cc = {0};
    cc.dwSize = sizeof(INITCOMMONCONTROLSEX);
    cc.dwICC = ICC_TREEVIEW_CLASSES;
    InitCommonControlsEx(&cc);

    HWND hwnd = CreateWindowEx(0, TEXT("TestWindowClass"), TEXT("Test TreeView"),
        WS_VISIBLE | WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION, CW_USEDEFAULT,
        CW_USEDEFAULT, 400, 300, NULL, NULL, hInst, 0);

    MSG msg = {0};
    for (BOOL res = TRUE; res != 0; )
    {
        res = GetMessage(&msg, 0, 0, 0);
        if (res != -1)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if (msg.message == WM_QUIT)
            res = 0;
    }

    HIMAGELIST hil = (HIMAGELIST) SendMessage(hwndTV_, TVM_GETIMAGELIST, (WPARAM) TVSIL_NORMAL, 0);
    if (hil)
        ImageList_Destroy(hil);

    UnregisterClass(TEXT("TestWindowClass"), g_hInst);

    return 0;
}

// Please build using /subsystem:console
int main(int /*argc*/, char** /*argv*/)
{
    return run(GetModuleHandle(0));
}
feldmann
  • 36
  • 5

1 Answers1

0

They don't disappear, you are simply not looking for them in your WindowProc(). Like many APIs, notifications can also have Ansi and Unicode versions, but you are only looking for one or the other depending on whether you are compiling for MBCS or Unicode. It is possible for an Ansi TreeView to receive Unicode notifications, and vice versa. You should be looking for both A and W versions of the notifications and handle them accordingly:

switch (nmhdr->code)
{
    ...

    case TVN_ITEMCHANGINGA:
        std::cout << "!!! TVN_ITEMCHANGINGA !!!\n";
        break;

    case TVN_ITEMCHANGINGW:
        std::cout << "!!! TVN_ITEMCHANGINGW !!!\n";
        break;

    case TVN_ITEMCHANGEDA:
        std::cout << "!!! TVN_ITEMCHANGEDA !!!\n";
        break;

    case TVN_ITEMCHANGEDW:
        std::cout << "!!! TVN_ITEMCHANGEDA !!!\n";
        break;

    case TVN_SELCHANGEDA:
        std::cout << "TVN_SELCHANGEDA\n";
        break;

    case TVN_SELCHANGEDW:
        std::cout << "TVN_SELCHANGEDW\n";
        break;

    case TVN_SELCHANGINGA:
        std::cout << "TVN_SELCHANGINGA\n";
        break;

    case TVN_SELCHANGINGW:
        std::cout << "TVN_SELCHANGINGW\n";
        break;

    case TVN_ITEMEXPANDEDA:
        std::cout << "TVN_ITEMEXPANDEDA\n";
        break;

    case TVN_ITEMEXPANDEDW:
        std::cout << "TVN_ITEMEXPANDEDW\n";
        break;

    case TVN_ITEMEXPANDINGA:
        std::cout << "TVN_ITEMEXPANDINGA\n";
        break;

    case TVN_ITEMEXPANDINGW:
        std::cout << "TVN_ITEMEXPANDINGW\n";
        break;

    ...
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Ahhh, sometimes I wonder how I manage to get my shoes on in the morning! Thank you very much, this acutally solves it! Although I do not quite get why the remaining messages appear "as usual" while TVN_ITEMCHANG(ED/ING) behave differently and require distinction... – feldmann Oct 21 '14 at 23:32
  • See [`TVM_GETUNICODEFORMAT`](http://msdn.microsoft.com/en-us/library/windows/desktop/bb773730.aspx) and [`TVM_SETUNICODEFORMAT`](http://msdn.microsoft.com/en-us/library/windows/desktop/bb773775.aspx). – Remy Lebeau Oct 21 '14 at 23:53
  • I actually already found those two messages and tried them before posting, but they did not work for TVN_ITEMCHANG(ED/ING). Having slept over and tried again, I found that TVM_SETUNICODEFORMAT affects merely other notifications, like TVN_SELCHANG(ED/ING), but not the two that caused my problems. Though I would consider this to be a bug, it is at least a (very anoying) inconsistency. Anyway, your answer lead me to a solution, so thanks again! – feldmann Oct 22 '14 at 09:16
  • What was the solution? – Remy Lebeau Oct 22 '14 at 18:01
  • I need to check both case labels: `case TVN_SELCHANGEDA: case TVN_SELCHANGEDW: /* some action*/ break;` and so on. There is no need for me to distiguish between the message "encoding" (yet), and in this way, the switch-case should be more robust. And since I could **NEVER** catch a `TVN_SELCHANGEDA`, regardless of whether I use MBCS or `TVM_SETUNICODEFORMAT`, I furthermore contacted Microsoft on this issue, but haven't reveived an answer yet. Anyway, without your answer, I would never have thought of checking for Unicode notifications. – feldmann Oct 23 '14 at 08:00