On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru








 

2. Органы управления

2.1. Кнопки

2.2. Статический орган управления

2.3. Полоса просмотра

2.4. Редактор текста

2.5. Список класса LISTBOX

2.6. Список класса COMBOBOX

Любое стандартное приложение Windows использует различные органы управления, такие как кнопки, полосы просмотра, редакторы текстов, меню и т. д. Как правило, эти органы управления используют дочерние окна, созданные на базе предопределенных в Windows классов.

Как вы знаете, при регистрации класса окна необходимо указать адрес функции окна, обрабатывающей все сообщения, предназначенные окну. Операционная система Windows регистрирует несколько предопределенных классов окна, обеспечивая для них свои функции окна. Приложение может создавать окна на базе предопределенных классов, при этом им не требуется определять для этих окон функции окна. Эти функции уже определены и находятся внутри Windows.

Например, приложение может создать дочернее окно на базе предопределенного класса "button". Окна этого класса - хорошо знакомые вам кнопки. Внешне поведение кнопки выглядит достаточно сложным. Когда вы устанавливаете на кнопку курсор мыши и нажимаете левую клавишу мыши, кнопка "уходит вглубь". Когда вы отпускаете клавишу мыши или выводите курсор мыши из области кнопки, кнопка возвращается в исходное положение. Если в диалоговой панели имеется несколько кнопок, вы можете при помощи клавиши <Tab> выбрать нужную, при этом на ней появится пунктирная рамка. Кнопку можно нажимать не только мышью, но и клавиатурой. Кнопка может находиться в активном и пассивном состоянии, причем в последнем случае вы не можете на нее нажать, а надпись, расположенная на кнопке, выводится пунктирным шрифтом. Кстати, надпись на кнопке может изменяться в процессе работы приложения.

Словом, поведение такого простого органа управления, как кнопка, при внимательном рассмотрении не выглядит простым. Если бы программист реализовал все функциональные возможности кнопки самостоятельно, это отняло бы у него много времени и сил. А ведь есть и более сложные органы управления!

Но, к счастью, Windows создана как объектно-ориентированная система. Для таких объектов, как органы управления, в Windows созданы классы окна. Например, в Windows имеется класс окна "button", на базе которого можно создавать кнопки. Функция окна, определенная внутри Windows для класса "button", обеспечивает все функциональные возможности кнопки, описанные выше. Поэтому программисту достаточно создать собственное дочернее окно на базе класса "button". Это окно сразу становится кнопкой и начинает вести себя как кнопка.

Так как дочерние окна располагаются на поверхности окна-родителя, "прилипая" к ним, приложение может создать в главном или любом другом окне несколько органов управления, которые будет перемещаться сами вместе с окном-родителем. Для этого достаточно просто создать нужные дочерние окна на базе предопределенных классов, указав их размеры, расположение и некоторые другие атрибуты. После этого приложение может взаимодействовать с органами управления, передавая им или получая от них различные сообщения.

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

Изучение органов управления, встроенных в Windows, мы начнем с кнопок, создаваемых на базе предопределенного класса окна "button".

2.1. Кнопки

Для создания кнопки, как мы уже говорили, ваше приложение должно создать дочернее окно на базе предопределенного класса "button". После этого родительское окно будет получать от кнопки сообщение с кодом WM_COMMAND. Этим сообщением кнопка информирует родительское окно о том, что с ней что-то сделали, например, нажали.

Создание кнопки

Для создания кнопки вам надо вызвать функцию CreateWindow. Мы уже рассказывали вам об этой функции в предыдущем томе. С помощью нее мы создавали окна во всех наших приложениях. Для удобства приведем прототип функции CreateWindow еще раз:

HWND CreateWindow(LPCSTR lpszClassName, 
   LPCSTR lpszWindowName, DWORD dwStyle,
   int x, int y, int nWidth, int nHeight,
   HWND hwndParent, HMENU hmenu, HINSTANCE hinst,
   void FAR* lpvParam);

Параметр функции lpszClassName - указатель на строку, содержащую имя класса, на базе которого создается окно. Для создания кнопки необходимо указать имя класса "button".

Параметр функции lpszWindowName - указатель на строку, содержащую заголовок окна (Title Bar). Эта строка будет написана на кнопке.

Параметр dwStyle - стиль создаваемого окна. Этот параметр задается как логическая комбинация отдельных битов. Для кнопки следует задать стиль как комбинацию констант WS_CHILD, WS_VISIBLE и константы, определяющей один из возможных стилей кнопки.

Парамеры x и y функции CreateWindow определяют горизонтальную (x) и вертикальную (y) координату кнопки относительно верхнего левого угла родительского окна.

Параметры nWidth и nHeight определяют, соответственно, ширину и высоту создаваемой кнопки.

Параметр hwndParent определяет идентификатор родительского окна, на поверхности которого создается кнопка.

Параметр hmenu - идентификатор меню или идентификатор порожденного (child) окна. Для каждого создаваемого вами дочернего окна вы должны определить собственный идентификатор. Родительское окно будет получать от дочерних окон сообщения. При помощи идентификатора дочернего окна функция родительского окна сможет определить дочернее окно, пославшее сообщение родительскому окну.

Параметр hinst - идентификатор приложения, которое создает окно. Необходимо использовать значение, передаваемое функции WinMain через параметр hInstance.

Последний параметр функции (lpvParam) представляет собой дальний указатель на область данных, определяемых приложением. Этот параметр передается в функцию окна вместе с сообщением WM_CREATE при создании окна. Для кнопки вы должны указать значение NULL.

Для создания кнопки с надписью "Help" в точке с координатами (10, 30) и размерами (40, 20) можно использовать, например, такой вызов функции CreateWindow:

hHelpButton = CreateWindow("button", "Help",
  WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
  10, 30,
  40, 20,
  hWnd,
  (HMENU)IDB_Help,
  hInstance, NULL);

Стиль кнопки влияет на ее внешний вид и поведение:

Стиль кнопкиВнешний вид Описание
BS_3STATE Переключатель, который может находится в одном из трех состояний: включенном (квадратик перечеркнут), выключенном (квадратик не перечеркнут), неактивном (квадратик отображается серым цветом)
BS_AUTO3STATE Аналогично стилю BS_3STATE, но внешний вид кнопки изменяется автоматически при ее переключении
BS_AUTOCHECKBOX Переключатель, который может находиться в одном из двух состояний: включенном или выключенном. Внешний вид кнопки изменяется автоматически при ее переключении
BS_AUTORADIOBUTTON Переключатель, который может находиться в одном из двух состояний: включенном (внутри окружности имеется жирная черная точка) или выключенном (окружность не закрашена). Внешний вид кнопки изменяется автоматически при ее переключении
BS_CHECKBOX Переключатель, который может находиться в одном из двух состояний: включенном или выключенном.
BS_DEFPUSHBUTTON Стандартная кнопка с толстой рамкой вокруг
BS_GROUPBOX Прямоугольная область, внутри которой могут находиться другие кнопки. Обычно используется в диалоговых панелях. Этот орган управления не воспринимает сообщения от мыши или клавиатуры
BS_LEFTTEXT Этот стиль указывается вместе с другими и означает, что текст, расположенный около кнопки, должен находиться слева, а не справа от кнопки
BS_OWNERDRAWВнешний вид определяется родительским окном Внешний вид кнопки определяется родительским окном, которое само рисует кнопку во включенном, выключенном или неактивном состоянии
BS_PUSHBUTTON Стандартная кнопка без рамки
BS_RADIOBUTTON Переключатель, который может находиться в одном из двух состояний: включенном или выключенном.
BS_USERBUTTONВнешний вид определяется родительским окном Устаревший стиль, аналогичный по назначению стилю BS_OWNERDRAW. Не рекомендуется к использованию. Этот стиль не описан в документации SDK для Windows версии 3.1, но определен в файле windows.h

Некоторые стили кнопок, описанные в этой таблице, используются для создания переключателей. Переключатели мы рассмотрим позже.

Таким образом, указав функции CreateWindow класс окна "button", мы создаем кнопку. Но с помощью класса "button" можно реализовать несколько перечисленных видов кнопок. Для уточнения вида кнопки мы дополнительно в стиле окна определяем стиль кнопки, указывая константу с префиксом имени BS_. За исключением константы BS_LEFTTEXT, допустимо указывать только одну из перечисленных выше констант. Константа BS_LEFTTEXT используется совместно с другими стилями кнопок, как правило, при создании кнопок в виде переключателей с текстом, расположенным слева от квадратика или кружка переключателя.

Сообщение WM_COMMAND

Сообщение с кодом WM_COMMAND передается функции родительского окна от органа управления, созданного этим окном. При создании органа управления (например, кнопки на базе класса "button") вы вызываете функцию CreateWindow, которой указываете идентификатор родительского окна и идентификатор органа управления.

Если орган управления изменяет свое состояние (например, когда вы нажали на кнопку), функция родительского окна получает сообщение WM_COMMAND. Вместе с этим сообщением функция родительского окна получает в параметре wParam идентификатор органа управления. Младшее слово параметра lParam содержит идентификатор дочернего окна, т. е. идентификатор окна органа управления. Старшее слово содержит код извещения от органа управления (notification code), по которому можно судить о том, какое действие было выполнено над органом управления.

Когда вы нажимаете на кнопку, родительское окно получает сообщение WM_COMMAND с кодом извещения, равным BN_CLICKED. Получив такое сообщение, приложение определяет, что была нажата кнопка, идентификатор которой находится в параметре wParam.

При использовании кнопки устаревшего стиля BS_USERBUTTON функция родительского окна может получить сообщения с кодами извещения BN_DISABLE, BN_DOUBLECLICKED, BN_HLITE, BN_PAINT, BN_UNHILITE. В новых приложениях эти коды извещения не используются.

Орган управления BS_GROUPBOX не обрабатывает сообщения от мыши или клавиатуры и не посылает в родительское окно сообщение WM_CONTROL. Он используется в качестве объединяющей рамки с заголовком, внутри которой располагаются другие органы управления, такие как кнопки или переключатели.

Приложение BUTTON

Приложение BUTTON демонстрирует способ создания стандартных кнопок и обработку сообщений от них. В главном окне приложения создается две кнопки с названием "Button 1" и "Button 2". Если нажать на одну из них, на экране появится диалоговая панель с сообщением о номере нажатой кнопки (рис. 2.1).

Рис. 2.1. Главное окно приложения BUTTON

Главный файл приложения BUTTON приведен в листинге 2.2.


Листинг 2.1. Файл button\button.cpp


// ----------------------------------------
// Стандартные кнопки
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <mem.h>

// Идентификаторы кнопок
#define IDB_Button1 1
#define IDB_Button2 2

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "ButtonAppClass";

// Заголовок окна
char const szWindowTitle[] = "Button Demo";

// =====================================
// Функция WinMain
// =====================================

