Итак, мы рассмотрим основы оранизации приложения в среде 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.