Представление списка подклассов для редактирования только его подпунктов - PullRequest
4 голосов
/ 28 декабря 2011

Я написал эту небольшую программу, которая отображает представление списка и позволяет редактировать элементы и подпункты.

Я хочу изменить это, чтобы редактировать только подэлементы. И я хотел бы, чтобы процедура окна представления списка стояла сама по себе, чтобы мне не приходилось пересылать WM_NOTIFY сообщения каждый раз, как я делаю это сейчас в WndProcMain. И цель в том, что я не использую только одно представление списка с редактируемыми подэлементами в моей программе, я собираюсь использовать его во многих различных окнах.

Уведомление LVN_ENDLABELEDIT обрабатывается WndProcList, поскольку bEditing необходимо изменить. Этот флаг используется для WM_PAINT, когда необходимо редактировать подпункты. Это исправление, в противном случае текст первого подпункта исчезает, поскольку он считает, что первый элемент редактируется. Однако я хотел бы также получить сообщение, подобное LVN_ENDLABELEDIT, в процедуре окна окна владельца списка (в данном случае WndProcMain), потому что я хочу также манипулировать пользовательским вводом.

Пожалуйста, спросите, если у вас есть вопросы.

Заранее спасибо

Midas

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 ]

1 голос
/ 07 января 2012

Первая проблема, которая возникла у меня при попытке скомпилировать ваш код, заключается в том, что вы либо не компилируете для Unicode, либо используете функцию не-Unicode sprintf для форматирования текста. Это первое, что нужно исправить: приложения Windows полностью Unicode уже более десяти лет. Замените каждый экземпляр char объявлений переменных на wchar_t (или TCHAR), добавьте к строковым литералам префикс L (или окружите их макросом TEXT()) и быстро замените вызовы на sprintf на звонки на wsprintf. Как указывается в документации, функции, безусловно, лучше использовать, чем wsprintf, но то же самое верно и для sprintf, и это позволяет скомпилировать код с минимальными усилиями.

Другая вещь, которая выглядит для меня не идиоматичной, это использование вами функций Get / SetClassLong и Get / SetWindowLong. В настоящее время я всегда пишу код с учетом 64-битной переносимости, и поэтому я бы заменил их на Get / SetClassLongPtr и * Макросы 1027 *Get / SetWindowLongPtr, которые автоматически преобразуются в правильный вызов функции, в зависимости от того, выполняете ли вы компиляцию для x86 или x64. Это не является нарушителем соглашения.

Сначала вы спросили, существует ли способ обработки сообщения WM_NOTIFY непосредственно из подклассифицированного элемента управления путем автоматической пересылки. К сожалению, это невозможно. Модель Win32 такова, что родители всегда владеют своими детьми, и поэтому они несут ответственность за управление событиями. Я согласен с вашей интуицией о разделении интересов, но единственный способ добиться этого - явная пересылка сообщения от родителя на соответствующий дочерний элемент управления самостоятельно. Фреймворки, такие как MFC (которые инкапсулируют Win32 API), делают это для вас, по-видимому, автоматически, но все еще должны пересылать уведомления от родителя к потомку. Они делают это, используя то, что называется «отражение сообщения», о котором вы можете прочитать здесь . Ничто не мешает вам реализовать нечто подобное в вашем собственном приложении, но в определенный момент вы должны остановиться и спросить себя, не стоит ли использовать одну из многих доступных структур GUI только для такого рода вещей.

В любом случае, насколько я понимаю, основной смысл вашего вопроса заключается в следующем:

Я хочу изменить это, чтобы сделать редактируемыми только подэлементы.

Это кажется довольно простым исправлением. Все, что вам нужно сделать, это проверить обработчик уведомлений LVN_BEGINLABELEDIT, который пользователь фактически запросил для редактирования подпункта. Поскольку вы использовали его в другом месте своего кода, вы знаете, что LVITEM.iSubItem член дает вам либо единичный индекс подпункта, либо 0, если структура ссылается на элемент , а не подпункт.

Вставьте эту строку, чтобы убедиться, что lvI.iSubItem не равно 0 в верхней части обработчика LVN_BEGINLABELEDIT:

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

Как указано в документации к сообщению LVN_BEGINLABELEDIT , возвращение 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. Возвращаемое значение говорит мне, сколько символов было скопировано в строковый буфер. Если количество скопированных символов указывает, что оно полностью заполнено доступным пространством в строковом буфере, я увеличу размер буфера (возможно, в два раза), а затем снова попытаюсь отправить сообщение LVM_GETITEMTEXT , Насколько я помню, MFC делает нечто подобное. Сказал тебе, что это было больно, но стоит все исправить.

Более простое решение (хотя и более ограничивающее) состояло бы в том, чтобы пользователь никогда не задавал длину одного из подпунктов в виде строки текста длиннее , чем 32 символа. Тогда вам не придется беспокоиться об обработке длинного ввода, потому что вы знаете, что его никогда не будет, и пользователь никогда не будет смущен поведением вашего элемента управления. Для этого отправьте элементу управления редактирования EM_LIMITTEXT сообщение в конце вашего обработчика LVN_BEGINLABELEDIT:

case LVN_BEGINLABELEDIT:
{
    // ... snip ...
    lvI.cchTextMax  = 32;
    SendMessage(hWnd, LVM_GETITEMTEXT, lvI.iItem, (long) &lvI);
    SetWindowText(hEdit, lvI.pszText);

    // (begin new code)
    SendMessage(hEdit, EM_LIMITTEXT, lvI.cchTextMax, 0);
}

Теперь пользователь не может ввести больше разрешенного количества символов, поэтому ваш код знает, что вам никогда не придется иметь дело с чем-то большим, чем , находящимся там (если вы не напишите код для их размещения). там сами, в таком случае ...).


Все это говорит, я думаю, что я согласен с Гансом:

Тьфу, ты будешь бороться с глюками вечно. Нет смысла использовать универсальные элементы управления сеткой.

1 голос
/ 07 января 2012

Я опубликовал ответ, связанный с вопросом, но он был на C #.Никогда не имел большого опыта в winapi, вместо того, чтобы играть с ним в качестве любителя давно. Ответ в конце этого поста выглядит многообещающим - http://cboard.cprogramming.com/windows-programming/122733-%5Bc%5D-editing-subitems-listview-win32-api.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...