#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Идентификаторы кнопок
  HWND hButton1, hButton2;

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  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);

  // Создаем первую кнопку
  hButton1 = CreateWindow("button", "Button 1",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 20,
    90, 30,
    hwnd,
    (HMENU) IDB_Button1,
    hInstance, NULL);

  // Создаем вторую кнопку
  hButton2 = CreateWindow("button", "Button 2",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 60,
    90, 30,
    hwnd,
    (HMENU) IDB_Button2,
    hInstance, NULL);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    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)
{
  switch (msg)
  {
    // Сообщение приходит, когда вы нажимаете
    // на одну из двух созданных кнопок
    case WM_COMMAND:
    {
      // Если нажата первая кнопка, выводим
      // сообщение
      if(wParam == IDB_Button1)
      {
        MessageBox(hwnd, "Нажата кнопка Button 1",
          "Message WM_COMMAND",MB_OK);
      }
      // Если нажата вторая кнопка, выводим
      // другое сообщение
      else if(wParam == IDB_Button2)
      {
        MessageBox(hwnd, "Нажата кнопка Button 2",
          "Message WM_COMMAND",MB_OK);
      }
      return 0;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Файл определения модуля приложения приведен в листинге 2.2.


Листинг 2.2. Файл button\button.def


; =============================
; Файл определения модуля
; =============================
NAME BUTTON
DESCRIPTION 'Приложение BUTTON, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

В начале главного файла приложения определены идентификаторы двух создаваемых кнопок:

#define IDB_Button1 1
#define IDB_Button2 2

Функция главного окна будет использовать эти идентификаторы для того чтобы различать кнопки. В этих определениях вы можете использовать произвольные численные значения.

После создания и вывода на экран главного окна приложения функция WinMain создает кнопки, вызывая функцию CreateWindow:

hButton1 = CreateWindow("button", "Button 1",
  WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
  20, 20,
  90, 30,
  hwnd,
  (HMENU) IDB_Button1,
  hInstance, NULL);

Для первой кнопки указывается предопределенный класс окна 'button", заголовок "Button 1", стиль кнопки BS_PUSHBUTTON, расположение, размеры, а также идентификатор кнопки IDB_Button1.

Вторая кнопка создается аналогично. Она имеет те же размеры, но расположена ниже, имеет заголовок "Button 2" и идентификатор IDB_Button2.

В функции окна добавился обработчик сообщения WM_COMMAND. Это сообщение поступает в функцию окна, когда вы нажимаете любую из двух созданных кнопок. Обработчик анализирует идентификатор кнопки, передаваемый вместе с сообщением в параметре wParam, и выводит соответствующее сообщение:

case WM_COMMAND:
{
  if(wParam == IDB_Button1)
  {
    MessageBox(hwnd, "Нажата кнопка Button 1",
      "Message WM_COMMAND",MB_OK);
  }
  else if(wParam == IDB_Button2)
  {
    MessageBox(hwnd, "Нажата кнопка Button 2",
      "Message WM_COMMAND",MB_OK);
  }
  return 0;
}

Управление кнопкой из приложения

У вас есть две возможности управления кнопкой из приложения Windows (или другим органом управления). Во-первых, вы можете, вызывая специальные функции, динамически перемещать орган управления, делать его активным или неактивным, скрывать его или отображать в окне. Во-вторых, кнопке или другому органу управления (как и большинству объектов Windows) можно посылать сообщения, в ответ на которые этот орган будет выполнять различные действия.

Вызов функций управления окном

Для перемещения органа управления внутри окна можно воспользоваться функцией MoveWindow, описанной нами ранее. Функция MoveWindow определяет новое расположение и размеры окна:

BOOL WINAPI MoveWindow(HWND hwnd,
   int nLeft, int nTop, int nWidth, int nHeight,
   BOOL fRepaint);

Параметр hwnd указывает идентификатор перемещаемого окна. Для перемещения органа управления вам необходимо указать его идентификатор, полученный от функции CreateWindow.

Параметр nLeft указывает новую координату левой границы окна, параметр nTop - новую координату нижней границы окна. Эти параметры определяют новое положение органа управления в системе координат, связанной с родительским окном. Напомним, что при перемещении обычного перекрывающегося (overlapped) или временного (pop-up) окна используется экранная система координат.

Параметры nWidth и nHeight определяют, соответственно, ширину и высоту окна. Если при перемещении органа управления необходимо сохранить его размеры, укажите значения, использованные при создании этого органа управления.

Последний параметр fRepaint представляет собой флаг, определяющий, надо ли перерисовывать окно после его перемещения. Если значение этого параметра равно TRUE, функция окна после перемещения окна получит сообщение WM_PAINT. Если указать это значение как FALSE, никакая часть окна не будет перерисована. При перемещении органа управления в качестве этого параметра следует указать TRUE.

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

Для блокирования и разблокирования органа управления следует пользоваться функцией EnableWindow:

BOOL WINAPI EnableWindow(HWND hWnd, BOOL fEnable);

Функция EnableWindow позволяет разрешать или запрещать поступление сообщений от клавиатуры или мыши в окно или орган управления, идентификатор которого задан параметром hWnd.

Параметр fEnable определяет, будет ли указанное окно заблокировано или наоборот, разблокировано. Для того чтобы заблокировать окно (или орган управления) необходимо для этого парамера указать значение FALSE. Если надо разблокировать окно, используйте значение TRUE.

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

BOOL WINAPI IsWindowEnabled(HWND hWnd);

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

Можно вообще убрать орган управления из окна, скрыв его при помощи функции ShowWindow:

BOOL ShowWindow(HWND hwnd, int nCmdShow);

Функция отображает окно, идентификатор которого задан параметром hwnd, в нормальном, максимально увеличенном или уменьшенном до пиктограммы виде, в зависимости от значения параметра nCmdShow. Если использовать эту функцию для органа управления, вы можете его скрыть, указав в параметре nCmdShow значение SW_HIDE.

Для восстановления органа управления надо вызвать эту функцию с параметром SW_SHOWNORMAL.

Можно изменить текст, написанный на кнопке. Для этого следует использовать функцию SetWindowText:

void WINAPI SetWindowText(HWND hWnd, LPCSTR lpszString);

Эта функция устанавливает новый текст для заголовка окна (или органа управления), идентификатор которого указан при помощи параметра hWnd. Параметр lpszString является дальним указателем на строку символов, закрытую двоичным нулем, которая будет использована в качестве нового заголовка.

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

BOOL WINAPI DestroyWindow(HWND hWnd);

Функция DestroyWindow уничтожает окно, идентификатор которого задан в качестве параметра hWnd, и освобождает все связанные с ним ресурсы.

Возвращаемое значение равно TRUE в случае успеха или FALSE при ошибке.

Передача сообщений органу управления

В операционной системе Windows широко используется практика передачи сообщений от одного объекта к другому. Приложение BUTTON демонстрирует передачу сообщения WM_COMMAND от кнопки родительскому окну, создавшему эту кнопку. Однако родительское окно может само послать сообщение кнопке или любому другому органу управления, если оно знает его идентификатор.

Существует два способа передачи сообщений.

Первый способ - запись сообщения в очередь приложения. Он основан на использовании функции PostMessage:

BOOL WINAPI PostMessage(HWND hWnd, UINT uMsg,
   WPARAM wParam, LPARAM lParam);

Функция PostMessage помещает сообщение в очередь сообщений для окна, указанного параметром hWnd, и сразу возвращает управление. Возвращаемое значение равно TRUE в случае успешной записи сообщения в очередь или FALSE при ошибке. Записанное при помощи функции PostMessage сообщение будет выбрано и обработано в цикле обработки сообщений.

Параметр uMsg задает идентификатор передаваемого сообщения. Параметры wParam и lParam используются для передачи параметров сообщения.

Второй способ - непосредственная передача сообщения функции окна минуя очередь сообщений. Этот способ реализуется функцией SendMessage:

LRESULT WINAPI SendMessage(HWND hWnd, UINT uMsg,
   WPARAM wParam, LPARAM lParam);

Параметры функции SendMessage используются аналогично параметрам функции PostMessage. Но в отличие от последней функция SendMessage вызывает функцию окна и возвращает управление только после возврата из функции окна.

Возвращаемое функцией SendMessage значение зависит от обработчика сообщения в функции окна.

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

Для управления кнопкой вы можете использовать сообщение BM_SETSTATE, которое позволяет установить кнопку в нажатое или отжатое состояние.

Для установки кнопки в нажатое состояние следует передать ей сообщение BM_SETSTATE с параметром wParam, равным TRUE, и lParam, равным 0:

SendMessage(hButton, BM_SETSTATE, TRUE, 0L);

Для возврата кнопки в исходное состояние передайте ей то же самое сообщение, но с параметром wParam, равным FALSE:

SendMessage(hButton, BM_SETSTATE, FALSE, 0L);

Приложение BUTNCTL

Приложение BUTNCTL создает в своем главном окне пять кнопок (рис. 2.2).

Рис. 2.2. Главное окно приложения BUTNCTL

Кнопка "Button 1" работает также, как и в предыдущем приложении. А именно, если на нее нажать, появится диалоговая панель с сообщением о том, что нажата первая кнопка.

Остальные кнопки управляют первой кнопкой. С помощью кнопки "PUSH" вы можете нажимать первую кнопку (и она останется в нажатом состоянии), с помощью кнопки "POP" вы сможете вернуть первую кнопку в исходное состояние. Кнопка "OFF" блокирует первую кнопку, не пропуская в ее функцию окна сообщения от клавиатуры и мыши, кнопка "ON" разблокирует первую кнопку.

Когда вы будете проверять работу приложения, обратите внимание, что с помощью кнопок "PUSH" и "POP" вы сможете изменять состояние первой кнопки, даже если она заблокирована. Это связано с тем, что заблокированное окно не получает сообщения от мыши и клавиатуры, но получает другие сообщения, например, от таймера, других окон или приложений.

Главный файл приложения BUTNCTL приведен в листинге 2.3.


Листинг 2.3. Файл butnctl\butnctl.cpp


// ----------------------------------------
// Управление стандартной кнопкой
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>

// Идентификаторы кнопок
#define IDB_Button1 1
#define IDB_Button2 2
#define IDB_Button3 3
#define IDB_Button4 4
#define IDB_Button5 5

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "ButtonCtlAppClass";

// Заголовок окна
char const szWindowTitle[] = "Button Control Demo";

// Идентификаторы кнопок
HWND hButton1, hButton2, hButton3, hButton4, hButton5;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  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);

  // Создаем пять кнопок
  hButton1 = CreateWindow("button", "Button 1",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 20, 90, 30,
    hwnd, (HMENU) IDB_Button1, hInstance, NULL);

  hButton2 = CreateWindow("button", "Button 2",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 60, 90, 30, hwnd,
    (HMENU) IDB_Button2, hInstance, NULL);

  hButton3 = CreateWindow("button", "Button 3",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 100, 90, 30, hwnd,
    (HMENU) IDB_Button3, hInstance, NULL);

  hButton4 = CreateWindow("button", "Button 4",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 140, 90, 30, hwnd,
    (HMENU) IDB_Button4, hInstance, NULL);

  hButton5 = CreateWindow("button", "Button 5",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 180, 90, 30, hwnd,
    (HMENU) IDB_Button5, hInstance, NULL);

  // Увеличиваем горизонтальный размер
  // первой кнопки
  MoveWindow(hButton1, 20, 20, 180, 30, TRUE);

  // Изменяем надписи на остальных кнопках
  SetWindowText(hButton2, "PUSH");
  SetWindowText(hButton3, "POP");
  SetWindowText(hButton4, "OFF");
  SetWindowText(hButton5, "ON");

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    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)
{
  switch (msg)
  {
    // Сообщение приходит, когда вы нажимаете
    // на одну из двух созданных кнопок
    case WM_COMMAND:
    {
      // Если нажата первая кнопка, выводим
      // сообщение
      if(wParam == IDB_Button1)
      {
          MessageBox(hwnd, "Нажата кнопка Button 1",
          "Message WM_COMMAND",MB_OK);
      }
      // Если нажата вторая кнопка,
      // переводим первую кнопку в нажатое состояние
      else if(wParam == IDB_Button2)
      {
        SendMessage(hButton1, BM_SETSTATE, TRUE, 0L);
      }
      // Если нажата третья кнопка,
      // возвращаем первую кнопку в исходное состояние
      else if(wParam == IDB_Button3)
      {
          SendMessage(hButton1, BM_SETSTATE, FALSE, 0L);
      }
      // Если нажата четвертая кнопка,
      // переводим первую кнопку в неактивное состояние
      else if(wParam == IDB_Button4)
      {
          EnableWindow(hButton1, FALSE);
      }
      // Если нажата пятая кнопка,
      // переводим первую кнопку в активное состояние
      else if(wParam == IDB_Button5)
      {
          EnableWindow(hButton1, TRUE);
      }
      return 0;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

В приложении определены идентификаторы пяти кнопок - от IDB_Button1 до IDB_Button5, а также пять переменных для хранения идентификаторов окон класса 'button".

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

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

Длина первой кнопки увеличивается до 180 пикселов, для чего вызывается функция MoveWindow:

MoveWindow(hButton1, 20, 20, 180, 30, TRUE);

Для всех остальных кнопок изменяются надписи:

SetWindowText(hButton2, "PUSH");
SetWindowText(hButton3, "POP");
SetWindowText(hButton4, "OFF");
SetWindowText(hButton5, "ON");

Функция окна обрабатывает сообщение WM_COMMAND, которое может поступать от всех пяти кнопок. Кнопки различаются по параметру wParam.

Если вы нажимаете первую кнопку, на экран выводится диалоговая панель с сообщением.

Если нажать на вторую кнопку (с надписью "DOWN"), функция окна передает сообщение первой кнопке, в результате чего она переходит в нажатое состояние:

SendMessage(hButton1, BM_SETSTATE, TRUE, 0L);

Если нажать на кнопку с надписью "POP", возвращается исходное состояние первой кнопки:

SendMessage(hButton1, BM_SETSTATE, FALSE, 0L);

Кнопка с надписью "OFF" предназначена для перевода первой кнопки в неактивное состояние. Для этого вызывается функция EnableWindow со значением второго параметра, равным FALSE:

EnableWindow(hButton1, FALSE);

И, наконец, последняя, пятая кнопка с надписью "ON" снова возвращает первую кнопку в активное состояние:

EnableWindow(hButton1, TRUE);

Файл определения модуля для приложения BUTNCTL приведен в листинге 2.4.


Листинг 2.4. Файл butnctl\butnctl.def


; =============================
; Файл определения модуля
; =============================
NAME BUTNCTL
DESCRIPTION 'Приложение BUTNCTL, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

Переключатели

Очень часто в приложениях Windows требуется организовать выбор различных режимов работы. На рис. 2.3. показана диалоговая панель "Options", которая используется в текстовом процессоре Microsoft Word for Windows для настройки параметров клавиатуры.

Рис. 2.3. Диалоговая панель для настройки параметров клавиатуры в текстовом процессоре Microsoft Word for Windows

В этой диалоговой панели есть множество различных управляющих органов. В правом нижнем углу расположен орган управления (группа) в виде прямоугольника с заголовком "Context". Это окно класса "button", имеющее стиль BS_GROUPBOX. Внутри него расположены два переключателя. Они созданы также на базе класса "button", но имеют стиль BS_RADIOBUTTON (или BS_AUTORADIOBUTTON).

Группа с названием "Short Key" содержит два переключателя, созданных на базе окна "button", но имеющих стиль BS_CHECKBOX (или BS_AUTOCHECKBOX).

Переключатели BS_RADIOBUTTON и BS_AUTORADIOBUTTON используются аналогично кнопкам переключения диапазонов в радиоприемнике (отсюда и название стиля таких переключателей). Обычно в одной группе располагают несколько таких "радиопереключателей", причем включенным может быть только один (ни один радиоприемник не позволит вам принимать передачи сразу в двух диапазонах). Такие переключатели называются переключателями с зависимой фиксацией, так как включение одного переключателя в группе вызывает выключение остальных. Разумеется, ваше приложение должно само обеспечить такой режим работы переключателей.

Переключатели BS_CHECKBOX, BS_AUTOCHECKBOX, BS_3STATE, BS_AUTO3STATE используются как отдельные независимые переключатели. Из них можно сделать переключатель с независимой фиксацией, в котором одновременно могут быть включены несколько переключателей.

Разумеется, все сказанное выше относительно использования переключателей имеет скорее характер рекомендаций, чем обязательное требование. Однако при разработке приложения вам необходимо позаботиться о том, чтобы интерфейс пользователя соответствовал стандарту, изложенному в руководстве по разработке пользовательского интерфейса (это руководство поставляется в составе SDK и называется The Windows Interface: An Application Design Guide). В этом случае органы управления вашего приложения будут делать то, что ожидает от них пользователь, освоивший работу с другими приложениями Windows.

Обычно переключатели и группы переключателей используются в более сложных органах управления - в диалоговых панелях, которыми мы займемся позже. Однако ваше приложение может создавать отдельные переключатели в любом своем окне, как и рассмотренные нами ранее кнопки со стилями BS_PUSHBUTTON или BS_DEFPUSHBUTTON.

Вы можете работать с переключателями типа BS_AUTORADIOBUTTON или BS_AUTOCHECKBOX точно таким же образом, что и с кнопками типа BS_PUSHBUTTON или BS_DEFPUSHBUTTON. Когда вы устанавливаете курсор мыши на такой переключатель и нажимаете левую клавишу мыши, состояние переключателя меняется на противоположное. При этом неперечеркнутый квадратик становится перечеркнутым и наоборот, перечеркнутый квадратик становится неперечеркнутым. Состояние переключателя BS_AUTORADIOBUTTON отмечается жирной точкой, которая для включенного переключателя изображается внутри кружочка.

При изменении состояния переключателя родительское окно получает сообщение WM_COMMAND с кодом извещения BN_CLICKED.

Переключатель, имеющий стиль BS_3STATE или BS_AUTO3STATE, внешне похож на переключатель со стилем BS_CHECKBOX, но дополнительно имеет третье состояние. В этом третьем состоянии он изображается серым цветом и может использоваться, например, для индикации недоступного для установки параметра.

Слово "AUTO" в названии стиля переключателя используется для обозначения режима автоматической перерисовки переключателя при изменении его состояния. О чем здесь идет речь?

Когда вы нажимаете кнопку, имеющую стиль BS_PUSHBUTTON или BS_DEFPUSHBUTTON, она автоматически уходит "вглубь", т. е. автоматически перерисовывается в соответствии со своим текущим состоянием. Переключатели BS_CHECKBOX, BS_RADIOBUTTON, а также BS_3STATE не перерисовываются при их переключении. Вы должны их перерисовывать сами, посылая им сообщение BM_SETCHECK:

SendMessage(hButton, BM_SETCHECK, 1, 0L);

Параметр wParam сообщения BM_SETCHECK определяет состояние переключателя, которое необходимо установить:

ЗначениеОписание
0Установка переключателя в выключенное состояние (прямоугольник не перечеркнут, в кружке нет точки)
1Установка переключателя во включенное состояние (прямоугольник перечеркнут, в кружке имеется точка)
2Установка переключателя в неактивное состояние. Это значение используется только для переключателей, имеющих стиль BS_3STATE или BS_AUTO3STATE. При этом переключатель будет изображен серым цветом

Параметр lParam сообщения BM_SETCHECK должен быть равен 0.

В любой момент времени приложение может узнать состояние переключателя, посылая ему сообщение BM_GETCHECK:

WORD nState;
nState = (WORD) SendMessage(hButton, BM_GETCHECK, 0, 0L);

Парамеры wParam и lParam сообщения BM_GETCHECK должны быть равны 0.

Возвращаемое значение, которое будет записано в переменную nState, может быть равно 0 (для выключенного переключателя), 1 (для включенного) или 2 (для переключателя, который находится в неактивном состоянии и отображается серым цветом).

Мы еще вернемся к переключателям, когда будем заниматься диалоговыми панелями.

Кнопки, которые рисует родительское окно

Если вас не удовлетворяет внешний вид стандартных кнопок (или других стандартных органов управления, созданных на базе класса "button"), вы можете при создании кнопки указать стиль BS_OWNERDRAW. Этот стиль несовместим с остальными стилями кнопок.

Если вы создали кнопку со стилем BS_OWNERDRAW, она будет работать аналогично кнопкам других стилей, но процедура рисования кнопки возлагается на родительское окно. При этом оно может нарисовать кнопку с использованием пиктограмм (с помощью функции DrawIcon), графических изображений bitmap или любым другим способом.

Обычно кнопки BS_OWNERDRAW используют при отображении наборов инструментальных средств, называемых Toolbar. Примером такого набора может служить набор кнопок с пиктограммами, предназначенных для запуска команд в текстовом процессоре Microsoft Word for Windows (рис. 2.4).

Рис. 2.4. Кнопки с пиктограммами

Кнопка BS_OWNERDRAW, как и кнопка BS_PUSHBUTTON, посылает в родительское окно сообщение WM_COMMAND с кодом извещения BN_CLICKED. Дополнительно такая кнопка посылает в родительское окно сообщение WM_DRAWITEM, которое говорит о том, что надо нарисовать орган управления в том или ином состоянии.

Обработчик сообщения WM_DRAWITEM должен вернуть значение TRUE.

Параметр wParam сообщения WM_DRAWITEM содержит идентификатор органа управления, пославшего сообщение WM_DRAWITEM (для органа управления типа меню этот параметр равен 0).

Параметр lParam содержит дальний указатель на структуру типа DRAWITEMSTRUCT, описанную в файле windows.h:

typedef struct tagDRAWITEMSTRUCT
{
  UINT   CtlType;
  UINT   CtlID;
  UINT   itemID;
  UINT   itemAction;
  UINT   itemState;
  HWND   hwndItem;
  HDC    hDC;
  RECT   rcItem;
  DWORD  itemData;
} DRAWITEMSTRUCT;

В файле windows.h определены также ближний и дальний указатели на эту структуру:

typedef DRAWITEMSTRUCT NEAR* PDRAWITEMSTRUCT;
typedef DRAWITEMSTRUCT FAR* LPDRAWITEMSTRUCT;

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

Приведем назначение отдельных полей структуры DRAWITEMSTRUCT.

Имя поляОписание
CtlTypeТип органа управления. Может принимать следующие значения:
ODT_BUTTONODT_BUTTON - кнопка;
ODT_COMBOBOXODT_COMBOBOX - орган COMBOBOX (рассмотрим позже);
ODT_LISTBOXODT_LISTBOX - орган LISTBOX (рассмотрим позже);
ODT_MENUODT_MENU - меню
CtlIDИдентификатор органа управления. Не используется для меню
itemIDИдентификатор строки для органов COMBOBOX, LISTBOX или меню
itemActionДействия, которые необходимо выполнить при изображении органа управления. Определен в виде отдельных битовых флагов:
ODA_DRAWENTIREODA_DRAWENTIRE - требуется перерисовать весь орган управления;
ODA_FOCUSODA_FOCUS - этот бит устанавливается в 1, если орган управления получил или потерял фокус ввода, новое состояние органа управления можно узнать, проанализировав содержимое поля itemState;
ODA_SELECTODA_SELECT - изменилось состояние органа управления (он стал включенным, выключенным или неактивным), для уточнения состояния необходимо использовать поле itemState
itemStateВид, в котором необходимо изобразить орган управления. Определен в виде отдельных битовых флагов:
ODS_CHECKEDODS_CHECKED - выбрана строка меню (этот бит используется только для меню);
ODS_DISABLEDODS_DISABLED - орган управления неактивен;
ODS_FOCUSODS_FOCUS - орган управления получил фокус ввода;
ODS_GRAYEDODS_GRAYED - строка меню должна быть изображена серым цветом (этот бит используется только для меню);
ODS_SELECTEDODS_SELECTED - орган управления выбран
hwndItemДля кнопок, органов управления COMBOBOX и LISTBOX это поле содержит идентификатор окна. Для меню это поле содержит идентификатор меню
hDCКонтекст устройства, который необходимо использовать для рисования органа управления
rcItemПрямоугольные границы органа управления, внутри которого его необходимо нарисовать
itemDataИспользуется только для органов управления COMBOBOX и LISTBOX

Приложение OWNBUT

Приложение OWNBUT создает кнопку со стилем BS_OWNERDRAW. Функция главного окна приложения рисует кнопку при помощи трех изображений bitmap, загружая их при необходимости из ресурсов. Первое изображение предназначено для рисования отжатой кнопки, второе - нажатой, и третье - неактивной.

Внешний вид главного окна приложения показан на рис. 2.5.

Рис. 2.5. Главное окно приложения OWNBUT

В верхнем левом углу главного окна расположена кнопка со стилем BS_OWNERDRAW. Остальные кнопки предназначены для управления первой кнопкой и работают так же, как и предыдущем приложении.

Главный файл приложения OWNBTN представлен в листинге 2.5.


Листинг 2.5. Файл ownbut\ownbut.cpp


// ----------------------------------------
// Управление нестандартной кнопкой
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>
#include "ownbut.hpp"

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawButton(LPDRAWITEMSTRUCT);
void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap);

// Имя класса окна
char const szClassName[]   = "OwnButtonAppClass";

// Заголовок окна
char const szWindowTitle[] = "Owner Draw Button Demo";

// Идентификаторы кнопок
HWND hButton1, hButton2, hButton3, hButton4, hButton5;

// Идентификатор копии приложения
HINSTANCE hInst;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, 
  HINSTANCE hPrevInstance,
  LPSTR     lpszCmdLine, 
  int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Сохраняем идентификатор текущей копии приложения
  // в глобальной переменной 
  hInst = hInstance;

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  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);

  // Создаем пять кнопок

  // Эту кнопку будет рисовать функция
  // родительского окна
  hButton1 = CreateWindow("button", "Button 1",
    WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
    20, 20, 180, 30,
    hwnd, (HMENU) IDB_Button1, hInstance, NULL);

  hButton2 = CreateWindow("button", "PUSH",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 60, 90, 30, hwnd,
    (HMENU) IDB_Button2, hInstance, NULL);

  hButton3 = CreateWindow("button", "POP",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 100, 90, 30, hwnd,
    (HMENU) IDB_Button3, hInstance, NULL);

  hButton4 = CreateWindow("button", "OFF",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 140, 90, 30, hwnd,
    (HMENU) IDB_Button4, hInstance, NULL);

  hButton5 = CreateWindow("button", "ON",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 180, 90, 30, hwnd,
    (HMENU) IDB_Button5, hInstance, NULL);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    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)
{
  switch (msg)
  {
    // Сообщение приходит, когда вы нажимаете
    // на одну из двух созданных кнопок
    case WM_COMMAND:
    {
      // Если нажата первая кнопка, выводим
      // сообщение
      if(wParam == IDB_Button1)
      {
        MessageBox(hwnd, "Нажата кнопка Button 1",
          "Message WM_COMMAND",MB_OK);
      }
      else if(wParam == IDB_Button2)
      {
        SendMessage(hButton1, BM_SETSTATE, TRUE, 0L);
      }
      else if(wParam == IDB_Button3)
      {
        SendMessage(hButton1, BM_SETSTATE, FALSE, 0L);
      }
      else if(wParam == IDB_Button4)
      {
        EnableWindow(hButton1, FALSE);
      }
      else if(wParam == IDB_Button5)
      {
        EnableWindow(hButton1, TRUE);
      }
      return 0;
    }

    // Это сообщение приходит при изменении состояния
    // дочернего окна органа управления, когда окно
    // нужно перерисовать
    case WM_DRAWITEM:
    {
      // Перерисовываем кнопку
      DrawButton( (LPDRAWITEMSTRUCT)lParam );
      break;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

// =====================================
// Функция DrawButton
// Перерисовывает кнопку
// =====================================

void DrawButton(LPDRAWITEMSTRUCT lpInfo)
{
   HBITMAP hbm;
   int ResourceID;

   // Обрабатываем сообщение WM_DRAWITEM
   // только если оно поступило от кнопки 
   if(lpInfo->CtlType != ODT_BUTTON)
     return;

   // Так как в приложении может быть несколько
   // кнопок, посылающих сообщение WM_DRAWITEM,
   // проверяем идентификатор кнопки
   switch (lpInfo->CtlID)
   {
      case IDB_Button1:
      {
        // Загружаем идентификатор изображения
        // кнопки в нормальном (отжатом) состоянии
        ResourceID = IDR_BUTTONUP;
        break;
      }
      default:
        return;
   }

   // Если кнопка выбрана, рисуем ее в нажатом
   // состоянии
   if (lpInfo->itemState & ODS_SELECTED)
   {
     ResourceID = IDR_BUTTONDOWN;
   }

   // если кнопка неактивна, загружаем идентификатор
   // изображения кнопки в неактивном состоянии
   else if (lpInfo->itemState & ODS_DISABLED)
   {
     ResourceID = IDR_BUTTONGR;
   }

   // Загружаем изображение кнопки из ресурсов приложения
   hbm = LoadBitmap(hInst, MAKEINTRESOURCE(ResourceID));

   // При ошибке ничего не рисуем
   if(!hbm) return;

   // Если кнопка выбрана и ее надо целиком
   // перерисовать, вызываем функцию DrawBitmap
   if((lpInfo->itemAction & ODA_DRAWENTIRE) ||
      (lpInfo->itemAction & ODA_SELECT))
   {
     // Рисуем кнопку
     DrawBitmap(lpInfo->hDC,
       (lpInfo->rcItem).left,
       (lpInfo->rcItem).top , hbm);
   }

   // Удаляем изображение кнопки, так как оно
   // нам больше не нужно
   DeleteObject(hbm);
}

В начале своей работы функция WinMain сохраняет идентификатор текущей копии приложения в глобальной переменной:

hInst = hInstance;

Затем она инициализирует главное окно приложения и создает пять кнопок, первая из которых имеет стиль BS_OWNERDRAW:

hButton1 = CreateWindow("button", "Button 1",
  WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
  20, 20, 180, 30,
  hwnd, (HMENU) IDB_Button1, hInstance, NULL);

Функция главного окна приложения OWNBTN кроме сообщения WM_COMMAND дополнительно обрабатывает сообщение WM_DRAWITEM, в ответ на которое она вызывает функцию DrawButton, определенную в нашем приложении:

case WM_DRAWITEM:
{
  DrawButton( (LPDRAWITEMSTRUCT)lParam );
  break;
}

Функция DrawButton перерисовывает кнопку. В качестве параметра этой функции передается указатель на структуру DRAWITEMSTRUCT, содержащую всю информацию, необходимую для перерисовки кнопки.

Вначале эта функция проверяет тип органа управления, приславшего сообщение WM_DRAWITEM. Тип должен быть равен ODT_BUTTON.

Затем проверяется идентификатор кнопки. В нашем приложении есть только одна кнопка со стилем BS_OWNERDRAW, но их могло бы быть и несколько. В переменную ResourceID загружается идентификатор ресурса (изображения bitmap), соответствующего изображению кнопки в нормальном (отжатом) состоянии:

   switch (lpInfo->CtlID)
   {
      case IDB_Button1:
      {
        ResourceID = IDR_BUTTONUP;
        break;
      }
      default: return;
   }

Далее в зависимости от текущего состояния кнопки на момент прихода сообщения WM_DRAWITEM содержимое переменной ResourceID заменяется на идентификатор соответствующего изображения кнопки.

Для выбранной кнопки (в поле itemState структуры DRAWITEMSTRUCT установлен бит ODS_SELECTED) в переменную ResourceID записывается идентификатор изображения нажатой кнопки IDR_BUTTONDOWN.

Для неактивной кнопки в эту же переменную записывается идентификатор изображения неактивной кнопки IDR_BUTTONGR.

После этого нужное изображение загружается из ресурсов приложения, для чего вызывается функция LoadBitmap:

hbm = LoadBitmap(hInst, MAKEINTRESOURCE(ResourceID));

Далее функция DrawButton проверяет действие, которое нужно выполнить при рисовании кнопки. Если состояние кнопки изменилось (установлен бит ODA_SELECT) и требуется перерисовать всю кнопку (установлен бит ODA_DRAWENTIRE), выполняется вызов функции DrawBitmap, определенной в нашем приложении.

Создавая функцию DrawBitmap, мы сделали так, что количество, назначение и формат ее параметров в точности соответствует функции DrawIcon, с помощью которой мы рисовали пиктограммы (вы можете легко изменить исходный текст приложения OWNBUT для того чтобы для рисования кнопки использовать не изображения bitmap, а пиктограммы; сделайте это самостоятельно). В нашем примере в качестве первого параметра функции передается контекст устройства, полученный в структуре DRAWITEMSTRUCT вместе с сообщением WM_DRAWITEM. Второй и третий параметры используются для определения координат верхнего левого угла кнопки в системе координат, связанной с родительским окном. Последний параметр - идентификатор изображения bitmap, который необходимо нарисовать:

DrawBitmap(lpInfo->hDC,
  (lpInfo->rcItem).left,
  (lpInfo->rcItem).top , hbm);

После того как кнопка нарисована, следует удалить bitmap, загруженный функцией LoadBitmap. Для этого наше приложение вызывает функцию DeleteObject:

DeleteObject(hbm);

Идентификаторы кнопок и изображений bitmap определены в файле ownbut.hpp (листинг 2.6).


Листинг 2.6. Файл ownbut\ownbut.hpp


// Идентификаторы кнопок

#define IDB_Button1 1
#define IDB_Button2 2
#define IDB_Button3 3
#define IDB_Button4 4
#define IDB_Button5 5

#define IDR_BUTTONUP     200
#define IDR_BUTTONDOWN   201
#define IDR_BUTTONGR     202

В файле описания ресурсов приложения (листинг 2.7) определены три изображения bitmap, используемые для рисования кнопки в различных состояниях.


Листинг 2.7. Файл ownbut\ownbut.rc


#include "ownbut.hpp"
IDR_BUTTONUP   BITMAP  mybtnup.bmp
IDR_BUTTONDOWN BITMAP  mybtndn.bmp
IDR_BUTTONGR   BITMAP  mybtngr.bmp

В листинге 2.8 изображен файл mybtnup.bmp. Этот файл содержит объемное изображение кнопки в нормальном (отжатом) состоянии.


Листинг 2.8. Файл ownbut\mybtnup.bmp



Файл mybtndn.bmp (листинг 2.9) хранит изображение кнопки в нажатом состоянии.


Листинг 2.9. Файл ownbut\mybtndn.bmp



Для изображения кнопки в неактивном состоянии используется файл mybtngr.bmp (листинг 2.10).


Листинг 2.10. Файл ownbut\mybtngr.bmp



Для рисования изображения bitmap мы подготовили функцию DrawBitmap (листинг 2.11). Детальное описание этой функции мы отложим до главы, посвященной изображениям bitmap.


Листинг 2.11. Файл ownbut\drawbmp.cpp


// ----------------------------------------
// Рисование изображения типа bitmap
// ----------------------------------------

#define STRICT
#include <windows.h>

void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap)
{
  HBITMAP hbm, hOldbm;
  HDC hMemDC;
  BITMAP bm;
  POINT  ptSize, ptOrg;

  // Создаем контекст памяти, совместимый
  // с контекстом отображения
  hMemDC = CreateCompatibleDC(hDC);

  // Выбираем изображение bitmap в контекст памяти
  hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);

  // Если не было ошибок, продолжаем работу
  if (hOldbm)
  {
    // Для контекста памяти устанавливаем тот же
    // режим отображения, что используется в
    // контексте отображения
    SetMapMode(hMemDC, GetMapMode(hDC));

    // Определяем размеры изображения
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);

    ptSize.x = bm.bmWidth;   // ширина
    ptSize.y = bm.bmHeight;  // высота

    // Преобразуем координаты устройства в логические
    // для устройства вывода
    DPtoLP(hDC, &ptSize, 1);

    ptOrg.x = 0;
    ptOrg.y = 0;

    // Преобразуем координаты устройства в логические
    // для контекста памяти
    DPtoLP(hMemDC, &ptOrg, 1);

    // Рисуем изображение bitmap
    BitBlt(hDC, x, y, ptSize.x, ptSize.y,
        hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);

    // Восстанавливаем контекст памяти
    SelectObject(hMemDC, hOldbm);
  }

  // Удаляем контекст памяти
  DeleteDC(hMemDC);
}

Файл определения модуля для приложения OWNBUT представлен в листинге 2.12.


Листинг 2.12. Файл ownbut\ownbut.def


; =============================
; Файл определения модуля
; =============================
NAME OWNBUT
DESCRIPTION 'Приложение OWNBUT, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

Кнопки и клавиатура

Обычно для работы с кнопками используется мышь. Но, как мы уже говорили, с приложениями Windows вы можете работать и без мыши. В частности, в диалоговых панелях вы можете, нажимая клавишу <Tab>, передавать фокус ввода от одной кнопки к другой. Если кнопка имеет фокус ввода, ее функция окна будет получать сообщения от клавиатуры. Кнопка реагирует только на клавишу пробела - если вы нажмете пробел, когда кнопка имеет фокус ввода, кнопка (или переключатель, который есть ни что иное, как разновидность кнопки) изменит свое состояние.

Для того чтобы ваше приложение могло использовать клавишу <Tab> для передачи фокуса ввода от одного органа управления другому, оно должно создать для клавиши <Tab> свой обработчик сообщения WM_CHAR. Этот обработчик должен установить фокус ввода на следующий (из имеющихся) орган управления, вызвав функцию SetFocus.

К счастью, в Windows имеется объект, специально предназначенный для объединения нескольких органов управления - диалоговая панель. Функции поддержки диалоговых панелей определены внутри Windows. Они выполняют всю работу, необходимую для организации передачи фокуса ввода между различными органами управления, расположенными на диалоговой панели.

2.2. Статический орган управления

Статический орган управления - это окно, создаваемое на базе предопределенного класса "static". Строго говоря, статический орган управления нельзя использовать для управления работой приложения, так как он не воспринимает щелчки мыши и не способен обрабатывать сообщения от клавиатуры. Статический орган управления не посылает родительскому окну сообщение WM_COMMAND.

Когда курсор мыши перемещается над статическим органом управления, Windows посылает функции окна этого органа сообщение WM_NCHITTEST. В ответ на это сообщение статический орган возвращает Windows значение HTTRANSPARENT. В результате Windows посылает сообщение WM_NCHITTEST родительскому окну, лежащему под органом управления. В результате все сообщения от мыши попадают через "прозрачное" окно статического органа управления в родительское окно.

Зачем же нужен такой орган управления, который ничем не управляет?

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

Создание статического органа управления

Для создания статического органа управления вы должны использовать функцию CreateWindow. В качестве первого параметра этой функции следует указать класс окна "static":

HWND hStatic;
hStatic = CreateWindow("static", NULL,
  WS_CHILD | WS_VISIBLE | SS_BLACKFRAME,
  20, 40, 100, 50,
  hWnd, (HMENU)-1, hInstance, NULL);

Второй параметр определяет текст, который будет расположен внутри органа управления. Вы можете указать этот параметр как NULL, если текст не используется.

В третьем параметре следует указать один из стилей статического органа управления. В нашем примере указан стиль SS_BLACKFRAME.

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

Стили статического органа управления

Стили статического органа управление определяют внешний вид и применение органа.

Прямоугольные рамки

Стили SS_BLACKFRAME, SS_GRAYFRAME и SS_WHITEFRAME предназначены для создания прямоугольных рамок. При помощи этих стилей создаются, соответственно, черные, серые и белые рамки. Внутренняя область рамки остается незакрашенной.

Цвета рамки соответствуют системным цветам, определенным в Windows. Эти цвета можно изменить при помощи стандартного приложения Windows с названием Control Panel. Черный цвет соответствует системному цвету COLOR_WINDOWFRAME, используемому для изображения рамок окон Windows. Белый цвет соответствует цвету COLOR_WINDOW. Это цвет внутренней области окон Windows. И, наконец, серый цвет соответствует цвету фона экрана COLOR_BACKGROUND.

При создании статических органов управления со стилями SS_BLACKFRAME, SS_GRAYFRAME и SS_WHITEFRAME текст заголовка окна не используется. Соответствующий параметр функции CreateWindow следует указать как NULL.

Закрашенные прямоугольники

Для создания закрашенных прямоугольников используются стили SS_BLACKRECT, SS_GRAYRECT и SS_WHITERECT. Эти стили позволяют создать статические органы управления в виде закрашенных прямоугольников, соответственно, черного, серого и белого цветов (используются системные цвета, как это описано выше).

Для этих стилей текст заголовка окна не используется. Соответствующий параметр функции CreateWindow следует указать как NULL.

Текст

Статические органы управления удобно использовать для вывода текста. Вы можете использовать пять базовых стилей SS_LEFT, SS_RIGHT, SS_CENTER, SS_LEFTNOWORDWRAP, SS_SIMPLE и один модификатор SS_NOPREFIX.

При использовании стиля SS_LEFT приложение задает размер органа управления, внутри которого будет выведен текст. Орган управления выводит текст (используя для этого функцию DrawText), выравнивая его влево и выполняя свертку слов. Текст, который не поместился в окне, обрезается. Выполняется замена символов табуляции на пробелы.

Стили SS_RIGHT и SS_CENTER используются аналогично, но текст выравнивается, соответственно, по правой границе органа управления или центрируется.

При использовании стиля SS_LEFTNOWORDWRAP текст выводится без использования свертки слов и выравнивается по левой границе. Часть текста, которая не поместилась в окне, обрезается. Выполняется замена символов табуляции на пробелы.

Стиль SS_SIMPLE похож на стиль SS_LEFTNOWORDWRAP, но вывод текста выполняется быстрее (используется функция TextOut) и замена символов табуляции на пробелы не выполняется. Часть текста, которая не поместилась в окне, обрезается. При повторном выводе текста содержимое окна не стирается, поэтому новая строка не должна быть короче той, которая была выведена в окно раньше. Этот стиль имеет смысл комбинировать со стилем SS_NOPREFIX. В этом случае для вывода текста используется более быстрая функция ExtTextOut.

Модификатор SS_NOPREFIX используется также в тех случаях, когда необходимо отменить специальную обработку символа "&". Обычно этот символ не выводится статическими органами управления на экран, а следующий за ним символ изображается подчеркнутым (для изображения символа "&" его надо повторить два раза подряд).

Пиктограммы

Стиль SS_ICON используется для изображения пиктограмм в диалоговых панелях. Мы расскажем о нем в главе, посвященной диалоговым панелям.

Приложение STATIC

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

Рис. 2.6. Главное окно приложения STATIC

Главный файл приложения приведен в листинге 2.13.


Листинг 2.13. Файл static\static.cpp


// ----------------------------------------
// Статические органы управления
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "StaticAppClass";

// Заголовок окна
char const szWindowTitle[] = "Static Control Demo";

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Идентификаторы органов управления
  HWND hSt1, hSt2, hSt3, hSt4;

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  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);

  // Создаем четыре статических
  // органа управления
  hSt1 = CreateWindow("static", NULL,
    WS_CHILD | WS_VISIBLE | SS_BLACKRECT,
    20, 20, 180, 20,
    hwnd, (HMENU)-1, hInstance, NULL);

  hSt2 = CreateWindow("static", NULL,
    WS_CHILD | WS_VISIBLE | SS_BLACKFRAME,
    20, 50, 180, 20,
    hwnd, (HMENU)-1, hInstance, NULL);

  hSt3 = CreateWindow("static", "",
    WS_CHILD | WS_VISIBLE | SS_LEFT,
    20, 80, 180, 40,
    hwnd, (HMENU)-1, hInstance, NULL);

  hSt4 = CreateWindow("static", "Simple Control",
    WS_CHILD | WS_VISIBLE | SS_SIMPLE,
    20, 130, 180, 40,
    hwnd, (HMENU)-1, hInstance, NULL);

  // Выводим текст в окно, имеющее стиль SS_LEFT.
  // Этот текст будет выведен в режиме свертки слов
  SetWindowText(hSt3, (LPSTR) "Этот текст будет выведен"
      " внутри окна в две строки");

  // Выводим текст в окно, имеющее стиль SS_SIMPLE.
  // Этот текст будет выведен на одной строке,
  // причем часть строки, которая выходит за границы
  // окна, будет обрезана
  SetWindowText(hSt4, (LPSTR) "Этот текст будет выведен"
      " внутри окна в одну строку и обрезан");

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    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)
{
  switch (msg)
  {
    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

После создания и отображения главного окна приложения функция WinMain создает четыре статических органа управления, вызывая функцию CreateWindow. Если создается прямоугольник или рамка, адрес строки текста заголовка окна указывается как NULL:

hSt1 = CreateWindow("static", NULL,
  WS_CHILD | WS_VISIBLE | SS_BLACKRECT,
  20, 20, 180, 20,
  hwnd, (HMENU)-1, hInstance, NULL);

Идентификатор органа управления не используется, так как орган не посылает сообщений родительскому окну. В качестве идентификатора мы выбрали число -1, хотя можно использовать и другие значения.

Если создается окно с текстом, этот текст можно задать как заголовок окна:

hSt4 = CreateWindow("static", "Simple Control",
  WS_CHILD | WS_VISIBLE | SS_SIMPLE,
  20, 130, 180, 40,
  hwnd, (HMENU)-1, hInstance, NULL);

После создания статических органов управления функция WinMain изменяет текст в третьем и четвертом органе, вызывая функцию SetWindowText:

  SetWindowText(hSt3, (LPSTR) "Этот текст будет выведен"
      " внутри окна в две строки");
  SetWindowText(hSt4, (LPSTR) "Этот текст будет выведен"
      " внутри окна в одну строку и обрезан");

Для последнего органа управления мы намеренно выбрали текст такой длины, чтобы он не поместился в одной строке. Как и следовал ожидать, при выводе этот текст был обрезан (рис. 2.6).

Файл определения модуля для приложения STATIC представлен в листинге 2.14.


Листинг 2.14. Файл static\static.def


; =============================
; Файл определения модуля
; =============================
NAME STATIC
DESCRIPTION 'Приложение STATIC, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

2.3. Полоса просмотра

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

Полоса просмотра представляет собой орган управления, созданный на базе предопределенного класса "scrollbar". Горизонтальная и вертикальная полоса просмотра посылают в функцию родительского окна сообщения WM_HSCROLL и WM_VSCROLL, соответственно. Параметр WParam этих сообщений несет в себе информацию о действии, которое вы выполнили над полосой просмотра.

Полоса просмотра состоит из нескольких объектов, имеющих различное назначение. На рис. 2.7 показано назначение объектов вертикальной полосы просмотра при ее использовании для свертки текстовых документов.

Рис. 2.7. Вертикальная полоса просмотра

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

Если установить курсор мыши в область полосы просмотра выше ползунка, но ниже верхней кнопки и нажимать на левую клавишу мыши, документ сдвигается вниз на одну страницу. Аналогично, если щелкнуть левой клавишей мыши в области ниже ползунка, но выше нижней кнопки, документ сдвинется на одну страницу вверх. Ползунок при этом устанавливается скачком в новое положение. Дискретность перемещения ползунка задается приложением при инициализации полосы просмотра.

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

Понятие "страница" и "строка" больше подходят для текстовых документов. Для свертки других типов документов можно использовать другие термины, однако в любом случае полоса просмотра обеспечивает два типа позиционирования - грубое и точное. Грубое позиционирование выполняется при помощи ползунка или областей полосы просмотра, расположенных между ползунком и кнопками со стрелками, точное позиционирование выполняется кнопками, расположенными на концах полосы просмотра. Следует заметить, что понятия "грубое позиционирование" и "точное позиционирование" отражают факт наличия двух типов позиционирования. Вся логика, обеспечивающая свертку окна, выполняется вашим приложением при обработке сообщений, поступающих от полосы просмотра.

Горизонтальная полоса просмотра состоит из тех же объектов, что и вертикальная. Она обеспечивает свертку документа в горизонтальном направлении.

Создание полосы просмотра

Существует два способа создания полос просмотра в окне приложения.

Во-первых, вы можете создать полосу просмотра с помощью функции CreateWindow, указав предопределенный класс окна "scrollbar". Этот способ аналогичен используемому при создании кнопок или статических органов управления. Во-вторых, при создании окна на базе своего собственного класса вы можете указать, что окно должно иметь горизонтальную, вертикальную или обе полосы просмотра.

Использование класса "scrollbar"

Для создания полосы просмотра с помощью функции CreateWindow вы должны в первом параметре функции указать класс окна "scrollbar":

#define IDC_SCROLLBAR 1
HWND hScroll;
hScroll = CreateWindow("scrollbar", NULL,
  WS_CHILD | WS_VISIBLE | SBS_HORZ,
  20, 40, 100, 50,
  hWnd, IDC_SCROLLBAR, hInstance, NULL);

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

Третий параметр, определяющий стиль окна, наряду с константами WS_CHILD и WS_VISIBLE должен содержать определение стиля полосы просмотра. Существует девять стилей для полосы просмотра. Соответствующие символические константы определены в файле windows.h и имеют префикс имени SBS_ (например, SBS_HORZ).

Девятый параметр функции CreateWindow должен задавать идентификатор полосы просмотра.

Стили полосы просмотра

При создании полосы просмотра функцией CreateWindow вы можете указать в третьем параметре следующие стили.

СтильОписание
SBS_BOTTOMALIGNСоздается горизонтальная полоса просмотра, высота которой равна высоте системной полосы просмотра. Выполняется выравнивание нижнего края полосы просмотра по нижнему краю прямоугольника, координаты и размер которого определен при вызове функции CreateWindow. Этот стиль должен использоваться вместе со стилем SBS_HORZ
SBS_HORZСоздается горизонтальная полоса просмотра. Размер и расположение полосы просмотра определяются при вызове функции CreateWindow
SBS_LEFTALIGNСоздается вертикальная полоса просмотра, ширина которой равна ширина системной полосы просмотра. Левый край полосы просмотра выравнивается по левому краю прямоугольника, координаты и размер которого определен при вызове функции CreateWindow. Этот стиль должен использоваться вместе со стилем SBS_VERT
SBS_RIGHTALIGNСоздается вертикальная полоса просмотра, ширина которой равна ширина системной полосы просмотра. Правый край полосы просмотра выравнивается по правому краю прямоугольника, координаты и размер которого определен при вызове функции CreateWindow. Этот стиль должен использоваться вместе со стилем SBS_VERT
SBS_SIZEBOXСоздается орган управления с небольшим прямоугольником серого цвета (Size Box). Если вы установите курсор мыши внутрь органа управления, нажмете левую клавишу мыши и будете перемещать мышь, родительское окно будет получать сообщения, аналогичные сообщениям от рамки, предназначенной для изменения размера окна.
SBS_SIZEBOXBOTTOMRIGHTALIGNАналогично предыдущему, но правый нижний угол прямоугольника выравнивается по правому нижнему углу прямоугольника, координаты и размер которого определен при вызове функции CreateWindow. Этот стиль должен использоваться вместе со стилем SBS_SIZEBOX. Для высоты и ширины органа управления используются системные значения
SBS_SIZEBOXTOPLEFTALIGNАналогично SBS_SIZEBOX, но верхний левый угол прямоугольника выравнивается по верхнему левому углу прямоугольника, координаты и размер которого определен при вызове функции CreateWindow. Этот стиль должен использоваться вместе со стилем SBS_SIZEBOX. Для высоты и ширины органа управления используются системные значения
SBS_TOPALIGNСоздается горизонтальная полоса просмотра, высота которой равна высоте системной полосы просмотра. Выполняется выравнивание верхнего края полосы просмотра по верхнему краю прямоугольника, координаты и размер которого определен при вызове функции CreateWindow. Этот стиль должен использоваться вместе со стилем SBS_HORZ
SBS_VERTСоздается вертикальная полоса просмотра. Размер и расположение полосы просмотра определяются при вызове функции CreateWindow

Определение полос просмотра при создании окна

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

Для того чтобы у окна появились вертикальная и горизонтальная полосы просмотра, при регистрации класса окна в третьем парамере функции CreateWindow необходимо указать стили окна WS_VSCROLL или WS_HSCROLL (для того чтобы окно имело и вертикальную, и горизонтальную полосы просмотра, следует указать оба стиля):

hwnd = CreateWindow(szClassName, szWindowTitle,
    // стиль окна
    WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
    CW_USEDEFAULT, CW_USEDEFAULT,
    CW_USEDEFAULT, CW_USEDEFAULT,
    0, 0, hInstance, NULL);

Сообщения от полосы просмотра

Все горизонтальные полосы просмотра, определенные для окна (одним из описанных выше способов) посылают в окно сообщение WM_HSCROLL, а все вертикальные - WM_VSCROLL.

Если полоса просмотра была создана первым способом (как орган управления), эти сообщения будет получать функция родительского окна. Если полоса просмотра была создана вторым способом (определена при создании окна), сообщения от нее будут поступать в функцию окна, имеющего полосы просмотра.

Параметр wParam сообщений полосы просмотра содержит так называемый код полосы просмотра. Этот код соответствует действию, совершенном пользователем над полосой просмотра. Возможны следующие значения (символические константы для них определены в файле windows.h).

Код полосы просмотраОписание
SB_LEFT, SB_TOP (используются одинаковые значения констант для разных символических имен) Сдвиг влево в начало документа (горизонтальная полоса просмотра), сдвиг вверх в начало документа (вертикальная полоса просмотра)
SB_LINELEFT, SB_LINEUPСдвиг влево на одну строку, сдвиг вверх на одну строку
SB_LINERIGHT, SB_LINEDOWNСдвиг вправо на одну строку, сдвиг вниз на одну строку
SB_PAGELEFT, SB_PAGEUPСдвиг на одну страницу влево, сдвиг на одну страницу вверх
SB_PAGERIGHT, SB_PAGEDOWNСдвиг на одну страницу вправо, сдвиг на одну страницу вниз
SB_RIGHT, SB_BOTTOMСдвиг вправо в конец документа, сдвиг вниз в конец документа
SB_THUMBPOSITIONСдвиг в абсолютную позицию. Текущая позиция определяется младшим словом параметра lParam
SB_ENDSCROLLСообщение приходит в тот момент, когда вы отпускаете клавишу мыши после работы с полосой просмотра. Это сообщение обычно игнорируется (передается функции DefWindowProc)
SB_THUMBTRACKПеремещение ползунка полосы просмотра. Текущая позиция определяется младшим словом параметра lParam

В ответ на сообщения полосы просмотра соответствующая функция окна должна вернуть нулевое значение.

Для сообщений SB_THUMBTRACK и SB_THUMBPOSITION младшее слово параметра lParam определяет текущую позицию ползунка на полосе просмотра. Для других сообщений полосы просмотра младшее слово параметра lParam не используется.

Старшее слово параметра lParam содержит идентификатор окна для полосы просмотра (если временное окно имеет полосу просмотра, старшее слово параметра lParam не используется).

Несмотря на то, что в файле windows.h определены константы SB_LEFT, SB_TOP, SB_RIGHT, SB_BOTTOM, полоса просмотра никогда не посылает сообщений со значением параметра wParam, равным этим константам. Однако приложению имеет смысл предусмотреть обработчик для таких сообщений. Это нужно для подключения к полосе просмотра клавиатурного интерфейса.

Если запустить любое стандартное приложение Windows, работающее с текстовыми документами, можно убедится в том, что для свертки окна, отображающего документ, можно использовать не только полосу просмотра, но и клавиши. Обычно это клавиши перемещения курсора и клавиши <PgUp>, <PgDn>. Как правило, с помощью клавиш <Home> и <End> вы можете перейти, соответственно, в начало и в конец документа.

Так как действия, выполняемые при свертке, одинаковы для полосы просмотра и дублирующих ее клавиш, имеет смысл предусмотреть единый обработчик сообщений от полосы просмотра. Для добавления клавиатурного интерфейса обработчик клавиатурного сообщения WM_KEYDOWN может посылать в функцию окна сообщения полосы просмотра. Например, если обработчик сообщения WM_KEYDOWN обнаружил, что вы нажали клавишу <PgUp>, он может послать в функцию окна сообщение WM_VSCROLL со значением wParam, равным SB_PAGEUP. Результат будет в точности такой же, как будто для свертки документа на одну страницу вверх вы воспользовались полосой просмотра, а не клавиатурой.

Если же обработчик клавиатурного сообщения WM_KEYDOWN обнаружил, что вы нажали клавишу <Home> или <End>, он может послать в функцию окна, соответственно, сообщение WM_VSCROLL со значением wParam, равным SB_TOP или SB_BOTTOM. Если в приложении имеются обработчики этих сообщений, они выполнят переход в начало или в конец документа.

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

Инициализация полосы просмотра

Для полосы просмотра определены понятия текущая позиция и диапазон изменения значений позиции. При передвижении ползунка вдоль полосы просмотра текущая позиция принимает дискретные значения внутри диапазона изменения значений позиции. Если ползунок находится в самом левом (для горизонтальной полосы просмотра) или самом верхнем (для вертикальной полосы просмотра) положении, текущая позиция равна минимальной. Если же ползунок находится в самом правом или самом нижнем положении, текущая позиция равна максимальной.

После того как вы создали полосу просмотра одним из описанных выше способов, ее необходимо проинициализировать, указав диапазон изменений значений позиции. Для этого следует вызвать функцию SetScrollRange:

void WINAPI SetScrollRange(HWND hwnd, 
   int fnBar, int nMin, int nMax, BOOL fRedraw);

Параметр hwnd определяет идентификатор окна, имеющего полосу просмотра, или идентификатор полосы просмотра, созданного как орган управления.

Параметр fnBar определяет тип полосы просмотра, для которой выполняется установка диапазона изменения значений позиции:

ЗначениеОписание
SB_CTLУстановка диапазона для полосы просмотра, созданной как орган управления класса "scrollbar". В этом случае параметр hwnd функции SetScrollRange должен указывать идентификатор органа управления, полученный при его создании от функции CreateWindow.
SB_HORZУстановка диапазона горизонтальной полосы просмотра для окна, при создании которого был использован стиль окна WS_HSCROLL. Параметр hwnd функции SetScrollRange должен указывать идентификатор окна, имеющего полосу просмотра
SB_VERTУстановка диапазона вертикальной полосы просмотра для окна, при создании которого был использован стиль окна WS_VSCROLL. Параметр hwnd функции SetScrollRange должен указывать идентификатор окна, имеющего полосу просмотра

Параметры nMin и nMax определяют, соответственно, минимальное и максимальное значение для диапазона. Разность между nMax и nMin не должна превышать 32767.

Параметр fRedraw определяет, следует ли перерисовывать полосу просмотра для отражения изменений. Если указано значение TRUE, после установки диапазона полоса просмотра будет перерисована, а если FALSE - не будет.

В любой момент времени вы можете узнать диапазон для полосы просмотра, вызвав функцию GetScrollRange:

void WINAPI GetScrollRange(HWND hwnd, int fnBar,
   int FAR* lpnMinPos, int FAR* lpnMaxPos);

Параметры hwnd и fnBar аналогичны параметрам функции SetScrollRange. Первый из них определяет идентификатор окна или органа управления, второй - тип полосы просмотра.

Параметры lpnMinPos и lpnMaxPos - указатели на переменные, в которые после возврата из функции будут записаны, соответственно, минимальное и максимальное значение диапазона.

Управление полосой просмотра

Для установки ползунка в заданную позицию следует использовать функцию SetScrollPos:

int WINAPI SetScrollPos(HWND hwnd, int fnBar,
   int nPos, BOOL fRepaint);

Параметры hwnd и fnBar определяют, соответственно, идентификатор окна или органа управления и тип полосы просмотра.

Параметр nPos определяет новое положение ползунка. Значение этого параметра должно находиться в пределах установленного диапазона.

Параметр fRepaint определяет, нужно ли перерисовывать полосу просмотра после установки новой позиции. Если указано TRUE, полоса будет перерисована, если FALSE - нет.

Функция SetScrollPos возвращает значение предыдущей позиции или 0 в случае ошибки.

Для определения текущей позиции надо вызвать функцию GetScrollPos:

int WINAPI GetScrollPos(HWND hwnd, int fnBar);

Параметры этой функции определяют, соответственно, идентификатор окна или органа управления и тип полосы просмотра

Функция возвращает текущую позицию или 0, если идентификатор окна указан неправильно или окно не имеет полосы просмотра.

Иногда бывает нужно убрать из окна одну или обе полосы просмотра. Это нужно, например, в тех случаях, когда, например, после изменения размера окна документ поместился в нем целиком. С помощью функции ShowScrollBar вы можете скрывать или показывать полосы просмотра:

void WINAPI ShowScrollBar(HWND hwnd, int fnBar, BOOL fShow);

Параметр hwnd определяет идентификатор окна, имеющего полосу просмотра, или идентификатор полосы просмотра, созданного как орган управления.

Параметр fnBar определяет тип полосы просмотра, для которой выполняется установка диапазона изменения значений позиции. Кроме описанных нами ранее констант SB_CTL, SB_HORZ и SB_VERT вы можете использовать константу SB_BOTH. Эта константа предназначена для работы сразу с обеими полосами просмотра, определенными в стиле окна.

Параметр fShow определяет действие, выполняемое функцией. Если этот параметр равен TRUE, полоса просмотра (или обе полосы просмотра, если указано SB_BOTH) появляются в окне. Если же указать значение FALSE, полоса просмотра исчезнет.

Программный интерфейс операционной системы Windows версии 3.1 имеет в своем составе функцию EnableScrollBar, позволяющую разрешать или запрещать работу полосы просмотра:

BOOL WINAPI EnableScrollBar(HWND hwnd, int fnBar,
    UINT fuArrowFlag);

Первые два парамера этой функции аналогичны параметрам функции ShowScrollBar.

Параметр fuArrowFlag определяет, какие из кнопок полосы просмотра должны быть заблокированы или разблокированы:

ЗначениеОписание
ESB_ENABLE_BOTHОбе кнопки полосы просмотра разблокированы
ESB_DISABLE_BOTHОбе кнопки полосы просмотра заблокированы
ESB_DISABLE_LEFT, ESB_DISABLE_UP, ESB_DISABLE_LTUP Заблокирована левая кнопка горизонтальной полосы просмотра или верхняя кнопка вертикальной полосы просмотра
ESB_DISABLE_RIGHT, ESB_DISABLE_DOWN, ESB_DISABLE_RTDN Заблокирована правая кнопка горизонтальной полосы просмотра или нижняя кнопка вертикальной полосы просмотра

Функция возвращает значение TRUE при успешном завершении или FALSE при ошибке (если, например, кнопки уже находятся в требуемом состоянии).

Приложение SCROLL

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

Рис. 2.8. Главное окно приложения SCROLL

Главный файл приложения представлен в листинге 2.15.


Листинг 2.15. Файл scroll\scroll.cpp


// ----------------------------------------
// Работа с полосой просмотра
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>

// Идентификатор полосы просмотра
#define ID_SCROLL 1

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "ScrollAppClass";

// Заголовок окна
char const szWindowTitle[] = "Scroll Demo";

// Текущая позиция полосы просмотра
int nPosition;

// Идентификатор окна полосы просмотра
HWND hScroll;

// Идентификатор статического органа
// управления
HWND hStatic;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  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);

  // Создаем полосу просмотра
  hScroll = CreateWindow("scrollbar", NULL,
    WS_CHILD | WS_VISIBLE | SBS_HORZ,
    20, 60,
    200, 15,
    hwnd,
    (HMENU) ID_SCROLL,
    hInstance, NULL);

  // Устанавливаем текущую позицию
  nPosition = 100;

  // Устанавливаем минимальное и максимальное
  // значения для полосы просмотра
  SetScrollRange(hScroll, SB_CTL, 1, 200, TRUE);

  // Устанавливаем ползунок в середину
  // полосы просмотра
  SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);

  // Создаем статический орган управления в виде
  // черного прямоугольника. Длина этого
  // прямоугольника будет определяться текущей
  // позицией полосы просмотра
  hStatic = CreateWindow("static", NULL,
    WS_CHILD | WS_VISIBLE | SS_BLACKRECT,
    20, 40,
    nPosition, 15,
    hwnd, (HMENU) -1,hInstance, NULL);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    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)
{
  switch (msg)
  {
    // Сообщение от горизонтальной полосы просмотра
    case WM_HSCROLL:
    {
      // В зависимости от параметра сообщения
      // изменяем текущую позицию
      switch (wParam)
      {
        case SB_PAGEDOWN:
        {
          nPosition += 10;
          break;
        }
        case SB_LINEDOWN:
        {
          nPosition += 1;
          break;
        }
        case SB_PAGEUP:
        {
          nPosition -= 10;
          break;
        }
        case SB_LINEUP:
        {
          nPosition -= 1;
          break;
        }
        case SB_TOP:
        {
          nPosition = 0;
          break;
        }
        case SB_BOTTOM:
        {
          nPosition = 200;
          break;
        }
        case SB_THUMBPOSITION:
        {
          nPosition = LOWORD (lParam);
          break;
        }
        case SB_THUMBTRACK:
        {
          nPosition = LOWORD (lParam);
          break;
        }
        default:
          break;
      }

      // Ограничиваем пределы изменения текущей
      // позиции значениями от 1 до 200
      if(nPosition > 200) nPosition = 200;
      if(nPosition < 1) nPosition = 1;

      // Устанавливаем ползунок полосы просмотра
      // в соответствии с новым значением
      // текущей позиции
      SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);

      // Устанавливаем новый размер статического
      // органа управления
      MoveWindow(hStatic, 20, 40, nPosition, 15, TRUE);

      return 0;
    }

    // Обеспечиваем управление полосой просмотра
    // при помощи клавиатуры
    case WM_KEYDOWN:
    {
      // В зависимости от кода клавиши функция окна
      // посылает сама себе сообщения, которые
      // обычно генерируются полосой просмотра
      switch (wParam)
      {
        case VK_HOME:
        {
          SendMessage(hwnd, WM_HSCROLL, SB_TOP, 0L);
          break;
        }
        case VK_END:
        {
          SendMessage(hwnd, WM_HSCROLL, SB_BOTTOM, 0L);
          break;
        }
        case VK_LEFT:
        {
          SendMessage(hwnd, WM_HSCROLL, SB_LINEUP, 0L);
          break;
        }
        case VK_RIGHT:
        {
          SendMessage(hwnd, WM_HSCROLL, SB_LINEDOWN, 0L);
          break;
        }
        case VK_PRIOR:
        {
          SendMessage(hwnd, WM_HSCROLL, SB_PAGEUP, 0L);
          break;
        }
        case VK_NEXT:
        {
          SendMessage(hwnd, WM_HSCROLL, SB_PAGEDOWN, 0L);
          break;
        }
      }
      return 0;
    }
    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

После инициализации приложения и создания главного окна приложения функция WinMain создает на базе предопределенного класса "scrollbar" орган управления - полосу просмотра. Для этого она вызывает функцию CreateWindow:

hScroll = CreateWindow("scrollbar", NULL,
    WS_CHILD | WS_VISIBLE | SBS_HORZ,
    20, 60,
    200, 15,
    hwnd,
    (HMENU) ID_SCROLL,
    hInstance, NULL);

В глобальной переменной nPosition хранится значение, соответствующее текущему положению ползунка. Сразу после создания полосы просмотра в эту переменную записывается значение 100.

Далее при помощи функции SetScrollRange функция WinMain задает диапазон полосы просмотра - от 1 до 200:

SetScrollRange(hScroll, SB_CTL, 1, 200, TRUE);

После этого ползунок устанавливается в позицию, соответствующую содержимому переменной nPosition:

SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);

