3. Работа с таблицами и списками

Многие приложения отображают информацию в виде таблиц или списков. Операционная система Microsoft Windows 95 предоставляет в распоряжение программиста очень мощный встроенный орган управления List View .

Этот орган управления позволяет отображать списки в нескольких видах:

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

Вы можете получить некоторое представление о возможностях органа управления List View , посмотрев рисунки из раздела "Приложение List Application", расположенного в конце этой главы (рис. 3.1 - 3.4).

Список состоит из элементов, каждому из которых назначается пиктограмма, название (label) и дополнительные текстовые строки, которые могут отображаться в столбцах при использовании режима детального просмотра (рис. 3.1). Кроме того, приложение может отмечать состояние элемента с помощью изображений из дополнительного списка (на рис. 3.1 - 3.4 не показано).

3.1. Создание органа управления List View

Для использования органа управления List View ваше приложение должно выполнить несколько действий:

Теперь мы подробно расскажем вам о том, как выполнить перечисленные выше действия.

Создание окна 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

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

3.2. Обработка извещений

Приложение, создавшее орган управления 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.

LVN_GETDISPINFO

Так как при добавлении элементов в поле 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.

LVN_COLUMNCLICK

Проверяя работу органа управления 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).

LVN_BEGINLABELEDIT

LVN_ENDLABELEDIT

Если при создании органа управления 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 мы выполняем выбор элемента двойным щелчком левой клавишей мыши по пиктограмме или строке названия элемента.

Как это можно осуществить?

Извещение NM_DBLCLK

Когда пользователь делает двойной щелчок левой клавишей мыши внутри окна органа управления 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. Макрокоманда вызывается несколько раз - для основного и всех дополнительных элементов.

3.3. Сообщения для органа управления List View

Приложение может посылать органу управления 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 Обновление элемента списка

3.4. Изменение режима просмотра списка

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

Тем не менее, данная задача выполнима.

С помощью пары функций 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.

3.5. Приложение List Application

В качестве примера мы приведем исходные тексты приложения 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 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

Функция WinMain сохраняет идентификатор приложения и проверяет, не было ли это приложение запущено ранее. Если было, то активизируется окно работающего приложения.

Далее функция регистрирует класс главного окна приложения, создает и отображает это окно, а затем запускает обычный цикл обработки сообщений.

WndProc

В задачу функции WndProc входит обработка следующих сообщений: WM_CREATE, WM_DESTROY, WM_COMMAND, WM_NOTIFY, WM_SIZE. Обработка выполняется с использованием макрокоманды HANDLE_MSG.

WndProc_OnCreate

Эта функция обрабатывает сообщение WM_CREATE, создавая и инициализируя орган управления List View. Размеры окна органа управления устанавливаются равными размерам внутренней области главного окна приложения и в дальнейшем изменяются соответствующим образом обработчиком сообщения WM_SIZE.

WndProc_OnDestroy

Функция WndProc_OnDestroy вызывается, когда пользователь завершает работу приложения. Она уничтожает окно органа управления List View и останавливает цикл обработки сообщений, вызывая функцию PostQuitMessage.

WndProc_OnCommand

Эта функция обрабатывает сообщение 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

Функция WndProc_OnNotify обрабатывает сообщение WM_NOTIFY, поступающее от органа управления List View.

Процедура обработки извещений была описана ранее. Отметим только, что из-за того что различные извещения используют разный формат структуры данных, адрес которой передается через параметр lParam сообщения WM_NOTIFY, мы выполняем преобразование указателя NMHDR* pnmhdr к типам LV_DISPINFO и NM_LISTVIEW.

Кроме того, функция WndProc_OnNotify проверяет параметр idFrom чтобы убедиться в том, что извещение пришло именно от органа управления List View с идентификатором IDC_LISTVIEW.

WndProc_OnSize

Для того чтобы размеры органа управления List View всегда соответствовали размерам внутренней области главного окна приложения List Application, обработчик сообщения WM_SIZE, расположенный в функции WndProc_OnSize, изменяет размеры окна органа управления. Изменения выполняются функцией WndProc_OnSize.

LVCompareProc

Функция LVCompareProc нужна для сортировки элементов списка. Она выполняет сравнение двух элементов списка. Через первые два параметра передаются адреса структур данных, соответствующих сравниваемым элементам. Последний параметр содержит номер дополнительного элемента или нуль, если сравниваются названия элементов.

Текстовые строки сравниваются при помощи функции strcmpi. Для сравнения численных значений мы использовали обычную операцию вычитания.