Многие приложения отображают информацию в виде таблиц или списков. Операционная система Microsoft Windows 95 предоставляет в распоряжение программиста очень мощный встроенный орган управления List View .
Этот орган управления позволяет отображать списки в нескольких видах:
Такой набор подходит для большинства случаев. Например, детальный многоколоночный отчет удобен для организации просмотра и редактирования содержимого баз данных. Окно с пиктограммами можно использовать для работы с графическими изображениями.
Вы можете получить некоторое представление о возможностях органа управления List View , посмотрев рисунки из раздела "Приложение List Application", расположенного в конце этой главы (рис. 3.1 - 3.4).
Список состоит из элементов, каждому из которых назначается пиктограмма, название (label) и дополнительные текстовые строки, которые могут отображаться в столбцах при использовании режима детального просмотра (рис. 3.1). Кроме того, приложение может отмечать состояние элемента с помощью изображений из дополнительного списка (на рис. 3.1 - 3.4 не показано).
Для использования органа управления List View ваше приложение должно выполнить несколько действий:
Теперь мы подробно расскажем вам о том, как выполнить перечисленные выше действия.
Окно органа управления List View создается функцией CreateWindowEx на базе класса окна WC_LISTVIEW , например так:
hwndList = CreateWindowEx(0L, WC_LISTVIEW, "", WS_VISIBLE | WS_CHILD | WS_BORDER | LVS_REPORT | LVS_EDITLABELS, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWnd, (HMENU)IDC_LISTVIEW, hInst, NULL);
Перед вызовом этой функции вы должны загрузить в память DLL-библиотеку COMCTL32.DLL , для чего следует вызвать функцию InitCommonControls без параметров.
В дополнение к обычным стилям окна, таким как WS_VISIBLE, WS_CHILD и WS_BORDER, необходимо указать специальные стили с префиксом имени LVS_, определяющие внешний вид и поведение органа управления List View. Как минимум, следует указать один из следующих четырех стилей: LVS_REPORT , LVS_ICON, LVS_SMALLICON или LVS_LIST.
Ниже мы привели краткое описание этих, а также
остальных стилей окна List View.
Стиль |
Описание |
LVS_REPORT |
Список отображается в виде детального
отчета, состоящего из нескольких столбцов (рис.
3.1) |
LVS_ICON |
Список отображается в виде окна с
пиктограммами стандартного размера (рис. 3.2) |
LVS_SMALLICON |
Список отображается в виде окна с
пиктограммами уменьшенного размера (рис. 3.3) |
LVS_LIST |
Простой список с пиктограммами
уменьшенного размера (рис. 3.4) |
LVS_ALIGNLEFT |
Используется вместе с LVS_ICON и LVS_SMALLICON.
Если указан этот стиль, пиктограммы будут
выровнены по левой границе |
LVS_ALIGNTOP |
Используется вместе с LVS_ICON и LVS_SMALLICON.
Если указан этот стиль, пиктограммы будут
выровнены по верхней границе. Стиль LVS_ALIGNTOP
используется по умолчанию |
LVS_AUTOARRANGE |
Используется вместе с LVS_ICON и LVS_SMALLICON для
выполнения автоматического размещения
пиктограмм внутри окна органа управления List View |
LVS_NOSCROLL |
Отключение возможности свертки
содержимого окна органа управления List View |
LVS_EDITLABELS |
Этот стиль позволяет пользователю
редактировать название элемента списка. Если
указан стиль LVS_EDITLABELS, приложение должно
обрабатывать извещение LVN_ENDLABELEDIT, которое будет
описано позже |
LVS_NOCOLUMNHEADER |
Если указан стиль LVS_NOCOLUMNHEADER, в режиме
детального просмотра не отображается заголовок
столбцов, с помощью которого выполняется
сортировка и изменение размера столбцов |
LVS_NOLABELWRAP |
Подпись под пиктограммами отображается
в одной строке |
LVS_NOSORTHEADER |
Если указан этот стиль, с помощью
заголовка окна невозможно выполнить сортировку
столбцов в режиме детального просмотра |
LVS_OWNERDRAWFIXED |
Стиль позволяет родительскому окну
выполнить рисование содержимого списка. Для
этого родительское окно должно обрабатывать
сообщение WM_DRAWITEM |
LVS_SHAREIMAGELIST |
Этот стиль предназначен для организации
совместного использования списков изображений
несколькими органами управления List View |
LVS_SHOWSELALWAYS |
Выбранные элементы списка отображаются
с выделением даже в том случае, когда орган
управления List View не активен |
LVS_SINGLESEL |
Пользователь может выделить в списке
только один элемент (по умолчанию можно выделить
сразу несколько элементов) |
LVS_SORTASCENDING |
Выполнение сортировки текстовых строк
элементов в прямом порядке |
LVS_SORTDESCENDING |
То же, но в обратном порядке |
Как мы уже говорили, стили LVS_REPORT, LVS_ICON, LVS_SMALLICON и LVS_LIST определяют режим работы органа управления List View. При создании этого органа управления вы должны указать только один из перечисленных стилей. Однако в дальнейшем если появится необходимость изменить режим работы, это можно будет легко сделать при помощи функций GetWindowLong и SetWindowLong .
Когда список отображается в виде окна со стандартными или уменьшенными пиктограммами, у вас есть возможность выбрать один из нескольких способов выравнивания пиктограмм во внутренней области этого окна. Для этого вы должны указать один из следующих стилей: LVS_ALIGNLEFT, LVS_ALIGNTOP или LVS_AUTOARRANGE.
По умолчанию (если не указан стиль LVS_ALIGNLEFT) пиктограммы выравниваются по верхней границе окна. Такое поведение соответствует стилю LVS_ALIGNTOP. Вы можете также выровнять пиктограммы по левой границе окна, задав стиль LVS_ALIGNLEFT. Разумеется, стили LVS_ALIGNTOP и LVS_ALIGNLEFT несовместимы, поэтому вы можете использовать только один из них.
Для пользователя будет удобнее, если при изменении размеров окна просмотра пиктограммы будут автоматически перемещаться таким образом, чтобы по возможности занимать всю полезную площадь окна. Вы можете организовать автоматическое размещение пиктограмм, указав стиль LVS_AUTOARRANGE. Без этого стиля пользователю придется работать с полосой просмотра (Scrollbar), что не всегда удобно.
Иногда необходимо заблокировать возможность изменения размеров окна просмотра, исключив стиль WS_BORDER. Например, вы можете создать окно просмотра фиксированного размера, расположенное в диалоговой панели. В этом случае вы не должны указывать стиль LVS_NOSCROLL, так как иначе пользователь не получит доступа ко всем элементам списка.
Итак, вы создали окно органа управления List View, выбрав для него подходящий набор стилей. Теперь следует заняться наполнением этого окна изображениями и элементами списка.
В зависимости от того, в каких режимах будет работать создаваемый вами орган управления List View, вы должны создать один, два или три списка из следующего набора:
Как выбрать нужные списки?
Список стандартных пиктограмм нужен только в том случае, если вы будете использовать орган управления List View в режиме LVS_ICON (рис. 3.2 в разделе "Приложение List Application").
Список пиктограмм уменьшенного размера нужен для всех остальных режимов (рис. 3.1, 3.3, 3.4).
|
Что же касается списка пиктограмм
состояния (state image list), то он используется для
выделения состояния отдельных элементов списка.
В SDK есть исходные тексты приложения ListCtrl,
создающего список пиктограмм состояния. |
Для создания списка изображения вы должны вызвать функцию ImageList_Create :
HIMAGELIST WINAPI ImageList_Create( int cx, // ширина изображения int cy, // высота изображения UINT flags, // тип изображения int cInitial, // первоначальное количество изображений int cGrow); // количество изображений, на которое // увеличится размер списка при добавлении новых изображений
Параметры cx и cy определяют, соответственно, ширину и высоту добавляемых изображений. Если вы создаете список пиктограмм стандартного или уменьшенного размера, для определения значения этих параметров следует использовать функцию GetSystemMetrics , передав ей в первом случае параметры SM_CXICON и SM_CYICON , а во втором - параметры SM_CXSMICON и SM_CYSMICON .
Функция GetSystemMetrics была нами описана в 11 томе "Библиотеки системного программиста".
Параметр flags определяет тип изображений, из
которых состоит список. Вы можете создавать
список из пиктограмм, аппаратно-зависимых
битовых изображений DDB или аппаратно-независимых
битовых изображений DIB. Приведем список
возможных значений параметра flags:
Значение |
Описание |
ILC_COLOR4 |
4-битовое изображение DIB |
ILC_COLOR8 |
8-битовое изображение DIB |
ILC_COLOR16 |
16-битовое изображение DIB |
ILC_COLOR24 |
24-битовое изображение DIB |
ILC_COLOR32 |
32-битовое изображение DIB |
ILC_PALETTE |
Используются цветовые палитры |
ILC_COLORDDB |
Изображение DDB |
ILC_MASK |
Использование маски. Изображение
состоит из двух изображений, причем одно из них
является монохромной маской. Такой формат имеют
пиктограммы и курсоры |
При создании списка изображений из пиктограмм в SDK значение параметра flags рекомендуется указывать как TRUE, однако логичнее указать значение ILC_MASK.
Параметр cInitial определяет размер блока памяти, выделяемого для хранения изображений, так как этот размер зависит от количества изображений. При добавлении в созданный список новых изображений сверх значения, указанного в этом параметре, происходит динамическое изменение размера заказанного блока памяти. Такое изменение выполняется в соответствии со значением параметра cGrow.
Функция ImageList_Create при нормальном завершении возвращает идентификатор созданного списка изображений, который надо сохранить для дальнейшего использования. В случае ошибки возвращается значение NULL.
Ниже приведен пример создания двух списков, в которые первоначально будет добавлено девять изображений:
himlSmall = ImageList_Create( GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON), ILC_MASK, 9, 1); himlLarge = ImageList_Create( GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), ILC_MASK, 9, 1);
Мы только что создали списки изображений, однако пока они пустые и орган управления List View ничего о них "не знает".
Для добавления изображений вы можете воспользоваться функцией ImageList_AddIcon :
int WINAPI ImageList_AddIcon( HIMAGELIST himl, // идентификатор списка изображений HICON hicon // идентификатор добавляемого изображения );
Если вы добавляете в список пиктограммы, определенные в ресурсах приложения, вызов функции ImageList_AddIcon можно выполнять в цикле, загружая пиктограммы функцией LoadIcon :
for(i = IDI_ICON1; i <= IDI_ICON9; i++) { hIcon = LoadIcon(hInst, MAKEINTRESOURCE(i)); ImageList_AddIcon(himlSmall, hIcon); ImageList_AddIcon(himlLarge, hIcon); }
Заметьте, что после добавления пиктограммы не уничтожаются явным образом, так как при необходимости система сделает это автоматически.
На данном этапе мы создали и заполнили списки изображений. Теперь их надо подключить к органу управления List View, вызвав макрокоманду ListView_SetImageList :
HIMAGELIST ListView_SetImageList( HWND hwnd, // идентификатор окна органа List View HIMAGELIST himl, // идентификатор подключаемого списка int iImageList); // тип изображений в списке
Первые два параметра макрокоманды задают, соответственно, идентификатор окна органа List View и идентификатор подключаемого списка, который мы только что создали и заполнили.
Параметр iImageList может иметь следующие значения:
Значение |
Содержимое списка |
LVSIL_NORMAL |
Пиктограммы стандартного размера |
LVSIL_SMALL |
Пиктограммы уменьшенного размера |
LVSIL_STATE |
Пиктограммы состояния элементов списка |
Макрокоманда ListView_SetImageList посылает органу управления List View сообщение LVM_SETIMAGELIST и определена следующим образом:
#define ListView_SetImageList(hwnd, himl, iImageList) \ (HIMAGELIST)(UINT)SendMessage((hwnd), LVM_SETIMAGELIST, \ (WPARAM)(iImageList), (LPARAM)(UINT)(HIMAGELIST)(himl))
Вы можете посылать сообщение LVM_SETIMAGELIST непосредственно, однако макрокомандой удобнее пользоваться. Вот соответствующий фрагмент исходного текста приложения, подключающий два списка:
ListView_SetImageList(hwndList, himlSmall, LVSIL_SMALL); ListView_SetImageList(hwndList, himlLarge, LVSIL_NORMAL);
Если вы собираетесь просматривать список в виде детального отчета (стиль LVS_REPORT), необходимо вставить нужное количество столбцов, а также определить для каждого столбца заголовок и ширину.
Вставка столбцов выполняется макрокомандой ListView_InsertColumn , посылающей органу управления сообщение LVM_INSERTCOLUMN :
int ListView_InsertColumn( HWND hwnd, // идентификатор органа List view int iCol, // номер столбца const LV_COLUMN FAR * pcol); // адрес структуры LV_COLUMN
Нумерация столбцов начинается с нуля.
Перед тем как вызывать макрокоманду ListView_InsertColumn, вы должны по очереди для каждого столбца заполнить структуру LV_COLUMN , определенную следующим образом:
typedef struct _LV_COLUMN { UINT mask; // маска использования полей структуры LV_COLUMN int fmt; // тип выравнивания для столбца int cx; // ширина столбца в пикселах LPTSTR pszText; // адрес строки заголовка столбца int cchTextMax; // размер буфера, адрес которого // задан в pszText int iSubItem; // номер дополнительного элемента } LV_COLUMN;
Поле маски mask определяет, какие из полей
структуры LV_COLUMN будут использованы. Ниже мы
перечислили возможные значения масок, которые
можно объединять при помощи логической операции
ИЛИ:
Маска |
Заполненное поле структуры LV_COLUMN |
LVCF_FMT |
fmt |
LVCF_SUBITEM |
iSubItem |
LVCF_TEXT |
pszText |
LVCF_WIDTH |
cx |
Поле fmt определяет тип выравнивания для столбца. Можно указывать одно из следующих значений: LVCFMT_LEFT, LVCFMT_RIGHT или LVCFMT_CENTER. Они задают, соответственно, выравнивание влево, вправо или по центру столбца.
Ширина столбца в пикселах задается полем cx.
Для того чтобы задать заголовок столбца, вы должны определить буфер и записать в него соответствующую текстовую строку, закрыв ее двоичным нулем. Размер буфера должен быть указан в поле cchTextMax (в байтах).
Теперь о поле iSubItem.
Как мы уже говорили раньше, список содержит элементы, каждый из которых имеет имя и связанную с ним пиктограмму (привязка пиктограммы выполняется на этапе добавления строк). Кроме того, для каждого элемента можно задать дополнительные элементы. В SDK элементы называются item, а дополнительные элементы - subitem.
В режиме детального отчета пиктограмма и название элемента отображаются в самом левом столбце, имеющим номер 0. Дополнительные элементы, заданные в виде текстовых строк, отображаются в остальных столбцах детального отчета (с номерами 1, 2 и т. д.).
В поле iSubItem вы должны записать номер дополнительного элемента, который будет связан с текущим столбцом отчета.
Ниже приведен фрагмент кода, в котором выполняется вставка трех столбцов с номерами 0, 1 и 2:
GetClientRect(hWnd, &rc); memset(&lvc, 0, sizeof(lvc)); lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT; lvc.cx = (rc.right - rc.left) / 4; lvc.iSubItem = 0; lvc.pszText = "Application Name"; ListView_InsertColumn(hwndList, 0, &lvc); lvc.iSubItem = 1; lvc.pszText = "Icon Name"; ListView_InsertColumn(hwndList, 1, &lvc); lvc.iSubItem = 2; lvc.pszText = "Cost, USD"; ListView_InsertColumn(hwndList, 2, &lvc); ListView_SetColumnWidth(hwndList,2,(rc.right - rc.left) / 8);
Ширина первого и второго столбца устанавливается равной четверти ширины главного окна приложения (которая в нашем случае равна ширине окна органа управления List View).
Ширина третьего столбца устанавливается равной одной восьмой ширины окна, причем для разнообразия мы делаем это при помощи макрокоманды ListView_SetColumnWidth . Эта макрокоманда посылает органу List View сообщение LVM_SETCOLUMNWIDTH . Такую процедуру можно выполнять и позже, а не только в момент создания окна органа управления List View.
На последнем этапе вы должны вставить в список элементы. Проще всего это сделать с помощью макрокоманды ListView_InsertItem , посылающей органу управления List View сообщение LVM_INSERTITEM :
int ListView_InsertItem( HWND hwnd, // идентификатор окна List View const LV_ITEM FAR * pitem); // адрес структуры LV_ITEM
Перед тем как вставлять элемент, вы должны записать его атрибуты в структуру LV_ITEM , определенную следующим образом:
typedef struct _LV_ITEM { UINT mask; // маска использования полей структуры LV_ITEM int iItem; // номер элемента int iSubItem; // номер дополнительного элемента UINT state; // текущее состояние элемента UINT stateMask; // маска состояния элемента LPTSTR pszText; // адрес текстового буфера int cchTextMax; // размер текстового буфера int iImage; // номер пиктограммы элемента LPARAM lParam;// 32-битовое значение, связанное с элементом } LV_ITEM;
Поле маски mask определяет, какие из полей
структуры LV_ITEM будут использованы при добавлении
элементов. Возможны следующие значения масок (их
можно объединять при помощи логической операции
ИЛИ):
Маска |
Заполненное поле структуры LV_ITEM |
LVIF_TEXT |
pszText |
LVIF_IMAGE |
iImage |
LVIF_PARAM |
lParam |
LVIF_STATE |
state |
Ниже мы привели фрагмент исходного текста приложения List Application, в котором к созданному ранее списку добавляется 9 элементов:
memset(&lvi, 0, sizeof(lvi)); lvi.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM; lvi.pszText = LPSTR_TEXTCALLBACK; for(i=0; i<9; i++) { lvi.iItem = i; lvi.iSubItem = 0; lvi.cchTextMax = 40; lvi.lParam = (LPARAM)&rgApplInfo[i]; lvi.iImage = i; ListView_InsertItem(hwndList, &lvi); lvi.iItem = i; lvi.iSubItem = 1; ListView_InsertItem(hwndList, &lvi); lvi.iItem = i; lvi.iSubItem = 2; ListView_InsertItem(hwndList, &lvi); }
Так как в нашем примере пиктограммы состояния элементов не используются, мы не заполняем поля state и stateMask.
В поле iItem мы записываем номер элемента, изменяя его в цикле от 0 до 9.
Аналогичным образом мы поступаем и с полем iImage, записывая в него номер пиктограммы для элемента. Напомним, что в разных режимах работы органа управления List View будут использованы пиктограммы из разных списков (стандартного и уменьшенного размера).
В поле pszText можно было бы записать адрес текстовой строки, соответствующей названию элемента или дополнительному элементу, однако мы занесли туда значение LPSTR_TEXTCALLBACK . В этом случае адрес нужной строки будет предоставляться при обработке соответствующего извещения, о чем речь еще впереди.
На этом мы завершим описание процесса создания и наполнения органа управления List View. Из-за ограниченного объема книги мы не рассмотрели все особенности процесса. За более подробной информацией вы можете обратиться к справочной документации, которая поставляется вместе с SDK.
Приложение, создавшее орган управления List View, должно обрабатывать сообщение WM_NOTIFY , поступающее в функцию окна, создавшего этот орган управления.
Код извещения передается через поле code структуры NMHDR . Напомним, что адрес этой структуры находится в параметре lParam сообщения WM_NOTIFY.
Родительское окно может получить следующие
коды извещений:
Код извещения |
Описание |
LVN_BEGINDRAG |
Начало операции переноса "drag and drop" |
LVN_BEGINLABELEDIT |
Начало операции редактирования
названия элемента |
LVN_BEGINRDRAG |
Начало операции переноса "drag and drop"
с использованием правой клавиши мыши |
LVN_COLUMNCLICK |
Пользователь сделал щелчок мышью по
заголовку столбца в режиме детального отчета |
LVN_DELETEALLITEMS |
Удаление всех элементов списка |
LVN_DELETEITEM |
Удаление определенного элемента списка |
LVN_ENDLABELEDIT |
Завершение операции редактирования
названия элемента |
LVN_GETDISPINFO |
Орган управления запрашивает
информацию, необходимую для отображения
элемента. Это извещение приходит, в частности,
когда при добавлении элемента вместо адреса
реальной текстовой строки была указана
константа LPSTR_TEXTCALLBACK |
LVN_INSERTITEM |
Вставка в список нового элемента |
LVN_ITEMCHANGED |
Произошло изменение элемента |
LVN_ITEMCHANGING |
С помощью этого извещения родительскому
окну предоставляется возможность отменить
предполагаемое изменение элемента |
LVN_KEYDOWN |
Была нажата клавиша |
LVN_SETDISPINFO |
Родительское окно должно обновить
информацию об элементах списка, которую оно
хранит в своих структурах данных |
Рассмотрим особенности обработки некоторых извещений на примере нашего приложения List Application. Подробную информацию об остальных извещениях вы сможете найти в справочной системе SDK.
Так как при добавлении элементов в поле pszText была записана константа LPSTR_TEXTCALLBACK, для отображения списка орган управления List View "попросит" родительское окно предоставить ему адрес реальной текстовой строки. В результате родительское окно получит извещение с кодом LVN_GETDISPINFO.
Вместе с этим извещением в параметре lParam передается указатель на структуру LV_DISPINFO , которая будет использоваться для передачи информации:
typedef struct tagLV_DISPINFO { NMHDR hdr; // для любого сообщения WM_NOTIFY LV_ITEM item; // информация об элементе списка } LV_DISPINFO;
Структура LV_ITEM была описана выше в разделе "Вставка элементов списка".
При получении извещения LVN_GETDISPINFO родительское
окно должно проверить содержимое поля mask
структуры item. Маски в этом поле определяют, какая
информация должна быть предоставлена при
обработке извещения. Возможны следующие
значения:
Значение |
Запрашиваемая информация |
LVIF_IMAGE |
Необходимо заполнить в структуре item
поле iImage (номер изображения в списке изображений)
|
LVIF_STATE |
Поле state (состояние элемента списка) |
LVIF_TEXT |
В поле pszText необходимо записать адрес
буфера, содержащего строку текста |
Вот пример обработки извещения LVN_GETDISPINFO:
LV_DISPINFO * lpLvdi = (LV_DISPINFO *)pnmhdr; APPLINFO * lpAppinfo = (APPLINFO *)(lpLvdi->item.lParam); static char szBuf[20]; ... case LVN_GETDISPINFO: { if(lpLvdi->item.mask & LVIF_TEXT) { switch(lpLvdi->item.iSubItem) { case 0: lpLvdi->item.pszText = lpAppinfo->szAppName; break; case 1: lpLvdi->item.pszText = lpAppinfo->szIconName; break; case 2: itoa(lpAppinfo->iCost, szBuf, 10); lpLvdi->item.pszText = szBuf; break; default: break; } break; } }
Если в поле mask установлен флаг LVIF_TEXT , обработчик извещения анализирует поле iSubItem структуры item.
В том случае, когда содержимое этого поля равно 0, требуется получить текстовую строку названия элемента списка. Адрес этой строки определяется с помощью поля lParam структуры item (соответствующее значение было записано в это поле при добавлении элементов к списку макрокомандой ListView_InsertItem ).
Аналогично обрабатывается извещение для первого дополнительного элемента. Второй дополнительный элемент содержит не текстовое, а числовое значение. Поэтому нам приходится выполнять преобразование при помощи функции itoa.
Проверяя работу органа управления List View, вы сможете убедиться в том что с его помощью можно легко выполнить сортировку элементов списка по имени или по содержимому дополнительных элементов. Для этого вам достаточно лишь сделать щелчок мышью по заголовку нужного столбца.
Можно было бы подумать, что орган управления List View выполняет сортировку сам, без дополнительных усилий со стороны программиста, однако это не так. Очевидно, функции окна List View не известно, какой именно алгоритм сортировки должен быть использован в вашем приложении.
Тем не менее вы без особого труда сможете организовать простейшую сортировку текстовых и числовых полей. Для этого вам нужно создать обработчик извещения LVN_COLUMNCLICK и написать свою функцию сравнения двух элементов.
Обработчик извещения LVN_COLUMNCLICK может выглядеть, например, так:
NM_LISTVIEW *lpNm = (NM_LISTVIEW *)pnmhdr; ... case LVN_COLUMNCLICK: { ListView_SortItems(lpNm->hdr.hwndFrom, LVCompareProc, (LPARAM)(lpNm->iSubItem)); return 0L; break; }
Обработка извещения LVN_COLUMNCLICK сводится к вызову единственной макрокоманды ListView_SortItems, посылающей окну органа управления List View сообщение LVM_SORTITEMS :
BOOL ListView_SortItems( HWND hwnd, // идентификатор окна органа List View PFNLVCOMPARE pfnCompare, // указатель на функцию сравнения LPARAM lParamSort); // произвольное значение, которое // передается функции сравнения
В качестве последнего параметра мы передаем макрокоманде номер дополнительного элемента, по которому выполняется сортировка.
При вызове обработчика извещения LVN_COLUMNCLICK в параметре lParam сообщения WM_NOTIFY передается адрес структуры NM_LISTVIEW , определенной следующим образом:
typedef struct tagNM_LISTVIEW { NMHDR hdr; // для любого сообщения WM_NOTIFY int iItem; // номер элемента списка int iSubItem; // номер дополнительного элемента списка UINT uNewState; // новое состояние элемента UINT uOldState; // старое состояние элемента UINT uChanged; // изменившиеся атрибуты элемента POINT ptAction; // позиция, в котором произошло событие LPARAM lParam; // дополнительное значение } NM_LISTVIEW;
Наш обработчик извещения LVN_COLUMNCLICK получает номер дополнительного элемента, по которому выполняется сортировка, из поля iSubItem структуры NM_LISTVIEW.
Функция сравнения должна выглядеть так (имя функции может быть любым):
int CALLBACK LVCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { int iResult; ........ return(iResult); }
Параметры lParam1 и lParam2 указывают на данные, относящиеся к сравниваемым элементам. Параметр lParamSort содержит значение, которое было передано через последний параметр макрокоманде ListView_SortItems. В нашем случае это номер дополнительного элемента списка, по которому выполняется сравнение.
Пример функции сравнения вы найдете в исходных текстах приложения List Application (листинг 3.1).
Если при создании органа управления List View был указан стиль LVS_EDITLABELS, пользователь сможет редактировать имена элементов списка.
Для этого достаточно выделить нужный элемент и сделать по нему щелчок левой клавишей мыши. Примерно через секунду появится окно редактирования имени "по месту". Такой способ пригоден в любом режиме работы органа управления List View.
Перед началом редактирования имени элемента списка родительское окно получает извещение LVN_BEGINLABELEDIT. Если обработчик этого сообщения возвратит ненулевое значение, редактирование будет заблокировано. Таким образом можно запретить пользователю редактировать названия некоторых элементов списка.
В простейшем случае обработчик извещения LVN_BEGINLABELEDIT может выглядеть так:
case LVN_BEGINLABELEDIT: { return 0L; break; }
Когда пользователь завершил редактирование имени элемента списка, родительскому окну передается извещение LVN_ENDLABELEDIT. При этом параметр lParam сообщения WM_NOTIFY содержит адрес структуры LV_DISPINFO.
Обработчик извещения LVN_ENDLABELEDIT может, например, обновить имя элемента в структуре данных, где это имя хранится.
Если пользователь отменил редактирование, в поле item.iItem будет находиться значение -1. В этом случае обновление выполнять не нужно:
case LVN_ENDLABELEDIT: { if((lpLvdi->item.iItem != -1) && (lpLvdi->item.pszText != NULL)) lstrcpy(lpAppinfo->szAppName, lpLvdi->item.pszText); return 0L; break; }
Как правило, список нужен не только для просмотра, но и для того чтобы сделать из него выбор одного или нескольких элементов.
Прежде чем выбрать элементы из списка, пользователь вначале должен их выделить. Один элемент списка можно выделить, щелкнув по нему левой клавишей мыши. Для того чтобы выделить несколько элементов, можно дополнительно воспользоваться клавишами <Shift> и <Control>. В первом случае будет выделен непрерывный диапазон элементов, во втором - любой набор элементов (на обязательно расположенных рядом).
Если окно органа управления List View создано внутри диалоговой панели, после выделения нужных элементов пользователь может нажать кнопку, подтверждающую выбор. В том случае, когда выбирается только один элемент, можно предоставить возможность выбора с помощью двойного щелчка левой клавишей мыши по изображению пиктограммы или по строке названия нужного элемента.
В любом случае у программиста возникает необходимость организовать поиск в списке выделенных элементов. Это можно сделать с помощью макрокоманды ListView_GetNextItem , посылающей окну органа управления List View сообщение LVM_GETNEXTITEM :
int ListView_GetNextItem( HWND hwnd, // идентификатор органа List View int iStart, // номер элемента, с которого начинается поиск UINT flags); // условие поиска
Параметр iStart может быть равен -1. В этом случае поиск продолжается до тех пор, пока не будет найден первый элемент, удовлетворяющий условию поиска. Если же задан конкретный номер элемента, с которого начинается поиск, то этот элемент в поиске не участвует.
Условие поиска задается в виде флагов, определяющих геометрическое расположение элементов, участвующих в поиске, и флагов, определяющих состояние элемента.
В первом наборе определено пять флагов:
Флаг расположения |
Расположение элементов, участвующих в
поиске |
LVNI_ABOVE |
Выше указанного |
LVNI_ALL |
Поиск выполняется во всех элементах (это
значение используется по умолчанию) |
LVNI_BELOW |
Ниже указанного |
LVNI_TOLEFT |
Слева от указанного |
LVNI_TORIGHT |
Справа от указанного |
Приведем возможные значения флагов состояния:
Флаг состояния |
Состояние элемента |
LVNI_CUT |
LVIS_CUT отмечен для удаления и последующей
вставки |
LVNI_DROPHILITED |
LVIS_DROPHILITED выделен как целевой элемент
для операции перемещения "drag and drop" |
LVNI_FOCUSED |
LVIS_FOCUSED элемент имеет фокус ввода |
LVNI_SELECTED |
LVIS_SELECTED элемент выделен |
Для поиска выделенных элементов вам нужно использовать флаг состояния LVNI_SELECTED и флаг расположения LVNI_ALL.
В нашем приложении List Application мы выполняем выбор элемента двойным щелчком левой клавишей мыши по пиктограмме или строке названия элемента.
Как это можно осуществить?
Когда пользователь делает двойной щелчок левой клавишей мыши внутри окна органа управления List View, родительское окно получает сообщение WM_NOTIFY с кодом извещения NM_DBLCLK. Обработчик этого извещения может определить номер выделенного элемента с помощью макрокоманды ListView_GetNextItem , как это сделано в приведенном ниже фрагменте кода:
case NM_DBLCLK: { int index; LV_ITEM lvi; char szBuf[256]; strcpy(szBuf, "Selected item:\n"); index = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED); if(index == -1) return 0; memset(&lvi, 0, sizeof(lvi)); lvi.mask = LVIF_TEXT; lvi.iItem = index; lvi.iSubItem = 0; ListView_GetItem(hwndList, &lvi); strcat(szBuf, lvi.pszText); lvi.iItem = index; lvi.iSubItem = 1; ListView_GetItem(hwndList, &lvi); strcat(szBuf, " : "); strcat(szBuf, lvi.pszText); lvi.iItem = index; lvi.iSubItem = 2; ListView_GetItem(hwndList, &lvi); strcat(szBuf, " : $"); strcat(szBuf, lvi.pszText); MessageBox(hWnd, szBuf, szAppName, MB_OK); return 0L; break; }
Если пользователь сделал двойной щелчок там, где нет пиктограммы или строки названия элемента, макрокоманда ListView_GetNextItem вернет значение -1. В этом случае обработчик извещения NM_DBLCLK завершает свою работу, так как пользователь не выбрал ни одного элемента.
Если же выбор сделан, обработчик извещения получает текстовую информацию о выделенном элементе списка с помощью макрокоманды ListView_GetItem. Эта макрокоманда заполняет поля структуры LV_ITEM, отмеченные в поле mask. Макрокоманда вызывается несколько раз - для основного и всех дополнительных элементов.
Приложение может посылать органу управления List View многочисленные сообщения. С помощью этих сообщений можно изменять некоторые режимы работы органа, получать информацию об элементах списка, добавлять и удалять элементы списка и т. д.
Ограниченный объем книги не позволяет подробно
описать все параметры этих сообщений, поэтому мы
приведем только краткий список сообщений. За
дополнительной информацией обращайтесь к
справочной системе SDK. Там же вы найдете описание
макрокоманд, с помощью которых удобно посылать
эти сообщения. Некоторые из этих макрокоманд мы
уже использовали в предыдущих разделах.
Сообщение |
Описание |
LVM_ARRANGE |
Выравнивание пиктограмм в окне
просмотра списка |
LVM_CREATEDRAGIMAGE |
Создание изображения, необходимого для
выполнения операции перемещения "drag and drop" (в
нашей книге эта возможность органа управления List
View не описана) |
LVM_DELETEALLITEMS |
Удаление всех элементов из списка |
LVM_DELETECOLUMN |
Удаление столбца из детального отчета |
LVM_DELETEITEM |
Удаление конкретного элемента из списка
|
LVM_EDITLABEL |
Начать процесс редактирования имени
элемента |
LVM_ENSUREVISIBLE |
Размещение элементов в окне просмотра
таким образом, чтобы они были видны полностью или
по крайней мере частично. При необходимости
добавляются полосы просмотра |
LVM_FINDITEM |
Поиск элемента в списке по имени или по
строке, соответствующей дополнительному
элементу |
LVM_GETBKCOLOR |
Определение фонового цвета окна List View |
LVM_GETCALLBACKMASK |
Определение маски функций обратного
вызова |
LVM_GETCOLUMN |
Определение атрибутов столбца |
LVM_GETCOLUMNWIDTH |
Определение ширины столбца |
LVM_GETCOUNTPERPAGE |
Определение количества элементов,
которые можно разместить в видимой части окна
просмотра по вертикали в режиме списка или
детального отчета |
LVM_GETEDITCONTROL |
Определение идентификатора
однострочного редактора текста EDIT, который
применяется для редактирования названия
элемента. Этот идентификатор может быть затем
использован для изменения параметров редактора
текста, например, для ограничения длины нового
имени элемента |
LVM_GETIMAGELIST |
Получение идентификатора списка
изображений |
LVM_GETISEARCHSTRING |
Получение инкрементальной строки
поиска |
LVM_GETITEM |
Получение всех или некоторых атрибутов
элемента списка |
LVM_GETITEMCOUNT |
Определение количества элементов в
списке |
LVM_GETITEMPOSITION |
Определение позиции элемента списка |
LVM_GETITEMRECT |
Определение границ, занимаемых
элементом в окне просмотра |
LVM_GETITEMSPACING |
Определение расстояния между
изображениями, соответствующими элементам
списка |
LVM_GETITEMSTATE |
Определение состояния элемента |
LVM_GETITEMTEXT |
Получение имени элемента или текстовой
строки, соответствующей заданному
дополнительному элементу |
LVM_GETNEXTITEM |
С помощью этого сообщения можно
получить элемент, расположенный вблизи
заданного (выше, ниже, правее или левее) |
LVM_GETORIGIN |
Текущие координаты (view origin) окна органа
управления List View |
LVM_GETSELECTEDCOUNT |
Определение количества выделенных
элементов списка |
LVM_GETSTRINGWIDTH |
Определение ширины заданной текстовой
строки, которая получится при использовании
шрифта, выбранного для органа управления List View |
LVM_GETTEXTBKCOLOR |
Определение цвета фона для текста в окне
органа управления List View |
LVM_GETTEXTCOLOR |
Определение цвета текста в окне органа
управления List View |
LVM_GETTOPINDEX |
Определение номера самого верхнего
отображаемого элемента списка |
LVM_GETVIEWRECT |
Определение координат воображаемого
прямоугольника, ограничивающего изображение
элемента списка при просмотре в режиме
стандартных или уменьшенных пиктограмм |
LVM_HITTEST |
Определение элемента, расположенного в
данной позиции |
LVM_INSERTCOLUMN |
Добавление столбца |
LVM_INSERTITEM |
Добавление элемента |
LVM_REDRAWITEMS |
Принудительная перерисовка элементов
списка, заданных диапазоном номеров |
LVM_SCROLL |
Свертка содержимого окна органа
управления List View |
LVM_SETBKCOLOR |
Установка фонового цвета окна List View |
LVM_SETCALLBACKMASK |
Установка маски функций обратного
вызова |
LVM_SETCOLUMN |
Установка атрибутов столбца |
LVM_SETCOLUMNWIDTH |
Установка ширины столбца |
LVM_SETIMAGELIST |
Подключение списка изображений к органу
управления List View |
LVM_SETITEM |
Установка всех или некоторых атрибутов
заданного элемента списка |
LVM_SETITEMCOUNT |
Подготовка списка для добавления в него
новых элементов (расширение списка) |
LVM_SETITEMPOSITION |
Перемещение элемента в заданную позицию
|
LVM_SETITEMPOSITION32 |
Перемещение элемента в заданную позицию
с использованием 32-разрядных координат |
LVM_SETITEMSTATE |
Установка состояния элемента |
LVM_SETITEMTEXT |
Установка названия элемента или текста,
соответствующего заданному дополнительному
элементу |
LVM_SETTEXTBKCOLOR |
Установка цвета фона для текста в окне
органа управления List View |
LVM_SETTEXTCOLOR |
Установка цвета текста в окне органа
управления List View |
LVM_SORTITEMS |
Сортировка элементов списка с
использованием заданной функции сравнения |
LVM_UPDATE |
Обновление элемента списка |
Просмотрев приведенный только что список сообщений, вы не найдете там сообщения, предназначенного для изменения режима просмотра списка. Таким образом, с помощью посылки сообщения приложение не может изменить внешний вид окна (например, перейти от режима просмотра детального отчета к просмотру списка в виде пиктограмм).
Тем не менее, данная задача выполнима.
С помощью пары функций GetWindowLong и SetWindowLong приложение может изменить стиль окна созданного ранее органа управления List View, что приведет к немедленному изменению режима просмотра. Эти функции были описаны в 13 томе "Библиотеки системного программиста" в разделе "Дополнительная память в структуре окна".
Ниже мы привели фрагмент обработчика сообщения от строки меню, с помощью которой пользователь может переключить орган управления в режим просмотра пиктограмм стандартного размера из любого другого режима:
case ID_OPTIONS_ICONVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_ICON) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_ICON); break; }
Изменение стиля окна выполняется только в том случае, если режим просмотра отличается от определяемого флагом LVS_ICON.
Аналогичным образом вы можете установить любой другой нужный вам режим просмотра, используя флаги LVS_SMALLICON, LVS_LIST и LVS_REPORT.
В качестве примера мы приведем исходные тексты приложения List Application, отображающего список приложений из 15 тома "Библиотеки системного программиста", посвященного созданию систем мультимедиа для Microsoft Windows.
В режиме детального отчета (рис. 3.1) для каждого приложения отображается его пиктограмма, название приложения, а также такие дополнительные элементы, как имя файла, содержащего приложение и стоимость в USD (не подумайте только, что мы и в самом деле продаем их по такой бешеной цене!).
Рис. 3.1. Просмотр списка в виде детального отчета
Нажимая заголовки столбцов, вы сможете отсортировать список по названию, по имени файла пиктограммы, или по цене. Ширина столбцов поддается регулировке. Причем, если сделать двойной щелчок левой клавишей мыши по разделителю заголовка столбцов, ширина столбца, расположенного слева от разделителя, станет такой, чтобы в столбце поместилась самая длинная текстовая строка.
Если из меню Options выбрать строку Icon view, режим просмотра списка изменится (рис. 3.2).
Рис. 3.2. Просмотр списка в виде пиктограмм стандартного размера
С помощью строки Small icon view можно просматривать список в виде пиктограмм уменьшенного размера (рис. 3.3).
Рис. 3.3. Просмотр списка в виде пиктограмм уменьшенного размера
И, наконец, выбрав из меню Options строку List view, вы можете перевести окно органа управления List View в режим просмотра простого списка (рис. 3.4).
Рис. 3.4. Простой список с пиктограммами уменьшенного размера
В любом режиме просмотра вы сможете редактировать название приложения, выбрав его и затем сделав по имени щелчок левой клавишей мыши.
Если же вы сделаете двойной щелчок левой клавишей мыши по пиктограмме или названию любого приложения, на экране появится диалоговая панель, в которой отображаются атрибуты выбранного вами элемента списка (рис. 3.5).
Рис. 3.5. Отображение информации, связанной с выбранным элементом списка
Все функции приложения List Application определены в файле list.c (листинг 3.1).
Листинг 3.1. Файл list\list.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "list.h" typedef struct tagAPPLINFO { char szAppName[40]; char szIconName[20]; UINT iCost; } APPLINFO; // ----------------------------------------------------- // Глобальные переменные // ----------------------------------------------------- APPLINFO rgApplInfo[]= { {"Generic", "appicon.ico ", 5}, {"Book", "book1.ico ", 2}, {"Driver List", "drvlist.ico ", 22}, {"MCI CD Player", "mcicdpl.ico ", 345}, {"MCI String Player", "mcistrvw.ico", 54}, {"MCI Wave Player", "mciwaver.ico", 32}, {"MCI Window Demo", "mciwnd.ico ", 0}, {"Sound Play", "sndplay.ico ", 0}, {"Wave Play", "wave.ico ", 4} }; HINSTANCE hInst; char szAppName[] = "ListApp"; char szAppTitle[] = "List Application"; HWND hwndList; // ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; hInst = hInstance; // Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; } // Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE; // Создаем главное окно приложения hWnd = CreateWindow(szAppName,szAppTitle,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,0,CW_USEDEFAULT,0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE); // Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); HANDLE_MSG(hWnd, WM_NOTIFY, WndProc_OnNotify); HANDLE_MSG(hWnd, WM_SIZE, WndProc_OnSize); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } } // ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { int i; RECT rc; HIMAGELIST himlSmall; HIMAGELIST himlLarge; HICON hIcon; LV_COLUMN lvc; LV_ITEM lvi; // Определяем размеры внутренней области главного окна GetClientRect(hWnd, &rc); // Инициализируем библиотеку стандартных органов управления InitCommonControls(); // Создаем орган управления List View hwndList = CreateWindowEx(0L, WC_LISTVIEW, "", WS_VISIBLE | WS_CHILD | WS_BORDER | LVS_REPORT | LVS_EDITLABELS, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWnd, (HMENU) IDC_LISTVIEW, hInst, NULL); if(hwndList == NULL) return FALSE; // Создаем список изображений himlSmall = ImageList_Create( GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON), ILC_MASK, 9, 1); himlLarge = ImageList_Create( GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), ILC_MASK, 9, 1); for(i = IDI_ICON1; i <= IDI_ICON9; i++) { hIcon = LoadIcon(hInst, MAKEINTRESOURCE(i)); ImageList_AddIcon(himlSmall, hIcon); ImageList_AddIcon(himlLarge, hIcon); } // Добавляем списки изображений ListView_SetImageList(hwndList, himlSmall, LVSIL_SMALL); ListView_SetImageList(hwndList, himlLarge, LVSIL_NORMAL); // Вставляем столбцы memset(&lvc, 0, sizeof(lvc)); lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT; lvc.cx = (rc.right - rc.left) / 4; lvc.iSubItem = 0; lvc.pszText = "Application Name"; ListView_InsertColumn(hwndList, 0, &lvc); lvc.iSubItem = 1; lvc.pszText = "Icon Name"; ListView_InsertColumn(hwndList, 1, &lvc); lvc.iSubItem = 2; lvc.pszText = "Cost, USD"; ListView_InsertColumn(hwndList, 2, &lvc); ListView_SetColumnWidth(hwndList,2,(rc.right-rc.left) / 8); // Вставляем строки memset(&lvi, 0, sizeof(lvi)); lvi.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM; lvi.pszText = LPSTR_TEXTCALLBACK; for(i=0; i<9; i++) { lvi.iItem = i; lvi.iSubItem = 0; lvi.cchTextMax = 40; lvi.lParam = (LPARAM)&rgApplInfo[i]; lvi.iImage = i; ListView_InsertItem(hwndList, &lvi); lvi.iItem = i; lvi.iSubItem = 1; ListView_InsertItem(hwndList, &lvi); lvi.iItem = i; lvi.iSubItem = 2; ListView_InsertItem(hwndList, &lvi); } return TRUE; } // ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { DestroyWindow(hwndList); PostQuitMessage(0); return 0L; } // ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { DWORD dwStyle = 0; switch (id) { case ID_OPTIONS_ICONVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_ICON) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_ICON); break; } case ID_OPTIONS_SMALLICONVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_SMALLICON) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_SMALLICON); break; } case ID_OPTIONS_LISTVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_LIST) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_LIST); break; } case ID_OPTIONS_REPORTVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_REPORT) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_REPORT); break; } case ID_FILE_EXIT: PostQuitMessage(0); return 0L; break; case ID_HELP_ABOUT: break; default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } // ----------------------------------------------------- // Функция WndProc_OnNotify // ----------------------------------------------------- LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr) { LV_DISPINFO * lpLvdi = (LV_DISPINFO *)pnmhdr; APPLINFO * lpAppinfo = (APPLINFO *)(lpLvdi->item.lParam); static char szBuf[20]; NM_LISTVIEW *lpNm = (NM_LISTVIEW *)pnmhdr; if(idFrom != IDC_LISTVIEW) return 0L; switch(pnmhdr->code) { case LVN_GETDISPINFO: { if(lpLvdi->item.mask & LVIF_TEXT) { switch(lpLvdi->item.iSubItem) { case 0: lpLvdi->item.pszText = lpAppinfo->szAppName; break; case 1: lpLvdi->item.pszText = lpAppinfo->szIconName; break; case 2: itoa(lpAppinfo->iCost, szBuf, 10); lpLvdi->item.pszText = szBuf; break; default: break; } break; } } case LVN_COLUMNCLICK: { ListView_SortItems(lpNm->hdr.hwndFrom, LVCompareProc, (LPARAM)(lpNm->iSubItem)); return 0L; break; } case LVN_BEGINLABELEDIT: { return 0L; break; } case LVN_ENDLABELEDIT: { if((lpLvdi->item.iItem != -1) && (lpLvdi->item.pszText != NULL)) lstrcpy(lpAppinfo->szAppName, lpLvdi->item.pszText); return 0L; break; } case NM_DBLCLK: { int index; LV_ITEM lvi; char szBuf[256]; strcpy(szBuf, "Selected item:\n"); // Определяем номер выделенного элемента index = ListView_GetNextItem(hwndList, -1, LVNI_ALL | LVNI_SELECTED); if(index == -1) return 0; // Подготавливаем структуру типа LV_ITEM // для получения текстовой информации об элементах memset(&lvi, 0, sizeof(lvi)); lvi.mask = LVIF_TEXT; // Получаем название элемента lvi.iItem = index; lvi.iSubItem = 0; ListView_GetItem(hwndList, &lvi); strcat(szBuf, lvi.pszText); // Получаем текстовую строку, связанную // с первым и вторым дополнительным элементом lvi.iItem = index; lvi.iSubItem = 1; ListView_GetItem(hwndList, &lvi); strcat(szBuf, " : "); strcat(szBuf, lvi.pszText); lvi.iItem = index; lvi.iSubItem = 2; ListView_GetItem(hwndList, &lvi); strcat(szBuf, " : $"); strcat(szBuf, lvi.pszText); // Выводим на экран текстовые строки // для выбранного элемента MessageBox(hWnd, szBuf, szAppName, MB_OK); return 0L; break; } } return 0L; } // ----------------------------------------------------- // Функция WndProc_OnSize // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy) { MoveWindow(hwndList, 0, 0, cx, cy, TRUE); return FORWARD_WM_SIZE(hwnd, state, cx, cy, DefWindowProc); } // ----------------------------------------------------- // Функция LVCompareProc // ----------------------------------------------------- int CALLBACK LVCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { APPLINFO *pAppInfo1 = (APPLINFO *)lParam1; APPLINFO *pAppInfo2 = (APPLINFO *)lParam2; LPSTR lpStr1, lpStr2; int iResult; if(pAppInfo1 && pAppInfo2) { switch(lParamSort) { case 0: lpStr1 = pAppInfo1->szAppName; lpStr2 = pAppInfo2->szAppName; iResult = strcmpi(lpStr1, lpStr2); break; case 1: lpStr1 = pAppInfo1->szIconName; lpStr2 = pAppInfo2->szIconName; iResult = lstrcmpi(lpStr1, lpStr2); break; case 2: iResult = pAppInfo1->iCost - pAppInfo2->iCost; break; default: iResult = 0; break; } } return(iResult); }
Файл list.h (листинг 3.2) содержит описание функций и определение константы IDC_LISTVIEW (идентификатора органа управления List View).
Листинг 3.2. Файл list\list.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR * pnmhdr); void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy); void WndProc_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDrawItem); int CALLBACK LVCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); #define IDC_LISTVIEW 1234
Файл описания ресурсов приложения, созданный для нашего проекта автоматически системой разработки Microsoft Visual C++, приведен в листинге 3.3. В нем определено главное меню приложения, пиктограммы, из которых формируются списки изображений для органа управления List View и таблица строк описания меню (в нашем приложении не используется, однако вы можете найти для нее применение, добавив орган управления Statusbar).
Листинг 3.3. Файл list\list.rc
//Microsoft Visual C++ generated resource script. #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Options" BEGIN MENUITEM "&Icon view", ID_OPTIONS_ICONVIEW MENUITEM "&Small icon view", ID_OPTIONS_SMALLICONVIEW MENUITEM "&List view", ID_OPTIONS_LISTVIEW MENUITEM "&Report view", ID_OPTIONS_REPORTVIEW END POPUP "&Help" BEGIN MENUITEM "&About List Application...", ID_HELP_ABOUT END END #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END ////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Icon // IDI_APPICON ICON DISCARDABLE "list.ico" IDI_APPICONSM ICON DISCARDABLE "listsm.ico" IDI_ICON1 ICON DISCARDABLE "appicon.ico" IDI_ICON2 ICON DISCARDABLE "book1.ico" IDI_ICON3 ICON DISCARDABLE "drvlist.ico" IDI_ICON4 ICON DISCARDABLE "mcicdpl.ico" IDI_ICON5 ICON DISCARDABLE "mcistrwv.ico" IDI_ICON6 ICON DISCARDABLE "mciwaver.ico" IDI_ICON7 ICON DISCARDABLE "mciwnd.ico" IDI_ICON8 ICON DISCARDABLE "sndplay.ico" IDI_ICON9 ICON DISCARDABLE "wave.ico" ////////////////////////////////////////////////////////////// // String Table // STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" ID_HELP_ABOUTLISTAPPLICATION "Displays program information and copyright" ID_OPTIONS_ICONVIEW "Each item appears as a full-sized icon" ID_OPTIONS_SMALLICONVIEW "Each item appears as a small icon" ID_OPTIONS_LISTVIEW "Each item appears as a small icon arranged in columns" ID_OPTIONS_REPORTVIEW "Each item appears with subitems arranged in columns" END #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
Файл resource.h (листинг 3.4) также создается автоматически. В нем находятся определения констант для ресурсов приложения.
Листинг 3.4. Файл list\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by list.rc // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDI_ICON1 105 #define IDI_ICON2 106 #define IDI_ICON3 107 #define IDI_ICON4 108 #define IDI_ICON5 109 #define IDI_ICON6 110 #define IDI_ICON7 111 #define IDI_ICON8 112 #define IDI_ICON9 113 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUTLISTAPPLICATION 40002 #define ID_HELP_ABOUT 40003 #define ID_OPTIONS_ICONVIEW 40004 #define ID_OPTIONS_SMALLICONVIEW 40005 #define ID_OPTIONS_LISTVIEW 40006 #define ID_OPTIONS_REPORTVIEW 40007 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 115 #define _APS_NEXT_COMMAND_VALUE 40008 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Займемся описанием функций приложения List View. Так как раньше мы уже приводили отдельные фрагменты этих функций, ограничимся кратким описанием.
Массив структур rgApplInfo предназначен для хранения элементов списка. Соответствующая структура имеет тип APPLINFO и определена следующим образом:
typedef struct tagAPPLINFO { char szAppName[40]; // название приложения char szIconName[20]; // имя файла пиктограммы UINT iCost; // стоимость приложения } APPLINFO;
В переменной hInst хранится идентификатор приложения, полученный функцией WinMain. Строчные массивы szAppName и szAppTitle хранят, соответственно, имя и заголовок приложения.
Переменная hwndList нужна для хранения идентификатора созданного органа управления List View.
Функция WinMain сохраняет идентификатор приложения и проверяет, не было ли это приложение запущено ранее. Если было, то активизируется окно работающего приложения.
Далее функция регистрирует класс главного окна приложения, создает и отображает это окно, а затем запускает обычный цикл обработки сообщений.
В задачу функции WndProc входит обработка следующих сообщений: WM_CREATE, WM_DESTROY, WM_COMMAND, WM_NOTIFY, WM_SIZE. Обработка выполняется с использованием макрокоманды HANDLE_MSG.
Эта функция обрабатывает сообщение WM_CREATE, создавая и инициализируя орган управления List View. Размеры окна органа управления устанавливаются равными размерам внутренней области главного окна приложения и в дальнейшем изменяются соответствующим образом обработчиком сообщения WM_SIZE.
Функция WndProc_OnDestroy вызывается, когда пользователь завершает работу приложения. Она уничтожает окно органа управления List View и останавливает цикл обработки сообщений, вызывая функцию PostQuitMessage.
Эта функция обрабатывает сообщение WM-COMMAND, поступающее от главного меню приложения. Строки временного меню Options (Icon view, Small icon view, List view и Report view) имеют идентификаторы, соответственно, ID_OPTIONS_ICONVIEW, ID_OPTIONS_SMALLICONVIEW, ID_OPTIONS_LISTVIEW и ID_OPTIONS_REPORTVIEW. Обработчики изменяют режим отображения списка с помощью функций GetWindowLong и SetWindowLong, как это было описано раньше.
Функция WndProc_OnNotify обрабатывает сообщение WM_NOTIFY, поступающее от органа управления List View.
Процедура обработки извещений была описана ранее. Отметим только, что из-за того что различные извещения используют разный формат структуры данных, адрес которой передается через параметр lParam сообщения WM_NOTIFY, мы выполняем преобразование указателя NMHDR* pnmhdr к типам LV_DISPINFO и NM_LISTVIEW.
Кроме того, функция WndProc_OnNotify проверяет параметр idFrom чтобы убедиться в том, что извещение пришло именно от органа управления List View с идентификатором IDC_LISTVIEW.
Для того чтобы размеры органа управления List View всегда соответствовали размерам внутренней области главного окна приложения List Application, обработчик сообщения WM_SIZE, расположенный в функции WndProc_OnSize, изменяет размеры окна органа управления. Изменения выполняются функцией WndProc_OnSize.
Функция LVCompareProc нужна для сортировки элементов списка. Она выполняет сравнение двух элементов списка. Через первые два параметра передаются адреса структур данных, соответствующих сравниваемым элементам. Последний параметр содержит номер дополнительного элемента или нуль, если сравниваются названия элементов.
Текстовые строки сравниваются при помощи функции strcmpi. Для сравнения численных значений мы использовали обычную операцию вычитания.