Так как ранее в эту переменную было записано значение 100, ползунок будет установлен в середину полосы просмотра.

Затем функция WinMain создает статический орган управления в виде черного прямоугольника, ширина которого равна значению, записанному в переменную nPosition:

hStatic = CreateWindow("static", NULL,
    WS_CHILD | WS_VISIBLE | SS_BLACKRECT,
    20, 40,
    nPosition, 15,
    hwnd, (HMENU) -1,hInstance, NULL);

Функция главного окна приложения получает от полосы просмотра сообщения с кодом WM_HSCROLL. Обработчик этого сообщения анализирует параметр wParam, определяя действие, послужившее причиной появления сообщения от полосы просмотра. В зависимости от значения параметра wParam обработчик увеличивает или уменьшает содержимое переменной nPosition. При этом он следит, чтобы это содержимое находилось в диапазоне от 1 до 200.

После этого ползунок устанавливается в новое положение:

SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);

Далее обработчик сообщения полосы просмотра устанавливает новую ширину статического органа управления, для чего вызывает функцию MoveWindow:

MoveWindow(hStatic, 20, 40, nPosition, 15, TRUE);

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

Параметр wParam сообщения WM_KEYDOWN содержит код виртуальной клавиши. Этот код анализируется. Если вы нажали, например, клавишу <Home>, функция окна посылает сама себе сообщение с кодом WM_HSCROLL с параметром wParam, имеющим значение SB_TOP. С этой целью вызывается функция SendMessage:

