Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 12, М.: Диалог-МИФИ, 1993, 255 стр. 2.5. Список класса LISTBOXПеред программистом часто встает задача организации списка, предназначенного для выбора строки из некоторого определенного заранее набора строк. Например, вам может потребоваться список файлов из текущего каталога, список названий цветов для раскраски какого-либо объекта приложения, список режимов работы приложения и т. д. Стандартные диалоговый панели "Open" и "Save As" содержат списки файлов, каталогов и дисковых устройств. Операционная система Windows имеет мощное средство организации списков - органы управления класса "listbox" и "combobox". В этом разделе мы рассмотрим список, созданный на базе класса "listbox". О том, как создавать и использовать список класса "combobox", вы узнаете из раздела с названием "Список класса COMBOBOX". С помощью класса "listbox" вы можете создавать одноколоночные и многоколоночные списки, имеющие вертикальную (для одноколоночных списков) и горизонтальную (для многоколоночных списков) полосу просмотра. Родительское окно может само рисовать элементы списка, аналогично тому, как оно рисует кнопки. Создание спискаДля создания списка приложение должно вызвать функцию CreateWindow, передав в качестве первого параметра указатель на строку "listbox": hListBox = CreateWindow("listbox", NULL, WS_CHILD | WS_VISIBLE | LBS_STANDARD | LBS_WANTKEYBOARDINPUT, 30, 30, 200, 100, hwnd, (HMENU) ID_LIST, hInst, NULL); Второй параметр функции должен быть указан как NULL. Дополнительно к стилям окна WS_CHILD и WS_VISIBLE при создании списка указываются специальные стили списка, символические имена которых имеют префикс LBS_. Остальные параметры функции CreateWindow указываются так же, как и для других органов управления. Параметры с четвертого по седьмой используются для определения расположения и размеров списка. Восьмой параметр - идентификатор родительского окна, в функцию которого будет поступать сообщение WM_COMMAND. Девятый параметр определяет идентификатор списка. Десятый указывает идентификатор копии приложения. Последний параметр должен быть задан как NULL. Стили спискаПриведем список стилей, которые используются для создания органа управления класса "listbox".
Для создания простейшего одноколоночного списка имеет смысл использовать стиль LBS_STANDARD. В этом случае, если все строки списка не помещаются в окне, у списка появится вертикальная полоса просмотра. Если при удалении из списка некоторого количества строк размеры окна списка станут достаточны для одновременного отображения всех строк, полоса просмотра исчезнет. В некоторых случаях такое поведение списка нежелательно, так как оно приводит к изменению внешнего вида списка. Если для списка указать стиль LBS_DISABLENOSCROLL, полоса просмотра будет существовать всегда. Если все строки списка помещаются в окне, эта полоса перейдет в неактивное состояние, но останется на экране. Иногда нужно сделать так, чтобы добавляемые в список строки отображались в порядке добавления, а не в алфавитном порядке. Для этого не следует указывать стиль LBS_SORT. Небольшое замечание относительно использования стиля LBS_WANTKEYBOARDINPUT. Если указан этот стиль, то сообщения WM_KEYDOWN и WM_CHAR, получаемые списком (имеющим фокус ввода), создают сообщения WM_VKEYTOITEM или WM_CHARTOITEM. Эти сообщения попадают в функцию родительского окна, благодаря чему последнее может отслеживать операции, выполняемые пользователем над списком при помощи клавиатуры. Если список имеет стиль LBS_HASSTRINGS, родительское окно будет получать сообщение WM_VKEYTOITEM, а если не имеет - сообщение WM_CHARTOITEM. Параметр wParam сообщения WM_VKEYTOITEM содержит виртуальный код нажатой клавиши. Например, если пользователь выделит строку в списке и нажмет клавишу <Enter>, родительское окно получит сообщение WM_VKEYTOITEM со значением параметра wParam, равным VK_RETURN. При этом оно может получить из списка выбранную строку и выполнить над ней необходимые действия. Если родительское окно получает сообщение WM_CHARTOITEM, параметр wParam содержит код символа, соответствующего нажатой клавише. Коды извещенияТак же как редактор текста, список посылает в родительское окно сообщение WM_COMMAND (если он создан со стилем LBS_NOTIFY). Параметр wParam этого сообщения содержит идентификатор органа управления (в данном случае, идентификатор списка). Младшее слово параметра lParam содержит идентификатор окна списка, а старшее - код извещения. Приведем список кодов извещения, поступающих от органа управления класса "listbox".
Сообщения для спискаДля управления списком приложение посылает ему сообщения, вызывая функцию SendMessage. Эта функция возвращает значение, которое зависит от выполняемой функции или коды ошибок LB_ERRSPACE (ошибка при получении дополнительной памяти), LB_ERR (затребованная операция не может быть выполнена). В файле windows.h определены сообщения, специально предназначенные для работы со списком. Символические имена этих сообщений имеют префикс LB_. Приведем список таких сообщений. LB_ADDSTRINGДобавление строки в список. Параметры: wParam = 0; lParam = (LPARAM)(LPCSTR)lpszStr; lpszStr - указатель на добавляемую строку. Возвращаемое значение: Номер строки в списке (первая строка имеет номер 0), или код ошибки. LB_DELETESTRINGУдаление строки из списка. Параметры: wParam = (WPARAM)nIndex; lParam = 0L; nIndex - номер удаляемой строки. Первая строка имеет номер 0. Возвращаемое значение: Количество строк, оставшихся в списке, или код ошибки. LB_DIRЗаполнение списка именами файлов и каталогов, расположенных в текущем каталоге, а также именами дисков. Параметры: wParam = (WPARAM)(UINT)uAttr; lParam = (LPARAM)(LPCSTR)lpszFileSpec; uAttr - атрибуты файлов; lpszFileSpec - указатель на строку, содержащую имя файла или шаблон имени файла. Возвращаемое значение: Номер последнего имени файла, добавленного в список, или код ошибки. LB_FINDSTRINGПоиск строки в списке, имеющей заданный префикс. Будет найдена строка, начальная часть которой совпадает с текстовой строкой, определенной в качестве префикса. Параметры: wParam = (WPARAM)nIndexStart; lParam = (LPARAM)(LPCSTR)lpszStr; nIndexStart - номер строки, с которой начинается поиск;lpszStr- адрес префикса строки, которую нужно найти в списке. Возвращаемое значение: Номер найденной строки, или код ошибки (если строки в списке нет). LB_FINDSTRINGEXACTПоиск строки в списке. Параметры: wParam = (WPARAM)nIndexStart; lParam = (LPARAM)(LPCSTR)lpszStr; nIndexStart - номер строки, с которой начинается поиск;lpszStr- адрес строки, которую нужно найти в списке. Возвращаемое значение: Номер найденной строки, или код ошибки (если строки в списке нет). LB_GETCARETINDEXОпределение номера строки, имеющей фокус ввода. Это сообщение используется в Windows версии 3.1 и более поздних версий. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Номер строки, имеющей фокус ввода или код ошибки. LB_GETCOUNTОпределение количества строк в списке. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Количество строк в списке или код ошибки. LB_GETCURSELОпределение номера выделенной строки. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Номер выделенной строки или код ошибки. LB_GETHORIZONTALEXTENTОпределение ширины сворачиваемой области списка, имеющего горизонтальную полосу просмотра. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Ширина сворачиваемой области списка в пикселях. LB_GETITEMDATAПолучение 32-битового значения, соответствующего заданной строке. Напомним, что каждому элементу списка ставится в соответствие некоторое число, занчение которого можно определить с помощью этого сообщения. Параметры: wParam = (WPARAM)nIndex; lParam = 0L; nIndex - номер строки, для которой нужно получить значение. Возвращаемое значение: Двойное слово, содержащее искомое значение, или код ошибки. LB_GETITEMHEIGHTОпределение высоты заданной строки в списке, который рисуется родительским окном и имеет переменную высоту элементов. Это сообщение используется в Windows версии 3.1 и более поздних версий. Параметры: wParam = (WPARAM)nIndex; lParam = 0L; nIndex - номер строки, для которой нужно получить значение. Возвращаемое значение: Высота строки в пикселях или код ошибки. LB_GETITEMRECTОпределение координат внутренней области окна, соответствующей заданной строке. Параметры: wParam = (WPARAM)nIndex; lParam = (LPARAM)(RECT FAR *)lprc; nIndex - номер строки, для которой нужно получить значение координат. lprc - адрес структуры типа RECT, в которую будут записаны искомые координаты. Возвращаемое значение: Код ошибки. LB_GETSELС помощью этого сообщения можно определить, выбрана ли указанная строка списка. Параметры: wParam = (WPARAM)nIndex; lParam = 0L; nIndex - номер строки, о которой нужно получить информацию. Возвращаемое значение: Положительное число, если строка выбрана, 0, если не выбрана или код ошибки. LB_GETSELCOUNTС помощью этого сообщения можно определить количество выбранных строк. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Количество выбранных строк или код ошибки. LB_GETSELITEMSЗаполнение буфера идентификаторами выбранных строк для списка, в котором можно выбирать несколько строк сразу. Параметры: wParam = (WPARAM)cItems; lParam = (LPARAM)(int FAR *)lpItems ; cItems - максимальное количество выбранных строк, чьи идентификаторы будут записаны в буфер. lpItems - указатель на буфер для записи идентификаторов выбранных строк. Возвращаемое значение: Количество идентификаторов, записанных в буфер, или код ошибки. LB_GETTEXTКопирование текста, соответствующего заданной строке, в буфер. Если список не содержит строк (определен без стиля LBS_HASSTRING), в буфер копируется двойное слово, соответствующее указанному элементу списка. Параметры: wParam = (WPARAM)nIndex; lParam = (LPARAM)(int FAR *)lpszBuffer ;nIndex - номер строки.l pszBuffer - адрес буфера. Возвращаемое значение: Длина строки в байтах (с учетом двоичного нуля, закрывающего строку), или код ошибки. LB_GETTEXTLENОпределение длины строки, содержащейся в списке. Параметры: wParam = (WPARAM)nIndex; lParam = 0L;nIndex - номер строки. Возвращаемое значение: Длина строки в байтах (с учетом двоичного нуля, закрывающего строку), или код ошибки. LB_GETTPOINDEXОпределение номера первой отображаемой строки. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: Номер строки или код ошибки. LB_INSERTSTRINGВставка элемента в заданную позицию списка. На расположение строки не влияет стиль LBS_SORT.Параметры:wParam = (WPARAM)nIndex;lParam = (LPARAM)(int FAR *)lpszBuffer;nIndex - номер позиции, в которую будет вставлена строка.lpszBuffer - адрес буфера.Возвращаемое значение:Номер позиции, в которую вставлена строка, или код ошибки. LB_RESETCONTENTУдаление всех строк из списка. Параметры: wParam = 0; lParam = 0L; Возвращаемое значение: не используется. LB_SELECTSTRINGПоиск строки в списке, которая начинается с символов, соответствующих образцу. Найденная строка становится выбранной. Параметры: wParam = (WPARAM)nIndexStart; lParam = (LPARAM)(int FAR *)lpszBuffer; nIndexStart - номер строки, с которой начинается поиск.lpszBuffer - адрес буфера, содержащего образец. Возвращаемое значение: Номер найденной строки или код ошибки. LB_SELITEMRANGEВыделение одной или нескольких расположенных рядом строк. Параметры: wParam = (WPARAM)(BOOL)fSelect; lParam = MAKELPARAM(wFirst, wLast); fSelect - если TRUE, указанные строки выбираются и выделяются, если FALSE - выбор и выделение отменяются. wFirst - номер первой выделяемой строки. wLast - номер последней выделяемой строки. Возвращаемое значение: Код ошибки. LB_SETCARETINDEXПередача фокуса ввода указанной строке. Если данная строка находится вне окна отображения, список сворачивается таким образом, чтобы строка стала видимой. Это сообщение используется в Windows версии 3.1 и более поздних версий. Параметры: wParam = (WPARAM)nIndex; lParam = MAKELPARAM(fScroll, 0); nIndex - номер строки. fScroll - если TRUE, свертка выполняется до тех пор, пока указанная строка не будет видна хотя бы частично, если FALSE - до тех пор, пока строка не будет видна полностью. Возвращаемое значение: Код ошибки. LB_SETCOLUMNWIDTHУстановка ширины колонки в многоколоночном списке. Параметры: wParam = (WPARAM)cxColumn; lParam = 0L; cxColumn - ширина колонок списка в пикселях. Возвращаемое значение: не используется. LB_SETCURSELВыбор указанной строки. Ранее выделенная строка становится невыделенной. Если данная строка находится вне окна отображения, список сворачивается таким образом, чтобы строка стала видимой. Параметры: wParam = (WPARAM)nIndex; lParam = 0L; nIndex - номер строки. Если указать -1, выделение всех строк будет отменено. При этом функция SendMessage вернет значение LB_ERR, что в данном случае не говорит об ошибке. Возвращаемое значение: Код ошибки (если значение nIndex не равно -1). LB_SETHORIZONTALEXTENTУстановка ширины, на которую может быть свернут список, имеющий стиль WS_HSCROLL. Параметры: wParam = (WPARAM)cxExtent; lParam = 0L; cxExtent - ширина в пикселях. Возвращаемое значение: не используется. LB_SETITEMDATAУстановка значения двойного слова, связанного с указанным элементом списка. Параметры: wParam = (WPARAM)nIndex; lParam = (LPARAM)dwData;nIndex - номер строки.dwData - значение двойного слова. Возвращаемое значение: Код ошибки. LB_SETITEMHEIGHTУстановка высоты элемента в списке, который рисует родительское окно и имеет переменную высоту элементов. Это сообщение используется в Windows версии 3.1 и более поздних версий. Параметры: wParam = (WPARAM)nIndex; lParam = MAKELPARAM(cyItem, 0); nIndex - номер строки. Если список не имеет стиль LBS_OWNERDRAWVARIABLE, значение этого параметра должно быть равно 0.cyItem - высота элемента в пикселах. Возвращаемое значение: Код ошибки. LB_SETSELУстановка высоты элемента в списке, который рисует родительское окно и имеет переменную высоту элементов. Это сообщение используется в Windows версии 3.1 и более поздних версий. Параметры: wParam = (WPARAM)(BOLL)fSelect; lParam = MAKELPARAM(nIndex, 0); fSelect - если TRUE, строка выбирается и выделяется, если FALSE - выделение и выбор строки отменяется.nIndex - номер строки. Можно указать как -1, в этом случае действие распространяется на все строки списка. Возвращаемое значение: Код ошибки. LB_SETTABSTOPSУстановка позиции табуляции в списке. Параметры: wParam = (WPARAM)cTabs; lParam = (LPARAM)(int FAR *)lpTabs; cTabs - количество позиций табуляции.lpTabs - адрес массива целых чисел, содержащих позиции табуляции. Возвращаемое значение: Ненулевое значение, если позиции табуляции были установлены, в противном случае возвращается 0. LB_SETTOPINDEXСвертка списка до тех пор, пока указанная строка не станет видимой. Параметры: wParam = (WPARAM)nIndex; lParam = 0L; nIndex - номер строки. Возвращаемое значение: Код ошибки. Приложение LISTBOXА теперь перейдем к практике. Приложение LISTBOX (рис. 2.17) создает простейший одноколоночный список.
Рис. 2.17. Главное окно приложения LISTBOX В этот список добавляется несколько текстовых строк. Вы можете выделять строки при помощи мыши или клавиш перемещения курсора. Выделенная строка отображается справа от списка. Для выбора строки вам надо ее выделить (щелчком мыши или при помощи клавиатуры), а затем нажать кнопку "OK" или клавишу <Enter>. Можно также сделать двойной щелчок левой клавишей мыши по нужной строке. Выбранная строк будет выведена на экран функцией MessageBox. Главный файл приложения LISTBOX приведен в листинге 2.26. Листинг 2.26. Файл listbox\listbox.cpp // ---------------------------------------- // Использование органа управления // класса "listbox" // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> // Идентификатор списка #define ID_LIST 1 // Идентификатор кнопки #define ID_BUTTON 2 // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Имя класса окна char const szClassName[] = "ListBoxAppClass"; // Заголовок окна char const szWindowTitle[] = "ListBox Demo"; // Идентификатор копии приложения HINSTANCE hInst; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Сохраняем идентификатор копии приложения // в глобальной переменной hInst = hInstance; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем расположение и размеры CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, // CW_USEDEFAULT, // 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Идентификатор органа управления "listbox" static HWND hListBox; // Идентификатор кнопки static HWND hButton; switch (msg) { case WM_CREATE: { // Создаем список hListBox = CreateWindow("listbox", NULL, WS_CHILD | WS_VISIBLE | LBS_STANDARD | LBS_WANTKEYBOARDINPUT, 30, 30, 200, 100, hwnd, (HMENU) ID_LIST, hInst, NULL); // Отменяем режим перерисовки списка SendMessage(hListBox, WM_SETREDRAW, FALSE, 0L); // Добавляем в список несколько строк SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Зеленый"); SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Красный"); SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Розовый"); SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Пурпурный"); SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Синий"); SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Желтый"); SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Фиолетовый"); SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Черный"); SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Белый"); // Включаем режим перерисовки списка SendMessage(hListBox, WM_SETREDRAW, TRUE, 0L); // Перерисовываем список InvalidateRect(hListBox, NULL, TRUE); // Создаем кнопку hButton = CreateWindow("button", "OK", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 250, 30, 50, 20, hwnd, (HMENU) ID_BUTTON, hInst, NULL); return 0; } // Когда главное окно приложения получает // фокус ввода, отдаем фокус списку case WM_SETFOCUS: { SetFocus(hListBox); return 0; } case WM_COMMAND: { // Обработка извещения списка об ошибке if(wParam == ID_LIST) { // Преобразуем к типу unsigned, так как // константа LBN_ERRSPACE определена как // отрицательное число if(HIWORD(lParam) == (unsigned)LBN_ERRSPACE) { MessageBox(hwnd, "Мало памяти", szWindowTitle, MB_OK); } // Если пользователь сделал двойной щелчок // по строке списка, выводим эту строку // на экран else if(HIWORD(lParam) == LBN_DBLCLK) { int uSelectedItem; char Buffer[256]; // Определяем номер выделенной строки uSelectedItem = (int)SendMessage(hListBox, LB_GETCURSEL, 0, 0L); // Если в списке есть выделенная строка, // выводим ее на экран if(uSelectedItem != LB_ERR) { // Получаем выделенную строку SendMessage(hListBox, LB_GETTEXT, uSelectedItem, (LPARAM)Buffer); // Выводим ее на экран MessageBox(hwnd, (LPSTR)Buffer, szWindowTitle, MB_OK); } } // Если пользователь изменил выделенную // строку, выводим в окно новую // выделенную строку else if(HIWORD(lParam) == LBN_SELCHANGE) { int uSelectedItem, nSize; char Buffer[256]; HDC hdc; // Определяем номер новой выделенной строки uSelectedItem = (int)SendMessage(hListBox, LB_GETCURSEL, 0, 0L); if(uSelectedItem != LB_ERR) { // Получаем строку SendMessage(hListBox, LB_GETTEXT, uSelectedItem, (LPARAM)Buffer); // Получаем контекст отображения hdc = GetDC(hwnd); // Определяем длину строки nSize = lstrlen(Buffer); // Очищаем место для вывода строки TextOut(hdc, 250, 60, (LPSTR)" ", 25); // Выводим строку TextOut(hdc, 250, 60, (LPSTR)Buffer, nSize); // Освобождаем контекст отображения ReleaseDC(hwnd, hdc); } } } // Сообщение от кнопки else if(wParam == ID_BUTTON) { int uSelectedItem; char Buffer[256]; // Определяем номер выделенной строки, // получаем текст строки и выводим // этот текст в окно uSelectedItem = (int)SendMessage(hListBox, LB_GETCURSEL, 0, 0L); if(uSelectedItem != LB_ERR) { SendMessage(hListBox, LB_GETTEXT, uSelectedItem, (LPARAM)Buffer); MessageBox(hwnd, (LPSTR)Buffer, szWindowTitle, MB_OK); } } return 0; } // Это сообщение поступает от списка, если он // имеет фокус ввода и пользователь работает // с клавиатурой case WM_VKEYTOITEM: { // Если пользователь нажал клавишу <Enter>, // посылаем в окно сообщение WM_COMMAND с // параметром wParam, равным идентификатору // кнопки if(wParam == VK_RETURN) { SendMessage(hwnd, WM_COMMAND, ID_BUTTON, 0L); } return -1; } case WM_PAINT: { HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 30, 10, "Выберите цвет и нажмите кнопку 'OK'", 35); EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } Функция WinMain создает главное окно приложения и запускает цикл обработки сообщений. Функция главного окна создает список во время обработки сообщения WM_CREATE: hListBox = CreateWindow("listbox", NULL, WS_CHILD | WS_VISIBLE | LBS_STANDARD | LBS_WANTKEYBOARDINPUT, 30, 30, 200, 100, hwnd, (HMENU) ID_LIST, hInst, NULL); Для того чтобы родительское окно могло получать от списка клавиатурные сообщения, дополнительно к стилю LBS_STANDARD указан стиль LBS_WANTKEYBOARDINPUT. После создания списка его надо заполнить строками. Перед началом этой процедуры функция главного окна приложения отменяет режим перерисовки списка после добавления в него очередной строки, посылая списку сообщение WM_SETREDRAW со значением параметр wParam, равным FALSE: SendMessage(hListBox, WM_SETREDRAW, FALSE, 0L); Вслед за этим функция окна добавляет в список несколько строк, посылая списку несколько раз сообщение LB_ADDSTRING: SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"Зеленый"); После заполнения списка вновь включается режим перерисовки, вслед за чем список перерисовывается: SendMessage(hListBox, WM_SETREDRAW, TRUE, 0L); InvalidateRect(hListBox, NULL, TRUE); Далее обработчик сообщения WM_CREATE создает кнопку, которая будет использована для выбора из списка выделенной строки. Так как для работы со списком используется клавиатура, получив сообщение WM_SETFOCUS, функция главного окна приложения отдает фокус ввода списку: case WM_SETFOCUS: { SetFocus(hListBox); return 0; } Обработчик сообщения WM_COMMAND получает управление, когда от списка приходит извещение о событии, или когда пользователь нажимает кнопку. Если для работы списка не хватает памяти, он посылает извещение LBN_ERRSPACE. В ответ на это приложение выводит на экран диалоговую панель с сообщением об ошибке. Если пользователь сделал двойной щелчок по строке списка, приходит извещение LBN_DBLCLK. Обработчик этого извещения определяет номер выделенной строки (т. е. строки по которой был сделан двойной щелчок), посылая списку сообщение LB_GETCURSEL: uSelectedItem = (int)SendMessage(hListBox, LB_GETCURSEL, 0, 0L); Для того чтобы переписать выделенную строку в буфер, списку посылается сообщение LB_GETTEXT: SendMessage(hListBox, LB_GETTEXT, uSelectedItem, (LPARAM)Buffer); Номер строки передается через параметр wParam, адрес буфера указывается через параметр lParam. Далее полученная строка выводится на экран при помощи функции MessageBox. Обработчик извещения LBN_SELCHANGE получает управление, когда пользователь изменяет выделенную строку (т. е. выделяет другую строку) списка. В этом случае обработчик извещения посылает окну сообщение LB_GETCURSEL для определения номера новой выделенной строки. Вслед за этим он переписывает эту строку в буфер, посылая списку сообщение LB_GETTEXT: SendMessage(hListBox, LB_GETTEXT, uSelectedItem, (LPARAM)Buffer); Полученная строка отображается в главном окне приложения при помощи функции TextOut: TextOut(hdc, 250, 60, (LPSTR)Buffer, nSize); Действия, выполняемые обработчиком сообщения WM_COMMAND при получении сообщения от кнопки, полностью аналогичны действиям при обработке извещения LBN_DBLCLK. В этом случае обработчик сообщения определяет номер выделенной строки, переписывает эту строку в буфер и выводит ее на экран. Так как при создании списка был указан класс LBS_WANTKEYBOARDINPUT, список посылает в родительское окно сообщение WM_VKEYTOITEM (если бы мы создавали список без стиля LBS_HASSTRINGS, вместо этого сообщения в родительское окно посылалось бы сообщение WM_CHARTOITEM). Параметр wParam для этого сообщения содержит значение виртуального кода нажатой клавиши. Наше приложение пользуется этим, отслеживая выбор строки клавишей <Enter> (клавише <Enter> соответствует виртуальный код VK_RETURN): case WM_VKEYTOITEM: { if(wParam == VK_RETURN) { SendMessage(hwnd, WM_COMMAND, ID_BUTTON, 0L); } return -1; } Обработчик сообщения WM_VKEYTOITEM посылает функции главного окна сообщение WM_COMMAND, имитируя выбор строки кнопкой "OK". Обработчик возвращает значение -1, позволяя списку выполнить обработку кода клавиши, назначенную по умолчанию. Вместо этого можно было бы вернуть значение -2. В таком случае список игнорирует сообщения клавиатуры, а все изменения в списке должны выполняться родительским окном. Если вернуть нулевое или положительное значение, список выделит выбранную строку и выполнит обработку кода клавиши по умолчанию. Файл определения модуля для приложения LISTBOX приведен в листинге 2.27. Листинг 2.27. Файл listbox\listbox.def ; ============================= ; Файл определения модуля ; ============================= NAME LISTBOX DESCRIPTION 'Приложение LISTBOX, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple Приложение LISTDIRПриложение LISTDIR использует список для выбора файла (рис. 2. 18).
Рис. 2.18. Главное окно приложения LISTDIR В списке отображаются имена файлов, каталогов и дисков. Вы можете входить в каталоги и выходить из них, переходить на другие диски и выбирать файлы. Имя выбранного файла выводится на экран при помощи функции MessageBox. Строго говоря, для выбора файлов лучше пользоваться стандартными диалоговыми панелями "Open" и "Save As" (как мы это делали в наших предыдущих приложениях). В этом случае внешний вид диалоговой панели будет привычен для пользователя. Однако для выбора файлов, каталогов или дисков вы можете использовать и свои средства. Главный файл приложения LISTDIR представлен в листинге 2.28. Листинг 2.28. Файл listdir\listdir.cpp // ---------------------------------------- // Использование органа управления // класса "listbox" для просмотра // содержимого каталога // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> #include <dir.h> // Идентификатор списка #define ID_LIST 1 // Идентификатор кнопки #define ID_BUTTON 2 // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Имя класса окна char const szClassName[] = "ListDirAppClass"; // Заголовок окна char const szWindowTitle[] = "Выбор файла"; // Идентификатор копии приложения HINSTANCE hInst; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Сохраняем идентификатор копии приложения // в глобальной переменной hInst = hInstance; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем расположение и размеры CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, // CW_USEDEFAULT, // 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Идентификатор органа управления "listbox" static HWND hListBox; // Идентификатор кнопки static HWND hButton; switch (msg) { case WM_CREATE: { // Создаем список hListBox = CreateWindow("listbox", NULL, WS_CHILD | WS_VISIBLE | LBS_STANDARD | LBS_WANTKEYBOARDINPUT, 30, 30, 200, 200, hwnd, (HMENU) ID_LIST, hInst, NULL); // Добавляем в список содержимое текущего // каталога SendMessage(hListBox, LB_DIR, DDL_READWRITE | DDL_READONLY | DDL_HIDDEN | DDL_SYSTEM | DDL_DIRECTORY | DDL_DRIVES | DDL_ARCHIVE, (LPARAM)(LPSTR)"*.*"); // Создаем кнопку hButton = CreateWindow("button", "OK", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 250, 30, 50, 20, hwnd, (HMENU) ID_BUTTON, hInst, NULL); return 0; } // Когда главное окно приложения получает // фокус ввода, отдаем фокус списку case WM_SETFOCUS: { SetFocus(hListBox); return 0; } case WM_COMMAND: { // Обработка извещения списка об ошибке if(wParam == ID_LIST) { if(HIWORD(lParam) == (unsigned)LBN_ERRSPACE) { MessageBox(hwnd, "Мало памяти", szWindowTitle, MB_OK); } // Двойной щелчок по имени файла, каталога // или диска else if(HIWORD(lParam) == LBN_DBLCLK) { // Имитируем приход сообщения от кнопки SendMessage(hwnd, WM_COMMAND, ID_BUTTON, 0L); return 0; } // Если пользователь изменил выделенную // строку, выводим в окно новую // выделенную строку else if(HIWORD(lParam) == LBN_SELCHANGE) { int uSelectedItem, nSize; char Buffer[256]; HDC hdc; // Определяем номер новой выделенной строки uSelectedItem = (int)SendMessage(hListBox, LB_GETCURSEL, 0, 0L); if(uSelectedItem != LB_ERR) { // Получаем строку SendMessage(hListBox, LB_GETTEXT, uSelectedItem, (LPARAM)Buffer); hdc = GetDC(hwnd); nSize = lstrlen(Buffer); TextOut(hdc, 250, 60, (LPSTR)" ", 25); TextOut(hdc, 250, 60, (LPSTR)Buffer, nSize); ReleaseDC(hwnd, hdc); } } } // Сообщение от кнопки else if(wParam == ID_BUTTON) { int uSelectedItem; char Buffer[256]; // Определяем номер выделенной строки uSelectedItem = (int)SendMessage(hListBox, LB_GETCURSEL, 0, 0L); if(uSelectedItem != LB_ERR) { // Получаем выделенную строку SendMessage(hListBox, LB_GETTEXT, uSelectedItem, (LPARAM)Buffer); // Если выбрали имя каталога или диска, // пытаемся изменить сначала текущий // каталог, а затем текущий диск if(Buffer[0] == '[') { // Выделяем в выбранной строке имя каталога Buffer[lstrlen(Buffer) - 1] = '\0'; // Пытаемся изменить каталог if(chdir(&Buffer[1]) != 0) { // Если не удалось, значит выбрали имя // диска. В этом случае выделяем букву // имени диска и добавляем строку ":\\: Buffer[3] = '\0'; lstrcat(Buffer, ":\\"); // Изменяем текущий каталог if(chdir(&Buffer[2]) == 0) { // Преобразуем букву диска AnsiLowerBuff(&Buffer[2], 1); // Устанавливаем новый диск setdisk(Buffer[2] - 'a'); } } // Сбрасываем содержимое списка SendMessage(hListBox, LB_RESETCONTENT, 0, 0L); // Заполняем список информацией о файлах // и каталогах в текущем каталоге, а также // вносим туда имена дисков SendMessage(hListBox, LB_DIR, DDL_READWRITE | DDL_READONLY | DDL_HIDDEN | DDL_SYSTEM | DDL_DIRECTORY | DDL_DRIVES | DDL_ARCHIVE, (LPARAM)(LPSTR)"*.*"); } // Если выбрали файл, выводим его имя на экран else { MessageBox(hwnd, Buffer, szWindowTitle, MB_OK); } } } return 0; } // Это сообщение поступает от списка, если он // имеет фокус ввода и пользователь работает // с клавиатурой case WM_VKEYTOITEM: { // Если пользователь нажал клавишу <Enter>, // посылаем в окно сообщение WM_COMMAND с // параметром wParam, равным идентификатору // кнопки if(wParam == VK_RETURN) { SendMessage(hwnd, WM_COMMAND, ID_BUTTON, 0L); } return -1; } case WM_PAINT: { HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 30, 10, "Выберите файл, каталог или диск", 31); EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } Функция WinMain приложения LISTDIR почти ничем не отличается от аналогичной функции предыдущего приложения. Она создает главное окно приложения и запускает цикл обработки сообщений. Функция главного окна при обработке сообщения WM_CREATE создает одноколоночный список: hListBox = CreateWindow("listbox", NULL, WS_CHILD | WS_VISIBLE | LBS_STANDARD | LBS_WANTKEYBOARDINPUT, 30, 30, 200, 200, hwnd, (HMENU) ID_LIST, hInst, NULL); Для заполнения этого списка именами файлов и каталогов, расположенных в текущем каталоге, а также именами дисков, списку посылается сообщение LB_DIR: SendMessage(hListBox, LB_DIR, DDL_READWRITE | DDL_READONLY | DDL_HIDDEN | DDL_SYSTEM | DDL_DIRECTORY | DDL_DRIVES | DDL_ARCHIVE, (LPARAM)(LPSTR)"*.*"); Параметр wParam этого сообщения представляет собой набор флагов, влияющих на содержимое списка после заполнения. Приведем список этих флагов и соответствующих им имен объектов, включаемых в список.
Параметр lParam сообщения LB_DIR должен содержать указатель на текстовую строку, используемую в качестве шаблона имени. В нашем случае используется шаблон "*.*", поэтому в список попадают имена всех файлов. Когда в функцию главного окна приложения приходит сообщение WM_COMMAND с извещением LBN_DBLCLK о двойном щелчке по строке списка, функция окна посылает сама себе сообщение WM_COMMAND с параметром wParam, равным идентификатору кнопки "OK", созданной приложением в главном окне справа от списка. Извещение LBN_SELCHANGE обрабатывается таким же образом, что и в предыдущем приложении. Обработчик сообщения от кнопки определяет номер выделенной строки и копирует эту строку в свой буфер, посылая в список последовательно сообщения LB_GETCURSEL и LB_GETTEXT. Далее выполняется анализ полученной строки. Если строка начинается с символа "[", то это может быть либо имя каталога (например, "[dos]"), либо имя диска (например, "[-c-]"). Вначале обработчик предполагает, что получено имя каталога (для простоты мы считаем что каталоги с экзотическими именами "-c-" и т. п. не используются). Закрывающая квадратная скобка заменяется на двоичный ноль, вслед за чем вызывается функция chdir (из стандартной библиотеки компилятора), предназначенная для смены текущего каталога. Если функция chdir вернула ненулевое значение, текущий каталог не содержит подкаталога с указанным именем. В этом случае обработчик сообщения считает, что полученная строка содержит имя диска, состоящее из буквы в обрамлении квадратных скобок и символов "-". Буква имени диска выделяется и к ней дописывается строка ":\\", вслед за чем выполняется повторная попытка изменения текущего каталога на корневой нового диска. Для изменения текущего диска вызывается функция setdisk из стандартной библиотеки компилятора. Обратите внимание на способ, которым мы преобразуем букву обозначения диска: AnsiLowerBuff(&Buffer[2], 1); Мы использовали функцию AnsiLowerBuff, которая выполняет преобразование из заглавной буквы в прописную (маленькую) с учетом используемого национального алфавита. После изменения текущего диска или каталога содержимое списка сбрасывается, вслед за чем список вновь наполняется именами файлов и каталогов: SendMessage(hListBox, LB_RESETCONTENT, 0, 0L); SendMessage(hListBox, LB_DIR, DDL_READWRITE | DDL_READONLY | DDL_HIDDEN | DDL_SYSTEM | DDL_DIRECTORY | DDL_DRIVES | DDL_ARCHIVE, (LPARAM)(LPSTR)"*.*"); Если обработчик так и не смог сменить каталог или диск, полученная от списка строка содержит имя файла. Это имя выводится на экран: MessageBox(hwnd, Buffer, szWindowTitle, MB_OK); Файл определения модуля приложения LISTDIR представлено в листинге 2.29. Листинг 2.29. Файл listdir\listdir.def ; ============================= ; Файл определения модуля ; ============================= NAME LISTDIR DESCRIPTION 'Приложение LISTDIR, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple |