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

Операционная система 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_DISABLENOSCROLL Если в одноколоночном списке помещаются все строки, вертикальная полоса просмотра изображается в неактивном состоянии. Без указания стиля LBS_DISABLENOSCROLL в аналогичной ситуации вертикальная полоса просмотра пропадает. Этот стиль можно указывать для Windows версии 3.1 и более поздних версий
LBS_EXTENDEDSEL Можно выделять не только отдельные строки, но и сразу несколько расположенных рядом строк. Для этого можно использовать клавишу <Shift> или мышь
LBS_HASSTRINGS Создание списка, содержащего строки. Этот стиль используется для всех списков, за исключением тех, которые рисуются родительским окном
LBS_MULTICOLUMN Создание многоколоночного списка. Для того чтобы задать количество колонок, в список необходимо послать сообщение LB_SETCOLUMNWIDTH
LBS_MULTIPLESEL Можно выделять в списке несколько строк сразу. Выделенные строки могут находиться в любом месте списка, а не только рядом (как при использовании стиля LBS_EXTENDEDSEL)
LBS_NOINTEGRALHEIGHT Допустимо частичное отображение строк (например, в нижней части списка можно отображать верхнюю половину строки)
LBS_NOREDRAW Для списка не выполняется перерисовка содержимого при добавлении или удалении строк. Этот стиль может быть динамически добавлен или удален посылкой списку сообщения WM_SETREDRAW
LBS_NOTIFY Родительское окно, создавшее список, получит извещение, если пользователь выполнит в списке двойной щелчок мышью по строке
LBS_OWNERDRAWFIXED Создается список, который рисуется родительским окном, причем все элементы в списке имеют одинаковую высоту
LBS_OWNERDRAWVARIABLE Аналогично предыдущему, но элементы списка могут иметь разную высоту
LBS_SORT Строки списка будут отсортированы
LBS_STANDARD Комбинация наиболее употребительных стилей списка: LBS_NOTIFY, LBS_SORT, WS_BORDER и WS_VSCROLL
LBS_USETABSTOPS При выводе строк списка будет выполняться преобразование символов табуляции. По умолчанию один символ табуляции расширяется на 32 единицы ширины (эти единицы используются в диалоговых панелях)
LBS_WANTKEYBOARDINPUT При использовании этого стиля родительское окно, создавшее список, будет получать сообщения WM_VKEYTOITEM или WM_CHARTOITEM, если список имеет фокус ввода и пользователь работает со списком при помощи клавиатуры

Для создания простейшего одноколоночного списка имеет смысл использовать стиль 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".

Код извещения Описание
LBN_DBLCLK Двойной щелчок левой клавишей мыши по строке списка
LBN_ERRSPACE Ошибка при попытке заказать дополнительную память
LBN_KILLFOCUS Список теряет фокус ввода
LBN_SELCANCEL Пользователь отменил выбор в списке. Это извещение используется в Windows версии 3.1 и более поздних версий
LBN_SELCHANGE Изменился номер выбранной строки (т. е. пользователь выбрал другую строку)
LBN_SETFOCUS Список получает фокус ввода

Сообщения для списка

Для управления списком приложение посылает ему сообщения, вызывая функцию 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 этого сообщения представляет собой набор флагов, влияющих на содержимое списка после заполнения. Приведем список этих флагов и соответствующих им имен объектов, включаемых в список.

Флаг Описание
DDL_ARCHIVE Файлы, для которых в байте атрибутов установлен бит архивирования
DDL_DIRECTORY Каталоги
DDL_DRIVES Дисковые устройства
DDL_EXCLUSIVE В список не будут включены имена обычных файлов
DDL_HIDDEN Скрытые файлы
DDL_READONLY Только читаемые файлы
DDL_READWRITE Файлы, над которыми можно выполнять операции чтения/записи, не имеющие дополнительных атрибутов
DDL_SYSTEM Системные файлы

Параметр 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

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