Untitled
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".
Для создания кнопки, как мы уже говорили, ваше приложение должно
создать дочернее окно на базе предопределенного класса "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. Файл buttonbutton.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. Файл buttonbutton.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. Файл butnctlbutnctl.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. Файл butnctlbutnctl.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. Файл ownbutownbut.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. Файл ownbutownbut.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. Файл ownbutownbut.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. Файл ownbutmybtnup.bmp
Файл mybtndn.bmp (листинг 2.9) хранит изображение кнопки в нажатом
состоянии.
Листинг 2.9. Файл ownbutmybtndn.bmp
Для изображения кнопки в неактивном состоянии используется файл
mybtngr.bmp (листинг 2.10).
Листинг 2.10. Файл ownbutmybtngr.bmp
Для рисования изображения bitmap мы подготовили функцию DrawBitmap
(листинг 2.11). Детальное описание этой функции мы отложим до
главы, посвященной изображениям bitmap.
Листинг 2.11. Файл ownbutdrawbmp.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. Файл ownbutownbut.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.
Они выполняют всю работу, необходимую для организации передачи
фокуса ввода между различными органами управления, расположенными
на диалоговой панели.
Статический орган управления - это окно, создаваемое на базе предопределенного
класса "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. Файл staticstatic.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. Файл staticstatic.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
Полосы просмотра (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. Файл scrollscroll.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. Файл scrollscroll.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. Файл scrlmetscrlmet.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. Файл scrlmetwndproc.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. Файл scrlmetscrlmet.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. Файл sizeboxsizebox.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. Файл sizeboxsizebox.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
В операционной системе 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. Файл editedit.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] = ' ';
// Выводим содержимое буфера на экран
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] = ' ';
MessageBox(hwnd, chBuff, szWindowTitle, MB_OK);
Обработчик сообщения WM_PAINT выводит в окно приглашение для ввода
текста, вызывая функцию TextOut:
TextOut(hdc, 30, 10,
"Введите строку и нажмите кнопку 'OK'", 36);
Файл определения модуля для приложения EDIT приведен в листинге
2.23.
Листинг 2.23. Файл editedit.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. Файл tedittedit.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, " |