1. Основы Presentation Manager

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

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

На первый взгляд создание приложений Presentation Manager может показаться очень непростым делом. В самом деле, даже если ваше приложение просто создает пустое окно, объем исходного текста может достигнуть нескольких десятков строк. Однако по мере изучения нашей книги вы убедитесь в том, что это кажущаяся сложность. В действительности исходные тексты сравнимой по выполняемым функциям программы MS-DOS окажутся намного более объемными, так как Presentation Manager берет на себя значительную часть работы.

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

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

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

Если вы программируете для операционной системы MS-DOS , решение проблемы совместимости с аппаратным обеспечением компьютера полностью ложится на ваши плечи. При этом в некоторых случаях, например, при программировании видеоадаптеров, вы можете воспользоваться такими стандартами, как VGA или VESA SVGA, однако для реализации всех возможностей современных видеоадаптеров вам придется программировать на уровне портов ввода/вывода.

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

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

Для решения проблем совместимости в составе операционных систем поставляются драйверы периферийных устройств. Эти драйверы разрабатываются либо изготовителем операционной системы, либо изготовителями устройств. Когда пользователь устанавливает операционную систему IBM OS/2 Warp, он автоматически или вручную устанавливает и все необходимые драйверы.

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

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

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

1.1. Базовые понятия и определения

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

Окна в приложениях Presentation Manager

Центральным объектом любого приложения Presentation Manager является главное окно (рис. 1.1).

Рис. 1.1. Главное окно приложения HyperACCESS Lite for OS/2, предназначенного для работы с модемом

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

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

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

В текстовом режиме в распоряжении приложения находится экран, состоящий из 25 строк по 80 символов в каждой. Такая модель очень проста для программирования, однако с ее помощью вы едва ли сможете создавать приложения, имеющие современный интрефейс пользователя.

Приложение Presentation Manager работает в графическом режиме, поэтому оно может отображать в своих окнах все что угодно. В любое время пользователь может произвольно изменять размер и расположение окна на экране, выбирая наиболее удобное для себя взаимное расположение окон (если такая возможность преднамеренно не заблокирована приложением), а также запускать другие приложения, которые в свою очередь создают собственные окна.

Все это усложняет процедуру отображения информации. В добавок, в отличие от программы MS-DOS приложение Presentation Manager не может полагать, что оно работает в одиночестве и ему безраздельно принадлежит экран монитора, клавиатура, мышь, принтер и другие устройства компьютера. Оно не может делать априорных предположений относительно размеров своего главного окна, а также относительно типа видеоадаптера, установленного в компьютере. Поэтому для организации вывода на экран, печати и обработки ввода необходимо использовать специальные приемы, которые мы подробно опишем в этой и следующих книгах, посвященных программированию для операционной системы IBM OS/2 Warp.

Функция окна

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

Чтобы вам было понятно назначение функции окна, проведем аналогию окон, создаваемых системой Presentation Manager, с объектами в языке программирования C++. Там объект определяется как структура данных и набор методов, предназначенных для обработки этих данных. В качестве методов выступают функции. Среди этих методов существуют два особых - конструктор и деструктор. Эти методы выполняют, соответственно, создание и уничтожение объекта.

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

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

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

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

Зачем, спросите вы, приложение создает функцию, которую оно не вызывает?

Дело в том, что функция окна вызывается системой Presentation Manager, когда над окном выполняются какие-либо действия. После вызова функция окна анализирует параметры, полученные ей от Presentation Manager, и выполняет соответствующий фрагмент, определяющий реакцию окна на данное действие.

Если вы знакомы со структурой драйверов MS-DOS , то вам будет понятна аналогия между функцией окна и функцией прерывания драйвера. Драйвер создает функцию прерывания, которая обрабатывает посылаемые ему команды, однако сам драйвер никогда напрямую не вызывает эту функцию.

