Программирование для IBM OS/2© Александр Фролов, Григорий ФроловТом 25, М.: Диалог-МИФИ, 1993, 286 стр. 1.2. Структура приложения Presentation ManagerТеперь, после того как вы познакомились с некоторыми наиболее важными понятиями, мы расскажем о структуре простейшего приложения Presentation Manager и приведем соответствующие фрагменты исходного текста (в дальнейшем для сокращения мы будем называть приложение Presentation Manager просто приложением, а приложение, предназначенное для текстового режима - текстовым приложением). Инициализация приложенияПрежде чем приложение сможет использовать функции программного интерфейса Presentation Manager, оно должно зарегистрировать себя в системе Presentation Manager при помощи функции WinInitialize . Перед завершением своей работы необходимо вызвать функцию WinTerminate , как это показано ниже: #define INCL_WIN #include <os2.h> int main () { // Идентификатор Anchor-block HAB hab; hab = WinInitialize (0); . . . // Строки исходного текста приложения . . . WinTerminate (hab); return(0); } Функция WinInitialize имеет единственный параметр, который всегда должен иметь значение, равное нулю. В случае успешной регистрации функция WinInitialize возвращает идентификатор задачи, который называется Anchor-block handle, а при ошибке - значение NULLHANDLE . Идентификатор Anchor-block имеет тип HAB , который, также как и сама функция, определен в include-файле os2.h. Как заметил Петцольд в своей книге Programming the OS/2 Presentation Manager, "морской" термин Anchor-block (anchor - якорь) родился в мире больших компьютеров и в контексте операционной системы IBM OS/2 ничего не означает. Файл os2.h имеет небольшие размеры и содержит команды условного включения других include-файлов, поставляемых в составе систем разработки приложений. Определение INCL_WIN требуется для подключения одного из таких файлов, необходимого для работы с функциями оконого интерфейса Presentation Manager. Заметьте, что если приложение создает несколько задач, то каждая задача, которая будет вызывать функции программного интерфейса Presentation Manager, должна зарегистрировать себя с помощью функции WinInitialize . В наших первых примерах создается только одна задача, поэтому функция WinInitialize вызывается только один раз в функции main . В паре с функцией WinInitialize вы должны использовать функцию WinTerminate , назначение которой заключается в освобожении ресурсов, полученных функцией WinInitialize при регистрации задачи в системе Presentation Manager. Забегая вперед, скажем, что перед вызовом функции WinTerminate вы должны уничтожить очередь сообщений, созданную на этапе инициализации приложения, уничтожить все созданные окна и освободить другие ресурсы, полученные у системы Presentation Manager. При успешном завершении функция WinTerminate возвращает значение TRUE, а при ошибке - FALSE. Создание очереди сообщенийПосле регистрации в системе Presentation Manager приложение должно создать очередь сообщений. Для этого необходимо воспользоваться функцией WinCreateMsgQueue . Соответствующий фрагмент кода показан ниже. #define INCL_WIN #include <os2.h> int main () { HMQ hmq; HAB hab; hab = WinInitialize (0); hmq = WinCreateMsgQueue (hab, 0); if(hmq == NULLHANDLE) { WinTerminate (hab); return(-1); } . . . // Строки исходного текста приложения . . . WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); } Прототип функции WinCreateMsgQueue представлен ниже: HMQ WinCreateMsgQueue (HAB hab, LONG lQueuesize); Через первый параметр этой функции необходимо передать идентификатор Anchor-block , полученный при регистрации от функции WinInitialize . Второй параметр определяет размер очереди сообщений, причем если указано нулевое значение, используется размер, принятый по умолчанию (10 сообщений). В случае успеха функция возвращает идентификатор созданной очереди сообщений, который имеет тип HMQ , а при ошибке - значение NULLHANDLE. Обратите внимание, что если нам не удалось создать очередь сообщений, то перед аварийным завершением работы приложение дожно вызвать функцию WinTerminate . Для уничтожения очереди сообщений следует использовать функцию WinDestroyMsgQueue , передав ей в качестве параметра идентификатор уничтожаемой очереди. В случае успеха функция возвращает значение TRUE, при ошибке - FALSE. Если приложение создает несколько задач, оно может также создать для каждой задачи свою очередь сообщений. В этом случае перед завершением работы приложения необходимо уничтожить функцией WinDestroyMsgQueue все созданные очереди сообщений. Регистрация класса главного окна приложенияНапомним, что окно в системе Presentation Manager можно рассматривать как объект, состоящий из структуры данных и функции окна, реализующей методы для работы с этими данными. Понятие класса окна используется для установки соответствия между окном и функцией окна. В системе Presentation Manager существуют так называемые предопределенные классы окна, для которых функции окна уже созданы и находятся внутри Presentation Manager. Если вы создаете окно на базе предопределенного класса окна, оно будет обладать функциональностью, заложенной в него разработчиками Presentation Manager. Для такого окна вам не нужно создавать свою функцию окна, так как эта функция уже создана. Тем не менее, вы можете переопределить реакцию этой функции на некоторые или все сообщения, создав на базе предопределенного класса собственное окно, поведение которого отличается от стандартного. Таким образом реализуется механизм наследования: вы создаете свое окно на базе предопределенного класса и изменяете только те методы, которые нужно. Наследование сильно упрощает жизнь программистов, так как они получают возможность использовать почти готовые объекты, при необходимости лишь немного изменяя их поведение. Создавая главное окно приложения, вы должны зарегистрировать собственный класс, определив внутри своего приложения функцию окна. Эта функция будет обрабатывать сообщения, поступающие в очередь сообщений для вашего окна. Фрагмент кода, в котором выполняется регистрация класса окна для главного окна приложения представлен ниже: MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM); BOOL fRc; CHAR szWndClass[] = "MYWINDOW"; fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinDestroyMsgQueue (hmq); WinTerminate (hab); return(-1); } Для регистрации класса окна должна использоваться функция WinRegisterClass , прототип которой приведен ниже: BOOL WinRegisterClass ( HAB hab, // идентификатор Anchor-block PSZ pszClassName, // имя класса окна PFNWP pfnWndProc, // функция окна ULONG flStyle, // стиль окна ULONG cbWindowData); // дополнительные данные Через параметр hab этой функции передается идентификатор Anchor-block, полученный от функции WinInitialize при регистрации приложения в системе Presentation Manager. В параметр pszClassName необходимо записать адрес символьной строки имени регистрируемого класса. Мы указали этот параметр как "MYWINDOW", что выбрано произвольно. Предопределенные классы окон также имеют свои имена, которые начинаются с префикса WC_, например, WC_BUTTON , WC_COMBOBOX и т. д. Выбирая имя для собственного класса окна, вы должны проследить, чтобы оно случайно не совпало с именем предопределенного класса окна. С помощью параметра pfnWndProc необходимо указать имя функции, которая будет служить функцией окна для всех окон, создаваемых на базе данного класса. Заметим, что зарегистрировав класс окна, вы можете создать на его базе несколько окон, и для всех этих окон будет использоваться одна функция окна. Это удобно, если приложение должно создать несколько окон, которые ведут себя одинаково - вам не придется в своем приложении определять несколько одинаковых функций окна. Приведенный выше фрагмент кода начинается с определения прототипа функции окна, которая в данном случае имеет имя WndProc. Это имя было нами выбрано произвольно. Параметр flStyle определяет стиль окна, создаваемого на базе класса, регистрируемого функцией WinRegisterClass . После регистрации стиль окна изменить невозможно. Стиль задается при помощи перечисленных ниже
констант, которые можно объединять при помощи
логической операции ИЛИ. Если назначение
отдельных констант вам пока не вполне понятно,
можете пропустить соответстующую строку
таблицы. Позже вы можете возвратиться к описанию
стилей окна.
Теперь рассмотрим последний параметр функции WinRegisterClass , который называется cbWindowData. С помощью этого параметра вы можете зарезервировать в структуре данных, создаваемой для регистрируемого класса окна, дополнительную память. Объем этой памяти в байтах равен значению параметра cbWindowData. При необходимости вы сможете хранить в этой памяти данные, которые будут доступны всем функциям окон, создаваемых на базе данного класса окна. Возвращаясь к фрагменту кода, демонстрирующему процесс регистрации класса окна, заметим, что в случае неудачи вы должны уничтожить очередь сообщений функцией WinDestroyMsgQueue и затем вызвать функцию WinTerminate . Если вы этого не сделаете, система может оказаться в неустойчивом состоянии. Создание главного окна приложенияПосле создания очереди сообщений и регистрации класса окна все готово для того, чтобы приступить к созданию главного окна приложения. В общем случае окно создается функцией WinCreateWindow , которую мы рассмотрим немного позже. Для создания главного окна приложения удобнее всего воспользоваться другой функцией, которая называется WinCreateStdWindow . Для того чтобы вам была понятнее описанная ниже процедура создания главного окна, вспомним, что на самом деле окно приложения, которое видит пользователь, состоит из нескольких окон. Остановимся на этом подробнее. Главное окно приложения называется Frame Window и на его поверхности расположены все остальные окна, а именно: окно пиктограммы системного меню, заголовок окна, вертикальные и горизонтальные полосы просмотра (если они есть), кнопки минимизации и максимизации окна, окно меню приложения, а также клиентское окно Client Window , внутри которого приложение обычно что-нибудь рисует. Когда ранее мы говорили про создание главного окна приложения, то несколько упрощали ситуацию. На самом деле необходимо создать окна Frame Window , Client Window , а также другие окна, которые служат в качестве органов управления - системное меню, полосы просмотра и т. д. К счастью, вы можете создать все эти окна при помощи одного вызова функции WinCreateStdWindow . Задавая соответствующим образом параметры этой функции, вы можете изменять внешний вид окна приложения, создавая (или не создавая) в окне Frame Window те или иные элементы управления. Особое значение имеет окно Client Window , которое мы будем также называть клиентским окном приложения. Именно в этом окне приложение выполняет рисование, используя его как рабочее (хотя при необходимости приложение может рисовать в любом месте окна Frame Window или даже в любом месте на поверхности рабочего стола графической оболочки Workplace Shell). Окно Frame Window , как и окна, соответствующие стандартным органам управления (таким как меню или кнопки), создаются на базе классов окна, предопределенных в системе Presentation Manager. Что же касается окна Client Window , то это окно создается всегда на базе класса, зарегистрированного приложением. Приложение должно определить функцию окна, которая будет обрабатывать сообщения, предназначенные окну Client Window. Для упрощения клиентское окно Client Window иногда называют главным окном приложения, хотя вы должны понимать, что настоящим главным окном является окно Frame Window . Функция окна Frame Window находится внутри системы Presentation Manager и вам не надо о ней беспокоиться. Ниже мы привели фрагмент кода, в котором создается главное окно приложения. Здесь предполагается, что создание очереди сообщений и регистрация класса окна уже выполнены. #define ID_APP_FRAMEWND 1 HWND hWndFrame; HWND hWndClient; ULONG flFrameFlags = FCF_SYSMENU | FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST | FCF_ICON; CHAR szAppTitle[] = "My First OS/2 Application"; CHAR szWndClass[] = "MYWINDOW"; hWndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE , &flFrameFlags, szWndClass, szAppTitle, 0, 0, ID_APP_FRAMEWND, &hWndClient); if(hWndFrame == NULLHANDLE) { WinDestroyMsgQueue (hmq); WinTerminate (hab); return(-1); } Ниже мы привели прототип функции WinCreateStdWindow с кратким описанием параметров: HWND WinCreateStdWindow ( HWND hwndParent, // идентификатор родительского окна ULONG flStyle, // стиль окна Frame Window PULONG pflCreateFlags, // флаги создания // окна Frame Window PSZ pszClassClient, // имя класса клиентского окна PSZ pszTitle, // заголовок окна ULONG flStyleClient, // стиль клиентского окна HMODULE Resource, // идентификатор ресурсов ULONG ulId, // идентфикатор окна Frame Window PHWND phwndClient); // идентификатор клиентского окна В качестве первого параметра hwndParent вы должны передать этой функции идентификатор родительского окна. В следующей главе мы рассмотрим подробнее "семейные" отношения в мире окон Presentation Manager. Сейчас ограничимся таким определением: родительское окно - это то окно, которое создало данное окно и потому связано с ним некоторыми "отношениями". Например, дочернее окно всегда отображается внутри родительского, не выходя за его границы. В нашем случае в качестве родительского выступает окно рабочего стола, которое имеет идентификатор HWND_DESKTOP , определенный в файле os2.h. Параметр flStyle определяет стиль окна Frame Window . В
качестве стиля вы можете использовать
комбинацию перечисленных ниже констант,
объединенных при помощи логической операции ИЛИ.
В нашем примере мы указали стиль WS_VISIBLE , поэтому сразу после создания окна вы увидите его на экране. Следующий параметр называется pflCreateFlags. Он представляет собой указатель на переменную типа ULONG, в которую перед вызовом функции WinCreateStdWindow необходимо записать флаги создания окна Frame Window . Ниже мы привели список возможных значений
флагов. Эти значения можно объединять при помощи
логической операции ИЛИ.
Обратите внимание, что в качестве параметра pflCreateFlags вы должны передать функции WinCreateStdWindow указатель на слово, содержащее флаги, а не само слово флагов. Продолжим изучение параметров функции WinCreateStdWindow . Через параметр pszClassClient вы должны передать функции имя зарегистрированного ранее класса окна Client Window . Напомним, что эта регистрация выполняется с помощью функции WinRegisterClass . Параметр pszTitle передает функции WinCreateStdWindow указатель на текстовую строку, содержащую заголовок окна. Параметр pszTitle игнорируется, если не указан флаг FCF_TITLEBAR . Параметр flStyleClient определяет стиль клиентского окна Client Window . Он игнорируется, если параметр pszClassClient содержит указатель на пустую текстовую строку. Теперь о параметре Resource. Этот параметр указывает идентификатор модуля, содержащий ресурсы приложения. На данном этапе мы будем указывать для этого параметра нулевое значение. При этом загрузка ресурсов будет выполняться из EXE-файла приложения. Что такое ресурсы приложения ? Это данные, которые могут быть физически расположены в загрузочном EXE-файле приложения или в DLL-файле библиотеки динамической загрузки. Вы можете таким образом создать и сохранить в загрузочном файле приложения пиктограммы, меню, диалоговые панели, текстовые строки и другие типы данных, в том числе произвольные данные. При запуске приложения те ресурсы, которые не нужны, могут оставаться на диске. Они не будут отнимать у приложений дефицитную оперативную память. В то же время как только они потребуются, операционная система загрузит их в память автоматически. Более подробное описание приемов работы с ресурсами мы приведем позже. В нашем первом приложении определен только один ресурс - пиктограмма. Следующий параметр, который вы должны указать для функции WinCreateStdWindow , это параметр ulId. Он задает идентификатор окна Frame Window . Все ресурсы, относящиеся к одному окну Frame Window, должны иметь одинаковый идентификатор. Вы можете использовать любое значение в диапазоне от 0 до 0xFFFF. Обращаем ваше внимание, что идентификатор окна, полученный от функции WinCreateStdWindow (или WinCreateWindow), и идентификатор, передаваемой этим функциям в качестве параметра ulId - разные по смыслу вещи. Первый из них используется для посылки сообщений окну или выполнения других операций, таких, например, как блокирование. Идентификатор ulId играет важную роль при обработке сообщений, поступающих от данного окна в родительское окно и позволяет определить, от какого окна пришло сообщение. Подробнее об использовании этого идентификатора вы узнаете из главы, посвященной органам управления. И, наконец, последний параметр phwndClient должен содержать указатель на переменную типа HWND, в которую будет записан идентификатор созданного клиентского окна Client Window . Обращаем внимание, что здесь нужно использовать именно указатель, а не имя переменной. Запуск цикла обработки сообщенийПосле вызова функции WinCreateStdWindow окно появится на экране, но оно еще не будет работать. Теперь вы должны запустить цикл обработки сообщений. Основная задача этого цикла - выборка сообщений из очереди приложения и передача их на обработку функции окна, зарегистрированной на предыдущем этапе. Цикл обработки сообщений выглядит следующим образом: QMSG qmsg; while(WinGetMsg (hab, &qmsg, 0, 0, 0)) WinDispatchMsg (hab, &qmsg); Здесь функция WinGetMsg выбирает сообщения из очереди приложения, записывая их в структуру qmsg типа QMSG . Если очередь сообщений пуста, функция переходит в состояние ожидания. Три последних нулевых параметра означают, что из очереди будут выбираться все сообщения для всех окон, созданных в данной задаче. После того как сообщение выбрано и записано в структуру qmsg, его нужно передать функции окна. Однако вы не можете просто вызвать функцию окна и в качестве одного из параметров передать указатель на данную структуру. Вспомните, мы говорили, что приложение создает функцию окна, но никогда ее не вызывает. Вместо этого выбранное сообщение передается функции WinDispatchMsg , которая возвращает сообщение системе Presentation Manager и та уже вызывает функцию окна, определенную в вашем приложении. На первый взгляд такая процедура может показаться бессмысленной, так как система Presentation Manager могла бы и сама выполнять такую обработку внутри себя. Однако цикл обработки сообщений, организованный в приложении, позволяет выполнять дополнительную обработку сообщений перед тем, как они возвратятся системе Presentation Manager и попадут в функцию окна. Такой контроль открывает перед приложением большие возможности в определении поведения своего главного окна. Например, приложение может контролировать весь поток сообщений, проходящих через очередь сообщений этого приложения. Займемся функцией WinGetMsg , прототип которой приведен ниже: BOOL WinGetMsg ( HAB hab, // идентификатор Anchor-block PQMSG pqmsgmsg, // указатель на структуру, // куда будет записано сообщение HWND hwndFilter,// фильтр окон ULONG ulFirst, // начальный код сообщения ULONG ulLast); // конечный код сообщения Через параметр hab функции WinGetMsg передается идентфикатор Anchor-block, полученный от функции WinInitialize . Здесь для вас нет ничего нового. Через параметр pqmsgmsg вы должны передать функции указатель на структуру типа QMSG , в которую будет записано извлеченное из очереди сообщение. Если значение параметра hwndFilter не равно нулю, функция WinGetMsg будет выбирать сообщения, предназначенные для всех окон, созданных в рамках данной задачи. Как правило, именно так и поступают. Однако вы можете указать здесь идентификатор какого-либо дочернего окна и организовать обработку сообщений только для него. Как мы уже говорили, для идентификации каждому сообщению присваивается определенный код. Если значения параметров ulFirst и ulLast равны нулю, функция WinGetMsg будет обрабатывать сообщения с любым кодом. Если же значение параметра ulFirst больше чем ulLast, будут обрабатываться все сообщения, кроме тех, коды которых попали в интервал от ulLast до ulFirst. Аналогично, если значение параметра ulFirst меньше, чем ulLast, будут обрабатываться только такие сообщения, коды которых попадают в интервал от ulFirst до ulLast. Как правило, обычно значения параметров ulFirst и ulLast устанавливаются равными нулю. Завершение работы приложенияНичто не продолжается бесконечно, поэтому рано или поздно вы должны завершить цикл обработки сообщений и закрыть приложение. Как это сделать? Для завершения работы приложения, не имеющего стандартного меню, вы должны сделать двойной щелчок левой клавишей мыши по системному меню, выбрать из этого меню строку Close или нажать комбинацию клавиш <Alt+F4>. В результате в очередь сообщений записывается сообщение с кодом WM_QUIT , определенным в файле os2.h. Функция WinGetMsg , выбирающая сообщения из очереди приложения, возвращает значение TRUE для всех сообщений, кроме сообщения с кодом WM_QUIT , чем мы и пользуемся в нашем цикле обработки сообщений. Как только функция WinGetMsg возвращает значение TRUE, цикл обработки сообщений завершается. Однако пока еще рано вызывать оператор return и возвращать управление операционной системе, завершая работу приложения. Перед этим вам необходимо уничтожить главное окно приложения, очередь сообщений и вызвать функцию WinTerminate . Финальный фрагмент фнункции main , выполняющий все перечисленные действия, показан ниже: while(WinGetMsg (hab, &qmsg, 0, 0, 0)) WinDispatchMsg (hab, &qmsg); WinDestroyWindow(hWndFrame); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); Для уничтожения главного окна приложения мы вызываем функцию WinDestroyWindow, передавая ей в качестве единственного параметра идентификатор окна Frame Window . При уничтожении окна Frame Window все остальные окна, созданные на его поверхности и являющиеся по отношению к нему дочерними, будут уничтожены автоматически. В частности, будет автоматически уничтожено клиентское окно Client Window , поэтому у вас нет необходимости уничтожать это окно явным образом. После уничтожения окна Frame Window вы должны уничтожить очередь сообщений, вызав функцию WinDestroyMsgQueue , а затем вызвать функцию WinTerminate . После вызова функции WinTerminate вы не можете пользоваться другими функциями программного интерфейса Presentation Manager. Теперь самое время завершить работу приложения с помощью оператора return, что мы и делаем в приведенном выше примере. Функция окнаТеперь настало время заняться функцией окна, которое было создано функцией main и после регистрации главного окна приложения, зажило своей жизнью. Рискуя быть навязчивыми, скажем еще раз, что приложение никогда не должно вызывать функцию окна напрямую. Ей передает управление система Presentation Manager, поэтому она вызывается извне приложения. Такие функции называют функциями обратного вызова. Прототип функции окна мы привели ниже. Учтите, что имя функции окна может быть любым. Главное, чтобы вы правильно указали это имя при регистрации класса окна функцией WinRegisterClass . MRESULT EXPENTRY WndProc( HWND hWnd, // идентификатор окна ULONG msg, // код сообщения MPARAM mp1, // первый параметр сообщения MPARAM mp2); // второй параметр сообщения Через параметр hWnd функции окна передается идентификатор окна. Параметр msg содержит код сообщения, а параметры mp1 и mp2 - первый и второй параметр сообщения, соответственно. Назначение параметров сообщения полностью определяется кодом сообщения. Сравните параметры функции окна со структурой, в которую записывает сообщение функция WinGetMsg : typedef struct _QMSG { HWND hwnd; // идентификатор окна ULONG msg; // код сообщения MPARAM mp1; // первый параметр сообщения MPARAM mp2; // второй параметр сообщения ULONG time; // время возникновения сообщения POINTL ptl; // позиция курсора мыши во время // возникновения сообщения ULONG reserved; // зарезервировано } QMSG ; typedef QMSG *PQMSG; Как видите, функция окна получает сообщение в "разобранном" виде, удобном для анализа. Анализ выполняется при помощи проверки кода сообщения, например, так, как это показано в следующем фрагменте кода: MRESULT EXPENTRY WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { switch (msg) { case WM_CREATE : { . . . } case WM_DESTROY : { . . . } case WM_ERASEBACKGROUND : return(MRFROMLONG(1L)); case WM_BUTTON1DOWN : { . . . } default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } } Если функция окна не обрабатывает какое-либо сообщение, она должна передать его функции WinDefWindowProc , выполняющей обработку по умолчанию. Параметры этой функции полностью аналогичны параметрам функции окна. При создании окна ему передается сообщение WM_CREATE . Обработчик этого сообщения может проверить параметры, переданные функции создания окна, или выполнить какие-либо инициализирующие действия, например, дианмическое получение блока памяти или инициализацию данных. Если такие действия не нужны, функция окна может не обрабатывать это сообщение, передавая его функции WinDefWindowProc . Аналогично, в процессе уничтожения окна функции окна передается сообщение WM_DESTROY . Обработчик этого сообщения должен освободить ресурсы, заказанные у операционной системы обработчиком сообщения WM_CREATE . Сравнивая окно с объектом языка программирования С++, можно сказать, что обработчик сообщения WM_CREATE выполняет функцию конструктора, а обработчик сообщения WM_DESTROY - функцию деструктора объекта. Функция окна может получать сообщения с десятками различных кодов, поступающих от мыши, клавиатуры, таймера, различных органов управления приложения, дочерних окон, операционной системы и других приложений. Мы будем рассказывать вам об этих сообщениях по мере необходимости. |