子类化列表视图以仅编辑其子项

时间:2011-12-28 15:56:42

标签: c winapi

我写了这个小程序,它显示了一个列表视图,并使项目和子项目可编辑。

我想更改此选项以使子项目可编辑。我想让列表视图窗口程序站立起来,我不必每次都转发WM_NOTIFY消息,就像我现在在WndProcMain中所做的那样。目的是我在程序中不使用一个带有可编辑子项的列表视图,我将在许多不同的窗口中使用它。

LVN_ENDLABELEDIT处理WndProcList通知,因为必须更改bEditing。必须编辑子项时,此标志用于WM_PAINT。这是一个修复,否则第一个子项中的文本会消失,因为它认为正在编辑第一个项目。但是,我还希望在列表视图所有者窗口(在本例中为LVN_ENDLABELEDIT)的窗口过程中收到类似WndProcMain的消息,因为我还想操纵用户输入。

请询问您是否有疑问。

提前致谢

迈达斯

WNDPROC     wpOrigEditProc;
RECT        rcSubItem;

LRESULT CALLBACK WndProcEditList(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_WINDOWPOSCHANGING:
            {
                WINDOWPOS *pos = (WINDOWPOS*) lParam;

                pos->x  = rcSubItem.left;
                pos->cx = rcSubItem.right;
            }
            break;
        default:
            return CallWindowProc(wpOrigEditProc, hWnd, uMsg, wParam, lParam);
    }
    return 1;
} 

LRESULT CALLBACK WndProcList(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    static HWND             hEdit;
    static RECT             rc;
    static LVITEM           lvI;
    static unsigned char    bEditing = 0;
    switch (uMsg) {
        case WM_NOTIFY:
            switch (((NMHDR*) lParam)->code) {
                case NM_CLICK:
                    lvI.iItem       = ((NMITEMACTIVATE*) lParam)->iItem;
                    lvI.iSubItem    = ((NMITEMACTIVATE*) lParam)->iSubItem;
                    break;
                case NM_DBLCLK:
                    SendMessage(hWnd, LVM_EDITLABEL, lvI.iItem, 0);
                    break;
                case LVN_BEGINLABELEDIT:
                    {
                        char    text[32];
                        bEditing        = 1;
                        hEdit           = (HWND) SendMessage(hWnd, LVM_GETEDITCONTROL, 0, 0);
                        rcSubItem.top   = lvI.iSubItem;
                        rcSubItem.left  = LVIR_LABEL;
                        SendMessage(hWnd, LVM_GETSUBITEMRECT, lvI.iItem, (long) &rcSubItem);
                        rcSubItem.right = SendMessage(hWnd, LVM_GETCOLUMNWIDTH, lvI.iSubItem, 0);
                        wpOrigEditProc  = (WNDPROC) SetWindowLong(hEdit, GWL_WNDPROC, (long) WndProcEditList);
                        lvI.pszText     = text;
                        lvI.cchTextMax  = 32;
                        SendMessage(hWnd, LVM_GETITEMTEXT, lvI.iItem, (long) &lvI);
                        SetWindowText(hEdit, lvI.pszText);
                    }
                    break;
                case LVN_ENDLABELEDIT:
                    bEditing = 0;
                    SetWindowLong(hEdit, GWL_WNDPROC, (long) wpOrigEditProc);
                    if (!lvI.iSubItem) return 1;
                    lvI.pszText = ((NMLVDISPINFO*) lParam)->item.pszText;
                    if (!lvI.pszText) return 1;
                    SendMessage(hWnd, LVM_SETITEMTEXT, lvI.iItem, (long) &lvI);
                    break;
                default:
                    return CallWindowProc((WNDPROC) GetClassLong(hWnd, GCL_WNDPROC), hWnd, uMsg, wParam, lParam);
            }
            break;
        case WM_PAINT:
            if (bEditing) {
                RECT rcItem;
                if (lvI.iSubItem > 0) {
                    rcItem.left = LVIR_LABEL;
                    if (SendMessage(hWnd, LVM_GETITEMRECT, lvI.iItem, (long) &rcItem))
                        ValidateRect(hWnd, &rcItem);
                }
            }
        default:
            return CallWindowProc((WNDPROC) GetClassLong(hWnd, GCL_WNDPROC), hWnd, uMsg, wParam, lParam);
    }
    return 0;
}

