Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система Windows 95 для программиста

© Александр Фролов, Григорий Фролов
Том 22, М.: Диалог-МИФИ, 1993, 271 стр.

[Назад] [Содеожание] [Дальше]

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

[Назад] [Содеожание] [Дальше]