Таким образом, для каждого окна приложение должно определить функцию окна и каким-то образом (каким - вы узнаете позже) указать системе Presentation Manager, что для данного окна будет использована именно эта функция. После создания окна система Presentation Manager будет при необходимости вызывать функцию окна.

Сообщения

В мультизадачной среде, которой является операционная система IBM OS/2 Warp, одновременно могут происходить многие события. Приложения могут работать параллельно и независимо, что в целом повышает производительность системы. Однако возможность мультизадачной обработки накладывает определенные требования на приемы работы с такими устройствами, как мышь и клавиатура.

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

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

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

Например, пусть мы запустили два приложения. Можно установить курсор мыши в окно первого приложения, нажать левую клавишу мыши, затем, не отпуская клавишу, переместить курсор мыши в окно второго приложения и там отпустить клавишу мыши. При этом в окне первого приложения произошло одно событие (пользователь нажал левую клавишу мыши), а в окне второго - другое (пользователь отпустил клавишу мыши). Очевидно, что использование традиционных для MS-DOS способов работы с мышью в среде Presentation Manager (если бы такое было возможно) может привести к большим проблемам при попытке приложений определить, что же все-таки пользователь сделал с курсором и клавишами мыши.

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

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

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

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

Фокус ввода

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

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

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

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

Цикл обработки сообщений

Что делает приложение с сообщениями, поступающими в его очередь?

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

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

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 . После регистрации стиль окна изменить невозможно.

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

Имя константы Описание стиля
CS_CLIPCHILDREN Окно не будет рисовать в области своих дочерних окон. Этот стиль может вызвать замедление процесса перерисовки содержимого окна, однако он может оказаться полезен, если процедура рисования внутри дочерних окон отнимает много времени
CS_CLIPSIBLINGS Стиль используется для дочерних окон, имеющих общее родительское окно и подавляет рисование одного такого окна внутри других дочерних окон, имеющих общего родителя
CS_FRAME Этот стиль имеет главное окно приложения, которое называется Frame Window и используется в качестве платформы для размещения других окон
CS_HITTEST Если окно обладает этим стилем, то если указатель мыши находится в области окна, Presentation Manager будет посылать функции окна сообщения WM_HITTEST , которое будет описано в разделе нашей книги, посвященном мыши
CS_MOVENOTIFY Если установлен этот стиль, то при перемещении окна пользователем по поверхности рабочего стола функция окна будет получать сообщения WM_MOVE
CS_PARENTCLIP Видимая область окна включает область родительского окна
CS_SAVEBITS Этот стиль может быть использован для ускорения перерисовки окна, когда пользователь перемещает его или восстанавливает после минимизации либо скрытия. Ускорение происходит за счет того, что изображение области рабочего стола, занимаемой окном, сохраняется как растровое изображение и затем восстанавливается после изменения расположения окна. Использование этого стиля приводит к неэкономному расходованию оперативной памяти, так как для хранения цветного изображения требуется много места
CS_SIZEREDRAW Если используется этот стиль, то при любом изменении размеров окна его внутренняя область будет полностью перерисована. С этой целью функция окна получит сообщение WM_PAINT
CS_SYNCPAINT Функция окна получит сообщение WM_PAINT сразу после того, как появится необходимость в перерисовке части окна. Если же этот стиль не указан, сообщение WM_PAINT поступит в функцию окна только в том случае, если в очереди нет других сообщений

Теперь рассмотрим последний параметр функции 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_SYNCPAINT Синхронное обновление окна. Этот стиль установлен для тех окон, которые созданы на базе класса окна со стилем CS_SYNCPAINT
WS_ANIMATE Включение режима анимации при открывании и закрывании окна. Если этот режим включен, то при изменении размеров окна пользователю будет казаться, что эти размеры плавно изменяются. Заметим, что если анимация окна отключена в блокноте свойств System-Settings, то этот стиль будет проигнорирован
WS_CLIPCHILDREN Если указан этот стиль, то область, занимаемая дочерними окнами, не перерисовывается
WS_CLIPSIBLINGS Если указан этот стиль, то область, занимаемая окнами, имеющими общих родителей, не перерисовывается
WS_DISABLED Окно заблокировано
WS_MAXIMIZED Окно создается максимизированным
WS_MINIMIZED Окно создается минимизированным
WS_PARENTCLIP Если указан этот стиль, окно не рисует вне занимаемой им прямоугольной области
WS_SAVEBITS В момент отображения окна выполняется сохранение графического изображения области под окном, имеющим этот стиль
WS_VISIBLE После создания окно становится видимым. Если этот стиль не указан, то окно создается невидимым, даже если оно не закрыто другими окнами

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

Следующий параметр называется pflCreateFlags. Он представляет собой указатель на переменную типа ULONG, в которую перед вызовом функции WinCreateStdWindow необходимо записать флаги создания окна Frame Window .

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

Флаг Описание
FCF_TITLEBAR Если указан этот флаг, в окне Farme Window будет создано окно заголовка
FCF_SYSMENU Будет создано системное меню
FCF_MENU -//- стандартное меню
FCF_MINMAX -//- кнопки минимизации и максимизации
FCF_MINBUTTON -//- кнопка минимизации
FCF_MAXBUTTON -//- кнопка максимизации
FCF_VERTSCROLL -//- вертикальная полоса просмотра
FCF_HORZSCROLL -//- горизонтальная полоса просмотра
FCF_SIZEBORDER -//- рамка для изменения размеров окна
FCF_BORDER Вокруг окна будет нарисована тонкая рамка
FCF_DLGBORDER Вокруг окна будет нарисована рамка, которая используется в стандартных диалоговых панелях
FCF_ACCELTABLE Из ресурса приложения, идентификатор которого указан в параметре Resource функции WinCreateStdWindow , будет загружена таблица акселераторов (будет рассмотрена позже)
FCF_ICON С создаваемым окном связывается пиктограмма, которая идентифицируется параметром Resource функции WinCreateStdWindow . Эта пиктограмма автоматически загружается при создании окна и также автоматически удаляется при уничтожении окна
FCF_SHELLPOSITION Размеры и расположение создаваемого окна определяет оболочка Workplace Shell (а не приложение)
FCF_SYSMODAL Создается системное модальное окно (об этом мы расскажем позже)
FCF_NOBYTEALIGN Если установлен этот флаг, при создании окна не выполняется оптимизация выравнивания соответствующих структур данных в оперативной памяти, которая в некоторых случаях могла бы привести к ускорению процесса отображения окна
FCF_TASKLIST Если установлен этот флаг, к заголовку окна добавляется название программы. Полученная таким образом строка используется при отображении списка задач, запущенных в операционной системе IBM OS/2
FCF_NOMOVEWITHOWNER При перемещении окна, которое является владельцем окна с установленным флагом FCF_NOMOVEWITHOWNER, последнее не должно перемещаться
FCF_AUTOICON Если окно минимизировано, ему не посылается сообщение WM_PAINT , предназначенное для инициирования процедуры перерисовки содержимого окна. Этот флаг оказывает благоприятное влияние на производительность системы
FCF_STANDARD Эквивалент следующей комбинации флагов:
FCF_TITLEBAR | FCF_SYSMENU |
FCF_MINBUTTON | FCF_TASKLIST |
FCF_MAXBUTTON |
FCF_SIZEBORDER | FCF_ICON |
FCF_ACCELTABLE | FCF_MENU |
FCF_SHELLPOSITION
Если указан флаг FCF_STANDARD, следует определить все необходимые ресурсы: пиктограмму приложения, меню, таблицу акселераторов

Обратите внимание, что в качестве параметра 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 - функцию деструктора объекта.

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

1.3. Приложение MYWINDOW

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

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

