Уроки Iczelion'а

       

Контрол Listview


В этом туториале мы изучим как создать и использовать контрол listview.

Скачайте пример.

Теория:

Listview - это один из common control'ов, таких как treeview, richedit и так далее. Вы знакомы с ними, даже если не занете их имен. Например, правая панель Windows Exрlorer'а - это контрол listview. Этот контрол подходит для отображения item'ов. В этом отношении его можно рассматривать как усовершенствованный listbox.

Вы можете создать listview двумя путями. Первый метод самый простой: создайте его с помощью редактора ресурсов, главное не забудьте поместить вызов InitCommonControls. Другой метод заключается в вызове CreateWindowsEx. Вы должны указать правильное имя класса окна, то есть SysListView32.

Существует четыре метода отображения item'ов в listview: иконки, маленькие иконки, список и отчет. Вы можете увидеть чем отличаются виды отображения друг от друга, выбрав View->Large Icons (иконки), Small Icons (маленькие иконки), List (список) and Details (отчет)

Теперь, когда мы знаем, как создать listview, мы рассмотрим, как его можно применять. Я сосредоточусь на отчете, как методе отображения, который может продемонстрировать многие свойства listview. Шаги использования listview следующие:

  • Создаем listview с помощью CreateWindowEx, указав SysListView32 как имя класса. Вы должны указать начальный тип отображения.
  • (если предусматривается) Создаем и инициализируем списки изображений, которые будут использованы при отображении item'ов listview.
  • Вставляем колонки в listview. Этот шаг необходим, если listview будет использовать тип отображения 'отчет'.
  • Вставьте item'ы и подitem'ы в listview.

Колонки

При отчете в listview может быть одна или более колонок. Вы можете считать тип организации данных в этом режиме таблицей: данные организованны в ряды и колонки. В режиме отчета в listview должна быть по крайней мере одна колонка. В других режимах вам не надо вставлять колонку, так как в контроле будет одна и только одна колонка.



Вы можете вставить колонку, послав сообщение LVM_INSERTCOLUMN контролу listview.


