API и FreeBasic. (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 (в FreeBasic она определена как LVCOLUMN) содержит информацию о колонке, которая должна быть вставлена. У нее следующее определение:

Type LVCOLUMN
    mask As UINT
    fmt As Integer
    cx As Integer
    pszText As LPSTR
    cchTextMax As Integer
    iSubItem As Integer
    iImage As Integer
    iOrder As Integer
End Type

  • mask - коллекция флагов, задающие, какие члены структуры верны. Этот параметр был введен, потому что не все члены этой структуры используются одновременно. Некоторые из них используются в особых ситуациях. Эта структура используются и для ввода и для вывода, поэтому важно, чтобы вы пометили, какие параметры верны. Существуют следующие флаги:

    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 = текст выравнивается справа.

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

Когда listview создан, вам нужно вставить в него одну или более колонок. Если не предполагается переключение в режим отчета, то это не нужно. Чтобы вставить колонку, вам нужно создать структуру LV_COLUMN, заполнить ее необходимой информацией, указать номер колонки, а затем послать структуру listview с помощью сообщения LVM_INSERTCOLUMN.

Dim lvc As LVCOLUMN
lvc.mask = LVCF_TEXT+LVCF_WIDTH
lvc.pszText = Strptr(Heading1)
lvc.cx = 150
SendMessage(hList, LVM_INSERTCOLUMN,0,Cast(LPARAM,@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 (В FreeBasic она определена как LVITEM) в lParam. LV_ITEM имеет следующее определение:

Type LVITEM
    mask As UINT
    iItem As Integer
    iSubItem As Integer
    state As UINT
    stateMask As UINT
    pszText As LPSTR
    cchTextMax As Integer
    iImage As Integer
    lParam As LPARAM
End Type

  • mask - множество флагов, которые задают, какие из параметров данной структуры будут верны. В сущности, это поле идентично параметру 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( _
    lParam1 As LPARAM, _
    lParam2 As LPARAM, _ 
    lParamSort As LPARAM )

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 и заполняем его именами и размерами полей текущей папки. Режим отображения элементов по умолчанию поставлен в 'отчет'. В этом режиме вы можете кликать по заголовку колонок и элементы будут отсортированы согласно восходящему/нисходящему порядку. Вы можете выбрать режим отображения в меню. Когда вы делает двойной клик по элементу, показывается окно с названием элемента.

Файл ресурсов:

#INCLUDE "resource.h"
#DEFINE IDM_MAINMENU 10000
#DEFINE IDM_ICON LVS_ICON
#DEFINE IDM_SMALLICON LVS_SMALLICON
#DEFINE IDM_LIST LVS_LIST
#DEFINE IDM_REPORT LVS_REPORT

IDM_MAINMENU MENU
{
 POPUP "&View"
        {
         MENUITEM "&Icon View",IDM_ICON
         MENUITEM "&Small Icon View",IDM_SMALLICON
         MENUITEM "&List View", IDM_LIST
         MENUITEM "&Report View",IDM_REPORT
        }
}

Основной файл

#INCLUDE "windows.bi"
#INCLUDE "win/commctrl.bi"

Declare Function WinMain(hInst As HINSTANCE,hPrevInst As HINSTANCE,CmdLine As LPSTR,CmdShow As DWORD) As Integer
Declare Function WndProc(hWnd As HWND, uMsg As UINT, wParam As WPARAM, lParam As LPARAM) As Integer


#DEFINE IDM_MAINMENU 10000
#DEFINE IDM_ICON LVS_ICON
#DEFINE IDM_SMALLICON LVS_SMALLICON
#DEFINE IDM_LIST LVS_LIST
#DEFINE IDM_REPORT LVS_REPORT

'В FreeBasic вместо RGB для WINAPI можно использовать BGR,
'но я уж решил мало отходить от исходника Asm, может пригодится
'для понимания.

' удаляем макрос RGB
#Undef Rgb
' объявляем новый макрос RGB
#DEFINE Rgb(r,g,b) (Cuint(r) Or (Cuint(g) Shl 8) Or (Cuint(b) Shl 16))

Dim Shared As String ClassName,AppName,ListViewClassName, _
Heading1,Heading2,FileNamePattern,template

ClassName = "ListViewWinClass"
AppName = "Testing a ListView Control"
ListViewClassName = "SysListView32"
Heading1 = "Filename"
Heading2 = "Size"
FileNamePattern = "*.*"
template = "%lu"

Dim Shared As Integer FileNameSortOrder,SizeSortOrder
Dim Shared As HINSTANCE hInstance
Dim Shared As HWND hList
Dim Shared As HMENU hMenu

hInstance = GetModuleHandle(NULL)
InitCommonControls()
ExitProcess(WinMain(hInstance,NULL, NULL, SW_SHOWDEFAULT))

Function WinMain(hInst As HINSTANCE,hPrevInst As HINSTANCE,CmdLine As LPSTR,CmdShow As DWORD) As Integer
    Dim wc As WNDCLASSEX
    Dim msg As MSG
    Dim hwnd As HWND
    wc.cbSize = Sizeof (WNDCLASSEX)
    wc.lpfnWndProc = @WndProc
    wc.hInstance = hInstance
    wc.hbrBackground = Cast(HBRUSH,COLOR_WINDOW+1)
    wc.lpszMenuName = Cast(LPSTR,IDM_MAINMENU)
    wc.lpszClassName = Strptr(ClassName)
    wc.hIcon = LoadIcon(NULL,IDI_APPLICATION)
    wc.hIconSm = wc.hIcon
    wc.hCursor = LoadCursor(NULL,IDC_ARROW)
    RegisterClassEx(@wc)
    hwnd = CreateWindowEx(NULL,Strptr(ClassName),Strptr(AppName), _
    WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, _
    CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL, _
    hInst,NULL)
    ShowWindow(hwnd,SW_SHOWNORMAL)
    UpdateWindow(hwnd)
    While GetMessage(@msg,NULL,0,0)
        TranslateMessage(@msg)
        DispatchMessage(@msg)
    Wend
    Return msg.wParam
End Function

Sub InsertColumn
    Dim lvc As LVCOLUMN
    lvc.mask = LVCF_TEXT+LVCF_WIDTH
    lvc.pszText = Strptr(Heading1)
    lvc.cx = 150
    SendMessage(hList, LVM_INSERTCOLUMN,0,Cast(LPARAM,@lvc))
    lvc.mask or= LVCF_FMT
    lvc.fmt = LVCFMT_RIGHT
    lvc.pszText = Strptr(Heading2)
    lvc.cx = 100
    SendMessage(hList, LVM_INSERTCOLUMN, 1 ,Cast(LPARAM,@lvc))
End Sub

Sub ShowFileInfo(row As Integer, Byref lpFind As WIN32_FIND_DATA Ptr)
    Dim lvi As LVITEM
    Dim buffer As String*20
    lvi.mask = LVIF_TEXT+LVIF_PARAM
    lvi.iItem = row
    lvi.iSubItem = 0
    lvi.pszText = Strptr(lpFind->cFileName)
    lvi.lParam = row
    SendMessage(hList, LVM_INSERTITEM,0,Cast(LPARAM,@lvi))
    lvi.mask = LVIF_TEXT
    lvi.iSubItem=1
    wsprintf(Strptr(buffer), Strptr(template),lpFind->nFileSizeLow)
    lvi.pszText = Strptr(buffer)
    SendMessage(hList,LVM_SETITEM, 0,Cast(LPARAM,@lvi))
End Sub

Sub FillFileInfo()
    Dim finddata As WIN32_FIND_DATA
    Dim FHandle As HANDLE
    Dim As Integer value, REZ=1
    FHandle = FindFirstFile(Strptr(FileNamePattern),@finddata)
    If FHandle<>INVALID_HANDLE_VALUE Then
        While REZ<>0
            If (finddata.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) = FALSE  Then
                ShowFileInfo(value, @finddata)
                value+=1
            Endif
            REZ = FindNextFile(FHandle,@finddata)
        Wend
        FindClose(FHandle)
    Endif
End Sub

/'Нет смысла использовать самопальную 
функцию String2Dword для преобразования из строки в число,
когда существует немало готовых и отлаженных функций, 
в том числе в FreeBasic это функции Val,Valint и пр.
Но для тех кому интересно как и что происходит в функции,
я написал подобие его функции '/

Function String2Dword(String_ As String)As Integer
    Dim As Integer result, Lenstring = Len(String_), degree = Lenstring - 1 
    For i As Integer = 0 To Lenstring - 1
        result+=(string_[i]-48)*(10^degree)
        degree-=1
    Next   
    Return result
End Function

Function CompareFunc(lParam1 As DWORD, lParam2 As DWORD, SortType As DWORD)As Integer
    Dim As ZString*256  buffer,buffer1
    Dim rez As Integer
    Dim lvi As LVITEM
    lvi.mask = LVIF_TEXT
    lvi.pszText = Strptr(buffer)
    lvi.cchTextMax = 256
    If SortType=1 Then
        lvi.iSubItem = 1
        SendMessage(hList,LVM_GETITEMTEXT,lParam1,Cast(LPARAM,@lvi))
        rez = Val(buffer)
        SendMessage(hList,LVM_GETITEMTEXT,lParam2,Cast(LPARAM,@lvi))
        rez -= Val(buffer)
    Elseif SortType = 2 Then
        lvi.iSubItem = 1
        SendMessage(hList,LVM_GETITEMTEXT,lParam1,Cast(LPARAM,@lvi))
        rez = Val(buffer)
        SendMessage(hList,LVM_GETITEMTEXT,lParam2,Cast(LPARAM,@lvi))
        rez = Val(buffer)-rez
    Elseif SortType=3 Then
        lvi.iSubItem = 0
        SendMessage(hList,LVM_GETITEMTEXT,lParam1,Cast(LPARAM,@lvi))
        lstrcpy(Strptr(buffer1),Strptr(buffer))
        SendMessage(hList,LVM_GETITEMTEXT,lParam2,Cast(LPARAM,@lvi))
        rez = lstrcmpi(Strptr(buffer1),Strptr(buffer))
    Else
        lvi.iSubItem = 0
        SendMessage(hList,LVM_GETITEMTEXT,lParam1,Cast(LPARAM,@lvi))
        lstrcpy(Strptr(buffer1),Strptr(buffer))
        SendMessage(hList,LVM_GETITEMTEXT,lParam2,Cast(LPARAM,@lvi))
        rez =lstrcmpi(Strptr(buffer),Strptr(buffer1))
    Endif
    Return rez
End Function

Sub UpdatelParam()
    Dim lvi As LVITEM
    Dim getcount As Integer
    getcount = SendMessage(hList, LVM_GETITEMCOUNT,0,0)
    lvi.mask = LVIF_PARAM
    lvi.iSubItem = 0
    lvi.iItem = 0
    While getcount>0
        lvi.lParam = lvi.iItem
        SendMessage(hList, LVM_SETITEM,0,Cast(LPARAM,@lvi))
        lvi.iItem += 1
        getcount -= 1
    Wend
End Sub

Sub ShowCurrentFocus()
    Dim lvi As LVITEM
    Dim buffer As String*256
    lvi.iItem = SendMessage(hList,LVM_GETNEXTITEM,-1,LVNI_FOCUSED)
    lvi.iSubItem = 0
    lvi.mask = LVIF_TEXT
    lvi.pszText = Strptr(buffer)
    lvi.cchTextMax = 256
    SendMessage(hList,LVM_GETITEM,0,Cast(LPARAM,@lvi))
    MessageBox(0, buffer,AppName,MB_OK)
End Sub

Function WndProc(hWnd As HWND, uMsg As UINT, wParam As WPARAM, lParam As LPARAM) As Integer
    If uMsg=WM_CREATE Then
        hList =  CreateWindowEx(NULL, Strptr(ListViewClassName), NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL)
        InsertColumn()
        FillFileInfo()
        SendMessage(hList,LVM_SETTEXTCOLOR,0,Rgb(255,255,255))
        SendMessage(hList,LVM_SETBKCOLOR,0,Rgb(0,0,0))
        SendMessage(hList,LVM_SETTEXTBKCOLOR,0,Rgb(0,0,0))
        hMenu = GetMenu(hWnd)
        CheckMenuRadioItem(hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED)
    Elseif uMsg=WM_COMMAND Then
        If lParam=0 Then
            Var STYLE = GetWindowLong(hList,GWL_STYLE)
            STYLE = (STYLE And Not LVS_TYPEMASK) Or (Wparam And &hffff)
            SetWindowLong(hList,GWL_STYLE,STYLE)
            CheckMenuRadioItem(hMenu,IDM_ICON,IDM_LIST,Wparam And &hffff,MF_CHECKED)
        Endif
    Elseif uMsg=WM_NOTIFY Then
        Dim NMH As NMHDR Ptr = Cast(NMHDR Ptr,lParam)
        If NMH->hwndFrom = hList Then
            If NMH->code = LVN_COLUMNCLICK Then
                Dim NML As NMLISTVIEW Ptr = Cast(NMLISTVIEW Ptr,lParam)
                If NML->iSubItem=1 Then
                    If SizeSortOrder=0 Or SizeSortOrder=2 Then
                        SendMessage(hList,LVM_SORTITEMS,1,Cast(LPARAM,@CompareFunc))
                        UpdatelParam()
                        SizeSortOrder = 1
                    Else
                        SendMessage(hList,LVM_SORTITEMS,2,Cast(LPARAM,@CompareFunc))
                        UpdatelParam()
                        SizeSortOrder = 2
                    Endif
                Else
                    If FileNameSortOrder=0 Or FileNameSortOrder=4 Then
                        SendMessage(hList,LVM_SORTITEMS,3,Cast(LPARAM,@CompareFunc))
                        UpdatelParam()
                        FileNameSortOrder = 3
                    Else
                        SendMessage(hList,LVM_SORTITEMS,4,Cast(LPARAM,@CompareFunc))
                        UpdatelParam()
                        FileNameSortOrder = 4
                    Endif
                Endif
            Elseif NMH->code = NM_DBLCLK Then
                ShowCurrentFocus()
            Endif
        Endif
    Elseif uMsg=WM_SIZE Then
        MoveWindow(hList, 0, 0, Loword(lParam),Hiword(lParam),TRUE)
    Elseif uMsg=WM_DESTROY Then
        PostQuitMessage(NULL)
    Else
        Return DefWindowProc(hWnd,uMsg,wParam,lParam)
    Endif
End Function




АНАЛИЗ

Первое, что должна сделать программа после того, как создано основное окно - это создать listview.

hList =  CreateWindowEx(NULL, Strptr(ListViewClassName), NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL)

Мы вызываем CreateWindowEx, передавая ей имя класса окна "SysListView32". Режим отображения по умолчанию задан стилем LVS_REPORT.

InsertColumn()

После того, как создан listview, мы вставляем в него колонку.

Dim lvc As LVCOLUMN
lvc.mask = LVCF_TEXT+LVCF_WIDTH
lvc.pszText = Strptr(Heading1)
lvc.cx = 150
SendMessage(hList, LVM_INSERTCOLUMN,0,Cast(LPARAM,@lvc))

Мы указываем название и ширину первой колонки, в которой будут отображаться имена файлов, в структуре LV_COLUMN, поэтому нам нужно установить в imask флаги LVCF_TEXT и LVCF_WIDTH. Мы заполняем рszText адресом названия и cx - шириной колонки в пикселях. Когда все сделано, мы посылаем сообщение LVM_INSERTCOLUMN listview, передавая ей структуру.

lvc.mask or= LVCF_FMT
lvc.fmt = LVCFMT_RIGHT

После вставления первой колонки, мы вставляем следующую, в которой будут отображаться размеры файлов. Так как нам нужно, чтобы размеры файлов выравнивались по правой стороне, нам необходимо указать флаг в параметре fmt, LVCFMT_RIGHT. Мы также указываем флаг LVCF_FMT в imask, в добавление к LVCF_TEXT и LVCF_WIDTH.

lvc.pszText = Strptr(Heading2)
lvc.cx = 100
SendMessage(hList, LVM_INSERTCOLUMN, 1 ,Cast(LPARAM,@lvc))

Оставшийся код прост. Помещаем адреса названия в рszText и ширину в cx. Затем посылаем сообщение LVM_INSERTCOLUMN listview, указывая номер колонки и адрес структуры.

Когда колонки вставлены, мы можем заполнить listview элементами.

FillFileInfo()

В FillFileInfo содержится следующий код.

Dim finddata As WIN32_FIND_DATA
Dim FHandle As HANDLE
FHandle = FindFirstFile(Strptr(FileNamePattern),@finddata)

Мы вызываем FindFirstFile, чтобы получить информацию о первом файле, который отвечает заданным условиям. У FindFirstFile следующий прототип:

FindFirstFile( _
    lpFileName As LPCTSTR,  _
    lpFindFileData As LPWIN32_FIND_DATA _
   ) As HANDLE

рFileName - это адрес имени файла, который надо искать. Эта строка может содержать "дикие" символы. В нашем примере мы используем *.*, чтобы искать все файлы в данной папке.

рWin32_Find_Data - это адрес структуры WIN32_FIND_DATA, которая будет заполнена информацией о файле (если что-нибудь будет найдено).

Эта функция возвращает INVALID_HANDLE_VALUE , если не было найдено соответствующих заданным критериям файлов. Иначе она возвратит хэндл поиска, который будет использован в последующих вызовах FindNextFile.

If FHandle<>INVALID_HANDLE_VALUE Then

Если файл будет найден, мы сохраним хэндл поиска в переменную, а потом обнулим edi, который будет использован в качестве индекса элемента (номер ряда).

While REZ<>0
    If (finddata.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) = FALSE  Then

В этом туториале я не хочу иметь дело с папками, поэтому отфильтровываю их проверяя параметр dwFileAttributes на предмет наличия установленного флага FILE_ATTRIBUTE_DIRECTORY. Если он есть, я сразу перехожу к вызову FindNextFile.

        ShowFileInfo(value, @finddata)
        value+=1
    Endif
    REZ = FindNextFile(FHandle,@finddata)
Wend

Мы вставляем имя и pазмеp файла в listview вызывая функцию ShowFileInfo. Затем мы повышаем значение value (текущий номер столбца). И, наконец, мы делаем вызов FindNextFile, чтобы найти следующий файл в нашей папке, пока FindNextFile не возвратит 0, что означает то, что больше файлов найдено не было.

        FindClose(FHandle)
    Endif
End Sub

Когда все файлы в текущей папке найдены, мы должны закрыть хэндл поиска.

Теперь давайте взглянем на функцию ShowFileInfo. Эта функция принимает два параметра, индекс элемента (номер ряда) и адрес структуры WIN32_FIND_DATA.

Sub ShowFileInfo(row As Integer, Byref lpFind As WIN32_FIND_DATA Ptr)
    Dim lvi As LVITEM
    Dim buffer As String*20

Параметр lpFind передается по ссылке, поэтому нет нужды создавать временный указатель на структуру WIN32_FIND_DATA.

lvi.mask = LVIF_TEXT+LVIF_PARAM
lvi.iItem = row
lvi.iSubItem = 0

Мы предоставляем название элемента и значение lParam, поэтому мы помещаем флаги LVIF_TEXT и LVIF_PARAM в mask. Затем мы устанавливаем приравниваем iItem номер ряда, переданный функции и, так как это главный элемент, мы должны приравнять iSubItem нулю (колонка 0).

lvi.pszText = Strptr(lpFind->cFileName)
lvi.lParam = row

Затем мы помещаем адрес названия, в данном случая это имя файла в структуре WIN32_FIND_DATA, в рszText. Так как мы реализуем свою сортировку, мы должны заполнить lParam определенным значением. Я решил помещать номер ряда в это параметр, чтобы я мог получать информацию об элементе по его индексу.

SendMessage(hList, LVM_INSERTITEM,0,Cast(LPARAM,@lvi))

Когда все необходимые поля в LV_ITEM заполнены, мы посылаем сообщение LVM_INSERTITEM listview, чтобы вставить в него элемент.

lvi.mask = LVIF_TEXT
lvi.iSubItem=1
wsprintf(Strptr(buffer), Strptr(template),lpFind->nFileSizeLow)
lvi.pszText = Strptr(buffer)

Мы установим подэлементы, ассоциированные с элементом. Подэлемент может иметь только название. Поэтому мы указываем в imask LVIF_TEXT. Затем мы указываем в iSubItem колонку, в которой должен находиться подэлемент. В этом случае мы устанавливаем его в 1. Названием этого элемента будет являться размер файла. Тем не менее, мы сначала должны сконвертировать его в строку, вызвать wsрrintf. Затем мы помещаем адрес строки в рszText.

    SendMessage(hList,LVM_SETITEM, 0,Cast(LPARAM,@lvi))
End Sub

Когда все требуемые поля в LV_ITEM заполнены, мы посылаем сообщение LVM_SETITEM listview, передавая ему адрес структуры LV_ITEM. Заметьте, что мы используем LVM_SETITEM, а не LVM_INSERTITEM, потому что подэлемент считается свойством элемента. Поэтому устанавливаем свойство элемента, а не вставляем новый элемент.

Когда все элементы вставлены в listview, мы устанавливаем текст и цвет бэкграунда контрола listview.

SendMessage(hList,LVM_SETTEXTCOLOR,0,Rgb(255,255,255))
SendMessage(hList,LVM_SETBKCOLOR,0,Rgb(0,0,0))
SendMessage(hList,LVM_SETTEXTBKCOLOR,0,Rgb(0,0,0))

Я использую макро RGB, чтобы конвертировать значения red, green, blue и для того, что указать нужное нам значение. Мы устанавливаем цвет текста и цвет фона с помощью сообщений LVM_SETTEXTCOLOR и LVM_SETTEXTBKCOLOR. Мы устанавливаем цвет фона listview сообщением LVM_SETBKCOLOR.

hMenu = GetMenu(hWnd)
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 Then
    MoveWindow(hList, 0, 0, Loword(lParam),Hiword(lParam),TRUE)

Когда родительское окно получает сообщение WM_SIZE, нижнее слово lParam содержит новую ширину клиентской области и верхнее словно новой высоты. Тогда мы вызываем MoveWindow, чтобы изменить pазмеp listview, чтобы тот покрывал всю клиентскую область родительского окна.

Когда пользователь выберет режим отображения в меню, мы должны соответственно отреагировать. Мы устанавливаем новый стиль контрола listview функцией SetWindowLong.

    Elseif uMsg=WM_COMMAND Then
        If lParam=0 Then
            Var STYLE = GetWindowLong(hList,GWL_STYLE)
            STYLE = (STYLE And Not LVS_TYPEMASK) Or (Wparam And &hffff)

Сначала мы получаем текущие стили listview. Затем мы стираем старый стиль отображения. LVS_TYPEMASK - это комбинированное значение всех четырех стилей отображения. Поэтому когда мы выполняем логическое умножение текущих флагов стилей со значением "not LVS_TYPEMASK", стиль текущего отображения стирается.

Во время проектирования меню я немного сжульничал. Я использовал в качестве ID пунктов меню константы стилей отображения.

#DEFINE IDM_ICON LVS_ICON
#DEFINE IDM_SMALLICON LVS_SMALLICON
#DEFINE IDM_LIST LVS_LIST
#DEFINE IDM_REPORT LVS_REPORT

Поэтому, когда родительское окно получает сообщение WM_COMMAND, нужный стиль отображения находится в нижнем слове wParam'а (как ID пункта меню).

Wparam And &&hffff

Мы получили стиль отображения в нижнем слове wParam. Все, что нам теперь нужно, это обнулить верхнее слово, что мы и делаем оператором OR.

И добавить стиль отображения к уже существующим стилям (текущий стиль отображения мы ранее оттуда убрали).

SetWindowLong(hList,GWL_STYLE,STYLE)

И установить новые стили функцией SetWindowLong.

    CheckMenuRadioItem(hMenu,IDM_ICON,IDM_LIST,Wparam And &hffff,MF_CHECKED)
Endif

Hам также требуется поместить radio button перед выбранным пунктом меню. Поэтому мы вызываем CheckMenuRadioItem, передавая ей текущий стиль отображения (а также ID пункта меню).

Когда пользователь кликает по заголовку колонки в pежиме отчета, нам нужно отсортировать элементы в listview. Мы должны отреагировать на сообщение WM_NOTIFY.

Elseif uMsg=WM_NOTIFY Then
    Dim NMH As NMHDR Ptr = Cast(NMHDR Ptr,lParam)
    If NMH->hwndFrom = hList Then

Когда мы получаем сообщение WM_NOTIFY, lParam содержит указатель на структуру NMHDR. Мы можем проверить, пришло ли это сообщение от listview, сравнив параметр hwndFrom структуры NMHDR с хэндлом контрола listview. Если они совпадают, мы можем заключить, что уведомление пришло от listview.

If NMH->code = LVN_COLUMNCLICK Then
    Dim NML As NMLISTVIEW Ptr = Cast(NMLISTVIEW Ptr,lParam)

Если уведомление пришло от listview, мы проверяем, равен ли код LVN_COLUMNCLICK. Если это так, это означает, что пользователь кликает на заголовке колонки. В случае, что код pавен LVN_COLUMNCLICK, мы считаем, что lParam содержит указатель на структуру NM_LISTVIEW, которая является супермножеством по отношению к структуре NMHDR (т.е. включает ее). Затем нам нужно узнать, по какому заголовку колонки кликнул пользователь. Эту информацию мы получаем из параметра iSubItem. Его значение можно считать номером колонки (отсчет начинается с нуля).

If NML->iSubItem=1 Then
    If SizeSortOrder=0 Or SizeSortOrder=2 Then

Если iSubItem равен 1, это означает, что пользователь кликнул по второй колонке. Мы используем глобальные переменные, чтобы сохранять текущий статус порядка сортировки. 0 означает "еще не отсортировано", 1 значит "восходящая сортировка", а 2 - "нисходящая сортировка". Если элементы/подэлементы в колонке ранее не были отсортированы или отсортированы по нисходящей, то мы устанавливаем сортировку по восходящей.

SendMessage(hList,LVM_SORTITEMS,1,Cast(LPARAM,@CompareFunc))

Мы посылаем сообщение LVM_SORTITEMS listview, передавая 1 через wParam и адрес нашей сравнивающей функции через lParam. Заметьте, что значение в wParam задается пользователем, вы можете использовать его как хотите. Я использовал его в нашем примере как метод сортировки. Сначала мы взглянем на сравнивающую функцию.

Function CompareFunc(lParam1 As DWORD, lParam2 As DWORD, SortType As DWORD)As Integer
    Dim As ZString*256  buffer,buffer1
    Dim rez As Integer
    Dim lvi As LVITEM
    lvi.mask = LVIF_TEXT
    lvi.pszText = Strptr(buffer)
    lvi.cchTextMax = 256

В сравнивающей функции контрол listview будет передавать lParam'ы (через LV_ITEM) двух элементов, которые нужно сравнить, через lParam1 и lParam2. Вспомните, что мы помещаем индекс элемента в lParam. Таким образом мы можем получить информацию об элементах, используя эти индексы. Информация, которая нам нужна - это названия сортирующихся элементов/подэлементов. Мы подготавливаем структуру LV_ITEM для этого, указывая в imask LVIF_TEXT и адрес буфера в рszText и размер буфера в cchTextMax.

If SortType=1 Then
    lvi.iSubItem = 1
    SendMessage(hList,LVM_GETITEMTEXT,lParam1,Cast(LPARAM,@lvi))

Если значение SortType pавно 1 или 2, мы знаем, что кликнута колонка размера файла. 1 означает, что необходимо отсортировать элементы в нисходящем порядке. 2 значит обратное. Таким образом мы указываем iSubItem равным 1 (чтобы задать колонку размера) и посылаем сообщение LVM_GETITEMTEXT контролу listview, чтобы получить название (строку с размером файла) подэлемента.

rez = Val(buffer)

Конвертируем строку в двойное слово для последующего сравнения.

SendMessage(hList,LVM_GETITEMTEXT,lParam2,Cast(LPARAM,@lvi))
rez -= Val(buffer)

Тоже самое мы делаем и с lParam2. После получения размеров обоих файлов, мы можем сравнить их.

Правила, которых придерживается функция сравнения, следующие:

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

В нашем случае нам нужно отсортировать элементы согласно их размерам в восходящем порядке. Поэтому мы просто можем вычесть размер первого элемента из второго и возвратить результат.

    Elseif SortType=3 Then
        lvi.iSubItem = 0
        SendMessage(hList,LVM_GETITEMTEXT,lParam1,Cast(LPARAM,@lvi))
        lstrcpy(Strptr(buffer1),Strptr(buffer))
        SendMessage(hList,LVM_GETITEMTEXT,lParam2,Cast(LPARAM,@lvi))
        rez = lstrcmpi(Strptr(buffer1),Strptr(buffer))

В случае, если пользователь кликнет по колонке с именем файла, мы должны сравнивать имена файлов. Мы должны получить имена файлов, а затем сравнить их с помощью функции lstrcmрi. Мы можем возвратить значение, возвращаемое этой функцией, так как оно использует те же правила сравния.

После того, как элементы отсортированы, нам нужно обновить значения lParam'ов для всех элементов, чтобы учесть изменившиеся индексы элементов, поэтому мы вызываем функцию UpdatelParam.

UpdatelParam()
SizeSortOrder = 1

Эта функция просто-напросто перечисляет все элементы в listview и обновляет значения lParam. Hам требуется это делать, иначе следующая сортировка не будет работать как ожидается, потому что мы исходим из того, что значение lParam - это индекс элемента.

Elseif NMH->code = NM_DBLCLK Then
    ShowCurrentFocus()
Endif

Когда пользователь делает двойной клик на элементе, нам нужно отобразить окно с сообщением с названием элемента. Мы должны проверить, равно ли поле code в NMHDR NM_DBLCLK. Если это так, мы можем перейти к получению названия и отображению его в окне с сообщением.

Sub ShowCurrentFocus()
    Dim lvi As LVITEM
    Dim buffer As String*256
    lvi.iItem = SendMessage(hList,LVM_GETNEXTITEM,-1,LVNI_FOCUSED)

Как мы можем узнать, по какому элементу кликнули два раза? Когда элемент кликнут (одинарным или двойным нажатием), он получает фокус. Даже если выбрано несколько элементов, фокус будет только у одного. Наши задача заключается в том, чтобы найти элемент у которого находится фокус. Мы делаем это, посылая сообщение LVM_GETNEXTITEM контролу listview, указав желаемое состояние элемента в lParam. -1 в wParam означает поиск по всем элементарм. Результатом работы функции будет индекс элемент

lvi.iSubItem = 0
lvi.mask = LVIF_TEXT
lvi.pszText = Strptr(buffer)
lvi.cchTextMax = 256
SendMessage(hList,LVM_GETITEM,0,Cast(LPARAM,@lvi))

Затем мы получаем название элемента с помощью сообщения LVM_GETITEM.

MessageBox(0, buffer,AppName,MB_OK)

И наконец, мы отображаем название элемента в окне сообщения.

Если вы хотите узнать, как использовать в контроле listview иконки, вы можете прочитать об этом в моем туториале о treeview. В случае с listview надо будет сделать примерно то же самое.

Перевод на русский с оригинала: Aquila, адаптация материалов под FreeBasic: Станислав Будинов

содержание | назад | вперед