Внешний вид приложения MYWINDOW и диалоговая панель с координатами курсора мыши показана на рис. 1.2.

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

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

Листинг 1.1. Файл mywindow\mywindow.c

// ===================================================
// Определения
// ===================================================

// Определения для файла os2.h
#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS

// Файл содержит определения, необходимые
// для любого приложения OS/2
#include <os2.h>

// Этот файл нужен для определения функции sprintf 
#include <stdio.h>

// Определение констант для приложения MYWINDOW
#include "mywindow.h"

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

// ===================================================
// Глобальные переменные
// ===================================================

// Идентификатор Anchor-block
HAB  hab;

// Идентификатор окна Frame Window  - главного окна приложения
HWND hWndFrame;

// Идентификатор окна Client Window 
HWND hWndClient;

// Заголовок приложения
CHAR szAppTitle[] = "My First OS/2 Application";

// ===================================================
// Главная функция приложения main 
// Получает управление при запуске приложения
// ===================================================

int main ()
{
  // Идентификатор очереди сообщений
  HMQ   hmq;

  // Структура, в которую записывается сообщение,
  // извлеченное из очереди
  QMSG   qmsg;

  // Переменная для хранения кода возврата
  BOOL  fRc;

  // Флаги для создания окна Frame Window 
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR       | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON;

  // Имя класса главного окна
  CHAR  szWndClass[] = "MYWINDOW";

  // Инициализация приложения, необходимая для 
  // использования функций Presentation Manager
  hab = WinInitialize (0);

  // При невозможности инициализации выводим 
  // сообщение об ошибке
  if(hab == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue (hab, 0);

  if(hmq == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);

    // Перед аварийным завершением приложения 
    // необходимо вызвать функцию WinTerminate 
    WinTerminate (hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass (hab, szWndClass, 
    (PFNWP)WndProc, 0, 0);

  if(fRc == FALSE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);

    // Перед аварийным завершением приложения 
    // уничтожаем созданную ранее очередь сообщений и
    // вызываем функцию WinTerminate 
    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow (HWND_DESKTOP, 
    WS_VISIBLE ,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);

  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);

    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Запускаем цикл обработки сообщений
  while(WinGetMsg (hab, &qmsg, 0, 0, 0))
    WinDispatchMsg (hab, &qmsg);

  // Уничтожаем главное окно приложения
  WinDestroyWindow(hWndFrame);

  // Удаляем очередь сообщений и вызываем
  // функцию WinTerminate 
  WinDestroyMsgQueue (hmq);
  WinTerminate (hab);

  // Возвращаем управление операционной системе
  return(0);
}

// ===================================================
// Функция главного окна приложения
// Обрабатывает сообщения, поступающие 
// в очередь приложения
// ===================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  // Временный буфер для подготовки сообщения
  CHAR szMsg[100];

  switch (msg)
  {
    // Обработчик сообщения WM_ERASEBACKGROUND 
    // Это сообщение поступает перед перерисовкой
    // внутренней области окна
    case WM_ERASEBACKGROUND :
      return(MRFROMLONG(1L));

    // Обработчик сообщений 
    // WM_BUTTON1DOWN  и WM_BUTTON2DOWN .
    // Эти сообщения поступают, когда пользователь 
    // делает в окне приложения щелчок левой и правой
    // кнопкой мыши, соответственно
    case WM_BUTTON1DOWN :
    case WM_BUTTON2DOWN :
    {
      // Определяем координаты курсора мыши и 
      // записываем их в виде текстовой строки 
      // во временный буфер
      sprintf (szMsg, "(x, y) = (%ld, %ld)",
        SHORT1FROMMP (mp1), SHORT2FROMMP (mp1));

      // Отображаем содержимое временного буфера 
      // на экране
      WinMessageBox (HWND_DESKTOP, hWnd,  szMsg,
        "Координаты курсора мыши", 0, 
        MB_INFORMATION | MB_OK);
    }

    // Если наша функция окна не обрабатывает 
    // сообщение, его следует обязательно передать 
    // функции WinDefWindowProc 
    default:
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));
  }
}

Файл mywindow.h, который содержит определение идентификатора окна Frame Window , представлен в листинге 1.2.

Листинг 1.2. Файл mywindow\mywindow.h

#define ID_APP_FRAMEWND 1

Определения и глобальные переменные

Исходные тексты любого приложения IBM OS/2 должны включать в себя файл os2.h, в котором определяются константы, типы данных и функции программного интерфейса операционной системы. Так как этот интерфейс очень обширный, для сокращения времени трансляции в файле os2.h используются операторы условного включения других include-файлов (которые, в свою очередь, также могут включать в себя файлы определений при помощи оператора #include). Изучите файл os2.h самостоятельно, обратив внимание на то, какие именно файлы включаются при определении макро INCL_WIN , INCL_GPI и INCL_WINDIALOGS , использованных в нашем приложении. Вы сможете найти этот файл в каталоге INCLUDE вашей системы разработки.

Так как в нашем приложении мы пользуемся станадртной функцией sprintf , мы включили файл определений stdio.h . Отметим, что в приложениях Presentation Manager нельзя использовать функции стандартного ввода/вывода в файлы и потоки, однако функция sprintf к таковым не относится - она используется в нашем приложении для формирования текстовой строки.

Далее в исходный текст нашего приложения включается файл mywindow.h, который содержит определение константы ID_APP_FRAMEWND (идентификатор окна Frame Window ). Этот идентификатор необходим для создания главного окна приложения.

Кроме того, мы определяем прототип функции окна WndProc:

MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

Эта функция обрабатывает сообщения, поступающие в окно Client Window нашего приложения.

Далее в исходном тексте определены несколько глобальных переменных.

Переменная hab служит для хранения идентификатора Anchor-block, полученного при регистрации основной задачи нашего приложения в системе Presentation Manager функцией WinInitialize .

В переменных hWndFrame и hWndClient хранятся, соответственно, идентификаторы окон Frame Window и Client Window .

Строка szAppTitle содержит заголовок приложения, который отображается в верхней части главного окна.

Функция main

Функция main , получающая управление при запуске приложения, создает главное окно и организует цикл обработки сообщений.

В переменной hmq хранится идентификатор очереди сообщений, созданной функцией WinCreateMsgQueue . При выборке сообщение записвается для временного хранения в структуру qmsg.

Переменная flFrameFlags хранит набор флагов, которые используются при создании окна Frame Window :

ULONG flFrameFlags =
  FCF_SYSMENU    | FCF_TITLEBAR       | FCF_MINMAX   |
  FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
  FCF_ICON;

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

В строке szWndClass записано имя класса, которое используется для окна Client Window .

Первое, что делает функция main после запуска приложения - это инициализация при помощи функции WinInitialize . Данная функция должна вернуть идентификатор блока Anchor-block при нормальном завершении и значение NULLHANDLE в случае возникновения ошибки.

Как обработать ошибку?

Можно, конечно, просто завершить работу приложения, однако в этом случае мы так и не узнаем, что же произошло. Гораздо лучше было бы вывести на экран сообщение об ошибке, однако в приложении Presentation Manager мы не можем воспользоваться для этого такими функциями, как puts или printf .

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

if(hab == NULLHANDLE)
{
  WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
    "Ошибка инициализации",
    "Ошибка", 0, MB_ICONHAND | MB_OK);
  return(-1);
}

Прототип функции WinMessageBox приведен ниже:

ULONG WinMessageBox (
  HWND hwndParent, // идентификатор родительского окна
  HWND hwndOwner,  // идентификатор окна владельца
  PSZ pszText,     // текст сообщения
  PSZ pszCaption,  // заголовок диалоговой панели
  ULONG idWindow,  // идентификатор окна 
                   // диалоговой панели
  ULONG flStyle);  // стиль диалоговой 
                   // панели с сообщением

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

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