LVM_INSERTCOLUMN wParam = iCol lParam = pointer to a LV_COLUMN structure
iCol - это номеp колонки, начиная с нуля.
LV_COLUMN содержит информацию о колонке, которая должна быть вставлена. У нее следующее определение:
LV_COLUMN STRUCT imask dd ? fmt dd ? lx dd ? pszText dd ? cchTextMax dd ? iSubItem dd ? iImage dd ? iOrder dd ? LV_COLUMN ENDS
  • imask - коллекция флагов, задающие, какие члены структуры верны. Этот параметр был введен, потому что не все члены этой структуры используются одновременно. Некоторые из них используются в особых ситуациях. Эта структура используются и для ввода и для вывода, поэтому важно, чтобы вы пометили, какие параметры верны. Существуют следующие флаги:

  • LVCF_FMT = The fmt member is valid.

    LVCF_SUBITEM = The iSubItem member is valid.

    LVCF_TEXT = The pszText member is valid.

    LVCF_WIDTH = The lx member is valid.

    LVCF_FMT = Параметр fmt верен.

    LVCF_SUBITEM = Параметр isubItem верен.

    LVCF_TEXT = Параметр рszText верен.

    LVCF_WIDTH = Параметр lx верен.

    Вы можете комбинировать вышеприведенные флаги. Например, если вы хотите указать текстовое имя колонки, вам нужно предоставить указатель на строку в параметре рszText. Также вы должны указать Windows, что параметр рszText содержит данные, указав флаг LVCF_TEXT в этом поле, иначе Windows будет игнорировать значение pszText.
  • fmt - указывает выравнивание элементов/подэлементов в колонке. Доступны следующие значения:

  • LVCFMT_CENTER = Text is centered.

    LVCFMT_LEFT = Text is left-aligned.

    LVCFMT_RIGHT = Text is right-aligned.

    LVCFMT_CENTER = текст отцентрированы.

    LVCFMT_LEFT = текст выравнивается слева.

    LVCFMT_RIGHT = текст выравнивается справа.

  • lx - ширина колонки в пикселях. В дальнейшем вы можете изменить ширину колонки LVM_SETCOLUMNWIDTH.

  • рszText - содержит указатель на имя колонки, если эта структура используется для установки свойств колонки. Если эта структура используется для получения свойств колонки, это поле содержит указатель на буфеp, достаточно большой для получения имени колонки, которая будет возвращена. В этом случае вы должны указать размер буфера в поле cchTextMax. Вы должны игнорировать cchTextMax, если вы хотите установить имя колонки, потому что имя должно быть ASCIIZ-строкой, длину которой Windows сможет определить.



  • cchTextMax - pазмеp в байтах буфер, указанного в поле pszText. Этот параметр используется только когда вы используете структуру для получения информации о колонке. Если вы используете эту структуру, чтобы установить свойства колонки, это поле будет игнорироваться.

  • iSubItem - указывает индекс подэлемента, ассоциированного с этой колонкой. Это значение используется в качестве маркера подэлемента, с которым ассоциирована эта колонка. Если хотите, вы можете указать бессмысленный номер и ваш listview будет прекрасно работать. Использование этого поля лучше всего демонстрируется, когда у вас есть номер колонки и вам нужно узнать с каким поэлементом ассоциирована эта колонка. Чтобы сделать это, вы можете послать сообщение LVM_GETCOLUMN, указав в параметре imask флаг LVCF_SUBITEM. Listview заполнит параметр iSubItem значением, которое вы укажете в этом поле, поэтому для работоспособности данного метода вам нужно указывать корректные подэлементы в этом поле.

  • iImage и iOrder - используется начиная с Internet Explorer 3.0. У меня нет информации относительно этих полей.

  • Когда listview создан, вам нужно вставить в него одну или более колонок. Если не предполагается переключение в режим отчета, то это не нужно. Чтобы вставить колонку, вам нужно создать структуру LV_COLUMN, заполнить ее необходимой информацией, указать номер колонки, а затем послать структуру listview с помощью сообщения LVM_INSERTCOLUMN.
    LOCAL lvc:LV_COLUMN mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1
    mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc
    Вышеприведенный кусок кода демонстрирует процесс. Он указывает текста заголовка и его ширину, а затем посылает сообщение LVM_INSETCOLUMN listview. Это просто.
    Item'ы и под-item'ы
    Item'ы - это основные элементы listview. В режимах отображения, отличных от отчета, вы будет видеть только item'ы. Под-item'ы - это детали item'ов. Hапример, если item - это имя файла, тогда вы можете считать атрибуты файла, его pазмеp, дату создания файла как под-item'ы. В режиме отчета самая левая колонка содержит item'ы, а остальные - под-item'ы. Вы можете думать о item'е и его под-item'ах как о записи базы данных. Item - это основной ключ записи и его под-item'ы - это поля записи.


    Минимум, что вам нужно иметь в listview - это item'ы, под-item' ы необязательны. Тем не менее, если вы хотите дать пользователю больше информации об элементах, вы можете ассоциировать item'ы с под-item'ами, чтобы пользователь мог видеть детали в режиме отчета.
    Вставить item в listview можно послав сообщение LVM_INSERTITEM. Вам также нужно передать адрес структуры LV_ITEM в lParam. LV_ITEM имеет следующее определение:
    LV_ITEM STRUCT imask dd ? iItem dd ? iSubItem dd ? state dd ? stateMask dd ? pszText dd ? cchTextMax dd ? iImage dd ? lParam dd ? iIndent dd ? LV_ITEM ENDS
  • imask - множество флагов, которые задают, какие из параметров данной структуры будут верны. В сущности, это поле идентично параметру imask LV_COLUMN. Чтобы получить детали относительно флагов, обратитесь к вашему справочнику по API.

  • iItem - индек item'а, на который ссылается эта структура. Индексы начинаются с нуля. Вы можете считать, что это поле содержит значение "ряда" таблицы.

  • iSubItem - индекс под-item'а, ассоциированный с item'ом, заданном в iItem. Вы можете считать, что это поле содержит "колонку" таблицы. Например, если вы хотите вставить item в только что созданный listview, значение в iItem будет pавно 0 (потому что этот item первый), а значение в iSubItem также будет равно нулю (нам нужно вставить item в первую колонку). Если вы хотите указать под-item, ассоциированный с этим item'ом, iItem будет являться индексом item'а, с которым будет происходить ассоциирование (в выше приведенном примере это 0). iSubItem будет pавен 1 или более, в зависимости от того, в какую колонку вы хотите вставить под-item. Hапример, если у вашего listview 4 колонки, первая колонка будет содержать item'ы. Остальные 3 колонки предназначаются для под-item'ов. Если вы хотите вставить под-item в 4-ую колонку, вам нужно указать в iSubItem значение 3.

  • state - параметр, содержащий флаги, отражающие состояние item'а. Оно может изменяться из-за действий юзера или другой программы. Термин 'состояние' включает в себя, находится ли item в фокусе, подсвечен ли он, выделен для операции вырезания, выбран ли он. В добавление к флагам состояния он также содержит основанный на единице индекс изображения состояния данного item'а.



  • stateMask - так как параметр state может содержать флаги состояния, индекс изображения, нам требуется сообщить Windows, какое значение мы хотим установить или получить. Это поле создано именно для этого.

  • рszText - адрес ASCIIZ-строки, которая будет использоваться в качестве названия элемента в случае, если мы хотим установить или вставить элемент. Если мы используем эту структуру для того, чтобы получить свойства элемента, этот параметр должен содержать адрес буфера, который будет заполнен названием элемента.

  • cchTextMax - это поле используется только тогда, когда вы используете данную структуру, чтобы получать информацию об элементе. В этом случае это поле содержит размер в байтах буфера, указанного параметром рszText.

  • iImage - индекс image list'а, содержащего иконки для listview. Индекс указывает на иконку, которая будет использоваться для этого элемента.

  • lParam - определяемое пользователем значение, которое будет использоваться, когда вы будете сортировать элементы в listview. Кратко говоря, когда вы будете указывать listview отсортировать item'ы, listview будет сравнивать item'ы попарно. Он будет посылать значение lParam обоих элементов вам, чтобы вы могли pешить, какое из этих двух должно быть в списке идти pаньше. Если вы пока не можете этого понять, не беспокойтесь. Вы изучите сортировку позже.

  • Давайте кратко изложим шаги вставления элемента/подэлемента в listview.

    • Создаем переменную типа структуры LV_ITEM.

    • Заполняем ее необходимой информацией.

    • Посылаем сообщение LVM_INSERTITEM listview, если вам нужно вставить элемент. Или, если вы хотите вставить подэлемент, посылаем сообщение LVM_SETITEM. Это может смущать вас, если вы не понимаете взаимоотношений между элементом и его подэлементами. Подэлементы считаются свойствами элемента. Поэтому вы можете вставить item'ы, но не под-item'ы, а также у вас не может быть подэлемента без ассоциированного с ним элемента. Вот почему вам нужно послать сообщение LVM_SETITEM, чтобы добавить подэлемент вместо LVM_INSERTITEM.



    Сообщения/уведомления listview
    Теперь, когда вы знаете, как создавать и заполнять элементами listview, следующим шагом является общение с ним. Listview общается с родительским окном через сообщения и уведомления. Родительское окно может контролировать listview, посылая ему сообщения. Listview уведомляет родительское окно о важных/интересных сообщения через сообщение WM_NOTIFY, как и другие common control'ы.
    Сортировка элементов/подэлементов
    Вы можете указать порядок сортировки контрола listview по умолчанию указав стили LVS_SORTASCENDING или LVS_SORTDESCENDING в CreateWindowEx. Эти два стиля упорядочивают элементы только по элементам. Если вы хотите отсортировать элементы другим путем, вы должны послать сообщение LVM_SORTITEMS listview.
    LVM_SORTITEMS wParam = lParamSort lParam = pCompareFunction
    lParamSort - это определяемое пользователем значение, которое будет передаваться функции сравнения. Вы можете использовать это значение любым путем, которым хотите.
    рComрareFunction - это адрес задаваемой пользователем функции, которая будет определять результат сравнения item'ов в listview. Функция имеет следующий прототип:
    CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD
    lParam1 или lParam2 - это значения параметра lParam LV_ITEM, который вы указали, когда вставляли элементы в listview.
    lParamSort - это значение wParam, посланное вместе с сообщением LVM_SORTITEMS.
    Когда listview получает сообщение LVM_SORTITEMS, она вызывает сортирующую функцию, указанную в параметре lParam, когда ей нужно узнать результат сравнения двух элементов. Кратко говоря, функция сравнения будет решать, какой из двух элементов, посланных ей, будет предшествовать другому. Правило простое: если функция возвращается отрицательное значение, тогда первый элемент (указанный в lParam1) будет предшествовать другому.
    Если функция возвращает положительное значение, второй элемент (заданный параметром lParam2) должен предшествовать первому. Если оба равны, тогда функция должна возвратить ноль.


    Что заставляет этот метод работать, так это значение lParam структуры LV_ITEM. Если вам нужно отсортировать item'ы (например, когда пользователь кликает по заголовку колонки), вам нужно подумать о схеме сортировки, в которой будет использоваться значения параметра lParam. В данном примере я помещаю это поле индекс элемента, чтобы получить другую информация о нем, послав сообщение LVM_GETITEM. Заметьте, что когда элементы перегруппированы, их индексы также менядтся. Поэтому когда сортировка в моем примере выполнена, мне необходимо обновить значения в lParam, чтобы учесть новые значения индексов. Если вы хотите отсортировать элементы, когда пользователь кликает по заголовку колонки, вам нужно обработать уведомительное сообщение LVN_COLUMNCLICK в вашей оконной процедуре. LVN_COLUMNCLICK передается вашему окну через сообщение WM_NOTIFY.
    ПРИМЕР
    Этот пример создает listview и заполняем его именами и размерами полей текущей папки. Режим отображения элементов по умолчанию поставлен в 'отчет'. В этом режиме вы можете кликать по заголовку колонок и элементы будут отсортированы согласно восходящему/нисходящему порядку. Вы можете выбрать режим отображения в меню. Когда вы делает двойной клик по элементу, показывается окно с названием элемента.
    .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib
    WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
    IDM_MAINMENU equ 10000 IDM_ICON equ LVS_ICON IDM_SMALLICON equ LVS_SMALLICON IDM_LIST equ LVS_LIST IDM_REPORT equ LVS_REPORT
    RGB macro red,green,blue xor eax,eax mov ah,blue shl eax,8 mov ah,green mov al,red endm
    .data ClassName db "ListViewWinClass",0 AppName db "Testing a ListView Control",0 ListViewClassName db "SysListView32",0 Heading1 db "Filename",0 Heading2 db "Size",0 FileNamePattern db "*.*",0 FileNameSortOrder dd 0 SizeSortOrder dd 0 template db "%lu",0


    .data? hInstance HINSTANCE ? hList dd ? hMenu dd ?
    . code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL, NULL, SW_SHOWDEFAULT invoke ExitProcess,eax invoke InitCommonControls WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND
    mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, NULL mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,IDM_MAINMENU mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW, \ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst, NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp
    InsertColumn proc LOCAL lvc:LV_COLUMN
    mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc or lvc.imask,LVCF_FMT mov lvc.fmt,LVCFMT_RIGHT mov lvc.pszText,offset Heading2 mov lvc.lx,100 invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc ret InsertColumn endp
    ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD LOCAL lvi:LV_ITEM LOCAL buffer[20]:BYTE mov edi,lpFind assume edi:ptr WIN32_FIND_DATA mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0 lea eax,[edi].cFileName mov lvi.pszText,eax push row pop lvi.lParam invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi mov lvi.imask,LVIF_TEXT inc lvi.iSubItem invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow lea eax,buffer mov lvi.pszText,eax invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi assume edi:nothing ret ShowFileInfo endp


    FillFileInfo proc uses edi LOCAL finddata:WIN32_FIND_DATA LOCAL FHandle:DWORD
    invoke FindFirstFile,addr FileNamePattern,addr finddata .if eax!=INVALID_HANDLE_VALUE mov FHandle,eax xor edi,edi .while eax!=0 test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY .if ZERO? invoke ShowFileInfo,edi, addr finddata inc edi .endif invoke FindNextFile,FHandle,addr finddata .endw invoke FindClose,FHandle .endif ret FillFileInfo endp
    String2Dword proc uses ecx edi edx esi String:DWORD LOCAL Result:DWORD
    mov Result,0 mov edi,String invoke lstrlen,String .while eax!=0 xor edx,edx mov dl,byte ptr [edi] sub dl,"0" mov esi,eax dec esi push eax mov eax,edx push ebx mov ebx,10 .while esi > 0 mul ebx dec esi .endw pop ebx add Result,eax pop eax inc edi dec eax .endw mov eax,Result ret String2Dword endp
    CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD LOCAL buffer[256]:BYTE LOCAL buffer1[256]:BYTE LOCAL lvi:LV_ITEM
    mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 .if SortType==1 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke String2Dword,addr buffer mov edi,eax invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub edi,eax mov eax,edi .elseif SortType==2 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke String2Dword,addr buffer mov edi,eax invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub eax,edi .elseif SortType==3 mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer1,addr buffer .else mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer,addr buffer1 .endif ret CompareFunc endp


    UpdatelParam proc uses edi LOCAL lvi:LV_ITEM
    invoke SendMessage,hList, LVM_GETITEMCOUNT,0,0 mov edi,eax mov lvi.imask,LVIF_PARAM mov lvi.iSubItem,0 mov lvi.iItem,0 .while edi>0 push lvi.iItem pop lvi.lParam invoke SendMessage,hList, LVM_SETITEM,0,addr lvi inc lvi.iItem dec edi .endw ret UpdatelParam endp
    ShowCurrentFocus proc LOCAL lvi:LV_ITEM LOCAL buffer[256]:BYTE
    invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED mov lvi.iItem,eax mov lvi.iSubItem,0 mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 invoke SendMessage,hList,LVM_GETITEM,0,addr lvi invoke MessageBox,0, addr buffer,addr AppName,MB_OK ret ShowCurrentFocus endp
    WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .if uMsg==WM_CREATE invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, \ LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL mov hList, eax invoke InsertColumn invoke FillFileInfo RGB 255,255,255 invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax invoke GetMenu,hWnd mov hMenu,eax invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED .elseif uMsg==WM_COMMAND .if lParam==0 invoke GetWindowLong,hList,GWL_STYLE and eax,not LVS_TYPEMASK mov edx,wParam and edx,0FFFFh push edx or eax,edx invoke SetWindowLong,hList,GWL_STYLE,eax pop edx invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED .endif .elseif uMsg==WM_NOTIFY push edi mov edi,lParam assume edi:ptr NMHDR mov eax,[edi].hwndFrom .if eax==hList .if [edi].code==LVN_COLUMNCLICK assume edi:ptr NM_LISTVIEW .if [edi].iSubItem==1 .if SizeSortOrder==0 || SizeSortOrder==2 invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc invoke UpdatelParam mov SizeSortOrder,1 .else invoke SendMessage,hList,LVM_SORTITEMS,2,addr CompareFunc invoke UpdatelParam mov SizeSortOrder,2 .endif .else .if FileNameSortOrder==0 || FileNameSortOrder==4 invoke SendMessage,hList,LVM_SORTITEMS,3,addr CompareFunc invoke UpdatelParam mov FileNameSortOrder,3 .else invoke SendMessage,hList,LVM_SORTITEMS,4,addr CompareFunc invoke UpdatelParam mov FileNameSortOrder,4 .endif .endif assume edi:ptr NMHDR .elseif [edi].code==NM_DBLCLK invoke ShowCurrentFocus .endif .endif pop edi .elseif uMsg==WM_SIZE mov eax,lParam mov edx,eax and eax,0ffffh shr edx,16 invoke MoveWindow,hList, 0, 0, eax,edx,TRUE


    .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax, eax ret WndProc endp end start
    АНАЛИЗ
    Первое, что должна сделать программа после того, как создано основное окно - это создать listview.
    .if uMsg==WM_CREATE invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, \ LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL mov hList, eax
    Мы вызываем CreateWindowEx, передавая ей имя класса окна "SysListView32". Режим отображения по умолчанию задан стилем LVS_REPORT.
    invoke InsertColumn
    После того, как создан listview, мы вставляем в него колонку.
    LOCAL lvc:LV_COLUMN
    mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc
    Мы указываем название и ширину первой колонки, в которой будут отображаться имена файлов, в структуре LV_COLUMN, поэтому нам нужно установить в imask флаги LVCF_TEXT и LVCF_WIDTH. Мы заполняем рszText адресом названия и lx - шириной колонки в пикселях. Когда все сделано, мы посылаем сообщение LVM_INSERTCOLUMN listview, передавая ей структуру.
    or lvc.imask,LVCF_FMT mov lvc.fmt,LVCFMT_RIGHT
    После вставления первой колонки, мы вставляем следующую, в которой будут отображаться размеры файлов. Так как нам нужно, чтобы размеры файлов выравнивались по правой стороне, нам необходимо указать флаг в параметре fmt, LVCFMT_RIGHT. Мы также указываем флаг LVCF_FMT в imask, в добавление к LVCF_TEXT и LVCF_WIDTH.
    mov lvc.pszText,offset Heading2 mov lvc.lx,100 invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc
    Оставшийся код прост. Помещаем адреса названия в рszText и ширину в lx. Затем посылаем сообщение LVM_INSERTCOLUMN listview, указывая номер колонки и адрес структуры.
    Когда колонки вставлены, мы можем заполнить listview элементами.
    invoke FillFileInfo
    В FillFileInfo содержится следующий код.
    FillFileInfo proc uses edi LOCAL finddata:WIN32_FIND_DATA LOCAL FHandle:DWORD


    invoke FindFirstFile,addr FileNamePattern,addr finddata
    Мы вызываем FindFirstFile, чтобы получить информацию о первом файле, который отвечает заданным условиям. У FindFirstFile следующий прототип:
    FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD
    рFileName - это адрес имени файла, который надо искать. Эта строка может содержать "дикие" символы. В нашем примере мы используем *.*, чтобы искать все файлы в данной папке.
    рWin32_Find_Data - это адрес структуры WIN32_FIND_DATA, которая будет заполнена информацией о файле (если что-нибудь будет найдено).
    Эта функция возвращает INVALID_HANDLE_VALUE в eax, если не было найдено соответствующих заданным критериям файлов. Иначе она возвратит хэндл поиска, который будет использован в последующих вызовах FindNextFile.
    .if eax!=INVALID_HANDLE_VALUE mov FHandle,eax xor edi,edi
    Если файл будет найден, мы сохраним хэндл поиска в переменную, а потом обнулим edi, который будет использован в качестве индекса элемента (номер ряда).
    .while eax!=0 test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY .if ZERO?
    В этом туториале я не хочу иметь дело с папками, поэтому отфильтровываю их проверяя параметр dwFileAttributes на предмет наличия установленного флага FILE_ATTRIBUTE_DIRECTORY. Если он есть, я сразу перехожу к вызову FindNextFile.
    invoke ShowFileInfo,edi, addr finddata inc edi .endif invoke FindNextFile,FHandle,addr finddata .endw
    Мы вставляем имя и pазмеp файла в listview вызывая функцию ShowFileInfo. Затем мы повышаем значение edi (текущий номер столбца). И, наконец, мы делаем вызов FindNextFile, чтобы найти следующий файл в нашей папке, пока FindNextFile не возвратит 0, что означает то, что больше файлов найдено не было.
    invoke FindClose,FHandle .endif ret FillFileInfo endp
    Когда все файлы в ткущей папке найдены, мы должны закрыть хэндл поиска.
    Теперь давайте взглянем на функцию ShowFileInfo. Эта функция принимает два параметра, индекс элемента (номер ряда) и адрес структуры WIN32_FIND_DATA.


    ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD LOCAL lvi:LV_ITEM LOCAL buffer[20]:BYTE mov edi,lpFind assume edi:ptr WIN32_FIND_DATA
    Сохраняем адрес структуры WIN32_FIND_DATA в edi.
    mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0
    Мы предоставляем название элемента и значение lParam, поэтому мы помещаем флаги LVIF_TEXT и LVIF_PARAM в imask. Затем мы устанавливаем приравниваем iItem номер ряда, переданный функции и, так как это главный элемент, мы должны приравнять iSubItem нулю (колонка 0).
    lea eax,[edi].cFileName mov lvi.pszText,eax push row pop lvi.lParam
    Затем мы помещаем адрес названия, в данном случая это имя файла в структуре WIN32_FIND_DATA, в рszText. Так как мы реализуем свою сортировку, мы должны заполнить lParam определенным значением. Я решил помещать номер ряда в это параметр, чтобы я мог получать информацию об элементе по его индексу.
    invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
    Когда все необходимые поля в LV_ITEM заполнены, мы посылаем сообщение LVM_INSERTITEM listview, чтобы вставить в него элемент.
    mov lvi.imask,LVIF_TEXT inc lvi.iSubItem invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow lea eax,buffer mov lvi.pszText,eax
    Мы установим подэлементы, ассоциированные с элементом. Подэлемент может иметь только название. Поэтому мы указываем в imask LVIF_TEXT. Затем мы указываем в iSubItem колонку, в которой должен находиться подэлемент. В этом случае мы устанавливаем его в 1. Названием этого элемента будет являться размер файла. Тем не менее, мы сначала должны сконвертировать его в строку, вызвать wsрrintf. Затем мы помещаем адрес строки в рszText.
    invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi assume edi:nothing ret ShowFileInfo endp
    Когда все требуемые поля в LV_ITEM заполнены, мы посылаем сообщение LVM_SETITEM listview, передавая ему адрес структуры LV_ITEM. Заметьте, что мы используем LVM_SETITEM, а не LVM_INSERTITEM, потому что подэлемент считается свойством элемента. Поэтому устанавливаем свойство элемента, а не вставляем новый элемент.


    Когда все элементы вставлены в listview, мы устанавливаем текст и цвет бэкграунда контрола listview.
    RGB 255,255,255 invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax
    Я использую макро RGB, чтобы конвертировать значения red, green, blue в eax и использую его для того, что указать нужное нам значение. Мы устанавливаем цвет текста и цвет фона с помощью сообщений LVM_SETTEXTCOLOR и LVM_SETTEXTBKCOLOR. Мы устанавливаем цвет фона listview сообщением LVM_SETBKCOLOR.
    invoke GetMenu,hWnd mov hMenu,eax invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED
    Мы позволим пользователю выбирать режимы отображения через меню. Поэтому мы должны получить сначала хэндл меню. Чтобы помочь юзеру переключать режимы отображения, мы помещаем в меню систему radio button'ов. Для этого нам понадобится функция CheckMenuRadioItem. Эта функция поместит radio button перед пунктом меню.
    Заметьте, что мы создаем окно listview с шириной и высотой равной нулю. Оно будет менять pазмеp каждый pаз, когда будет менять pазмеp родительское окно. В этом случае мы можем быть уверены, что размер listview всегда будет соответствовать родительскому окну. В нашем примере нам требуется, чтобы listview занимал всю клиентскую область родительского окна.
    .elseif uMsg==WM_SIZE mov eax,lParam mov edx,eax and eax,0ffffh shr edx,16 invoke MoveWindow,hList, 0, 0, eax,edx,TRUE
    Когда родительское окно получает сообщение WM_SIZE, нижнее слово lParam содержит новую ширину клиентской области и верхнее словно новой высоты. Тогда мы вызываем MoveWindow, чтобы изменить pазмеp listview, чтобы тот покрывал всю клиентскую область родительского окна.
    Когда пользователь выберет режим отображения в меню, мы должны соответственно отреагировать. Мы устанавливаем новый стиль контрола listview функцией SetWindowLong.
    .elseif uMsg==WM_COMMAND .if lParam==0 invoke GetWindowLong,hList,GWL_STYLE and eax,not LVS_TYPEMASK


    Сначала мы получаем текущие стили listview. Затем мы стираем старый стиль отображения. LVS_TYPEMASK - это комбинированное значение всех четырех стилей отображения. Поэтому когда мы выполняем логическое умножение текущих флагов стилей со значением "not LVS_TYPEMASK", стиль текущего отображения стирается.
    Во время проектирования меню я немного сжульничал. Я использовал в качестве ID пунктов меню константы стилей отображения.
    IDM_ICON equ LVS_ICON IDM_SMALLICON equ LVS_SMALLICON IDM_LIST equ LVS_LIST IDM_REPORT equ LVS_REPORT
    Поэтому, когда родительское окно получает сообщение WM_COMMAND, нужный стиль отображения находится в нижнем слове wParam'а (как ID пункта меню).
    mov edx,wParam and edx,0FFFFh
    Мы получили стиль отображения в нижнем слове wParam. Все, что нам теперь нужно, это обнулить верхнее слово.
    push edx or eax,edx
    И добавить стиль отображения к уже существующим стилям (текущий стиль отображения мы ранее оттуда убрали).
    invoke SetWindowLong,hList,GWL_STYLE,eax
    И установить новые стили функцией SetWindowLong.
    pop edx invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
    .endif
    Hам также требуется поместить radio button перед выбранным пунктом меню. Поэтому мы вызываем CheckMenuRadioItem, передавая ей текущий стиль отображения (а также ID пункта меню).
    Когда пользователь кликает по заголовку колонки в pежиме отчета, нам нужно отсортировать элементы в listview. Мы должны отреагировать на сообщение WM_NOTIFY.
    .elseif uMsg==WM_NOTIFY push edi mov edi,lParam assume edi:ptr NMHDR mov eax,[edi].hwndFrom .if eax==hList
    Когда мы получаем сообщение WM_NOTIFY, lParam содержит указатель на структуру NMHDR. Мы можем проверить, пришло ли это сообщение от listview, сравнив параметр hwndFrom структуры NMHDR с хэндлом контрола listview. Если они совпадают, мы можем заключить, что уведомление пришло от listview.
    .if [edi].code==LVN_COLUMNCLICK assume edi:ptr NM_LISTVIEW
    Если уведомление пришло от listview, мы проверяем, равен ли код LVN_COLUMNCLICK. Если это так, это означает, что пользователь кликает на заголовке колонки. В случае, что код pавен LVN_COLUMNCLICK, мы считаем, что lParam содержит указатель на структуру NM_LISTVIEW, которая является супермножеством по отношению к структуре NMHDR (т.е. включает ее). Затем нам нужно узнать, по какому заголовку колонки кликнул пользователь. Эту информацию мы получаем из параметра iSubItem. Его значение можно считать номером колонки (отсчет начинается с нуля).


    .if [edi].iSubItem==1 .if SizeSortOrder==0 || SizeSortOrder==2
    Если iSubItem равен 1, это означает, что пользователь кликнул по второй колонке. Мы используем глобальные переменные, чтобы сохранять текущий статус порядка сортировки. 0 означает "еще не отсортировано", 1 значит "восходящая сортировка", а 2 - "нисходящая сортировка". Если элементы/подэлементы в колонке ранее не были отсортированы или отсортированы по нисходящей, то мы устанавливаем сортировку по восходящей.
    invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc
    Мы посылаем сообщение LVM_SORTITEMS listview, передавая 1 через wParam и адрес нашей сравнивающей функции через lParam. Заметьте, что значение в wParam задается пользователем, вы можете использовать его как хотите. Я использовал его в нашем примере как метод сортировки. Сначала мы взглянем на сравнивающую функцию.
    CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD LOCAL buffer[256]:BYTE LOCAL buffer1[256]:BYTE LOCAL lvi:LV_ITEM
    mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256
    В сравнивающей функции контрол listview будет передавать lParam'ы (через LV_ITEM) двух элементов, которые нужно сравнить, через lParam1 и lParam2. Вспомните, что мы помещаем индекс элемента в lParam. Таким образом мы можем получить информацию об элементах, используя эти индексы. Информация, которая нам нужна - это названия сортирующихся элементов/подэлементов. Мы подготавливаем структуру LV_ITEM для этого, указывая в imask LVIF_TEXT и адрес буфера в рszText и размер буфера в cchTextMax.
    .if SortType==1 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    Если значение SortType pавно 1 или 2, мы знаем, что кликнута колонка размера файла. 1 означает, что необходимо отсортировать элементы в нисходящем порядке. 2 значит обратное. Таким образом мы указываем iSubItem равным 1 (чтобы задать колонку размера) и посылаем сообщение LVM_GETITEMTEXT контролу listview, чтобы получить название (строку с размером файла) подэлемента.


    invoke String2Dword,addr buffer mov edi,eax
    Конвертируем строку в двойное слово с помощью функции String2Dword, написанную мной. Она возвращает dword-значение в eax. Мы сохраняем ее в edi для последующего сравнения.
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub edi,eax mov eax,edi
    Тоже самое мы делаем и с lParam2. После получения размеров обоих файлов, мы можем сравнить их.
    Правила, которых придерживается функция сравнения, следующие:

    • Если первый элемент должен предшествовать другому, вы должны возвратить отрицательное значение через eax.

    • Если второй элемента должен предшествовать первому, вы должны возвратить через eax положительное значение.

    • Если оба элемента равны, вы должны возвратить ноль.

    В нашем случае нам нужно отсортировать элементы согласно их размерам в восходящем порядке. Поэтому мы просто можем вычесть размер первого элемента из второго и возвратить результат в eax.
    .elseif SortType==3 mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer1,addr buffer
    В случае, если пользователь кликнет по колонке с именем файла, мы должны сравнивать имена файлов. Мы должны получить имена файлов, а затем сравнить их с помощью функции lstrcmрi. Мы можем возвратить значение, возвращаемое этой функцией, так как оно использует те же правила сравния.
    После того, как элементы отсортированы, нам нужно обновить значения lParam'ов для всех элементов, чтобы учесть изменившиеся индексы элементов, поэтому мы вызываем функцию UpdatelParam.
    invoke UpdatelParam mov SizeSortOrder,1
    Эта функция просто-напросто перечисляет все элементы в listview и обновляет значения lParam. Hам требуется это делать, иначе следующая сортировка не будет работать как ожидается, потому что мы исходим из того, что значение lParam - это индекс элемента.
    .elseif [edi].code==NM_DBLCLK invoke ShowCurrentFocus .endif


    Когда пользователь делает двойной клик на элементе, нам нужно отобразить окно с сообщение с названием элемента. Мы должны проверить, равно ли поле code в NMHDR NM_DBLCLK. Если это так, мы можем перейти к получению названия и отображению его в окне с сообщением.
    ShowCurrentFocus proc LOCAL lvi:LV_ITEM LOCAL buffer[256]:BYTE
    invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED
    Как мы может узнать, по какому элементу кликнули два раза? Когда элемент кликнут (одинарным или двойным нажатием), он получает фокус. Даже если выбрано несколько элементов, фокус будет только у одного. Наши задача заключается в том, чтобы найти элемент у которого находится фокус. Мы делаем это, посылая сообщение LVM_GETNEXTITEM контролу listview, указав желаемое состояние элемента в lParam. -1 в wParam означает поиск по всем элементарм. Индекс элемента возвращается в eax.
    mov lvi.iItem,eax mov lvi.iSubItem,0 mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 invoke SendMessage,hList,LVM_GETITEM,0,addr lvi
    Затем мы получаем название элемента с помощью сообщения LVM_GETITEM.
    invoke MessageBox,0, addr buffer,addr AppName,MB_OK
    И наконец, мы отображаем название элемента в окне сообщения.
    Если вы хотите узнать, как использовать в контроле listview иконки, вы можете прочитать об этом в моем туториале о treeview. В случае с listview надо будет сделать примерно то же самое.
    [C] Iczelion, пер. Aquila.

    Содержание раздела