SendMessage(hwnd, WM_HSCROLL, SB_TOP, 0L);

Обработчик этого сообщения устанавливает начальную позицию, равную нулю:

case SB_TOP:
{
  nPosition = 0;
  break;
}

При этом ползунок будет установлен в крайнее левое положение.

Аналогично обрабатываются сообщения, попадающие в функцию окна, когда вы нажимаете другие клавиши. Для плавного изменения размера статического органа управления вы можете использовать клавиши перемещения курсора по горизонтали. Клавиши <PgUp> и <PgDn> обеспечивают скачкообразное изменение размера. И, наконец, для установки минимального и максимального размера вы можете использовать, соответственно, клавиши <Home> и <End>.

Файл определения модуля для приложения SCROLL приведен в листинге 2.16.


Листинг 2.16. Файл scroll\scroll.def


; =============================
; Файл определения модуля
; =============================
NAME SCROLL
DESCRIPTION 'Приложение SCROLL, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

Приложение SCRLMET

Наше следующее приложение демонстрирует использование вертикальной полосы просмотра, определенной в стиле окна, для свертки окна. Оно выводит в окно метрики текста (рис. 2.9).

Рис. 2.9. Главное окно приложения SCRLMET

Приложение сделано на базе приложения TMETRICS, описанного в предыдущем томе "Библиотеки системного программиста". Отличие заключается в том, что теперь главное окно приложения имеет вертикальную полосу просмотра, благодаря которой вы можете просмотреть все значения при почти любом вертикальном размере окна.