На следующем этапе функцией WinRegisterClass выполняется регистрация класса для окна Client Window . В качестве второго параметра этой функции мы указываем строку szWndClass, а в качестве третьего - имя функции окна WndProc.

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

После того как из очереди сообщений будет выбрано сообщение WM_QUIT , функция WinGetMsg вернет значение FALSE и цикл обработки сообщений завершит свою работу. После этого функция main уничтожит главное окно приложения. Затем она удалит очередь сообщений и освободит ресурсы, полученные у системы Presentation Manager, вызвав функцию WinTerminate .

На этом приложение завершит свою работу.

Функция окна

Функция WndProc обрабатывает сообщения, поступающие в окно Client Window нашего приложения.

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

Сообщение WM_ERASEBACKGROUND посылается в окно для того, чтобы функция окна подтвердила необходимость стирания фона окна Client Window . В зависимости от значения, которое возвратит функция окна в ответ на это сообщение, система Presentation Manager будет принимать решение о необходимости стирания. Если функция вренет значение 1, стирание следует выполнить, если 0 - нет. Экспериментируя с приложением, вы можете попробовать изменить возвращаемое значение на нулевое. Вы увидите, что при перемещении окно приложения Client Window не будет перерисовываться.

Более детально процесс рисования в окне мы рассмотрим позже. А сейчас займемся сообщениями WM_BUTTON1DOWN и WM_BUTTON2DOWN .

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

Вместе с сообщениями WM_BUTTON1DOWN и WM_BUTTON2DOWN передаются параметры - текущие координаты курсора мыши. Координата по оси X передается через параметр mp1, а координата по оси Y - через параметр mp2.

Для того чтобы отобразить эти координаты на экране, мы с помощью функции sprintf подготавливаем текстовую строку и вызываем функцию WinMessageBox . Обратите внимание, что в качестве второго параметра мы указываем идентификатор окна hWnd, который передается в функцию окна через первый параметр.

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

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

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

Ресурсы приложения

Создавая программы для операционной системы MS-DOS , вы получали загрузочный модуль программы из исходного текста, объектных модулей и библиотек объектных модулей. Технология была очень простой - транслятор выполнял преобразования файлов исходного текста в файлы объектных модулей, а редактор связей собирал все объектные модули и библиотеки объектных модулей в исполнимый exe-файл.

Полученный исполнимый файл содержал в себе все необходимое для работы (за исключением кода функций операционной системы MS-DOS и системы базового ввода/вывода BIOS , который вызывается через программные прерывания).

В мультизадачной среде этот подход приводит к неэкономному расходованию оперативной памяти, поэтому вместо статического редактирования используется динамическое. Основные принципы динамического редактирования в среде IBM OS/2 мы описали в 20 томе "Библиотеки системного программиста".

Дополнительно загрузочные файлы приложений IBM OS/2 (также, как и загрузочные файлы приложений Microsoft Windows), содержат данные, которые называются ресурсами приложений. Ресурсы описываются в файле определения ресурсов, который на этапе сборки загрузочного модуля приложения преобразуется в двоичный вид компилятором ресурсов и затем добавляется редактором связей к загрузочному модулю.

Наше приложение использует только один ресурс - пиктограмму . Этот ресурс описан в файле определения ресурсов mywindow.rc, который представлен в листинге 1.3

Листинг 1.3. Файл mywindow\mywindow.rc

#include <os2.h>
#include "mywindow.h"
ICON ID_APP_FRAMEWND MYWINDOW.ICO

