Окна приложений в среде Windows
Основы организации приложения в среде Windows
Итак, мы рассмотрим основы оранизации приложения в среде Windows и отметим несколько нюансов:
Приложение в среде Windows, как и в среде DOS, содержит так называемую “главную функцию” (WinMain), вызываемую при запуске приложения. Приложение завершается практически при окончании работы функции WinMain.
Обычно, хотя это и не обязятельно, функция WinMain реализует следующую схему:
1) выполняются требуемые инициализационные действия
2) создается главное окно приложения, для чего часто регистрируется новый класс окон (оконная функция);
3) организуется цикл обработки сообщений приложения. Обычно цикл завершается при закрытии главного окна приложения (не всегда)
4) после завершения цикла обработки сообщений выполняется “деинициализация” данных и освобождение занятых ресурсов, после чего функция WinMain() закнчивается.
Несколько замечаний:
Замечание 1. Если приложение содержит непродолжительные (порядка 1 сек.) операции, не требующие взаимодействия с пользователем (например, только файл-ориентированный ввод-вывод или настройка другого приложения), то эти действия могут быть выполнены непосредственно функцией WinMain() без создания окон и без организации цикла обработки сообщений.
Замечание 2. В некоторых случаях приложение может обойтись без регистрации класса окон и организации цикла обработки сообщений, применяя в качестве главного окна модальный диалог.
Замечание 3. В момент вызова функции WinMain() ей, через аргументы, передается несколько параметров, например хендл копии приложения (hInstance). До вызова WinMain() приложение “не знает” этих данных. Поэтому могут возникать сложности с использованием статических конструкторов объектно-ориентрованных языков (C++).
Эта особенность, вообще говоря совершенно неестественна. Дело в том, что функция WinMain() вызывается не непосредственно средой Windows, а промежуточным startup-кодом, являющимся частью run-time библиотеки (как и в DOS-приложениях). Этот код инициализирует стандартные переменные, кучу, стек, обнуляет неинициаизированные статические данные и вызывает конструкторы статических объектов до вызова функции WinMain().
Windows вызывает непосредственно этот startup-код, передавая ему нужные данные через регистры. То есть, в тот момент, когда вызываются конструкторы статических объектов, параметры функции WinMain() уже известны, и, более того, они даже сохранены в статических переменных. Однако по непонятным соображениям эти переменные не декларированы как публичные и являются локальными для startup-кода.
Замечание 4. Цикл обработки сообщений, в том виде, который рекомендован руководствами, не проверяет наличие окон у приложения. Для его завершения используется сообщение WM_QUIT, извлечение которого из очереди приводит к завершению цикла.
При этом требуется, что бы сообщение WM_QUIT посылалось с помощью функций PostMessage(), PostAppMessage() или PostQuitMessage() (только тогда оно попадает в очередь приложения). Обычно это сообщение посылается при уничтожении главного окна приложения (при обработке сообщения WM_DESTROY направленного этому окну). В более общем случае подразумевается последнее окно приложения.
Вы обязаны сами предусмотреть средства для посылки сообщения WM_QUIT, так как ни один стандартный обработчик не посылет его. Конечно, Вы можете предусмотреть собственные, альтернативные методы для прерывания цикла обработки сообщений. Так, например, Вы можете в цикле обработки сообщений проверять корректность хендла главного окна:
while ( IsWindow( hMainWnd ) ) {
if ( !GetMessage( &msg, NULL, NULL, NULL ) ) break;
TranslateMessage( &msg );
DispatchMessage( &msg );
}
Если цикл обработки сообщений не будет прерван при уничтожении последнего окна приложения, то приложение останется активным, а у Вас уже не будет средств для его завершения, кроме выхода из Windows. При этом Ваше приложение исчезнет из списка приложений Task Manager (этот список, вообще говоря, содержит не задачи, а главные окна приложений).
Замечание 5. Windows не является объектно-ориентированной средой. Хотя окно и может быть названо объектом ООП, но лишь с достаточной натяжкой. Самое существенное отличие окна в Windows от объекта ООП заключается в том, что сообщение, обрабатываемое оконной функцией, во многих случаях не выполняет действий, а является “информативным”, указывая на то, что над окном выполняется та или иная операция какиой-либо внешней функцией.
Поясним это на примере создания окна. В случае “чистого” ООП для создания объекта он должен получить сообщение “create”, обработка которого приведет к его инициализации. В Windows сообщение WM_CREATE не выполняет никаких функций по созданию окна. Оно только информирует окно о том, что в это время окно создается средствами обычной функциональной библиотеки, например посредством функции CreateWindowEx(). Вы можете вообще игнорировать это сообщение, возвращать любой результат, вызывать или не вызывать функцию обработки по умолчанию - окно все равно будет создано.
Если бы все сообщения, получаемые окном были только информационными, то к этому легко можно было бы приспособиться. Однако для некоторых сообщений должна выполняться обработка по умолчанию, если Вы ее не выполнили сами, а для других такая обработка должна выполняться обязательно, даже если Вы уже обработали это сообщение. Все это заметно усложняет написание приложений в среде Windows.
Ситуация дополнительно усугубляется тем, что в документации как правило ничего не сообщается о том, какая обработка сообщения выполняется по умолчанию и, кроме того, по некоторым сообщениям приводятся некорректные (или неполные) сведения об их параметрах, выполняемым функциям, условиям возникновения и возвращаемом результате. Help, сопровождающий компиляторы Borland наиболее полный из всех (но не исчерпывающий).
Для окон, использующих в качестве процедуры обработки сообщений по умолчанию не DefWindowProc(), а иную функцию (например, DefMDIChildProc()), можно уточнить список сообщений обязательно подлежащих обработке по умолчанию. Однако это уточнение касается только тех сообщений, обработку которых для DefWindowProc() можно игнорировать, а для иных функций нельзя, список же того, что можно игнорировать для DefWindowProc(), а что нельзя остается неизвестным.
Замечание 6. В Windows существует определенная путаница терминов. Попробуем разобраться с некоторыми из них. Как известно, окно может находиться в нескольких состояниях:
максимизированом, то есть быть “распахнутым” на весь экран - при этом внутренняя область окна занимает весь экран, кроме небольших полос сверху - где размещается заголовок и меню, снизу - горизонтальная полоса прокрутки и справа - вертикальная полоса прокрутки; рамка окна находится за пределами экрана, мы ее не видим, перемещение окна невозможно.
Для максимизации окна мы можем воспользоваться функцией ShowWindow со следущими возможными параметрами:
ShowWindow( hWnd, SHOW_FULLSCREEN );
ShowWindow( hWnd, SW_SHOWMAXIMIZED );
ShowWindow( hWnd, SW_MAXIMIZE );
максимизированое окно всегда активно и имеет фокус ввода. Когда какое-либо окно максимизируется, все остальные верхние окна получают сообщение WM_SIZE, информирующее о том, что они “закрыты” максимизированным окном.
Мы можем узнать, является ли наше окно максимизированным с помощью функции
BOOL IsZoomed( hWnd );
При использовании системного меню операции максимизации окна соответствует пункт Maximize, выбор которого порождает системную команду SC_MAXIMIZE (или синоним SC_ZOOM). (см. сообщение WM_SYSCOMMAND)
Здесь вместо термина maximize может использоваться zoom.
минимизированным, то есть представленным в виде иконки. Для того, что превратить окно в иконку мы должны воспользоваться одним из способов:
ShowWindow( hWnd, SHOW_ICONWINDOW );
ShowWindow( hWnd, SW_SHOWMINIMIZED );
ShowWindow( hWnd, SW_SHOWMINNOACTIVE );
ShowWindow( hWnd, SW_MINIMIZE );
CloseWindow( hWnd );
Разные способы, использующие ShowWindow, отличаются только правилами активации окна. SW_SHOWMINIMIZED и SHOW_ICONWINDOW отображает окно в виде иконки, делая его активным; SW_SHOWMINNOACTIVE не изменяет текущего активного окна; SW_MINIMIZE (как и функция CloseWindow()) делает активным следующее окно в списке Windows. Последний способ эффективен при минимизации главного окна приложения - так как минимизированное главное окно обычно обозначает передачу активности другому приложению.
Проверить состояние окна можно с помощью функции
BOOL IsIconic( hWnd );
При использовании системного меню превращению окна в иконку соответствует пункт ‘Minimize’, порождающий системную команду SC_MINIMIZE (или синоним SC_ICON). (см. сообщение WM_SYSCOMMAND)
В этом случае используется сразу три разных термина для обозначения одного и того-же: minimize, close и iconic. При этом функция CloseWindow() является единственной, интерпретирующей термин close таким способом; в остальных случаях close означает действительно закрытие (иногда уничтожение) окна. Здесь же надо, чтто термин open, применяемый к минимизированному окну обозначает его максимизацию или восстановление нормальных размеров.
нормальным, то есть мы видим (или можем увидеть) его рамку, мы можем перемещать окно по экрану. Когда окно находится в нормальном состоянии, то для него определены максимально и минимально допустимый размеры. Эти размеры нельзя путать с максимизированным и минимизированным состояниями. Максимальный размер нормального окна может даже превышать размер окна в максимизированном состоянии, минимальный размер это обычно такой размер, при котором окно еще может быть корректно представлено в виде окна.
Для перехода из минимизированого состояния к нормальному можно воспользоваться функцией OpenIcon( hWnd ); или, как из минимизированого, так и из максимизированого состояния можно пользоваться функцией ShowWindow() с параметрами:
ShowWindow( hWnd, SHOW_OPENWINDOW );
ShowWindow( hWnd, SW_SHOWNORMAL );
ShowWindow( hWnd, SW_RESTORE );
ShowWindow( hWnd, SW_SHOWNOACTIVATE );
В документации (SDK Help) указано, что SW_RESTORE и SW_SHOWNORMAL эквивалентны, но это далеко не так - SW_RESTORE восстанавливает предыдущее состояние, а не нормальное. То есть, если Вы минимизировали окно из максимизированного, то SW_RESTORE вернет Вас к максимизированному окну, а SW_SHOWNORMAL - к нормальному. SW_SHOWNORMAL имеет синоним SHOW_OPENWINDOW.
Если окно восстанавливается или максимизируется из минимизированного состояния, то Ваше окно получит сообщение WM_QUERYOPEN - обрабатывая которое Вы можете разрешить или запретить дальнейшие действия. Если Вы возвращаете TRUE, то окно будет раскрыто, а если Вы вернете FALSE, то окно останется минимизированным.
Замечание 7. Дополнительно надо разобраться с несколькими терминами Windows, которые постоянно применяются, но никак в документации не описаны. Речь идет о хендлах копии приложения (HINSTANCE), модуля (HMODULE) и задачи (HTASK). Все эти хендлы используются разными функциями, причем разница между ними никак не поясняется. Поэтому нам надо рассмотреть эти хендлы более подробно:
HTASK описывает задачу.
В Windows 3.x под задачей подразумевается конкретный запущеный процесс, для которого определены командная строка, текущая выполняемая инструкция, указатель на стек, переменные окружения, PDB (эквивалент префикса задачи (PSP) в среде DOS) и пр. Хендл задачи можно получить с помощью функции
HTASK GetCurrentTask( void );
В Win32 хендл задачи не применяется, а вместо него надо пользоваться хендлами и идентификаторами процесса и потока. Их можно получить с помощью функций:
HANDLE GetCurrentProcess( void );
HANDLE OpenProcess( fdwAccess, fInherit, dwIDProccess );
DWORD GetCurrentProcessId( void );
HANDLE GetCurrentThread( void );
DWORD GetCurrentThreadId( void );
Функции GetCurrentProcess и GetCurrentThread возвращают так называемый псевдодескриптор процесса (потока). Псевдодескриптор – это некоторая величина, рассматриваемая в качестве дескритора текущего процесса (потока). То есть эта величина, применяемая в контексте другого процесса (потока), будет описывать его, а не данный поток. Для получения “настоящего” хендла надо воспользоваться функцией:
BOOL DuplicateHandle(
hSourceProcess, hSourceHandle, hTargetProcess, lphTargetHandle,
fdwAccess, fInherit, fdwOptions
);
HINSTANCE описывает копию приложения.
в Windows 3.x этот хендл указывает на сегмент данных приложения, который содержит стек и локальную кучу. Для каждого запущенного приложения создается свой собственный сегмент данных, что позволяет однозначно определить конкретную копию приложения по его сегменту данных или организовать обмен данными между двумя копиями одного приложения. Так функция GetInstanceData позволяет скопировать данные, принадлежащие сегменту данных другой копии, в то-же самое место текущей копии.
int GetInstanceData( hInstance, pByte, cbData );
В функцию WinMain передается как хендл текущей копии приложения, так и хендл предыдущей копии, что позволяет организовать взаимодействие двух копий между собой, предотвратить запуск других копий, если это может привести к ошибке, или для выполнения действий, необходимых только в первой копии приложения.
В Win32 для каждого запущенного приложения (т.е. процесса) выделяется виртуальное адресное пространство в 4G в едином сегменте. Поэтому данный хендл описывает не сегмент данных (который описывает весь 4G сегмент), а адрес в виртуальном пространстве, с которого был загружен данный модуль. В адресном пространстве одного процесса никаких других приложений не существует, поэтому этот хендл не может применяться для обнаружения других копий приложения и тем более для обмена данными между разными копиями приложений. В приложениях Win32 hPrevInstance всегда равен NULL, а хендл текущей копии приложения в большинстве случаев совпадает. При необходимости обнаружения других копий приложения надо использовать какие–либо иные методы, например функцию:
HWND FindWindow( lpszClassName, lpszWindowTitle );
Хендл окна в Win32 является уникальным и может идентифицировать конкретное окно в любом приложении.
Для обмена данными между приложениями (процессами) приходится передавать данные из адресного пространства одного процесса в адресное пространство другого. Для выполнения этих операций предусмотрено сообщение WM_COPYDATA. Когда Вы посылаете это сообщение окну, созданному другим процессом, указанные Вами данные копируются в адресное пространство другого процесса и могут быть прочитаны оконной процедурой окна–получателя. Этот механизм может применяться и для обмена данными между 16-ти и 32-х битовыми приложениями, однако для этого необходимо определить номер сообщения WM_COPYDATA и специальную структуру COPYDATASTRUCT для 16-ти битовой платформы – так как файл windows.h не содержит этих определений:
#define WM_COPYDATA 0x004A
typedef struct tagCOPYDATASTRUCT {
DWORD dwData;
DWORD cbData;
LPVOID lpData;
} COPYDATASTRUCT, FAR* PCOPYDATASTRUCT;
HMODULE описывает отдельный модуль.
В Windows 3.x под модулем понимается отдельный выполняемый файл или библотека динамической компоновки. Для описания модуля создается специальный сегмент описания модуля, содержащий информацию о всех сегментах данного модуля и их атрибутах. Хендл модуля идентифицирует этот сегмент. Для любого приложения создается только один описывающий сегмент, который разделяется между всеми копиями этого приложения. Для получения хендла модуля Вы можете воспользоваться функциями:
HMODULE GetModuleHandle( lpszFileName );
int GetModuleFileName( hInstance, lpsBuffer, cbMaxSize );
В большинстве случаев, функции Windows API, работающие с хендлом модуля, корректно выполняются при передачи им хендла копии приложения, так что в документации возможен некоторый разнобой в используемых терминах.
В Win32 хендл модуля является синонимом хендла копии приложения. В документации встречаются оба термина, как они перекочевали из 16-ти битовых Windows, хотя они тождественны.
Сообщения. Посылка и передача сообщений
Ранее, на первых лекциях, мы рассматривали метод передачи сообщений, называемый “посылкой” сообщений (post). При использовании этого метода сообщение ставится в очередь приложения и позже извлекается из нее. Однако этот механизм не всегда удобен, так как не позволяет получить результата обработки сообщения, или дождаться его завершения. Точнее, позволяет, но очень громоздким способом.
Для решения этих задач вводится альтернативный механизм, называемый передачей сообщений. При этом сообщение в очередь не попадает и направляется непосредственно оконной функции. По сути его можно рассматривать как непосредственный вызов оконной функции с передачей ей указанных параметров. Это накладывает некоторые ограничения; так, например, нельзя передавать сообщение WM_QUIT - оно обязательно должно пройти через очередь сообщений, иные сообщения (скажем, клавиатуры) дополнительно обрабатываются в цикле обработки сообщений и их тоже надо посылать а не передавать и т.д.
Кроме того сложности возникают при использовании многопоточных приложений Win32 API - там принято, что сообщения, направленные окну, обязательно обрабатываются тем потоком, который это окно создал. Это существенно усложняет процесс передачи (не посылки) сообщений окну, созданному другим потоком - передающий сообщение поток помещает это сообщение в очередь принимающего потока с флагом ‘переданное сообщение’ и приостанавливается до получения ответа. Принимающий поток, закончив обработку текущего сообщения, извлекает из очереди первыми сообщения, помеченные как переданные, обрабатывет их и после этого возобновляет работу пославшего потока. Однако при этом возможно зависание обеих потоков, если поток, принимающий сообщение, пытается передать вызывающему потоку подтверждение - тот находится в остановленном состоянии и не может обработать подтверждения, а принимающий в свою очередь останавливается, пока подверждение не будет обработано.
Для посылки и передачи сообщений могут применяться следующие функции:
BOOL PostMessage( hWnd, wMsg, wPar, lPar );
Посылает указанное сообщение окну (через очередь сообщений).
LONG SendMessage( hWnd, wMsg, wPar, lPar );
Передает сообщение окну (прямой вызов обработчика сообщений) и звращает управление в вызвавшую процедуру после обработки указанного сообщения. При этом она возвращает значение, возвращаемое оконной функцией. При использовании этой функции сообщение вообще не поступает в очередь приложения (кроме приложений Win32 API). То есть Вы можете воспользоваться этой функцией для обработки тех или иных сообщений до организации цикла обработки сообщений.
BOOL PostAppMessage( hTask, wMsg, wPar, lPar ); // Windows 3.x
BOOL PostAppMessage( dwProccessId, wMsg, wPar, lPar ); // Win32 API
BOOL PostThreadMessage( dwThreadId, wMsg, wPar, lPar ); // Win32 API
Посылает сообщение конкретной задаче. При этом в очередь соответствующего приложения помещается сообщение, имеющее нулевой хендл окна - получателя. Соответственно такое сообщение не диспетчеризуется никакому окну.
Хендл задачи hTask не является хендлом копии приложения. Вы можете использовать функцию GetCurrentTask для получения хендла задачи в среде Windows 3.x, а также функции GetCurrentThreadId и GetCurrentProcessId в Win32 API.
для получения этого хендла.
BOOL PostQuitMessage( wPar );
Посылает сообщение WM_QUIT с заданным параметром wPar вашему приложению. Сообщение WM_QUIT используется для завершения главного цикла обработки сообщений.
Типичные последовательности сообщений, получаемых окном.
Сейчас мы займемся изучением основных сообщений, используемых окном. При этом мы рассмотрим несколько типичных последовательностей сообщений, получаемых окном. Так, например, мы рассмотрим последовательность сообщений, получаемых окном при его создании. При этом надо считать, что приводимые сообщения в последовательности являются как бы “скелетом”, а реальная последовательность может иметь большее число сообщений, или некоторые могут отсутствовать. Это определяется характеристиками окна, его свойствами и выполняемой дополнительной обработкой сообщений. (Так, например, последовательности сообщений, получаемых “скрытыми” окнами или окнами в минимизированном состоянии могут существенно отличаться).
В рассматриваемых нами последовательностях сообщений большинство сообщений не проходят через очередь сообщений, а передаются непосредственно оконной процедуре какой-либо функцией. Именно логика выполнения этой функции и определяет последовательность получения сообщений. Кроме того, некоторые сообщения в этих цепочках являются как бы “вложенными” - то есть они порождаются при обработке какого-либо иного сообщения.
Инициализация окна
Для создания окна используется функция CreateWindow(). Во время ее выполнения окно получает несколько сообщений:
WM_GETMINMAXINFO 0 &MINMAXINFO
Информация о допустимых размерах окна; данные в структуре MINMAXINFO задаются ДО передачи сообщения, так что вы можете их не изменять, а только прочитать при необходимости. (Для получения данных Вы можете вместо обработки этого сообщения воспользоваться функцией GetWindowPlacement()).
WM_NCCREATE 0 &CREATESTRUCT
Создание внешней (non-client) области окна. Обработка, предусмотренная процедурой DefWindowProc() инициализирует необходимые структуры и, в частности, выделяет пространство для хранения заголовка окна. Никакого рисования не выполняется. Возвращаемый процедурой 0 указывает на возникшую ошибку и окно не создается; не 0 указывает на успешное создание внешней области.
WM_NCCALCSIZE flag &NCCALCSIZE_PARAMS
Определение размера внутренней (client) части окна; кроме этого определяется часть окна, которая может быть скопирована без изменений при перемещении окна или изменении его размеров.
WM_CREATE 0 &CREATESTRUCT
Предусмотрено для выполнения Вами необходимых действий для создания внутренней (client) области окна. При этом Вы производите инициализацию связанных объектов, создание дочерних окон и пр. Как и WM_NCCREATE функция возвращает подтвержение о успешном выполнении нужных действий. Однако “успех” обозначается 0, а ошибка: -1 (!).
Активация приложения
Сейчас мы рассмотрим еще несколько последовательностей сообщений, получаемых главным окном приложения. Рассматривать мы их будем на примере создания и отображения главного окна приложения. Для начала мы выделим несколько сообщений в две обычных последовательности, которые можно назвать “активацией” и “деактивацией” приложения. Эти цепочки сообщений возникают при передаче активности (фокуса ввода) от одного приложения другому. За время работы одного приложения эти цепочки могут возникать многократно. Обычно сразу за созданием главного окна приложения следует активация этого приложения.
Активация приложения:
WM_ACTIVATEAPP TRUE hTask
сообщение, информирующее об активации приложения. Если приложение имеет несколько окон “верхнего уровня” (т.е. окон стиля WS_OVERLAPPED или WS_POPUP), то все они получают эти сообщения. Младшее слово lParam содержит хендл той задачи, которая была активна до этого момента.
WM_NCACTIVATE TRUE minimized & hWnd
активация (или деактивация, смотря по параметру wParam) внешней области окна. Обработка этого сообщения по умолчанию перерисовывает внешнюю область окна для выделения цветом активного/неактивного состояний. При этом активность или неактивность окна запоминается в структуре описания окна. Вы можете сами посылать WM_NCACTIVATE для изменения состояния окна. В документации параметр ‘lParam’ не описывается, однако он может быть не 0, и содержать такие же данные, как и в сообщении WM_ACTIVATE (см. ниже). Это сообщение может быть получено не только главным окном приложения, так как информирует об активации окна, а не приложения.
Если окно активируется, то возвращаемое значение может быть любым, а если деактивируется то значение 0 запрещает его дальнейшую деактивацию. Дочерние не MDI окна (стиль WS_CHILD) часто этого сообщения не получают.
WM_GETTEXT bufsize &buffer
при обработке этого сообщения указанный буфер заполняется названием активируемого окна. В данном случае это сообщение порождается при перерисовке внешней области окна - WM_GETTEXT “вложено” в обработку WM_NCACTIVATE. Если окно не имеет заголовка (caption bar), то это сообщение не посылается.
WM_ACTIVATE 1 или 2 minimized & hWnd
активация внутренней области окна. Это сообщение может быть получено не только главным окном приложения, так как информирует об активации окна, а не приложения. Однако дочерние окна (например, управляющие элементы диалогов) могут этого сообщения не получать. Параметр wParam информирует о том, что окно активируется с помощью нажатия кнопки мыши (2) или иным путем (0). Параметр lParam содержит в старшем слове не 0, если окно минимизировано, а в младшем слове - хендл окна бывшего до этого активным.
<получение фокуса ввода>
сообщения получения/потери фокуса ввода окном см. ниже.
В этой цепочке можно выделить две фазы:
активация приложения (если требуется)
активация окна, которая тоже выполняется в несколько фаз:
активация внешней области
активация внутренней области
Вся цепочка часто порождается функцией SetWindowPos(...); при этом такая цепочка входит в более сложную группу, начинающуюся с WM_WINDOWPOSCHAINGING и заканчивающуюся WM_WINDOWPOSCHANGED. При активации окна “щелчком мышкой” перед этой цепочкой проходит WM_MOUSEACTIVATE и пара сообщений WM_WINDOWPOSCHAINGING, WM_WINDOWPOSCHANGED. (См. ниже)
Деактивация приложения
При деактивации приложения также можно выделить две фазы - деактивацию окна и деактивацию приложения. Однако вопреки обычной практике выполнения обратных действий в обратном порядке Windows выполняет деактивацию окна в том-же порядке, как и активацию - сначала внешнюю, а затем внутреннюю области.
WM_NCACTIVATE FALSE minimized & hWnd
деактивация начинается с сообщения о деактивации внешней области окна, о чем говорит параметр wParam == FALSE. Параметр lParam содержит в младшем слове хендл окна, становящегося активным, старшее указывает на состояние Вашего окна. (Более подробно - см. выше “активация приложения”)
WM_GETTEXT bufsize &buffer
Это сообщение порождается при перерисовке внешней области окна.
WM_ACTIVATE FALSE minimized & hWnd
затем деактивируется внутренняя область окна (wParam == FALSE). Параметр lParam содержит в младшем слове хендл окна, становящегося активным, старшее указывает на состояние Вашего окна.
WM_ACTIVATEAPP FALSE hTask
и в конце деактивируется все приложение. Параметр hTask указывает на приложение, которое становится активным.
<потеря фокуса ввода окном>
сообщения получения/потери фокуса ввода окном см. ниже.
Получение и потеря фокуса ввода окном
Цепочки сообщений активации и деактивации приложения сопровождаются сообщениями, передающими фокус ввода нужному окну активного приложения. Понятие фокус ввода требует некоторого пояснения. Ранее (на первой лекции) мы говорили о том, что окно, взаимодействующее с клавиатурой, является активным. Сейчас мы введем дополнительное понятие окна, имеющего фокус ввода - то есть получающего данные от клавиатуры. Различие двух понятий - активного окна и окна, имеющего фокус ввода мы рассмотрим позже. Пока что мы можем отметить, что окно имеющее фокус ввода всегда активно (но не наоборот).
Как правило последовательность собщений активации окна заканчивается сообщением о получении фокуса ввода, и сообщения о деактивации заканчиваются сообщением о потере фокуса ввода (опять-таки не в обратном порядке).
получение фокуса ввода окном:
WM_SETFOCUS hWnd losing focus 0
получаем фокус ввода, причем параметр wParam указывает хендл окна, теряющего фокус ввода (или NULL).
потеря фокуса ввода окном:
WM_KILLFOCUS hWnd received focus 0
теряем фокус ввода, причем параметр wParam указывает хендл окна, приобретающего фокус ввода (или NULL).
Отображение окна
Обычно, после создания главного окна приложения, мы вызываем функцию ShowWindow() для отображения главного окна в нужном нам состоянии.
Обработка ShowWindow():
WM_SHOWWINDOW TRUE/FALSE 0
Параметр wParam указывает на требуемое действие - показать (TRUE) или “спрятать” (FALSE) окно. Младшее слово lParam содержит 0, если сообщение послано функцией ShowWindow().
<Сообщения активации приложения>
Если мы вызываем ShowWindow() для активации приложения, то сейчас проходит цепочка сообщений об активации приложения и передачи фокуса ввода.
WM_NCPAINT 0 0
Это сообщение, как и WM_NCACTIVATE выполняет рисование внешней области окна (кроме них этим занимаются еще и другие сообщения, например WM_NCLBUTTONDOWN и WM_SYSCOMMAND). В документации указано, что параметры wParam и lParam не используются. В Borland Help указано, что wParam является хендлом региона, определяющего область где рисование требуется, а lParam не используется. На самом деле wParam может быть 0 (при этом внешняя область не перерисовывается), может быть хендлом региона, может быть 1 (рисование внешней области требуется). Параметр lParam может быть 0, или может содержать в младшем слове хендл окна а в старшем еще какие-то данные.
WM_GETTEXT bufsize &buffer
Как и в случае WM_NCACTIVATE это сообщение “вложено” в обработку WM_NCPAINT.
WM_ERASEBKGND hDC 0
Очищается фон внутренней области окна. Для этого обычно используется кисть, определенная в структуре описания окна. Параметр wParam задает хендл контекста устройства, соответствующего внутренней области окна. Если Вы планируете сами закрашивать фон окна, то для определения размеров внутренней области окна надо использовать функцию GetClientRect(). Это связано с тем, что при создании видимого окна (имеющего стиль WS_VISIBLE) сообщение WM_ERASEBKGND встречается до первого сообщения WM_SIZE, а сообщение WM_WINDOWPOSCHANGING в этом случае хотя и обрабатывается до WM_ERASEBKGND, но содержит нулевые размеры внутренней области окна.
WM_SIZE type height & width
Задается размер окна, причем параметр wParam информирует о “типе” размера - окно может быть максимизировано, может быть минимизировано или отображено в нормальном состоянии. Это сообщение, кроме того, посылается окну если оно закрывается каким-либо максимизированным окном, или становится видимым, когда закрывавшее его максимизированное окно стало нормальным или превратилось в иконку.
WM_MOVE 0 y & x
Задается положение окна на экране (или во внутренней области окна-родителя).
Изменение размеров, положения или состояния окна
Обычно при перемещении (в X, Y или Z направлении), изменении размеров окна или при изменении его состояния, помимо WM_SIZE и WM_MOVE Вы будете получать сообщения WM_WINDOWPOSCHANGING и WM_WINDOWPOSCHANGED. При этом все сообщения посылются функцией SetWindowPos(...) (или эквивалентной) в следующем порядке:
WM_WINDOWPOSCHANGING 0 &WINDOWPOS
cообщение указывает на то, что положение или состояние окна изменяется. Параметр lParam содержит указатель на структуру WINDOWPOS, описывающую положение окна и выполняемую операцию. Изменив соответствующие поля структуры можно изменить положение и размеры окна или повлиять на выполняемые действия.
<сообщения активации окна(или всего приложения)>
если окно просто активируется, то обычно следующее сообщение WM_WINDOWPOSCHANGED, завершающее всю цепочку, без всех рассматриваемых нами промежуточных сообщений, применяемых при изменении размеров или положения.
WM_GETMINMAXINFO 0 &MINMAXINFO
проверка новых размеров окна
WM_NCCALCSIZE flag &NCCALCSIZE_PARAMS
определение размера внутренней области
WM_NCPAINT 0 0
отображение внешней области окна
WM_ERASEBKGND hDC 0
очистка фона окна
WM_WINDOWPOSCHANGED 0 &WINDOWPOS
состояние окна изменено. Параметр lParam является указателем на структуру WINDOWPOS, содержащую данные о положении и размерах окна. Сообщения WM_WINDOWPOSCHANGING ... WM_WINDOWPOSCHANGED часто не содержат между собой никаких иных сообщений, если состояние, размер и положение не изменились. (Часто это бывает, если окно активируется в ответ на “щелчок мышкой”).
WM_MOVE 0 y & x
WM_SIZE type height & width
Эти сообщения информируют о новом положении и размерах окна. Они посылаются при соответствующих изменениях процедурой DefWindowProc() при обработке сообщения WM_WINDOWPOSCHANGED. Хотя в документации указано, что можно несколько ускорить работу, перехватывая вместо WM_MOVE и WM_SIZE сообщение WM_WINDOWPOSCHANGED и не вызывая при этом DefWindowProc(), но использовать этот прием нужно очень осторожно - так как сообщения WM_SIZE и WM_MOVE используются MDI окнами.
Рассмотренная нами цепочка сообщений порождается функцией SetWindowPos (или эквивалентной) и эти сообщения непосредственно передаются окну, а не посылаются.
WM_PAINT 0 0
рисование внутренней области окна. В результате выполненной операции окно (или его часть), как правило, маркируется как неверное, что приводит к перерисовке окна. Сообщение WM_PAINT извлекается из очереди сообщений и может обрабатываться с некоторой задержкой после изменения размеров, положения или состояния окна.
Обновление окна
После вызова ShowWindow() в WinMain() обычно следует процедура UpdateWindow(). Эта процедура проверяет наличие неверных прямоугольников и, если они есть, передает сообщение WM_PAINT (не ставит в очередь, а вызывает обработку этого сообщения).
Обработка UpdateWindow():
WM_PAINT 0 0
Нарисовать внутреннюю область окна. Если для получения хендла контекста устройства используется функция BeginPaint(), то она может передавать сообщение WM_ERASEBKGND для очистки фона в неверном прямоугольнике (если при его создании было указано, что фон должен быть восстановлен).
WM_ERASEBKGND hDC 0
Обрабатывая это сообщение, мы должны закрасить фон окна, используя переданный нам хендл контекста устройства, или вызвать обработку по умолчанию.
Уничтожение окна
Для уничтожения окна надо вызвать функцию DestroyWindow(), которая выполнит требуемые действия по закрытию окна. Причем при этом посылаются:
<сообщения деактивации (если надо)>
Иногда бывает так, что сообщения деактивации окна не поступают, происходит деактивация приложения и уничтожение окна;
WM_DESTROY 0 0
Уничтожение внутренней области окна.
WM_NCDESTROY 0 0
Уничтожение внешней области окна.
Сообщения WM_DESTROY и WM_NCDESTROY являются последними сообщениями, получаемыми окном. После WM_NCDESTROY окно не получит ни одного сообщения, поэтому Вы можете смело разрушать все созданные для окна структуры данных.
Сообщение WM_DESTROY удобно применять для уничтожения всех объектов, связянных с данным окном, в том числе и дочерних окон.
Внимание: во время обработки сообщений WM_DESTROY и WM_NCDESTROY нельзя активировать каких-либо дочерних окон. (В том числе нельзя применять функцию MessageBox, передавая ей хендл уничтожаемого окна в качестве родительского) - это приведет к появлению дополнительных сообщений, направленных уничтожаемому окну, и, в конечном результате, к ошибке “General protection fault...”
Если Вы хотите получить подтверждение перед закрытием окна, то Вы должны использовать сообщение WM_CLOSE. Функция DefWindowProc, обрабатывая это сообщение, вызывает функцию DestroyWindow. Вы можете легко вставить собственные средства для обработки сообщения WM_CLOSE, и вызывать DestroyWindow или процедуру по умолчанию только при положительном ответе на запрос. Сообщение WM_CLOSE лучше посылать, а не передавать.
Событие WM_CLOSE используется самой средой, причем обычно предваряется последовательностью сообщений, приводящей к закрытию окна. Например такой: активация системного меню — выбор пункта ‘Close’ — завершение меню — посылка WM_SYSCOMMAND с параметром SC_CLOSE — посылка WM_CLOSE — уничтожение окна.
Завершение работы Windows
Существует еще два сообщения, которое могут привести к закрытию окна:
WM_QUERYENDSESSION 0 0
Это сообщение информирует о том, что Windows заканчивает работу когда приложение активно. Оно посылается ко всем запущеным приложениям. Если все возвращают TRUE, то Windows завершает работу.
Обрабатывая это сообщение, Вы должны вернуть TRUE, если Ваше приложение может быть завершено, или FALSE в противном случае. При этом Windows продолжит нормальную работу.
WM_ENDSESSION TRUE/FALSE 0
Это сообщение посылается активному приложению, если оно ответило TRUE на сообщение WM_QUERYENDSESSION. Флаг в параметре ‘wPar’ равный TRUE указывает, что работа Windows может завершиться сразу после обработки этого сообщения. Вы не обязаны уничтожать окно и посылать себе WM_QUIT для завершения работы, если завершается работа всего Windows.
Клавиатура. Сообщения клавиатуры
Сейчас мы рассмотрим основные сообщения клавиатуры и несколько функций, предназначенных для работы с ней. Когда мы нажимаем на любую кнопку, наше (точнее - активное) приложение получает сообщение
WM_KEYDOWN VkCode dwKeyData
Параметр wPar содержит виртуальный код нажатой клавиши. В Windows поддерживается специальная система нумерации клавиш, которая представлена их виртуальными номерами.
Параметр dwKeyData присутствует для всех сообщений клавиатуры и содержит информацию о текущем событии.
Для большинства клавиш их виртуальный код соответствует той заглавной букве, которая на них нанесена (для английской клавиатуры). Например, виртуальный код клавиши A равен ASCII коду символа ‘A’. Для тех клавиш, которые не соответствуют буквам и символам предусмотрены специальные номера, например VK_F6 для клавиши F6, VK_MENU для клавиши Alt, VK_RETURN для клавиши Enter.
Если посмотреть на список виртуальных кодов (это можно сделать в WINDOWS.H), то мы увидим несколько странных клавиш:
VK_LBUTTON, VK_RBUTTON, VK_MBUTTON
соответствующих кнопкам мыши. Однако эти виртуальные коды используются несколько иначе, и приложение не получает сообщений WM_KEYDOWN при нажатии кнопок мыши.
Параметр dwKeyData присутствует для всех сообщений клавиатуры и содержит следующую информацию:
WM_KEYUP VkCode dwKeyData
Кроме сообщения WM_KEYDOWN Windows посылает сообщения WM_KEYUP при отпускании клавиши. Параметры этого сообщения такие же, как и у сообщения WM_KEYDOWN.
Когда Ваше приложение извлекает из очереди сообщение WM_KEYDOWN, то обычно оно транслируется с помощью функции TranslateMessage (это происходит в главном цикле обработки сообщений). Эта функция осуществляет трансляцию сообщений клавиатуры и в результате посылает сообщения WM_CHAR, соответствующие нажатому символу или WM_DEADCHAR.
WM_CHAR nCharacter dwKeyData
Сообщает приложению о том, что нажатая клавиша соответствует данному (nCharacter) символу. Если Вы разрабатываете, например, собственный редактор, то Вы должны обрабатывать сообщения WM_CHAR для формирования текста, а не сообщения WM_KEYDOWN.
WM_DEADCHAR nDeadChar dwKeyData
Это сообщение информирует приложение о нажатии так называемой Dead-Key. Dead-Key клавиши обычно соответствуют акцентным символам. Так, например, немецкая клавиатура имеет несколько Dead-Key, скажем ‘. Если нажать эту клавишу, а, затем, букву A, то результат должен соответствовать A с ударением. При этом нажатый ‘ сам по себе не соответствует никакому символу (это Dead-Key), а служит модификатором для следующей клавиши. Таким образом на последовательность сообщений WM_KEYDOWN для клавиш ‘ и A будут генерироваться сообщения WM_DEADCHAR для ‘ и WM_CHAR для A с ударением.
Окно, имеющее фокус ввода, и активное окно
Помимо рассмотренных четырех сообщений Windows использует еще 4 сообщения, содержащих такую же информацию, как и уже рассмотренные сообщения клавиатуры:
WM_SYSKEYDOWN VkCode dwKeyData
WM_SYSKEYUP VkCode dwKeyData
WM_SYSCHAR nCharacter dwKeyData
WM_SYSDEADCHAR nDeadChar dwKeyData
Обычно эти сообщения посылаются при нажатии клавиш совместно с клавишей Alt. Вы обязательно должны предусмотреть обработку этих сообщений по умолчанию, так как, если этого не делать, то будет нарушен стандартный интерфейс Windows – перестануть работать такие клавиши как Alt–Tab, Ctrl–Esc и пр.
Сейчас мы должны будем разобраться в различиях понятий “активное окно” и “окно, имеющее фокус ввода”. Сформулируем несколько правил, позволяющих различать эти понятия.
окно, имеющее фокус ввода, всегда активно.
в любой момент времени существует активное окно.
минимизированное окно не может иметь фокуса ввода, если активное окно минимизировано, то фокус ввода не принадлежит никакому окну.
если активное окно не минимизировано, то оно имеет фокус ввода.
Все, что мы сейчас выяснили о сообщениях клавиатуры относится к окну, имеющему фокус ввода. Если наше окно активно и не имеет фокуса ввода, то вместо сообщений WM_KEYDOWN, WM_KEYUP, WM_CHAR и WM_DEADCHAR оно будет получать сообщения WM_SYSKEYDOWN, WM_SYSKEYUP, WM_SYSCHAR и WM_SYSDEADCHAR.
Для чего в Windows используются эти два понятия? Представим себе, что мы разрабатываем какой-либо редактор. Для управления им мы используем обычные (не системные) сообщения клавиатуры. Теперь мы минимизировали наш редактор и сделали его активным. Если бы при этом он по-прежнему получал обычные сообщения клавиатуры, то пришлось бы специально предусматривать блокировку ввода данных в минимизированном состоянии - так как работа “в слепую” вряд-ли будет удобна. А, так как в минимизированном состоянии он теряет фокус ввода, то вместо обычных сообщений он будет получать системные, что исключит нежелательный эффект.
Однако, обрабатывая системные сообщения, нельзя предполагать, что клавиша Alt была нажата заранее - минимизированное активное окно будет получать эти сообщения даже для обычных клавиш. Надо обязательно проверять бит с номером 1D (Context code) в dwKeyData, что бы убедиться в активности Alt-статуса.
Здесь же надо обратить внимание на то, что в документации о Windows встречается две трактовки понятия “активное окно”
с одной стороны это может быть окно, получающее сообщения от клавиатуры; Такое окно может быть только одно.
с другой стороны активном считается всякое окно, чье дочернее имеет фокус ввода. Активное окно выделяется цветом рамки и заголовка. В этом случае активных окон может быть много.
Определение состояния отдельных клавиш
Работая с клавиатурой иногда приходится проверять состояние той или иной конкретной клавиши. Для этого Windows содержит две функции
int GetKeyState( nVkCode );
void GetKeyboardState( lpbKeyStates );
Функция GetKeyState() возвращает слово, характризующее состояние заданной клавиши. Если старший бит (маска 0x8000) равен 1, клавиша нажата, если 0, то отпущена. Младший бит (маска 1) указывает состояние “переключаемой” клавиши (например, CapsLock).
Функция GetKeyboardState() заполняет массив из 256 байт состояниями всех клавиш. В качестве индекса массива используется виртуальный код клавиш. Каждый байт массива описывает одну клавишу, его страший бит (маска 0x80) описывает состояние клавиши, младший (маска 1) - состояние переключаемой клавиши.
В дополнение к этим функциям Windows содержит еще одну функцию
void SetKeyboardState( lpKeyStates );
которая позволяет задать состояние конкретных клавиш. Эта функция только корректирует соответствующий массив, с которым работает сама система, но не посылает никаких сообщений, симулирующих работу с клавиатурой. Применять эту функцию надо очень осторожно, так как клавиатура является разделяемым ресурсом.
Каретка
Говоря о клавиатуре мы должны ввести еще одно понятие - каретка (caret). Дело в том, что Windows использует два указующих элемента - курсор (cursor), который показывает положение мыши, и каретка, которая показывает место, в которое будет вводиться текст. Каретка управляется клавиатурой.
При работе с кареткой надо учитывать некоторые особенности. Практически все они связаны с тем, что каретка является разделяемым ресурсом, поэтому Вы должны внимательно следить за корректным использованием каретки. Так, например, Вы можете создавать собственную каретку; однако Вы должны предусмотреть средства для ее “скрывания” и уничтожения в тот момент, когда Ваше приложение теряет фокус ввода, и создавать или показывать ее, только если Ваше окно имеет фокус ввода.
Для работы с кареткой предназначены следующие функции:
void ShowCaret( hWnd );
void HideCaret( hWnd );
С их помощью Вы можете показать каретку или сделать ее невидимой. Параметр hWnd может быть NULL, если используется текущее окно. Видимая каретка автоматически начинает мигать.
void SetCaretPos( nX, nY );
void GetCaretPos( lpPoint );
void SetCaretBlinkTime( mMSec );
UINT GetCaretBlinkTime( void );
Эти функции позволяют задать положение каретки в координатах окна и период мигания каретки, в миллисекундах. Кроме того Вы можете создавать собственную каретку с помощью функции
void CreateCaret( hWnd, hBmp, nWidth, nHeight );
Параметр ‘hBmp’ может быть хендлом битмапа, при этом параметры ‘nWidth’ и ‘nHeight’ игнорируются, или одним из двух значений: 0 - сплошная каретка, или 1 - “серая” каретка; в этом случае параметры ‘nWidth’ и ‘nHeight’ задают размеры каретки.
void DestroyCaret( void );
Эта функция уничтожает текущую каретку (если каретка использует битмап, то битмап сохраняется).
1