Главный файл приложения SCRLMET приведен в листинге 2.17.


Листинг 2.17. Файл scrlmet\scrlmet.cpp


// ----------------------------------------
// Просмотр метрик шрифта
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[]   = "ScrlMetAppClass";
char const szWindowTitle[] = "SCRLMET Application";

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine,
        int       nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  if(!InitApp(hInstance))
      return FALSE;

  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна

    WS_OVERLAPPEDWINDOW | WS_VSCROLL, // стиль окна

    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))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна

  memset(&wc, 0, sizeof(wc));

  // Определяем стиль класса окна, при
  // использовании которого окно требует
  // перерисовки в том случае, если
  // изменилась его ширина или высота 
  wc.style = CS_HREDRAW | CS_VREDRAW;

  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);
}

При создании главного окна в качестве третьего параметра функции CreateWindow передается значение WS_OVERLAPPEDWINDOW | WS_VSCROLL. Благодаря этому в правой части главного окна приложения появляется вертикальная полоса просмотра.

Никаких других особенностей функция WinMain не имеет.

Исходный текст функции главного окна представлен в листинге 2.18.


Листинг 2.18. Файл scrlmet\wndproc.cpp


// =====================================
// Функция WndProc
// =====================================

#define STRICT
#include <windows.h>
#include <stdio.h>
#include <string.h>

void Print(HDC, int, char *);

static int cxChar, cyChar;
static int cxCurrentPosition;
static int cyCurrentPosition;
static int nScrollPos;

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  static WORD cxClient, cyClient;

  HDC hdc;              // индекс контекста устройства
  PAINTSTRUCT ps;       // структура для рисования
  static TEXTMETRIC tm; // структура для записи метрик
                        // шрифта

  switch (msg)
  {
    case WM_CREATE:
    {
      // Получаем контекст отображения,
      // необходимый для определения метрик шрифта
      hdc = GetDC(hwnd);

      // Заполняем структуру информацией
      // о метрике шрифта, выбранного в
      // контекст отображения
      GetTextMetrics(hdc, &tm);

      // Запоминаем значение ширины для
      // самого широкого символа
      cxChar = tm.tmMaxCharWidth;

      // Запоминаем значение высоты букв с
      // учетом межстрочного интервала
      cyChar = tm.tmHeight + tm.tmExternalLeading;

      // Инициализируем текущую позицию
      // вывода текста
      cxCurrentPosition = cxChar;
      cyCurrentPosition = 0;

      // Освобождаем контекст
      ReleaseDC(hwnd, hdc);

      // Начальное значение позиции
      nScrollPos = 0;

      // Задаем диапазон изменения значений
      SetScrollRange(hwnd, SB_VERT, 0, 20, FALSE);

      // Устанавливаем ползунок в начальную позицию
      SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE);

      return 0;
    }

    // Определяем размеры внутренней области окна
    case WM_SIZE:
    {
      cxClient = LOWORD(lParam);
      cyClient = HIWORD(lParam);
      return 0;
    }

    // Сообщение от вертикальной полосы просмотра
    case WM_VSCROLL:
    {
      switch(wParam)
      {
        case SB_TOP:
        {
          nScrollPos = 0;
          break;
        }
        case SB_BOTTOM:
        {
          nScrollPos = 20;
          break;
        }
        case SB_LINEUP:
        {
          nScrollPos -= 1;
          break;
        }
        case SB_LINEDOWN:
        {
          nScrollPos += 1;
          break;
        }
        case SB_PAGEUP:
        {
          nScrollPos -= cyClient / cyChar;
          break;
        }
        case SB_PAGEDOWN:
        {
          nScrollPos += cyClient / cyChar;
          break;
        }
        case SB_THUMBPOSITION:
        {
          nScrollPos = LOWORD(lParam);
          break;
      }
      // Блокируем для того чтобы избежать
      // мерцания содержимого окна при
      // перемещении ползунка
      case SB_THUMBTRACK:
      {
        return 0;
      }
      default:
          break;
      }

      // Ограничиваем диапазон изменения значений
      if(nScrollPos > 20) nScrollPos = 20;
      if(nScrollPos < 0) nScrollPos = 0;

      // Устанавливаем ползунок в новое положение
      SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE);

      // Обновляем окно
      InvalidateRect(hwnd, NULL, TRUE);

      return 0;
    }

    case WM_PAINT:
    {
      // Инициализируем текущую позицию
      // вывода текста
      cxCurrentPosition = cxChar;
      cyCurrentPosition = 0;

      hdc = BeginPaint(hwnd, &ps);

      // Выводим параметры шрифта, полученные во
      // время создания окна при обработке
      // сообщения WM_CREATE
      Print(hdc, tm.tmHeight,          "tmHeight");
      Print(hdc, tm.tmAscent,          "tmAscent");
      Print(hdc, tm.tmDescent,         "tmDescent");
      Print(hdc, tm.tmInternalLeading, "tmInternalLeading");
      Print(hdc, tm.tmExternalLeading, "tmExternalLeading");
      Print(hdc, tm.tmAveCharWidth,    "tmAveCharWidth");
      Print(hdc, tm.tmMaxCharWidth,    "tmMaxCharWidth");
      Print(hdc, tm.tmWeight,          "tmWeight");
      Print(hdc, tm.tmItalic,          "tmItalic");
      Print(hdc, tm.tmUnderlined,      "tmUnderlined");
      Print(hdc, tm.tmStruckOut,       "tmStruckOut");
      Print(hdc, tm.tmFirstChar,       "tmFirstChar");
      Print(hdc, tm.tmLastChar,        "tmLastChar");
      Print(hdc, tm.tmDefaultChar,     "tmDefaultChar");
      Print(hdc, tm.tmBreakChar,       "tmBreakChar");
      Print(hdc, tm.tmPitchAndFamily,  "tmPitchAndFamily");
      Print(hdc, tm.tmCharSet,         "tmCharSet");
      Print(hdc, tm.tmOverhang,        "tmOverhang");
      Print(hdc, tm.tmDigitizedAspectX,"tmDigitizedAspectX");
      Print(hdc, tm.tmDigitizedAspectY,"tmDigitizedAspectY");

      EndPaint(hwnd, &ps);
      return 0;
    }

    // Обеспечиваем управление полосой просмотра
    // при помощи клавиатуры
    case WM_KEYDOWN:
    {
      // В зависимости от кода клавиши функция окна
      // посылает сама себе сообщения, которые
      // обычно генерируются полосой просмотра
      switch (wParam)
      {
        case VK_HOME:
        {
          SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0L);
          break;
        }
        case VK_END:
        {
          SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0L);
          break;
        }
        case VK_UP:
        {
          SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0L);
          break;
        }
        case VK_DOWN:
        {
          SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0L);
          break;
        }
        case VK_PRIOR:
        {
          SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0L);
          break;
        }
        case VK_NEXT:
        {
          SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0L);
          break;
        }
      }
      return 0;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

// ==========================================
// Функция для вывода параметров шрифта
// в окно
// ==========================================

void Print(HDC hdc, int tmValue, char *str)
{
  char buf[80];
  int i, y;

  // Вычисляем начальную позицию для вывода
  y = cyCurrentPosition + cyChar * (1 - nScrollPos);

  // Подготавливаем в рабочем буфере
  // и выводим в окно начиная с текущей
  // позиции название параметра
  sprintf(buf, "%s", str);
  i = strlen(str);

  TextOut(hdc,
    cxCurrentPosition, y, buf, i);

  // Подготавливаем в рабочем буфере
  // и выводим в текущей строке окна
  // со смещением значение параметра
  sprintf(buf, "= %d", tmValue);
  i = strlen(buf);

  TextOut(hdc,
    cxCurrentPosition + 12 * cxChar, y, buf, i);

  // Увеличиваем текущую позицию по
  // вертикали на высоту символа
  cyCurrentPosition += cyChar;
}

При создании главного окна обработчик сообщения WM_CREATE получает информацию о метрике шрифта и сохраняет ее в структуре tm. Затем обработчик этого сообщения запоминает высоту и ширину букв системного шрифта и инициализирует текущую позицию для вывода текста. Далее устанавливается начальное значение позиции полосы просмотра, устанавливается диапазон изменения значений и устанавливается ползунок полосы просмотра:

nScrollPos = 0;
SetScrollRange(hwnd, SB_VERT, 0, 20, FALSE);
SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE);

Так как всего выводится 20 параметров, окно содержит 20 строк. Поэтому устанавливается диапазон полосы просмотра (0, 20).

При изменении размера окна (а также в процессе создания окна) функция окна получает сообщение WM_SIZE, которое используется для определения размеров внутренней области окна:

case WM_SIZE:
{
  cxClient = LOWORD(lParam);
  cyClient = HIWORD(lParam);
  return 0;
}

Функция главного окна приложения содержит также обработчик сообщения WM_VSCROLL, поступающего от вертикальной полосы просмотра. Этот обработчик устанавливает новое значение для позиции полосы просмотра в переменной nScrollPos.

Небольшое замечание относительно сообщения WM_VSCROLL, для которого значение wParam равно SB_THUMBTRACK.

Напомним, что такое сообщение поступает в окно при передвижении ползунка по полосе просмотра. Мы обрабатывали это сообщение в предыдущем приложении, изменяя ширину статического органа управления синхронно с передвижениями ползунка. Теперь же мы игнорируем это сообщение:

case SB_THUMBTRACK:
{
  return 0;
}

Зачем мы так поступаем? Дело в том, что вслед за передвижением ползунка нам надо перерисовать главное окно приложения. А это длительный процесс. В тех случаях, когда невозможно обеспечить большую скорость перерисовки окна, нет смысла обрабатывать сообщение полосы просмотра с кодом SB_THUMBTRACK. Вы можете ограничиться обработкой сообщения с кодом SB_THUMBPOSITION, перерисовывая окно только после окончания процесса перемещения ползунка.

После обновления содержимого переменной nScrollPos функция окна устанавливает ползунок в новое положение и объявляет все окно требующим перерисовки:

SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE);
InvalidateRect(hwnd, NULL, TRUE);

Это приведет к тому, что функции главного окна приложения будет передано сообщение WM_PAINT.

Обработчик этого сообщения мало отличается от аналогичного обработчика приложения TMETRICS, рассмотренного нами в предыдущем томе. Он выводит параметры шрифта с помощью функции Print, определенной в приложении.

Функция Print вычисляет начальную позицию по вертикали для вывода текста на основании текущей позиции полосы просмотра:

y = cyCurrentPosition + cyChar * (1 - nScrollPos);

Если значение переменной nScrollPos таково, что начальная позиция по вертикали меньше нуля, текст будет "выводиться" выше внутренней области окна. Строки, выведенные вне внутренней области окна, будут обрезаны.

Файл определения модуля для приложения SCRLMET приведен в листинге 2.19.


Листинг 2.19. Файл scrlmet\scrlmet.def


; =============================
; Файл определения модуля
; =============================
NAME        SCRLMET
DESCRIPTION 'Приложение SCRLMET, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   5120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

Приложение SIZEBOX

Наше следующее приложение создает полосу просмотра, имеющую стиль SBS_SIZEBOX (рис. 2.10).

Рис. 2.10. Главное окно приложения SIZEBOX

Как нетрудно заметить, такая полоса просмотра мало напоминает то, что мы видели раньше. Мы получили полосу просмотра в виде маленького квадратика серого цвета, расположенного в главном окне приложения.

С помощью созданной нами полосы просмотра мы можем изменять размеры окна, не имеющего ни толстой рамки (которая обычно используется для изменения размера окна), ни кнопок минимизации или максимизации окна. Для изменения размера окна установите курсор мыши на квадратик и нажмите левую кнопку мыши. Далее, не отпуская кнопки, перемещайте мышь до тех пор, пока окно не примет нужную форму. Затем отпустите клавишу мыши.

Исходный текст приложения SIZEBOX приведен в листинге 2.20.


Листинг 2.20. Файл sizebox\sizebox.cpp


// ----------------------------------------
// Работа с полосой просмотра,
// имеющей стиль SBS_SIZEBOX
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>

// Идентификатор полосы просмотра
#define ID_SCROLL 1

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "ScrollSizeBoxAppClass";

// Заголовок окна
char const szWindowTitle[] = "SizeBox Demo";