Файл ресурсов не является программой, составленной на языке программирования C, несмотря на то что в него можно включать файлы определений с помощью команды #include. Это обычный текстовый файл, содержащий специальные команды, которые мы подробно рассмотрим позже. Сейчас только заметим, что файл mywindow.h включен для определения константы ID_APP_FRAMEWND - идентификатора окна Frame Window . Этот идентификатор в данном случае используется для ссылки на пиктограмму MYWINDOW.ICO.

Пиктограмма MYWINDOW.ICO была нарисована с использованием редактора пиктограмм ICONEDIT , входящего в комплект операционной системы IBM OS/2 Warp и есть на дискете, которую вы можете купить вместе с книгой (на этой дискете есть исходные тексты всех программ, приведенных в нашей книге).

Файл определения модуля

Еще один файл, который используется редактором связей при создании загрузочного модуля приложения IBM OS/2 и который не имеет аналога в операционной системе MS-DOS, называется файлом определения модуля . В этом файле указывается имя загрузочного модуля, строка описания, режим работы приложения, размер областей памяти, выделенных для кучи и стека, а также перечисляются имена функций обратного вызова.

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

Листинг 1.4. Файл mywindow\mywindow.def

NAME        MYWINDOW   WINDOWAPI
DESCRIPTION 'MyWindow Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc

В строке описания имени приложения есть параметр WINDOWAPI , который означает, что данное приложение является приложением Presentation Manager.

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

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

Трансляция исходных текстов приложения

Если вы будете транслировать исходные тексты приложений, приведенных в нашей книге с помощью системы разработки Borland C++ for OS/2, то сможете воспользоваться навыками, полученными при программировании в среде MS-DOS или Microsoft Windows. Все, что вам нужно сделать, это создать новый проект, добавив в него файлы с именами *.c, *.rc и *.def. Затем вы можете транслировать и собирать проект обычным образом. К сожалению, из-за недостатка места в нашей книге мы не сможем описать в деталях этот процесс, однако он не сложен и мы уверены, что вы справитесь с этим сами, точно также, как и с установкой среды разработки Borland C++ for OS/2.

Что же касается системы разработки IBM VisualAge C++, то мы подготовили mak-файл, с помощью которого вы сможете собрать проект из командного приглашения OS/2. Этот способ хотя и не слишком удобен по сравнению с использованием интегрированных систем разработки, позволит вам работать с компьютером, оснащенным небольшим объемом оперативной памяти или медленным процессором. Файл называется mywindow.mak (листинг 1.5).

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

c:\os2prg\src\mywindow>nmake mywindow.mak

Листинг 1.5. Файл mywindow\mywindow.mak

PRJ    = mywindow
CC     = icc /Ti /c /Ge /Gd- /Se /Re /ss /Gm+
LFLAGS = /NOFREE /NOE /NOD /ALIGN:16 /EXEPACK /M /BASE:0x10000 
LINK   = ILINK  $(LFLAGS)
LIBS   = CPPOM30 + OS2386
HEADERS  = $(PRJ).h
ALL_OBJ  = $(PRJ).obj

.SUFFIXES: .rc .res .obj .lst .c

.c.lst:
    $(CC) -Fc$*.lst -Fo$*.obj $*.c

.c.obj:
    $(CC) -Fo$*.obj $*.c

.rc.res:
    rc -r $*.rc

all: $(PRJ).exe

$(PRJ).l: $(PRJ).mak
    echo $(ALL_OBJ)  >  $(PRJ).l
    echo $(PRJ).exe  >> $(PRJ).l
    echo $(PRJ).map  >> $(PRJ).l
    echo $(LIBS)     >> $(PRJ).l
    echo $(PRJ).def  >> $(PRJ).l

$(PRJ).res: $(PRJ).rc $(PRJ).ico $(PRJ).h

$(PRJ).obj: $(PRJ).c $(HEADERS)

$(PRJ).exe: $(ALL_OBJ)  $(PRJ).def $(PRJ).l $(PRJ).res
    $(LINK) @$(PRJ).l
    rc -p -x $(PRJ).res $(PRJ).exe