Windows TreeView常见控件通知和多字节字符集的问题

Problems with Windows TreeView Common Control Notifications and Multi-Byte Character Set

本文关键字:多字节 字符集 问题 通知 TreeView 常见 控件 Windows      更新时间:2023-10-16

在用C++和Windows API(没有MFC或其他!)编写的Windows应用程序中使用TreeView公共控件时,我遇到了一个奇怪的问题:两个重要通知TVN_ITEMCHANGED和TVN_ITEMCANGING(自Windows Vista起可用)仅在加载了ComCtl32.dll的6.0版时发送(通过清单说服链接器这样做)并且如果使用Unicode字符集。使用多字节字符集会导致上面提到的两个通知消失。使用Unicode和ComCtl32.dll的5.82版本会产生相同的结果。顺便说一句,我使用的是Windows7x64和VisualStudio2010。

下面,您可以找到一个"最小"(>180行代码:/)的工作示例。在Visual Studio 2010下使用Unicode字符集(配置属性>常规>字符集)进行构建可以使程序按预期工作,但使用多字节字符集会使TVN_ITEMCHANGED和TVN_ITEMCANGING消失。其他通知仍然会到达。

我是忽略了什么,还是在Common Controls实现中遇到了错误?我真诚地希望这是我以前的猜测,我非常感谢你对此事的回答和想法!

谨致问候,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");
    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");
    SendMessage(hwndTV_, TVM_INSERTITEM, 0, (LPARAM) &tvis);
    tvis.item.pszText = TEXT("item 2");
    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_CUSTOMDRAWn";
                    break;
                case NM_CLICK:
                    std::cout << "NM_CLICKn";
                    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_SELCHANGEDn";
                    break;
                case TVN_SELCHANGING:
                    std::cout << "TVN_SELCHANGINGn";
                    break;
                case TVN_ITEMEXPANDED:
                    std::cout << "TVN_ITEMEXPANDEDn";
                    break;
                case TVN_ITEMEXPANDING:
                    std::cout << "TVN_ITEMEXPANDINGn";
                    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));
}

它们不会消失,您只是不在WindowProc()中查找它们。与许多API一样,通知也可以有Ansi和Unicode版本,但您只是在寻找其中一个版本,这取决于您是为MBCS还是Unicode编译。Ansi TreeView可以接收Unicode通知,反之亦然。您应该同时查找AW版本的通知,并相应地处理它们:

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_SELCHANGEDAn";
        break;
    case TVN_SELCHANGEDW:
        std::cout << "TVN_SELCHANGEDWn";
        break;
    case TVN_SELCHANGINGA:
        std::cout << "TVN_SELCHANGINGAn";
        break;
    case TVN_SELCHANGINGW:
        std::cout << "TVN_SELCHANGINGWn";
        break;
    case TVN_ITEMEXPANDEDA:
        std::cout << "TVN_ITEMEXPANDEDAn";
        break;
    case TVN_ITEMEXPANDEDW:
        std::cout << "TVN_ITEMEXPANDEDWn";
        break;
    case TVN_ITEMEXPANDINGA:
        std::cout << "TVN_ITEMEXPANDINGAn";
        break;
    case TVN_ITEMEXPANDINGW:
        std::cout << "TVN_ITEMEXPANDINGWn";
        break;
    ...
}