// Идентификатор окна полосы просмотра
HWND hScroll;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна

    // стиль окна
    WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,

    CW_USEDEFAULT,       // задаем расположение
    CW_USEDEFAULT,       // окна, принятое по умолчанию
    200, 200,
    0,                   // идентификатор родительского окна
    0,                   // идентификатор меню
    hInstance,           // идентификатор приложения
    NULL);               // указатель на дополнительные
                         // параметры
  // Если создать окно не удалось, завершаем приложение
  if(!hwnd)
    return FALSE;

  // Рисуем главное окно
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  // Создаем полосу просмотра
  hScroll = CreateWindow("scrollbar", NULL,
    WS_CHILD | WS_VISIBLE |
    SBS_SIZEBOX | SBS_SIZEBOXTOPLEFTALIGN,
    30, 30, 0, 0,
    hwnd, (HMENU) ID_SCROLL, hInstance, NULL);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    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)
{
  switch (msg)
  {
    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Для главного окна приложения мы указываем стиль окна следующим образом:

WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU

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

Далее после инициализации главного окна мы создаем орган управления - полосу просмотра:

hScroll = CreateWindow("scrollbar", NULL,
    WS_CHILD | WS_VISIBLE |
    SBS_SIZEBOX | SBS_SIZEBOXTOPLEFTALIGN,
    30, 30, 0, 0,
    hwnd, (HMENU) ID_SCROLL, hInstance, NULL);

Этот орган управления создается в главном окне приложения в точке с координатами (30, 30). Размер органа управления не указан, так как стиль SBS_SIZEBOXTOPLEFTALIGN предполагает использование предопределенных размеров.

Особенность функции окна для данного приложения заключается в том, что она не обрабатывает никаких сообщений, поступающих от созданной нами полосы просмотра. Это, однако, не означает, что наша полоса просмотра не посылает в окно никакие сообщения. Полоса просмотра, имеющая стиль SBS_SIZEBOX, посылает в функцию окна те же самые сообщения, что и толстая рамка, предназначенная для изменения размера окна. Поэтому наш орган управления можно использовать для изменения размеров окна без дополнительных усилий со стороны программиста.

Файл определения модуля приложения SIZEBOX приведен в листинге 2.21.


Листинг 2.21. Файл sizebox\sizebox.def


; =============================
; Файл определения модуля
; =============================
NAME SIZEBOX
DESCRIPTION 'Приложение SIZEBOX, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

2.4. Редактор текста

В операционной системе Microsoft Windows зарегистрирован класс окна с именем "edit", на базе которого вы можете создать однострочный или многострочный текстовый редактор. Такой редактор может быть использован для ввода значений текстовых или числовых переменных, а также для создания и редактирования текстовых файлов (без функций форматирования текста). Встроенный текстовый редактор умеет выполнять функции выделения текста, может работать с универсальным буфером обмена Clipboard.

Для того чтобы в среде Windows сделать свой собственный текстовый редактор, вам достаточно создать на базе класса "edit" орган управления, вызвав функцию CreateWindow. После этого функция родительского окна будет получать от редактора сообщение с кодом WM_COMMAND (как и от других аналогичных органов управления). Вместе с этим сообщением в функцию окна будут передаваться коды извещения, отражающие изменения состояния редактора текста. Вы также можете с помощью функции SendMessage посылать текстовому редактору около трех десятков различных управляющих сообщений, с помощью которых можно изменять редактируемый текст, получать отдельные строки, выделять фрагменты текста, копировать выделенный фрагмент текста в Clipboard и т. д.

Использование предопределенного класса "edit" - самый простой способ создания в приложении редактора текста. Фактически в этом случае вы будете использовать готовый текстовый редактор. Вам не придется самостоятельно обрабатывать сообщения от клавиатуры, управлять текстовым курсором, учитывать ширину букв в пропорциональном шрифте, выполнять свертку текста в окне редактирования по вертикали или горизонтали, заниматься выделением фрагментов текста, работать с Clipboard или решать другие задачи, возникающие при создании текстовых редакторов.

Создание редактора текста

Для создания редактора текста (однострочного или многострочного) следует вызвать функцию CreateWindow, передав ей в качестве первого параметра указатель на строку "edit":

hwndEdit = CreateWindow("edit", NULL,
       WS_CHILD | WS_VISIBLE | WS_BORDER |
         ES_LEFT,
         30, 30, 300, 30,
         hwnd, (HMENU) ID_EDIT, hInst, NULL);

Заголовок окна не используется, поэтому второй параметр следует указать как NULL.

Если при создании текстового редактора не указать стиль окна WS_BORDER, область редактирования не будет выделена. Это неудобно для пользователя, особенно если в окне имеется несколько редакторов. При использовании стиля WS_BORDER вокруг редактора будет нарисована рамка.

Кроме обычных стилей окна для текстового редактора указывают стили, символическое имя которых начинается с префикса ES_. Это стили редактора текста. Они влияют на внешний вид редактора и выполняемые им функции. Подробно стили редактора текста будут описаны в следующем разделе.

Остальные параметры функции CreateWindow указываются так же, как и для других органов управления. Параметры с четвертого по седьмой используются для определения расположения и размеров текстового редактора. Восьмой параметр - идентификатор родительского окна, в функцию которого будет поступать сообщение WM_COMMAND. Девятый параметр определяет идентификатор редактора текста. Десятый указывает идентификатор копии приложения. Последний параметр должен быть задан как NULL.

Стили редактора текста

Приведем список стилей окна, которые используются при создании редактора текста.

СтильОписание
ES_AUTOHSCROLLВыполняется автоматическая свертка текста по горизонтали. Когда при наборе текста достигается правая граница окна ввода, весь текст сдвигается влево на 10 символов
ES_AUTOVSCROLLВыполняется автоматическая свертка текста по вертикали. Когда при наборе текста достигается нижняя граница окна ввода, весь текст сдвигается вверх на одну строку
ES_CENTERЦентровка строк по горизонтали в многострочном текстовом редакторе
ES_LEFTВыравнивание текста по левой границе окна редактирования
ES_LOWERCASEВыполняется автоматическое преобразование введенных символов в строчные (маленькие)
ES_MULTILINEСоздается многострочный редактор текста
ES_NOHIDESELЕсли редактор текста теряет фокус ввода, при использовании данного стиля выделенный ранее фрагмент текста отображается в инверсном цвете. Если этот стиль не указан, при потере фокуса ввода выделение фрагмента пропадает и появляется вновь только тогда, когда редактор текста вновь получает фокус ввода
ES_OEMCONVERTВыполняется автоматическое преобразование кодировки введенных символов из ANSI в OEM и обратно. Обычно используется для ввода имен файлов
ES_PASSWORDЭтот стиль используется для ввода паролей или аналогичной информации. Вместо вводимых символов отображается символ "*" или другой, указанный при помощи сообщения EM_SETPASSWORDCHAR (см. ниже раздел, посвященный сообщениям для редактора текста)
ES_READONLYСоздаваемый орган управления предназначен только для просмотра текста, но не для редактирования. Этот стиль можно использовать в версии 3.1 операционной системы Windows или в более поздней версии
ES_RIGHTВыравнивание текста по правой границе окна редактирования
ES_UPPERCASEВыполняется автоматическое преобразование введенных символов в заглавные (большие)
ES_WANTRETURNСтиль используется в комбинации со стилем ES_MULTILINE. Используется только в диалоговых панелях. При использовании этого стиля клавиша <Enter> действует аналогично кнопке диалоговой панели, выбранной по умолчанию. Этот стиль можно использовать в версии 3.1 операционной системы Windows или в более поздней версии

Для создания однострочного редактора текста достаточно указать стиль ES_LEFT (который, кстати, определен в файле windows.h как 0). Для обеспечения свертки текста по горизонтали используйте дополнительно стиль ES_AUTOHSCROLL.

Если вам нужен многострочный редактор текста, укажите стиль ES_MULTILINE. Для обеспечения автоматической свертки текста по горизонтали и вертикали следует также указать стили ES_AUTOHSCROLL и ES_AUTOVSCROLL.

Если в многострочном редакторе текста не указан стиль ES_AUTOHSCROLL, но указан стиль ES_AUTOVSCROLL, при достижении в процессе ввода текста правой границы окна ввода выполняется автоматический перенос слова на новую строку. Если свертка не используется, в описанной выше ситуации будет выдан звуковой сигнал.

Многострочный редактор текста может иметь вертикальную и горизонтальную полосы просмотра. Для создания полос просмотра достаточно в стиле редактора указать константы WS_HSCROLL и WS_VSCROLL.

Коды извещения

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

Младшее слово параметра lParam содержит идентификатор окна, полученный от функции CreateWindow при создании редактора.

Старшее слово параметра lParam содержит код извещения. Анализируя этот код, приложение может определить событие, послужившее причиной появления сообщения WM_COMMAND.

Приведем список кодов извещений.

Код извещенияОписание
EN_CHANGEИзменилось содержимое текста в окне редактирования
EN_ERRSPACEПроизошла ошибка при попытке получить дополнительную память
EN_HSCROLLВыполнена свертка текста по горизонтали. Пользователь использовал горизонтальную полосу просмотра для свертки текста, но изменения в окне редактирования еще не произошли
EN_KILLFOCUSТекстовый редактор потерял фокус ввода
EN_MAXTEXTПри вводе очередного символа произошло переполнение, так как было превышен максимально допустимый для редактора размер текста
EN_SETFOCUSТекстовый редактор получил фокус ввода
EN_UPDATEСодержимое текстового редактора будет изменено. Пользователь ввел один символ текста или выполнил другую операцию редактирования, но выполнение этой операции еще не отразилось на содержимом окна редактирования. После этого извещения после отображения изменений придет извещение с кодом EN_CHANGE
EN_VSCROLLВыполнена свертка текста по вертикали. Пользователь использовал вертикальную полосу просмотра для свертки текста, но изменения в окне редактироания еще не произошли

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

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

С помощью функции SendMessage вы можете посылать в редактор текста различные сообщения. Коды сообщений, специально предназначенных для текстового редактора, имеют символические имена с префиксом EM_. Приведем список таких сообщений.

EM_CANUNDO

С помощью этого сообщения можно проверить, поддерживает ли редактор текста операцию отмены последнего действия редактирования. Эта операция выполняется по сообщению WM_UNDO, когда оно посылается в редактор текста.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

TRUE, если операция поддерживается, FALSE - если нет

EM_EMPTYUNDOBUFFER

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

EM_FMTLINES

Управление режимом добавления или удаления символов конца строки в процессе переноса слов на новую строку.

Параметры:

wParam = (WPARAM)(BOOL)fAddEOL;

lParam = 0L;

Значение флага fAddEOL: TRUE - вставка, FALSE - удаление.

Возвращаемое значение:TRUE - вставка, FALSE - удаление

EM_GETFIRSTVISIBLELINE

Получение номера самой верхней видимой строки в окне редактирования. Используется в Windows версии 3.1 и более поздних версиях.

Параметры: не используются.

Возвращаемое значение:

Номер строки. Первой строке соответствует значение 0

EM_GETHANDLE

Получение идентификатора локальной памяти, используемой редактором для хранения текста.Параметры: не используются.

Возвращаемое значение:

Идентификатор блока памяти

EM_GETLINE

Копирование строки из редактора текста в буфер.

Параметры:

wParam = (WPARAM)nLine;

lParam = (LPARAM)(LPSTR)lpCh;

nLine - номер строки, lpCh - адрес буфера для строки.

Возвращаемое значение:

Номер скопированных в буфер байт данных или 0, если указанный номер строки превосходит количество строк в тексте

EM_GETLINECOUNT

Определение количества строк в тексте.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

Количество строк текста или 1, если окно редактирования не содержит ни одной строки текста

EM_GETMODIFY

Определение значения флага обновления.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

TRUE, если текст был изменен или FALSE - если нет

EM_GETPASSWORDCHAR

Получение символа, используемого для вывода при вводе пароля. Используется в Windows версии 3.1 и более поздних версиях.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

Код символа

EM_GETRECT

Определение координат прямоугольной области, используемой для редактирования текста. Эта область по своим размерам не обязательно совпадает с областью, занятой самим органом управления.

Параметры:

wParam = 0;

lParam = (LPARAM)(RECT FAR *)lprc;

lprc - указатель на структуру RECT, в которую будут записаны искомые координаты.

Возвращаемое значение: не используется

EM_GETSEL

Определение положения первого и последнего символа в выделенном фрагменте текста.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

Двойное слово. Младшее слово содержит положение первого символа в выделенном фрагменте, старшее - положение символа, следующего за выделенным фрагментом текста

EM_GETWORDBREAKPROC

Получение адреса текущей функции, которая используется для переноса слов с одной строки на другую. Используется в Windows версии 3.1 и более поздних версиях.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

Адрес функции или NULL, если такая функция не существует

EM_LIMITTEXT

Определение максимального количества символов, которое можно ввести в окно редактирования.

Параметры:

wParam = (WPARAM)cCmax;lParam = 0L;

cCMax - размер текста.

Возвращаемое значение: не используется

EM_LINEFROMCHAR

Определение номера строки, содержащей символ в заданной позиции.

Параметры:

wParam = (WPARAM) iChar;

lParam = 0L;

iChar - номер позиции. Можно задать как -1, в этом случае используется текущая строка (строка, в которой установлен текстовый курсор), или строка, в которой начинается выделенный фрагмент текста (если в тексте есть выделенный фрагмент).

Возвращаемое значение:

Номер строки. Первой строке соответствует значение 0

EM_LINEINDEX

Определение смещения в байтах от начала текста заданной строки.

Параметры:

wParam = (WPARAM) nLine;

lParam = 0L;

nLine - номер строки. Можно задать как -1, в этом случае используется текущая строка.

Возвращаемое значение:

Смещение в байтах или -1, если указана строка с номером, превосходящим количество строк в окне редактирования

EM_LINELENGTH

Определение размера строки в байтах.

Параметры:

wParam = (WPARAM) iChar;

lParam = 0L;

iChar - номер позиции символа, который находится в строке. Можно задать как -1, в этом случае используется текущая строка, для которой возвращается количество невыбранных символов

Возвращаемое значение:

Размер строки в байтах

EM_LINESCROLL

Свертка заданного количества строк.

Параметры:

wParam = 0;

lParam = MAKELPARAM(dv, dh);

dv - количество сворачиваемых строк по вертикали,dh - количество символов для свертки по горизонтали, не используется для текста, выравненного по правой границе или центрированного.

Возвращаемое значение:

TRUE, если сообщение был послано многострочному редактору, или FALSE - если однострочному

EM_REPLACESEL

Заменить выделенный фрагмент текста. Если в тексте нет выделенных фрагментов, строка будет вставлена в текущей позиции.

Параметры:

wParam = 0;

lParam = (LPARAM)(LPCSTR)lpszStr

;lpszStr - адрес строки, которая должна заместить собой выделенный текст

Возвращаемое значение: не используется

EM_SETHANDLE

Назначение буфера для хранения текста.

Параметры:

wParam = (WPARAM)(HLOCAL)hLoc;

lParam = 0L;

hLoc - идентификатор локального блока памяти, полученный с помощью функции LocalAlloc.

Возвращаемое значение: не используется

EM_SETMODIFY

Установка флага обновления.

Параметры:

wParam = (WPARAM)(UINT)fMod;

lParam = 0L;

fMod - новое значение для флага обновления. TRUE, если текст надо отметить, как обновленный, FALSE - если как необновленный.

Возвращаемое значение: не используется

EM_SETPASSWORDCHAR

Установка символа, который используется для вывода текста (в редакторе, имеющим стиль ES_PASSWORD).

Параметры:

wParam = (WPARAM)(UINT)chChar;

lParam = 0L;

chChar - код символа.

Возвращаемое значение:

TRUE, если сообщение посылается редактору, созданному как орган управления

EM_SETREADONLY

Установка или сброс состояния редактора, в котором пользователю позволяется только просматривать текст, но не редактировать (режим "только чтение").

Параметры:

wParam = (WPARAM)(UINT)fReadOnly;

lParam = 0L;

fReadOnly - TRUE для установки режима "только чтение", FALSE - для сброса.

Возвращаемое значение:

TRUE, если установка выполнена без ошибок или FALSE при ошибке

EM_SETRECT

Изменение размеров или расположения области, используемой для редактирования текста. Эта область находится внутри окна органа управления и сразу после создания совпадает с этим окном по размерам и расположению.

Параметры:

wParam = 0;

lParam = (LPARAM)(RECT FAR *)lprc;

lprc - указатель на структуру RECT, в которую будут записаны новые координаты области.

Возвращаемое значение: не используется

EM_SETRECTNP

Аналогично предыдущему, за исключением того что окно редактирования не перерисовывается

EM_SETSEL

Выделение заданных символов в окне редактирования.

Параметры:

wParam = (WPARAM)(UINT)fScroll;

lParam = MAKELPARAM(ichStart, ichEnd);

fScroll - если этот параметр равен 1, текстовый курсор сворачивается, если 0 - нет.

ichStart - начальная позиция.

ichEnd - конечная позиция. Если начальная позиция равна 0, а конечная -1, выбирается весь текст. Если начальная позиция равна -1, выделение фрагмента (если оно было) исчезает.

Возвращаемое значение:

TRUE, если сообщение посылается редактору, созданному как орган управления

EM_SETTABSTOPS

Установка позиций табуляции.

Параметры:

wParam = (WPARAM)cTabs;

lParam = (LPARAM)(const int FAR *) lpTabs;

cTabs - расстояние для табуляции. Если этот параметр указан как 0, используется значение по умолчанию - 32.

lpTabs - массив беззнаковых целых чисел, определяющих расположение позиций табуляции в единицах, используемых для диалоговых панелей. Эти единицы будут описаны в следующей главе.

Возвращаемое значение:

TRUE, если позиции табуляции были установлены ил FALSE при ошибке

EM_SETWORDBREAKPROC

Установка новой функции для переноса слов с одной строки на другую. Используется в Windows версии 3.1 и более поздних версиях.

Параметры:

wParam = 0;

lParam = (LPARAM)(EDITWORDBREAKPROC) ewpbrc;

ewpbrc - адрес переходника для новой функции, которая будет использована для переноса слов. Этот адрес необходимо получить при помощи функции MakeProcInstance, указав последней адрес функции переноса слов.

Возвращаемое значение: не используется

EM_UNDO

Отмена последней операции редактирования текста.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение: не используется

Помимо описанных выше, текстовому редактору можно посылать некоторые сообщения, символические имена которых начинаются с префикса WM_. Это сообщения WM_COPY, WM_PASTE, WM_CUT, WM_CLEAR. Приведем краткое описание этих сообщений.

WM_COPY

Копирование выделенного фрагмента текста в универсальный буфер обмена Clipboard.

Параметры: wParam = 0; lParam = 0L;

Возвращаемое значение: не используется

WM_PASTE

Вставка текста из буфера обмена Clipboard в текущую позицию редактируемого текста.

Параметры: wParam = 0; lParam = 0L;

Возвращаемое значение: не используется

WM_CUT

Удаление выделенного текста с записью его в Clipboard. Удаленный текст можно восстановить, если послать в редактор сообщение EM_UNDO.

Параметры: wParam = 0; lParam = 0L;

Возвращаемое значение: не используется

WM_CLEAR

Удаление выделенного текста без записи в Clipboard. Удаленный текст можно восстановить, если послать в редактор сообщение EM_UNDO.

Параметры: wParam = 0; lParam = 0L;

Возвращаемое значение: не используется

Приложение EDIT

После описания всех стилей и сообщений текстового редактора у вас могло сложиться впечатление, что создание текстового редактора - очень сложная задача. Однако вам редко нужны все возможности органа управления класса "edit". Обычно для приложения требуется несложный однострочный или многострочный редактор, позволяющий вводить отдельные строки текста.

Приложение EDIT (рис. 2.11) демонстрирует работу с простейшим однострочным текстовым редактором.

Рис. 2.11. Главное окно приложения EDIT

Это приложение создает в своем главном окне однострочный редактор текста и кнопку с надписью "OK". Вы можете вводить текст при помощи клавиатуры, выделять фрагменты текста мышью или клавиатурой, а также копировать текст в буфер Clipboard или вставлять текст из этого буфера в окно редактирования. Для выделения текста и работы с буфером Clipboard вы можете использовать стандартные приемы, описанные в руководстве пользователя операционной системы Windows.

После ввода текста нажмите клавишу "OK". На экране появится сообщение, состоящее из введенного вами текста (рис. 2.12).

Рис. 2.12. Сообщение приложения EDIT

Главный файл приложения EDIT приведен в листинге 2.22.


Листинг 2.22. Файл edit\edit.cpp


// ----------------------------------------
// Редактор текста
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>

// Идентификатор редактора текста
#define ID_EDIT   1

// Идентификатор кнопки
#define ID_BUTTON 2

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "EditAppClass";

// Заголовок окна
char const szWindowTitle[] = "Edit 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)
{
  // Идентификатор редактора текста
  static HWND hEdit;

  // Идентификатор кнопки
  static HWND hButton;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Создаем редактор текста
      hEdit = CreateWindow("edit", NULL,
         WS_CHILD | WS_VISIBLE | WS_BORDER |
         ES_LEFT,
         30, 30, 300, 30,
         hwnd, (HMENU) ID_EDIT, hInst, NULL);

      // Создаем кнопку
      hButton = CreateWindow("button", "OK",
         WS_CHILD | WS_VISIBLE |
         BS_PUSHBUTTON,
         30, 80, 100, 30,
         hwnd, (HMENU) ID_BUTTON, hInst, NULL);

      return 0;
    }

    // Когда главное окно приложения получает
    // фокус ввода, отдаем фокус редактору текста
    case WM_SETFOCUS:
    {
      SetFocus(hEdit);
      return 0;
    }

    case WM_COMMAND:
    {
      // Обработка извещения текстового редактора
      // об ошибке
      if(wParam == ID_EDIT)
      {
        if(HIWORD(lParam) == EN_ERRSPACE)
        {
          MessageBox(hwnd, "Мало памяти",
           szWindowTitle, MB_OK);
        }
      }

      // Сообщение от кнопки
      else if(wParam == ID_BUTTON)
      {
         BYTE chBuff[80];
         WORD cbText;

         // Записываем в первое слово буфера
         // значение размера буфера в байтах
         * (WORD *) chBuff = sizeof (chBuff) - 1;

         // Получаем от редактора текста содержимое
         // первой строки. Функция возвращает количество
         // байт, скопированных в буфер
         cbText = SendMessage(hEdit, EM_GETLINE, 0,
           (LPARAM)(LPSTR)chBuff);

         // Закрываем буфер двоичным нулем
         chBuff[cbText] = '\0';

         // Выводим содержимое буфера на экран
         MessageBox(hwnd, chBuff,
         szWindowTitle, MB_OK);
      }
      return 0;
    }

    case WM_PAINT:
    {
      HDC hdc;
      PAINTSTRUCT ps;

      // Получаем индекс контекста устройства
      hdc = BeginPaint(hwnd, &ps);

      // Выводим текстовую строку
      TextOut(hdc, 30, 10,
        "Введите строку и нажмите кнопку 'OK'", 36);

      // Отдаем индекс контекста устройства
      EndPaint(hwnd, &ps);
      return 0;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

В начале исходного текста определены идентификаторы редактора текста и кнопки:

#define ID_EDIT   1
#define ID_BUTTON 2

Функция WinMain приложения EDIT не имеет никаких особенностей. Она создает одно главное окно и организует цикл обработки сообщений. Так как текстовый редактор работает с символьными сообщениями, в цикле обработки сообщений вызывается функция TranslateMessage:

while(GetMessage(&msg, 0, 0, 0))
{
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}

В функции главного окна приложения определены две статические переменные для хранения идентификаторов созданных органов управления - редактора текста и кнопки:

static HWND hEdit;
static HWND hButton;

По сообщению WM_CREATE функция окна создает редактор текста и кнопку, вызывая функцию CreateWindow. При создании текстового редактора используется комбинация стилей WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT. При этом создается дочернее окно, которое изначально является видимым и имеет рамку. Для текста задается выравнивание по левой границе:

hEdit = CreateWindow("edit", NULL,
   WS_CHILD | WS_VISIBLE | WS_BORDER |
   ES_LEFT,
   30, 30, 300, 30,
   hwnd, (HMENU) ID_EDIT, hInst, NULL);

Когда главное окно приложения получает фокус ввода, в его функцию передается сообщение WM_SETFOCUS. Но нам нужно, чтобы фокус ввода получил редактор текста, так как именно он будет обрабатывать символьные сообщения, поступающие от клавиатуры. Поэтому в ответ на сообщение WM_SETFOCUS функция главного окна отдает фокус ввода текстовому редактору, вызывая функцию SetFocus:

case WM_SETFOCUS:
{
  SetFocus(hEdit);
  return 0;
}

Сообщение WM_COMMAND может приходить в функцию главного окна от текстового редактора или от кнопки.

Если сообщение пришло от текстового редактора, параметр wParam этого сообщения содержит идентификатор редактора ID_EDIT. В этом случае обработчик сообщения получает код извещения из старшего байта параметра lParam. Обрабатывается только одно извещение с кодом EN_ERRSPACE (мало памяти).

Если же сообщение WM_COMMAND пришло от кнопки, параметр wParam этого сообщения содержит идентификатор кнопки ID_BUTTON. Обработчик такого сообщения читает содержимое первой (и единственной) строки текстового редактора в специальным образом подготовленный буфер и выводит строку на экран, вызывая функцию MessageBox.

Подготовка буфера заключается в том, что в его первое слово записывается размер буфера в байтах:

* (WORD *) chBuff = sizeof (chBuff) - 1;

Затем текстовому редактору посылается сообщение EM_GETLINE:

cbText = SendMessage(hEdit, EM_GETLINE, 0,
  (LPARAM)(LPSTR)chBuff);

В качестве парамера lParam используется адрес подготовленного буфера.

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

chBuff[cbText] = '\0';
MessageBox(hwnd, chBuff, szWindowTitle, MB_OK);

Обработчик сообщения WM_PAINT выводит в окно приглашение для ввода текста, вызывая функцию TextOut:

TextOut(hdc, 30, 10,
   "Введите строку и нажмите кнопку 'OK'", 36);

Файл определения модуля для приложения EDIT приведен в листинге 2.23.


Листинг 2.23. Файл edit\edit.def


; =============================
; Файл определения модуля
; =============================
NAME EDIT
DESCRIPTION 'Приложение EDIT, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

Приложение TEDIT

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

Рис. 2.13. Главное окно приложения TEDIT

В верхней части главного окна приложения TEDIT расположены четыре кнопки. Окно редактирования имеет горизонтальную и вертикальную полосу просмотра. Размер главного окна приложения можно изменять при помощи толстой рамки. При этом также изменяется размер окна редактирования.

Кнопка "New" предназначена для создания нового текста. Если перед тем как нажать на эту кнопку вы загрузили в редактор текст (или набрали его при помощи клавиатуры), на экран будет выдано предупреждающее сообщение о том, что содержимое редактируемого файла была изменено и его нужно сохранить (рис. 2.14).

Рис. 2.14. Предупреждающее сообщение

Если нажать на кнопку "Yes", вы вернетесь в режим редактирования и сможете сохранить текст, нажав кнопку "Save". Если же нажать на кнопку "No", содержимое окна редактирования будет стерто и вы сможете набирать новый текст.

Кнопка "Open" предназначена для загрузки в редактор текстового файла. Если нажать на эту кнопку, на экране появится стандартная диалоговая панель "Open", с помощью которой вы сможете загрузить файл (рис. 2.15).

Рис. 2.15. Диалоговая панель "Open"

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

Размер загружаемого файла не должен превосходить 32000 байт, в противном случае на экран будет выведено предупреждающее сообщение.

Кнопка "Save" предназначена для сохранения текста в файле. Если нажать на эту кнопку, на экране появится стандартная диалоговая панель "Save As" (рис. 2.16), с помощью которой можно выбрать файл для сохранения текста.

Рис. 2.16. Диалоговая панель "Save As"

И, наконец, последняя кнопка с надписью "Exit" завершает работу приложения. Если перед завершением в окне редактирования имеется несохраненный текст, на экране появляется знакомое вам предупреждающее сообщение (рис. 2.14).

Из приведенного выше описания видно, что наш редактор текста выполняет достаточно сложные функции. Однако тем не менее листинг основного файла исходного текста приложения (листинг 2.24) занимает чуть больше десяти страниц, а размер загрузочного модуля составляет примерно 7 Кбайт. Это возможно благодаря тому, что вся основная работа выполняется не приложением, а модулями, расположенными в библиотеках динамической загрузки операционной системы Windows.


Листинг 2.24. Файл tedit\tedit.cpp


// ----------------------------------------
// Редактор текстовых файлов
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <commdlg.h>
#include <mem.h>
#include <string.h>
#include <stdlib.h>

// Идентификатор редактора текста
#define ID_EDIT   1

// Идентификаторы кнопок
#define ID_NEW    2
#define ID_OPEN   3
#define ID_SAVE   4
#define ID_EXIT   5

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
HFILE OpenFile(void);
HFILE OpenSaveFile(void);

// Имя класса окна
char const szClassName[]   = "TEditAppClass";

// Заголовок окна
char const szWindowTitle[] = "Text Editor";

// Идентификатор копии приложения
HINSTANCE hInst;

// Флаг изменений в тексте
BOOL bUpdate;

// =====================================
// Функция 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)
{
  // Идентификатор редактора текста
  static HWND hEdit;

  // Идентификаторы кнопок
  static HWND hButtNew;
  static HWND hButtOpen;
  static HWND hButtSave;
  static HWND hButtExit;

  // Идентификаторы файлов
  static HFILE hfSrcFile, hfDstFile;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Создаем редактор текста
      hEdit = CreateWindow("edit", NULL,
         WS_CHILD | WS_VISIBLE | WS_BORDER |
         WS_HSCROLL | WS_VSCROLL |
         ES_LEFT | ES_AUTOHSCROLL | ES_AUTOVSCROLL |
         ES_MULTILINE,
         0, 0, 0, 0,
         hwnd, (HMENU) ID_EDIT, hInst, NULL);

      // Устанавливаем максимальную длину
      // редактируемого текста, равную 32000 байт
      SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L);

      // Сбрасываем флаг обновления текста
      bUpdate = FALSE;

      // Создаем кнопки
      hButtNew = CreateWindow("button", "New",
         WS_CHILD | WS_VISIBLE |
         BS_PUSHBUTTON,
         0, 0, 50, 20,
         hwnd, (HMENU) ID_NEW, hInst, NULL);

      hButtOpen = CreateWindow("button", "Open",
         WS_CHILD | WS_VISIBLE |
         BS_PUSHBUTTON,
         50, 0, 50, 20,
         hwnd, (HMENU) ID_OPEN, hInst, NULL);

      hButtSave = CreateWindow("button", "Save",
         WS_CHILD | WS_VISIBLE |
         BS_PUSHBUTTON,
         100, 0, 50, 20,
         hwnd, (HMENU) ID_SAVE, hInst, NULL);

      hButtExit = CreateWindow("button", "Exit",
         WS_CHILD | WS_VISIBLE |
         BS_PUSHBUTTON,
         150, 0, 50, 20,
         hwnd, (HMENU) ID_EXIT, hInst, NULL);

      return 0;
    }

    case WM_SIZE:
    {
      // Устанавливаем размер органа управления
      // (текстового редактора) в соответствии
      // с размерами главного окна приложения
      MoveWindow(hEdit, 0, 20, LOWORD(lParam),
        HIWORD(lParam) - 20, TRUE);
      return 0;
    }

    // Когда главное окно приложения получает
    // фокус ввода, отдаем фокус редактору текста
    case WM_SETFOCUS:
    {
      SetFocus(hEdit);
      return 0;
    }

    case WM_COMMAND:
    {
      // Обработка извещений текстового редактора
      if(wParam == ID_EDIT)
      {
        // Ошибка
        if(HIWORD(lParam) == EN_ERRSPACE)
        {
           MessageBox(hwnd, "Мало памяти",
             szWindowTitle, MB_OK);
        }

        // Произошло изменение в редактируемом
        // тексте
        else if(HIWORD(lParam) == EN_UPDATE)
        {
           // Устанавливаем флаг обновления текста
           bUpdate = TRUE;
        }
        return 0;
      }

      // Нажата кнопка сохранения текста
      else if(wParam == ID_SAVE)
      {
        WORD wSize;
        HANDLE hTxtBuf;
        NPSTR  npTextBuffer;

        // Открываем выходной файл
        hfDstFile = OpenSaveFile();
        if(!hfDstFile) return 0;

        // Определяем размер текста
        wSize = GetWindowTextLength(hEdit);

        // Получаем идентификатор блока памяти,
        // в котором находится редактируемый текст
        hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE,
         0, 0L);

        // Фиксируем блок памяти и получаем указатель
        // на него
        npTextBuffer = (NPSTR)LocalLock(hTxtBuf);

        // Записываем содержимое блока памяти в файл
        if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize))
        {
          // При ошибке закрываем файл и выдаем сообщение
          _lclose(hfDstFile);
          MessageBox(hwnd, "Ошибка при записи файла",
             szWindowTitle, MB_OK);
          return 0;
        }

        // Закрываем файл
        _lclose(hfDstFile);

        // Расфиксируем блок памяти
        LocalUnlock(hTxtBuf);

        // Так как файл был только что сохранен,
        // сбрасываем флаг обновления 
        bUpdate = FALSE;

        return 0;
      }

      // Создание нового файла
      else if(wParam == ID_NEW)
      {
        // Проверяем флаг обновления
        if(bUpdate)
        {
          if(IDYES == MessageBox(hwnd,
           "Файл был изменен. Желаете сохранить?",
           szWindowTitle, MB_YESNO | MB_ICONQUESTION))
             return 0;
        }

        // Сбрасываем содержимое текстового редактора
        SetWindowText(hEdit, "\0");

        return 0;
      }

      // Загрузка файла для редактирования
      else if(wParam == ID_OPEN)
      {
        LPSTR lpTextBuffer;
        DWORD dwFileSize, dwCurrentPos;

        // Проверяем флаг обновления
        if(bUpdate)
        {
          if(IDYES == MessageBox(hwnd,
           "Файл был изменен. Желаете сохранить?",
           szWindowTitle, MB_YESNO | MB_ICONQUESTION))
             return 0;
        }

        // Открываем входной файл.
        hfSrcFile = OpenFile();
        if(!hfSrcFile) return 0;

        // Определяем размер файла
        dwCurrentPos = _llseek(hfSrcFile, 0L, 1);
        dwFileSize   = _llseek(hfSrcFile, 0L, 2);
        _llseek(hfSrcFile, dwCurrentPos, 0);

        // Размер файла не должен превосходить 32000 байт
        if(dwFileSize >= 32000)
        {
          _lclose(hfSrcFile);
          MessageBox(hwnd, "Размер файла больше 32000 байт",
             szWindowTitle, MB_OK);
          return 0;
        }

        // Заказываем память для загрузки файла
        lpTextBuffer = (LPSTR)malloc(32000);
        if(lpTextBuffer == NULL)
           return 0;

        // Загружаем текст из файла в буфер
        _lread(hfSrcFile, lpTextBuffer, dwFileSize);

        // Закрываем буфер двоичным нулем
        lpTextBuffer[(WORD)dwFileSize] = '\0';

        // Закрываем файл
        _lclose(hfSrcFile);

        // Переносим содержимое буфера в
        // текстовый редактор
        SetWindowText(hEdit, lpTextBuffer);

        // Освобождаем буфер
        free((void *)lpTextBuffer);

        // сбрасываем флаг обновления
        bUpdate = FALSE;
      }

      else if(wParam == ID_EXIT)
      {
        // Проверяем флаг обновления
        if(bUpdate)
        {
          if(IDYES == MessageBox(hwnd,
           "Файл был изменен. Желаете сохранить?",
           szWindowTitle, MB_YESNO | MB_ICONQUESTION))
             return 0;
        }

        // Посылаем в функцию главного окна
        // сообщение WM_CLOSE
        SendMessage(hwnd, WM_CLOSE, 0, 0L);
        return 0;
      }
      return 0;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