LRESULT CALLBACK WndProcMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    static HWND hList;
    static RECT rc;
    switch (uMsg) {
        case WM_NOTIFY:
            switch (((NMHDR*) lParam)->code) {
                case NM_CLICK:
                case NM_DBLCLK:
                case LVN_BEGINLABELEDIT:
                case LVN_ENDLABELEDIT:
                    return CallWindowProc(WndProcList, ((NMHDR*) lParam)->hwndFrom, uMsg, wParam, lParam);
            }
            break;
        case WM_CREATE:
            {
                LVCOLUMN        lvc;
                LVITEM          lvI;
                unsigned int    i;
                float           vertex;
                char            text[32];

                hList = CreateWindow(WC_LISTVIEW, 0, WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_EDITLABELS, rc.left, rc.top, rc.right, rc.bottom, hWnd, 0, hInstance, 0);
                SendMessage(hList, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
                SetWindowLong(hList, GWL_WNDPROC, (long) WndProcList);

                lvc.mask    = LVCF_WIDTH;
                lvc.cx      = 30;
                SendMessage(hList, LVM_INSERTCOLUMN, 0, (LPARAM) &lvc);

                lvc.mask    = LVCF_TEXT;
                lvc.pszText = "Vertex";
                SendMessage(hList, LVM_INSERTCOLUMN, 1, (LPARAM) &lvc);
                SendMessage(hList, LVM_SETCOLUMNWIDTH, 1, LVSCW_AUTOSIZE_USEHEADER);

                lvI.mask    = LVIF_TEXT;
                lvI.pszText = text;

                for (i = 0; i < 10; i++) {
                    vertex = (float) i;
                    lvI.iItem       = i;
                    lvI.iSubItem    = 0;    
                    sprintf(text, "%d", i);
                    SendMessage(hList, LVM_INSERTITEM, 0, (LPARAM) &lvI);
                    lvI.iSubItem    = 1;    
                    sprintf(text, "%f, %f, %f", vertex - 1, vertex, vertex + 1);
                    SendMessage(hList, LVM_SETITEM, 0, (LPARAM) &lvI);
                }
            }
            break;
        case WM_SIZE:
            GetClientRect(hWnd, &rc);
            MoveWindow(hList, rc.left, rc.top, rc.right, rc.bottom, 1);
            SendMessage(hList, LVM_SETCOLUMNWIDTH, 1, LVSCW_AUTOSIZE_USEHEADER);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    return 0;
}

2 个答案:

答案 0 :(得分:1)

我发布了与该问题相关的答案,但它是在C#中。从来没有在winapi上有太多的专业知识,而不是很久以前作为业余爱好者玩它,这篇文章末尾的答案很有希望 - http://cboard.cprogramming.com/windows-programming/122733-%5Bc%5D-editing-subitems-listview-win32-api.html

答案 1 :(得分:1)

在尝试编译代码时出现的第一个问题是,您要么不编译Unicode,要么使用非Unicode sprintf函数格式文本。这是第一件需要解决的问题:Windows应用程序已经完全使用Unicode十多年了。将char变量声明的每个实例替换为wchar_t(或TCHAR),将字符串文字前缀为L(或用TEXT()宏包围它们),并通过拨打wsprintf快速替换拨打sprintf的电话。正如文档所指出的那样,使用的函数肯定比wsprintf更好,但sprintf也是如此,这样就可以用最少的工作量来编译代码。

对我而言,另一件看起来非惯用的事情是您使用Get / SetClassLongGet / SetWindowLong函数。如今,我总是编写具有64位可移植性的代码,因此我将Get / SetClassLongPtrGet /替换为{ SetWindowLongPtr宏,它会自动解析为正确的函数调用,具体取决于您是否正在为x86或x64进行编译。但这不是一个交易破坏者。

您首先询问是否有办法直接从子类控件中处理WM_NOTIFY消息,方法是自动转发它们。不幸的是,这是不可能的。 Win32模型使得父母总是拥有自己的孩子,因此他们有责任处理事件。我同意你对关注点分离的直觉,但实现这一点的唯一方法是自己明确地将消息从父级传递给适当的子控件。像MFC这样的框架(它封装了Win32 API)显然是自动执行此操作,仍然必须将通知消息从父级转发给子级。他们使用名为&#34; message reflection&#34;的东西来做,你可以阅读here。没有什么可以阻止你在你自己的应用程序中实现类似的东西,但在某个时刻你必须停下来问自己是否值得使用众多可用的GUI框架中的一个仅仅是为了这种类型的的事情。

无论如何,据我所知,你问题的主旨是:

  

我想更改此选项以仅使子项目可编辑。

这似乎是一个非常简单的修复。您所要做的就是检查用户实际上已请求编辑子项的LVN_BEGINLABELEDIT通知消息处理程序。由于您已在代码中的其他地方使用过它,因此您知道LVITEM.iSubItem member为您提供子项的基于一的索引,如果结构引用了项,则为0 而不是子项目。

因此,插入此行以确保lvI.iSubItem处理程序顶部的LVN_BEGINLABELEDIT不等于0:

if (lvI.iSubItem == 0) return TRUE;  // prevent editing

正如LVN_BEGINLABELEDIT message的文档所示,返回FALSE允许用户编辑标签,并且返回TRUE会阻止他们进行编辑。由于我们返回TRUE,因此在编辑开始之前,我们会阻止编辑除子项之外的任何内容。

在我看来,您已尝试使用此行在LVN_ENDLABELEDIT通知消息处理程序中执行类似的操作:

if (!lvI.iSubItem) return 1;

但为时已晚!如果编辑已经结束,那么您已经给用户留下了他们能够编辑主要项目的印象,您不想这样做。拿出那条线,它应该按预期工作。

请注意,您的实现至少有一个明显的错误:您不能阻止用户将子项的内容修改为超过32个字符的字符串,但填充编辑控件的代码只接受最多32个字符的字符串:

TCHAR text[32];
// ... snip ...
lvI.pszText     = text;
lvI.cchTextMax  = 32;
SendMessage(hWnd, LVM_GETITEMTEXT, lvI.iItem, (long) &lvI);
SetWindowText(hEdit, lvI.pszText);

正确的方式编写代码(并且我怀疑为什么你没有以正确的方式完成它)是一个巨大的痛苦。通常,我会创建一个我认为足够长的字符串缓冲区,尝试获取子项的文本,并检查LVM_GETITEMTEXT消息的返回值。返回值告诉我有多少字符被复制到字符串缓冲区中。如果复制的字符数表示完全填充字符串缓冲区中的可用空间,我将使缓冲区变大(可能是大小加倍),然后尝试发送{{ 1}}再次发送消息。我记得,MFC做了类似的事情。告诉你这是一种痛苦,但值得把这些事情做好。

更简单的解决方案(尽管限制更多)是防止用户将其中一个子项的长度设置为更长的字符串,而不是32个字符。然后你不必担心处理长输入,因为你知道它永远不会存在,并且用户永远不会对控件的行为感到困惑。为此,请在LVM_GETITEMTEXT处理程序的末尾发送编辑控件EM_LIMITTEXT message

LVN_BEGINLABELEDIT

现在,用户无法输入超过允许的字符数,因此您的代码知道您将永远不必处理那些 (除非您编写代码)把它们放在那里,在这种情况下......)。


所有这些都说,我认为我同意汉斯:

  

呃,你会永远战斗小故障。网格控件普遍可用,这一点很少。