Элемент диалоговых окон ListView (или SysListView32) - очень удобный элемент, особенно для табличного отображения данных или ведения логов. При всем своем удобстве, у него есть недостаток, что при заполнении списка нельзя указывать цвета текста и фона для отдельных строк. Но способ раскрасить строки ListView все-таки есть. При отрисовке списка окну приложения посылается сообщение WM_NOTIFY, которое можно перехватить и самостоятельно обработать. В lParam сообщения передается указатель на структуру NMLVCUSTOMDRAW, в которой и описаны все подробности об отрисовываемой строке. Это составная структура и ее составляющие зависят от версии библиотеки Comctl32, но описываемый способ не сработает разве что на Windows 95, если вы его где-то откопаете. Однако компилятор FASM тоже ни сном, ни духом про структуру
NMLVCUSTOMDRAW, придется сделать описание самостоятельно:
; Структура для обработки сообщений
struct NMLVCUSTOMDRAW
; NMHDR
hwndFrom dd ?
idFrom dd ?
code dd ?
; NMCUSTOMDRAWINFO
dwDrawStage dd ?
hdc dd ?
left dd ?
top dd ?
right dd ?
bottom dd ?
dwItemSpec dd ?
uItemState dd ?
lItemlParam dd ?
; NMLVCUSTOMDRAW
clrText dd ?
clrTextBk dd ?
ends
Теперь разберем значения полей структуры. В hwndFrom должен быть хэндл списка ListView, вcode - уведомление NM_CUSTOMDRAW. Если эти два условия выполняются, то требуется задействовать наш обработчик. А вот дальше есть важные особенности. Отрисовка строки выполняется в два этапа: сперва в dwDrawStage приходит уведомление
CDDS_PREPAINT, на которое окно приложения должно ответить установкой статуса
CDRF_NOTIFYITEMDRAW, то есть надо сообщить системе, что мы хотим самостоятельно внести коррективы в процесс отрисовки. В этом случае система посылает еще одно уведомление - CDDS_ITEMPREPAINT, и именно на этом втором этапе мы можем указать цвет фона и текста для строки. Цвет текста задается в clrText, а цвет фона - в clrTextBk. Весь этот процесс описан в MSDN, но как-то слишком разрозненно.
После теоретической подготовки переходим к практике. Сперва сделаем список с раскраской "зеброй", то есть чередование светлого и более темного фона строк. Это значительно повышает наглядность, особенно на широких таблицах. В параметре dwItemSpec структурыNMLVCUSTOMDRAW передается индекс (порядковый номер) строки. Проверяем его, и, если индекс четный, то устанавливаем один фон, если нечетный - другой. Обработчик WM_NOTIFYдля окна ListView будет выглядеть примерно так:
; Сообщение NM_CUSTOMDRAW?
cmp dword [eax+nml.code],NM_CUSTOMDRAW
jne .processed
; Пред-отрисовка строки?
cmp dword [eax+nml.dwDrawStage],CDDS_PREPAINT
jne @f
; Установить ответ окна
invoke SetWindowLong,[hwnddlg],DWL_MSGRESULT,CDRF_NOTIFYITEMDRAW
mov eax,CDRF_NOTIFYITEMDRAW
jmp .processed
@@:
; Требуется нарисовать строку?
cmp dword [eax+nml.dwDrawStage],CDDS_ITEMPREPAINT
jne @f
; Цвет фона нечетных строчек
mov esi,0E0E0E0h
; Проверить индекс строки на четность
test dword [eax+nml.dwItemSpec],1
jnz .row_odd
; Цвет фона четных строчек
mov esi,0FFFFFFh
.row_odd:
; Установить цвет текста строки
mov dword [eax+nml.clrText],0
; Установить цвет фона
mov dword [eax+nml.clrTextBk],esi
@@:
jmp .processed
Как видите, ничего сложного. Дополнительный плюс в том, что при добавлении или удалении строчек в списке, его раскраска "зеброй" будет каждый раз автоматически пересчитываться и правильно перерисовываться. Но этот же плюс выходит боком в случае, если индивидуальная раскраска требуется только для отдельных строчек списка. При прокручивании или обновлении окна списка вызывается его перерисовка, поэтому раскраску каждой строки придется хранить где-нибудь в памяти и обрабатывать индивидуально. Разберем это на более сложном примере, теперь строки списка будут раскрашены в случайные цвета, значения которых будут храниться в массиве.
section '.data' data readable writeable
...
colors rd 1000 ; Массив фоновых цветов строк
...
При заполнении списка сохраним в массиве цвет каждой строки:
; Заполнить список строчками
xor ebx,ebx
@@:
...
; Тут вызывается какая-нибудь функция генерации случайного числа,
; результат которой записывается в EAX
...
; Сохранить цвет фона в массиве
mov ecx,ebx
shl ecx,2
and eax,0FFFFFFh
mov [colors+ecx],eax
...
; Добавляем строку в список
...
inc ebx
cmp ebx,1000
jb @b
Обработчик WM_NOTIFY для окна ListView немного меняется. Теперь цвет фона не зависит от четности строки, а берется из массива цветов по номеру строки. Для простоты цвет текста берется инверсным к цвету фона, в реальном проекте можно также задавать его при добавлении строк и хранить в соответствующем массиве.
; Сообщение NM_CUSTOMDRAW?
cmp dword [eax+nml.code],NM_CUSTOMDRAW
jne .processed
; Пред-отрисовка строки?
cmp dword [eax+nml.dwDrawStage],CDDS_PREPAINT
jne @f
; Установить ответ окна
invoke SetWindowLong,[hwnddlg],DWL_MSGRESULT,CDRF_NOTIFYITEMDRAW
mov eax,CDRF_NOTIFYITEMDRAW
jmp .processed
@@:
; Требуется нарисовать строку?
cmp dword [eax+nml.dwDrawStage],CDDS_ITEMPREPAINT
jne @f
; Получить номер строки
mov ebx,dword [eax+nml.dwItemSpec]
; Вычислить индекс массива фоновых цветов
shl ebx,2
mov esi,[colors+ebx]
; Вычислить текст текста - инверсный от цвета фона
mov edi,esi
not edi
and edi,0FFFFFFh
; Установить цвет текста строки
mov dword [eax+nml.clrText],edi
; Установить цвет фона
mov dword [eax+nml.clrTextBk],esi
@@:
jmp .processed
Некоторая сложность этого метода заключается в том, что при добавлении или удалении строчек в списке обязательно требуется вносить изменения и в массив цветов, также удаляя или добавляя в него нужные значения.
В приложении две программы с исходными тестами, реализующие оба описанных в статье способа. Первая программа создает список, раскрашенный "зеброй", двойной клик удаляет выбранную строку из списка. Вторая программа создает список, в котором строки раскрашены случайным образом.