// -------------------------------
// Функция OpenFile
// Сохранение файла
// -------------------------------

HFILE OpenFile(void)
{
  // Структура для выбора файла
  OPENFILENAME ofn;

  // Буфер для записи пути к выбранному файлу
  char szFile[256];

  // Буфер для записи имени выбранного файла
  char szFileTitle[256];

  // Фильтр расширений имени файлов
  char szFilter[256] =
         "Text Files\0*.txt;*.doc\0Any Files\0*.*\0";

  // Идентификатор открываемого файла
  HFILE hf;

  // Инициализация имени выбираемого файла
  // не нужна, поэтому создаем пустую строку
  szFile[0] = '\0';

  // Записываем нулевые значения во все поля
  // структуры, которая будет использована для
  // выбора файла
  memset(&ofn, 0, sizeof(OPENFILENAME));

  // Инициализируем нужные нам поля

  // Размер структуры
  ofn.lStructSize       = sizeof(OPENFILENAME);

  // Идентификатор окна
  ofn.hwndOwner         = NULL;

  // Адрес строки фильтра
  ofn.lpstrFilter       = szFilter;

  // Номер позиции выбора
  ofn.nFilterIndex      = 1;

  // Адрес буфера для записи пути
  // выбранного файла
  ofn.lpstrFile         = szFile;

  // Размер буфера для записи пути
  // выбранного файла
  ofn.nMaxFile          = sizeof(szFile);

  // Адрес буфера для записи имени
  // выбранного файла
  ofn.lpstrFileTitle    = szFileTitle;

  // Размер буфера для записи имени
  // выбранного файла
  ofn.nMaxFileTitle     = sizeof(szFileTitle);

  // В качестве начального каталога для
  // поиска выбираем текущий каталог
  ofn.lpstrInitialDir   = NULL;

  // Определяем режимы выбора файла
  ofn.Flags =   OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
          | OFN_HIDEREADONLY;
  // Выбираем входной файл
  if (GetOpenFileName(&ofn)) {

    // Открываем выбранный файл
    hf = _lopen(ofn.lpstrFile, OF_READ);

    // Возвращаем идентификатор файла
    return hf;
  }
  // При отказе от выбора возвращаем
  // нулевое значение
  else return 0;
}

// -------------------------------
// Функция OpenSaveFile
// Выбор файла для редактирования
// -------------------------------

HFILE OpenSaveFile(void)
{
  OPENFILENAME ofn;

  char szFile[256];
  char szFileTitle[256];
  char szFilter[256] =
         "Text Files\0*.txt\0Any Files\0*.*\0";

  HFILE hf;

  szFile[0] = '\0';

  memset(&ofn, 0, sizeof(OPENFILENAME));

  ofn.lStructSize       = sizeof(OPENFILENAME);
  ofn.hwndOwner         = NULL;
  ofn.lpstrFilter       = szFilter;
  ofn.nFilterIndex      = 1;
  ofn.lpstrFile         = szFile;
  ofn.nMaxFile          = sizeof(szFile);
  ofn.lpstrFileTitle    = szFileTitle;
  ofn.nMaxFileTitle     = sizeof(szFileTitle);
  ofn.lpstrInitialDir   = NULL;
  ofn.Flags             = OFN_HIDEREADONLY;

  // Выбираем выходной файл
  if (GetSaveFileName(&ofn)) {

    // При необходимости создаем файл
    hf = _lcreat(ofn.lpstrFile, 0);
    return hf;
  }
  else return 0;
}

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

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

По сообщению WM_CREATE создается редактор текста:

hEdit = CreateWindow("edit", NULL,
   WS_CHILD   | WS_VISIBLE | WS_BORDER |
   WS_HSCROLL | WS_VSCROLL |
   ES_LEFT    | ES_AUTOHSCROLL | ES_AUTOVSCROLL |
   ES_MULTILINE,
   0, 0, 0, 0,
   hwnd, (HMENU) ID_EDIT, hInst, NULL);

В нашем приложении используется многострочный редактор текста с рамкой, вертикальной и горизонтальной полосой просмотра, выполняющий функции автоматической свертки текста по вертикали и горизонтали, с выравниванием текста по левой границе.

Пусть вас не смущает, что мы указали нулевые значения для координат и размеров редактора текста. Размеры редактора текста зависят от размеров главного окна приложения, поэтому они будут установлены при обработке сообщения WM_SIZE.

Далее обработчик сообщения WM_CREATE устанавливает максимальную длину редактируемого текста, для чего посылает редактору сообщение EM_LIMITTEXT:

SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L);

После этого сбрасывается флаг обновления текста:

bUpdate = FALSE;

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

Далее в верхней части главного окна приложения создаются четыре кнопки: "New", "Open", "Save" и "Exit".

Во время обработки сообщения WM_SIZE (поступающего в функцию окна при создании окна и при изменении его размера) устанавливаются правильные размеры и расположение редактора текста:

case WM_SIZE:
{
  MoveWindow(hEdit, 0, 20, LOWORD(lParam),
    HIWORD(lParam) - 20, TRUE);
  return 0;
}

Редактор текста располагается на 20 пикселов ниже верхней границы внутренней области окна и имеет высоту, на 20 пикселов меньшую, чем высота внутренней области окна. В результате в верхней части основного окна приложения остается место для четырех кнопок, управляющих работой приложения.

Так же как и в предыдущем приложении, обработчик сообщения WM_SETFOCUS передает фокус ввода текстовому редактору, для чего вызывает функцию SetFocus.

Обработчик сообщения WM_COMMAND получает сообщения, приходящие от окна редактирования и кнопок.

Если сообщение пришло от окна редактирования, проверяется код извещения.

Код извещения EN_ERRSPACE соответствует ошибке при запросе дополнительной памяти. При его обработке выдается предупреждающее сообщение. Код извещения EN_UPDATE поступает при любом изменении содержимого редактируемого текста. Обработчик этого кода извещения устанавливает флаг обновления текста, сигнализируя о том, что вы изменили текст и его необходимо сохранить:

else if(HIWORD(lParam) == EN_UPDATE)
{
   // Устанавливаем флаг обновления текста
   bUpdate = TRUE;
}

Если нажата кнопка сохранения текста, обработчик сообщения WM_COMMAND открывает выходной файл, вызывая функцию OpenSaveFile, определенную в нашем приложении. Последняя использует стандартную диалоговую панель "Save As", с которой вы уже знакомы.

Далее обработчик определяет размер текста, находящегося в окне редактирования (в байтах), вызывая функцию GetWindowTextLength:

wSize = GetWindowTextLength(hEdit);

Далее, посылая сообщение EM_GETHANDLE, функция определяет идентификатор блока памяти, используемой редактором для хранения текста:

hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L);

Управление памятью в Windows мы рассмотрим позже. Сейчас только отметим, что получив идентификатор блока памяти, мы еще не имеем к этой памяти непосредственного доступа. Для получения доступа, а заодно и адреса блока памяти, этот блок следует зафиксировать, вызвав (в нашем случае) функцию LocalLock:

npTextBuffer = (NPSTR)LocalLock(hTxtBuf);

После этого мы записываем весь блок памяти в файл, закрываем файл и освобождаем зафиксированный блок памяти:

if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize))
{
  _lclose(hfDstFile);
  MessageBox(hwnd, "Ошибка при записи файла",
     szWindowTitle, MB_OK);
  return 0;
}
_lclose(hfDstFile);
LocalUnlock(hTxtBuf);

Так как файл был только что записан на диск, мы сбрасываем флаг обновления:

bUpdate = FALSE;

При создании нового файла прежде всего проверяется флаг обновления. Если он сброшен, содержимое текстового редактора сбрасывается, для чего в него записывается пустая строка:

SetWindowText(hEdit, "\0");

При загрузке файла для редактирования после проверки флага обновления вызывается функция OpenFile. Эта функция, определенная в нашем приложении, открывает файл с помощью стандартной диалоговой панели "Open", с которой вы уже знакомы.

Далее определяется размер файла, который не должен превосходить 32000 байт. Если файл имеет подходящий размер, приложение заказывает буфер для загрузки файла, вызывая функцию malloc (для приложений Windows есть и другие способы получения памяти, но этот тоже работает):

lpTextBuffer = (LPSTR)malloc(32000);

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

_lread(hfSrcFile, lpTextBuffer, dwFileSize);
lpTextBuffer[(WORD)dwFileSize] = '\0';

Для переноса текста из буфера в редактор вызывается функция SetWindowText:

SetWindowText(hEdit, lpTextBuffer);

После этого буфер можно освободить, вызвав функцию free.

При завершении работы приложения с помощью кнопки "Exit" после проверки флага обновления в функцию главного окна приложения посылается сообщение WM_CLOSE:

SendMessage(hwnd, WM_CLOSE, 0, 0L);

Файл определения модуля приложения TEDIT приведен в листинге 2.25.


Листинг 2.25. Файл tedit\tedit.def


; =============================
; Файл определения модуля
; =============================
NAME TEDIT
DESCRIPTION 'Приложение TEDIT, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

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

2.6. Список класса COMBOBOX

В этом разделе мы рассмотрим орган управления, создаваемый на базе предопределенного класса "combobox". Этот орган является комбинацией списка и однострочного редактора текста, поэтому для списка "combobox" используются стили, коды извещения и сообщения, аналогичные списку "listbox", а также некоторые сообщения, специфические для редактора текста класса "edit".

Создание списка COMBOBOX

Для того чтобы создать список класса "combobox" приложение должно вызвать функцию CreateWindow, передав в качестве первого параметра указатель на строку "combobox":

hComboBox = CreateWindow("ComboBox", NULL,
   WS_CHILD | WS_VISIBLE | WS_VSCROLL |
   CBS_AUTOHSCROLL | CBS_SIMPLE,
   30, 30, 200, 200,
   hwnd, (HMENU) ID_LIST, hInst, NULL);

Второй параметр функции должен быть указан как NULL.

При создании списка "combobox" указываются специальные стили списка, символические имена которых имеют префикс CBS_.

Остальные параметры функции CreateWindow указываются так же, как и для списка класса "listbox".

Стили списка

Приведем список стилей, которые используются для создания органа управления класса "combobox". Многие из этих стилей вам уже знакомы.

Имя стиляОписание
CBS_AUTOHSCROLLВыполняется автоматическая свертка текста по горизонтали в окне редактирования
СBS_DISABLENOSCROLLЕсли в одноколоночном списке помещаются все строки, вертикальная полоса просмотра изображается в неактивном состоянии. Этот стиль можно указывать для Windows версии 3.1 и более поздних версий
CBS_DROPDOWNСписок остается в невидимом состоянии до тех пор, пока пользователь не нажмет пиктограмму, специально предназначенную для отображения списка
CBS_DROPDOWNLISTАналогично предыдущему, но однострочный текстовый редактор может быть использован только для отображения текста, но не для редактирования
СBS_HASSTRINGSСоздание списка, содержащего строки, который рисуется родительским окном
СBS_NOINTEGRALHEIGHTДопустимо частичное отображение строк
СBS_OEMCONVERTПри вводе символов в окне редактирования выполняется их преобразование из кодировки ANSI в OEM и обратно. Этот стиль используется только совместно со стилями CBS_SIMPLE и CBS_DROPDOWN
СBS_OWNERDRAWFIXEDСоздается список, который рисуется родительским окном, причем все элементы в списке имеют одинаковую высоту
СBS_OWNERDRAWVARIABLEАналогично предыдущему, но элементы списка могут иметь разную высоту
СBS_SIMPLEСоздается список, который всегда виден и расположен под окном однострочного редактора текста, содержащего выделенную в списке строку.
СBS_SORTСтроки списка будут отсортированы

Среди описанных выше стилей можно выделить три базовых.

Стиль CBS_SIMPLE соответствует списку с окном редактирования (или, как его еще называют, окном выбора). Внешний вид такого списка показан на рис. 2.19.

Рис. 2. 19. Список, имеющий стиль CBS_SIMPLE

Если в окне редактирования вводить строку символов (образец), то по мере ввода в списке будут появляться (и выделяться) строки, совпадающие по начальным символам с образцом. Например, если ввести букву "a", в списке окажется выделенной строка, начинающаяся с этой буквы. Если вслед за буквой "a" набрать букву "b", в списке будет выделена строка, начинающаяся с букв "ab" и т. д. Это очень удобно, например, для поиска строки по известным вам начальным буквам.

Если список имеет стиль CBS_DROPDOWN, в исходном состоянии (рис. 2.20) он состоит из окна редактирования и расположенной справа от этого окна пиктограммы со стрелкой (кнопкой, предназначенной для отображения списка).

Рис. 2.20. Список, имеющий стиль CBS_DROPDOWN, в свернутом состоянии

Если нажать на эту пиктограмму левой клавишей мыши, под окном редактирования появится список (рис. 2.21).

Рис. 2.21. Список, имеющий стиль CBS_DROPDOWN, в развернутом состоянии

Стиль CBS_DROPDOWNLIST аналогичен стилю CBS_DROPDOWN, но окно редактирования можно использовать только для просмотра выделенной строки, а не для редактирования или ввода.

Коды извещения

Список "combobox" посылает в родительское окно сообщение WM_COMMAND. Параметр wParam этого сообщения содержит идентификатор списка. Младшее слово параметра lParam содержит идентификатор окна списка, а старшее - код извещения.

Приведем список кодов извещения, поступающих от органа управления класса "combobox".

Код извещенияОписание
CBN_CLOSEUPСписок исчез (стал невидим)
CBN_DBLCLKДвойной щелчок левой клавишей мыши по строке списка, имеющего стиль CBS_SIMPLE
CBN_DROPDOWNСписок стал видимым
CBN_EDITCHANGEПользователь изменил содержимое окна редактирования, причем изменения уже отображены
CBN_EDITUPDATEПользователь изменил содержимое окна редактирования, изменения еще не отображены
CBN_ERRSPACEОшибка при попытке заказать дополнительную память
CBN_KILLFOCUSСписок теряет фокус ввода
CBN_SELENDCANCELПользователь отменил выбор в списке.
CBN_SELENDOKПользователь выбрал строку в списке.
CBN_SELCHANGEИзменился номер выбранной строки (т. е. пользователь выбрал другую строку)
CBN_SETFOCUSСписок получает фокус ввода

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

Для управления списком "combobox" используется набор сообщений, аналогичный набору сообщений для списка "listbox" и редактора текста "edit". Функция SendMessage, посылающая сообщения списку "combobox", возвращает значение, которое зависит от выполняемой функции или коды ошибок CB_ERRSPACE (ошибка при получении дополнительной памяти), CB_ERR (затребованная операция не может быть выполнена). Если операция выполнена без ошибок, возвращается значение CB_OKAY.

В файле windows.h определены сообщения, специально предназначенные для работы со списком "combobox". Символические имена этих сообщений имеют префикс CB_. Приведем список таких сообщений.

CB_ADDSTRING

Добавление строки в список.

Параметры:

wParam = 0;

lParam = (LPARAM)(LPCSTR)lpszStr;

lpszStr - указатель на добавляемую строку.

Возвращаемое значение:

Номер строки в списке (первая строка имеет номер 0), или код ошибки.

CB_DELETESTRING

Удаление строки из списка.

Параметры:

wParam = (WPARAM)nIndex;

lParam = 0L;

nIndex - номер удаляемой строки. Первая строка имеет номер 0.

Возвращаемое значение:

Количество строк, оставшихся в списке, или код ошибки.

CB_DIR

Заполнение списка именами файлов и каталогов, расположенных в текущем каталоге, а также именами дисков.

Параметры:

wParam = (WPARAM)(UINT)uAttr;

lParam = (LPARAM)(LPCSTR)lpszFileSpec;

uAttr - атрибуты файлов;

lpszFileSpec - указатель на строку, содержащую имя файла или шаблон имени файла.

Возвращаемое значение:

Номер последнего имени файла, добавленного в список, или код ошибки.

CB_FINDSTRING

Поиск строки в списке, имеющей заданный префикс.

Параметры:

wParam = (WPARAM)nIndexStart;

lParam = (LPARAM)(LPCSTR)lpszStr;

nIndexStart - номер строки, с которой начинается поиск;

lpszStr- адрес префикса строки, которую нужно найти в списке.

Возвращаемое значение:

Номер найденной строки, или код ошибки (если строки в списке нет).

CB_GETCOUNT

Определение количества строк в списке.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

Количество строк в списке или код ошибки.

CB_GETCURSEL

Определение номера выделенной строки.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

Номер выделенной строки или код ошибки.

CB_GETDROPPEDCONTROLRECT

Определение экранных координат видимой части списка. Используется в Windows версии 3.1 и более поздних версий.

Параметры:

wParam = 0;

lParam = (LPARAM)(RECT FAR *) lprc

;lprc - указатель на структуру RECT, в которую будут записаны искомые координаты.

Возвращаемое значение:

Всегда возвращается CB_OKAY.

CB_GETDROPPEDSTATE

С помощью этого сообщения можно определить, находится список в видимом или невидимом состоянии.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение: TRUE, если список виден, FALSE - если нет.

CB_GETEDITSEL

Определение положения первого и последнего символа в выделенном фрагменте текста.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

Двойное слово. Младшее слово содержит положение первого символа в выделенном фрагменте, старшее - положение символа, следующего за выделенным фрагментом текста

CB_GETEXTENDUI

С помощью этого сообщения можно определить, использует ли список расширенный интерфейс пользователя. Это сообщение используется в Windows версии 3.1 и более поздних версий.При использовании расширенного интерфейса щелчок в окне редактора текста для стиля CBS_DROPDOWMLIST приводит к отображению списка. Список также отображается, когда пользователь нажимает клавишу перемещения курсора вниз <Down>. Если список находится в невидимом состоянии, свертка окна редактирования не выполняется.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение:

TRUE, если расширенный интерфейс пользователя используется, FALSE - если нет.

СB_GETITEMDATA

Получение 32-битового значения, соответствующего заданной строке.

Параметры:

wParam = (WPARAM)nIndex;

lParam = 0L;

nIndex - номер строки, для которой нужно получить значение.

Возвращаемое значение:

Двойное слово, содержащее искомое значение, или код ошибки.

СB_GETITEMHEIGHT

Определение высоты заданной строки в списке, который рисуется родительским окном и имеет переменную высоту элементов. Это сообщение используется в Windows версии 3.1 и более поздних версий.

Параметры:

wParam = (WPARAM)nIndex;

lParam = 0L;

nIndex - номер строки, для которой нужно получить значение.

Возвращаемое значение:

Высота строки в пикселях или код ошибки.

CB_GETLBTEXT

Копирование текста, соответствующего заданной строке, в буфер.

Параметры:

wParam = (WPARAM)nIndex;

lParam = (LPARAM)(int FAR *)lpszBuffer;

nIndex - номер строки.

lpszBuffer - адрес буфера.

Возвращаемое значение:

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

CB_GETLBTEXTLEN

Определение длины строки, содержащейся в списке.

Параметры:

wParam = (WPARAM)nIndex;

lParam = 0L;

nIndex - номер строки.

Возвращаемое значение:

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

CB_INSERTSTRING

Вставка элемента в заданную позицию списка. На расположение строки не влияет стиль LBS_SORT.

Параметры:

wParam = (WPARAM)nIndex;

lParam = (LPARAM)(int FAR *)lpszBuffer;

nIndex - номер позиции, в которую будет вставлена строка.lpszBuffer - адрес буфера.

Возвращаемое значение:

Номер позиции, в которую вставлена строка, или код ошибки.

CB_LIMITTEXT

Определение максимального количества символов, которое можно ввести в окно редактирования.

Параметры:

wParam = (WPARAM)cCmax;

lParam = 0L;

cCMax - размер текста.

Возвращаемое значение: не используется

CB_RESETCONTENT

Удаление всех строк из списка.

Параметры:

wParam = 0;

lParam = 0L;

Возвращаемое значение: не используется.

CB_SELECTSTRING

Поиск строки в списке, которая начинается с символов, соответствующих образцу. Найденная строка становится выбранной.

Параметры:

wParam = (WPARAM)nIndexStart;

lParam = (LPARAM)(int FAR *)lpszBuffer;

nIndexStart - номер строки, с которой начинается поиск.lpszBuffer - адрес буфера, содержащего образец.

Возвращаемое значение:

Номер найденной строки или код ошибки.

CB_SETCURSEL

Выбор указанной строки. Ранее выделенная строка становится невыделенной. Если данная строка находится вне окна отображения, список сворачивается таким образом, чтобы строка стала видимой.

Параметры:

wParam = (WPARAM)nIndex;

lParam = 0L;

nIndex - номер строки. Если указать -1, выделение всех строк будет отменено. При этом функция SendMessage вернет значение CB_ERR, что в данном случае не говорит об ошибке.

Возвращаемое значение:

Код ошибки (если значение nIndex не равно -1).

CB_SETEDITSEL

Выделение заданных символов в окне редактирования.

Параметры:

wParam = (WPARAM)(UINT)fScroll;

lParam = MAKELPARAM(ichStart, ichEnd);

fScroll - если этот параметр равен 1, текстовый курсор сворачивается, если 0 - нет.ichStart - начальная позиция.

ichEnd - конечная позиция.

Если начальная позиция равна 0, а конечная -1, выбирается весь текст. Если начальная позиция равна -1, выделение фрагмента (если оно было) исчезает.

Возвращаемое значение:

TRUE, если сообщение посылается операция выполнена без ошибок или код ошибки.

CB_SETEXTENDEDUI

Установка режима использования расширенного интерфейса пользователя. Это сообщение используется в Windows версии 3.1 и более поздних версий.

Параметры:

wParam = (WPARAM)(BOOL)fExtended;

lParam = 0L;

fExtended - TRUE для установки режима, FALSE - для сброса.

Возвращаемое значение:

CB_OKAY, если сообщение посылается операция выполнена без ошибок или код ошибки CB_ERR.

CB_SETITEMDATA

Установка значения двойного слова, связанного с указанным элементом списка.

Параметры:

wParam = (WPARAM)nIndex;

lParam = (LPARAM)dwData;

nIndex - номер строки.

dwData - значение двойного слова.

Возвращаемое значение:

Код ошибки.

CB_SETITEMHEIGHT

Установка высоты элемента в списке, который рисует родительское окно и имеет переменную высоту элементов. Это сообщение используется в Windows версии 3.1 и более поздних версий.

Параметры:

wParam = (WPARAM)nIndex;

lParam = MAKELPARAM(cyItem, 0);

nIndex - номер строки.

Если список не имеет стиль LBS_OWNERDRAWVARIABLE, значение этого параметра должно быть равно 0.cyItem - высота элемента в пикселах.

Возвращаемое значение:

Код ошибки.

CB_SHOWDROPDOWN

Переключение списка в отображаемое или неотображаемое состояние.

Параметры:

wParam = (WPARAM)(BOOL)fExtended;

lParam = 0L;

fExtended - TRUE для отображения списка, FALSE - для переключения списка в неотображаемое состояние.

Возвращаемое значение:

всегда не равно 0

Приложение COMBO

Для иллюстрации методов работы со списком "combobox" приведем исходные тексты приложения COMBO (листинг 2.30). Это приложение создает в своем главном окне (рис. 2.22) список, предназначенный для выбора файлов, каталогов и дисков (аналогично предыдущему приложению LISTDIR).

Рис. 2.22. Главное окно приложения COMBO

В некоторых случаях орган управления "combobox" удобнее, чем "listbox". Например, если вам надо выбрать имя файла из каталога, содержащего сотни файлов, простой просмотр списка может отнять много времени. Типичный пример - поиск файлов win.ini и system.ini в каталоге операционной системы Windows. В нашем приложении COMBO создается список, имеющий стиль CBS_SIMPLE. Этот стиль позволяет упростить поиск, если вы знаете хотя бы несколько первых букв имени файла. Наберите начало имени в окне редактирования, и имя нужного файла окажется перед вашими глазами (рис. 2.22).

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


Листинг 2.30. Файл combo\combo.cpp


// ----------------------------------------
// Использование органа управления
// класса "combobox" для просмотра
// содержимого каталога
// ----------------------------------------

#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[]   = "ComboAppClass";
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)
{
  static HWND hComboBox;
  static HWND hButton;

  switch (msg)
  {
    case WM_CREATE:
    {
      hComboBox = CreateWindow("ComboBox", NULL,
         WS_CHILD | WS_VISIBLE | WS_VSCROLL |
         CBS_AUTOHSCROLL | CBS_SIMPLE,
         30, 30, 200, 200,
         hwnd, (HMENU) ID_LIST, hInst, NULL);

      SendMessage(hComboBox, CB_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(hComboBox);
      return 0;
    }

    case WM_COMMAND:
    {
      if(wParam == ID_LIST)
      {
        if(HIWORD(lParam) == (unsigned)CBN_ERRSPACE)
        {
          MessageBox(hwnd, "Мало памяти",
           szWindowTitle, MB_OK);
        }

        else if(HIWORD(lParam) == CBN_DBLCLK)
        {
          SendMessage(hwnd, WM_COMMAND, ID_BUTTON, 0L);
          return 0;
        }

        else if(HIWORD(lParam) == CBN_SELCHANGE)
        {
        int uSelectedItem, nSize;
        char Buffer[256];
        HDC hdc;

          uSelectedItem = (int)SendMessage(hComboBox,
             CB_GETCURSEL, 0, 0L);

          if(uSelectedItem != CB_ERR)
          {
             // Получаем строку
             SendMessage(hComboBox, CB_GETLBTEXT,
               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(hComboBox,
           CB_GETCURSEL, 0, 0L);
        if(uSelectedItem != LB_ERR)
        {
           SendMessage(hComboBox, CB_GETLBTEXT,
             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(hComboBox, CB_RESETCONTENT, 0, 0L);
             SendMessage(hComboBox, CB_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_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);
}

Файл определения модуля приложения COMBO приведен в листинге 2.31.


Листинг 2.31. Файл combo\combo.def


; =============================
; Файл определения модуля
; =============================
NAME COMBO
DESCRIPTION 'Приложение COMBO, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple
Назад       Содержание       Вперёд