Программирование для IBM OS2

         

Обработчик сообщения WM_PAINT


Обработчик сообщения WM_PAINT получает пространство отображения с помощью функции WinBeginPaint , освобождая его после рисования при помощи функции WinEndPaint .

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

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

WinFillRect (hps, &rec, CLR_WHITE );

После этого в окне Client Window рисуется строка текста, для чего вызывается функция GpiCharString At .



Обработчик сообщения WM_SIZE


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

WinInvalidateRect (hWnd, NULL, TRUE);

Функция WinInvalidateRect объявляет, что область внутри окна hWnd требует перерисовки, для чего в очередь приложения помещается сообщение WM_PAINT . При получении пространства отображения функцией WinBeginPaint в структуру rec будет записаны границы этой области.

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

BOOL WinInvalidateRect ( HWND hwnd, // идентификатор обновляемого окна PRECTL pwrc, // обновляемая область BOOL fIncludeChildren); // дополнительный признак

Через параметр hwnd передается идентификатор окна, область внутри которого необходимо обновить.

Границы обновляемой области задаются параметром pwrc. Если вместо указателя на структуру типа RECTL указано значение NULL, обновляется все окно.

Если значение параметра fIncludeChildren равно TRUE, дополнительно обновляются также все дочерние окна, функциям которых также будет передано сообщение WM_PAINT . Если же значение этого параметра равно FALSE, дочерние окна не будут перерисованы, если родительское окно имеет стиль WS_CLIPCHILDREN .

В случае успеха функция WinInvalidateRect возвращает значение TRUE, при ошибке - FALSE.

В нашем приложении обработчик сообщения WM_SIZE инициирует перерисовку всего окна Client Window , поэтому при любом изменении размеров главного окна приложения функция окна получит сообщение WM_PAINT . Обработчик этого сообщения вначале сотрет текущее содержимое окна, а затем нарисует текстовую строку Hello, PM! в точке с заданными координатами.



Окна в приложениях 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.



Окно Frame Window


Каждое приложение обычно создает окно Frame Window , которое всегда располагается на поверхности окна Desktop Window . При этом окно Desktop Window является родительским (Parent Window) для окна Frame Window. Соответственно, окно Frame Window по отношению к окну Desktop Window будет дочерним (Child Window).

Когда вы создаете стандартное окно Frame Window , у него обычно имеется несколько дочерних окон, таких как системное меню, заголовок, окно Client Window и т. д. Полный список этих окон вместе с их идентификаторами приведен ниже.

Дочернее окно Идентификатор
Системное меню FID_SYSMENU
Заголовок окна FID_TITLEBAR
Кнопка минимизации и максимизации FID_MINMAX
Меню FID_MENU
Вертикальная полоса просмотра FID_VERTSCROLL
Горизонтальная полоса просмотра FID_HORZSCROLL
Окно Client Window FID_CLIENT

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

Если вам будет нужно определить идентификатор одного из перечисленных выше органов управления, вы можете воспользоваться функцией WinWindowFromID , передав ей в качестве первого параметра идентификатор окна Frame Window , а в качестве второго - идентификатор соответствующего дочернего окна, например:

hwndMenu = WinWindowFromID (hwndFrameWindow, FID_MENU );



Окно рабочего стола


Какое окно является "основателем рода", т. е. родительским для всех остальных окон в Presentation Manager? Окна всех приложений располагаются в окне, представляющем собой поверхность рабочего стола Workplace Shell . Это окно, которое называется Desktop Window , создается автоматически при запуске операционной системы.

Однако окно Desktop Window само по себе является дочерним по отношению к другому окну - окну Object Window . Это окно не отображается и используется системой Presentation Manager для собственных нужд.

На рис. 2.2 показано, как соотносятся между собой окна Object Window , Desktop Window и окна Frame Window , создаваемые для каждого приложения.

Рис. 2.2. Взаимосвязь основных окон Presentation Manager

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

hwndDesktopWnd = WinQueryDesktopWindow(hab, NULL);

На окно Object Window при необходимости можно ссылаться при помощи идентификатора HWND_OBJECT . Второй способ получения этого идентификатора основан на использовании функции WinQueryObjectWindow :

hwndObjectWnd = WinQueryObjectWindow (HWND_DESKTOP);



Определение количества кнопок


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

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

LONG WinQuerySysValue ( HWND hwndDeskTop, // идентификатор окна рабочего стола LONG iSysValue); // код системного параметра

Для параметра hwndDeskTop вы можете указать значение HWND_DESKTOP. Параметр iSysValue определяет, значение какого системного параметра необходимо определить.

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

LONG lButtons; // количество кнопок lButtons = WinQuerySysValue (HWND_DESKTOP, SV_CMOUSEBUTTONS );

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



Определение координат курсора мыши


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



Определение параметров круглого регулятора


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



Определение параметров полосы просмотра


Для того чтобы узнать текущее положение движка, окну полосы просмотра необходимо послать сообщение SBM_QUERYPOS , например:

USHORT usSliderPos; usSliderPos = (USHORT)WinSendMsg(hwndYScroll, SBM_QUERYPOS, (MPARAM) NULL, (MPARAM) NULL);

Функция WinSendMsg возвратит искомое значение текущей позиции.

Для определения текущего диапазона изменения позиции окну полосы просмотра необходимо послать сообщение SBM_QUERYRANGE , например, так:

USHORT usMinimum, usMaximum; MRESULT mResult; mResult = WinSendMsg(hwndYScroll, SBM_QUERYRANGE, (MPARAM) NULL, (MPARAM) NULL); usMinimum = SHORT1FROMMR(mResult); // минимальная позиция usMaximum = SHORT2FROMMR(mResult); // максимальная позиция

Минимальная и максимальная позиции передаются, соответственно, через младшее и старшее слово, возвращаемое функцией WinSendMsg.



Определение полос просмотра при создании окна


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

Для того чтобы у окна появились вертикальная и горизонтальная полосы просмотра, при создании окна функцией WinCreateStdWindow необходимо указать флаги FCF_VERTSCROLL и FCF_HORZSCROLL (соответственно, для создания вертикальной и горизонтальной полосы просмотра):

HWND hWndFrame; ULONG flFrameFlags = FCF_SYSMENU | FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST | FCF_ICON | FCF_VERTSCROLL | FCF_HORZSCROLL ; hWndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE, &flFrameFlags, szWndClass, szAppTitle, 0, 0, ID_APP_FRAMEWND, &hWndClient);



Определение сосотояния клавиш мыши


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

Прототип функции WinGetKeyState представлен ниже:

LONG WinGetKeyState ( HWND hwndDeskTop, // идентификатор окна Desktop Window LONG vkey); // код виртуальной клавиши

Для того чтобы определить состояние кнопок мыши, необходимо указать в параметре vkey значения констант VK_BUTTON1 , VK_BUTTON2 или VK_BUTTON3 . Информацию о кнопках, расположенных на клавиатуре, вы можете получить, указав в этом параметре соответствующий код виртуальной клавиши. Эти коды приведены в главе нашей книги, посвященной клавиатуре.

Возвращаемое функцией значение может быть комбинацией следующих констант:

Константа Описание
0x0001 С момента запуска операционной системы клавиша была нажато нечетное количество раз
0x8000 Клавиша нажата



Определение состояния переключателя


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

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

Для определения текущего состояния переключателя в окно переключателя необходимо послать сообщение BM_QUERYCHECK (оба параметра этого сообщения не используются и должны быть равны нулю):

USHORT usState; usState = (USHORT)WinSendMsg (hWndCheckBox, BM_QUERYCHECK, MPFROMSHORT(0), NULL);

После того как в приведенном выше примере функция WinSendMsg возвратит управление, в переменную usState будет записано состояние кнопки:

Значение Состояние переключателя
0 Выключен и изображается без галочки
1 Включен и отмечен галочкой
2 Находится в неопределенном состоянии



Определения


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

Для каждого из перечисленных выше трех окон в области глобальных переменных хранятся идентификаторы окна Frame Window (hWndFrame1, hWndFrame2 и hWndChildFrame), а также идентификаторы окон Client Window (hWndClient1, hWndClient2 и hWndChildClient).

Кроме этого, в области глобальных переменных находятся заголовки для создаваемых окон (szAppTitle1, szAppTitle2 и szChildTitle).



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


Исходные тексты любого приложения 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 содержит заголовок приложения, который отображается в верхней части главного окна.


В программе определены две константы с именами XSIZE и YSIZE, которые задают верхнюю границу изменений значений, соответственно, горизонтальной и вертикальной полосы просмотра.

Обработчик сообщения WM_SIZE записывает в переменные cxClient и cyClient размеры окна Client Window.

В переменных cxChar, cyChar и cyDesc хранятся основные размеры символов для шрифта Courier. Мы выбрали этот шрифт для отображения информации в главном окне приложения.

Структура fm содержит метрики шрифта, которые вы будете просматривать в окне Client Window.

Для того чтобы посылать сообщения окнам полос просмотра, необходимо знать их идентификаторы. Эти идентификаторы хранятся в переменных hwndXScroll и hwndYScroll (соответственно, для горизонтальной и вертикальной полосы просмотра).

В переменных nXScrollPos и nYScrollPos хранятся текущие координаты движков полос просмотра. Эти координаты нужны для определения текущих координат, начиная с которых в окне Client Window будет выводиться текст. Начальные координаты для вывода текста записаны в переменных cxCurrentPosition, cyCurrentPosition и, разумеется, зависят от положения движков полос просмотра.



ОСНОВЫ 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, либо ничего, останавливаясь на текстовом приложении.


Отношения собственности


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

BOOL WinSetParent( HWND hwnd, // идентификатор окна HWND hwndNewParent, // новое родительское окно BOOL fRedraw); // флаг перерисовки

Эта функция устанавливает для окна с идентификатором hwnd новое родительское окно hwndNewParent. Если при этом значение флага fRedraw будет равно TRUE, выполняется перерисовка окна, если FALSE - перерисовка окна не выполняется.



Переключатели


Переключатели , как и кнопки, создаются при помощи функции WinCreateWindow на базе предопределенного класса окна WC_BUTTON . Однако в отличие от кнопок, для переключателей необходимо указывать стили BS_CHECKBOX , BS_AUTOCHECKBOX , BS_RADIOBUTTON или BS_AUTORADIOBUTTON :

hWndCheckBox = WinCreateWindow (hWnd, WC_BUTTON , "Text", WS_VISIBLE | BS_AUTOCHECKBOX, 0, 0, 0, 0, hWnd, HWND_TOP , CHCKBOX_ID, NULL, NULL);

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

На рис. 8.2 для примера показана диалоговая панель с переключателями круглой и прямоугольной формы.

Рис. 8.2. Диалоговая панель с переключателями



Плавающее меню


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

Обычно с помощью плавающих меню создают контекстные меню, состав строк котоых зависит от объекта, по которому был сделан щелчок правой клавишей мыши.

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

hwndPopupMenu = WinLoadMenu (hWnd, NULLHANDLE, POPUP_MENU); . . . WinPopupMenu (hWnd, hWnd, hwndPopupMenu, SHORT1FROMMP (mp1), SHORT2FROMMP (mp1), IDM_FILE_NEW, PU_POSITIONONITEM | PU_HCONSTRAIN | PU_VCONSTRAIN | PU_MOUSEBUTTON1 | PU_MOUSEBUTTON2 | PU_KEYBOARD);

Прототип функции WinLoadMenu представлен ниже:

HWND WinLoadMenu ( HWND hwndFrame, // родительское окно HMODULE hmod, // идентификатор ресурса HWND idMenu); // идентификатор окна меню

Через параметр hwndFrame функции WinLoadMenu следует передать идентификатор окна Frame Window , которое станет родительским для загружаемого меню. Можно также указывать идентификаторы объектного окна HWND_OBJECT и окна рабочего стола HWND_DESKTOP .

Параметр hmod должен содержать идентификатор модуля, из ресурсов которого загружается меню. В качестве такого модуля может выступать либо загрузочный файл приложения, либо библиотека динамической загрузки DLL. В первом случае значение параметра hmod может быть равно NULLHANDLE. Вторую возможность мы рассмотрим позже, когда будем рассказывать о библиотеках DLL.

Параметр idMenu предназначен для передачи идентификатора меню, с которым это меню определено в файле описания ресурсов.

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


Функция WinPopupMenu , при помощи которой выполняется отображение плавающего меню, имеет такой прототип:

BOOL WinPopupMenu ( HWND hwndParent, // родительское окно HWND hwndOwner, // окно-владелец HWND hwndMenu, // идентификатор плавающего меню LONG x, // координата X для отображения меню LONG y, // координата Y для отображения меню LONG idItem, // идентификатор строки меню, // которая будет выбрана по умолчанию ULONG fs); // дополнительные параметры

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

Параметр hwndMenu предназначен для передачи идентификатора отображаемого плавающего меню, загруженного ранее при помощи функции WinLoadMenu .

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

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

Значение Описание
PU_POSITIONONITEM Меню размещается таким образом, что строка меню, идентификатор которой указан при помощи параметра idItem, имеет координаты, заданные параметрами x и y
PU_HCONSTRAIN Меню размещается на экране таким образом, что оно полностью отображается по своей ширине
PU_VCONSTRAIN Аналогично предыдущему, но по высоте
PU_MOUSEBUTTON1DOWN Меню отображается, когда нажата левая клавиша мыши, и исчезает, когда пользователь отжимает левую клавишу мыши
PU_MOUSEBUTTON2DOWN Аналогично предыдущему, но для правой клавиши двухкнопочной мыши или средней клавиши трехкнопочной мыши
PU_MOUSEBUTTON3DOWN Аналогично предыдущему, но используется для правой клавиши трехклавишной мыши
PU_NONE Меню отображается без предварительного выбора какой-либо строки
PU_SELECTITEM Если дополнительно указано значение PU_NONE, при отображении меню выделяется строка с идентификатором, переданным через параметр idItem. Если же этот идентификатор соответствует вложенному меню, последнее отображается в раскрытом состоянии
PU_KEYBOARD Для работы с меню можно использовать клавиатуру
PU_MOUSEBUTTON1 Для работы с меню можно использовать левую клавишу мыши
PU_MOUSEBUTTON2 Для работы с меню можно использовать правую клавишу двухкнопочной мыши или среднюю клавишу трехкнопочной мыши
PU_MOUSEBUTTON3 Для работы с меню можно использовать правую клавишу трехкнопочной мыши
Если плавающее меню отображено, функция WinPopupMenu возвращает значение TRUE. При ошибке возвращается значение FALSE.


Подготовка шаблона меню


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

MENU MenuID BEGIN ... END

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

Между строками BEGIN и END (которые, кстати, можно заменить скобками { и }, соответственно) располагаются операторы описания временных меню SUBMENU и операторы описания отдельных строк меню MENUITEM .

Для примера приведем сокращенный образец шаблона меню, взятый из приложения MENUAPP, исходные тексты которого будут приведены в этой главе:

MENU ID_APP_FRAMEWND BEGIN SUBMENU "~File", IDM_FILE BEGIN MENUITEM "~New...", IDM_FILE_NEW MENUITEM "~Open...", IDM_FILE_OPEN MENUITEM SEPARATOR MENUITEM "~Save...", IDM_FILE_SAVE MENUITEM "Save ~as...", IDM_FILE_SAVEAS MENUITEM SEPARATOR MENUITEM "~Exit", IDM_FILE_EXIT END

SUBMENU "~Help", IDM_HELP BEGIN MENUITEM "Help ~index...", IDM_HELP_INDEX MENUITEM "~General help...", IDM_HELP_GENERAL MENUITEM "~Using help...", IDM_HELP_USING MENUITEM "~Keys help...", IDM_HELP_KEYS MENUITEM SEPARATOR MENUITEM "~Product information...",IDM_HELP_ABOUT END END

В этом шаблоне описано меню с идентификатором ID_APP_FRAMEWND. Этот идентификатор был использован при создании главного окна приложения.

В меню с помощью операторов SUBMENU определено два временных меню: File и Help.

Каждое временное меню имеет свой идентификатор, указанный в операторе SUBMENU. Описание каждого временного меню ограничено операторами BEGIN и END.

Общий вид оператора SUBMENU представлен ниже:

SUBMENU text, id

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

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


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

Оператор описания строк меню имеет следующий вид:

MENUITEM text, id, style, attribute

Поле text определяет текстовую строку, которая будет отображаться в строке меню. Для обеспечения ускоренного доступа к строке меню вы можете выделить один из символов строки подчеркиванием, указав перед ним символ ~.

Идентификатор id должен быть указан как целое число или символическая константа.

В качестве значения для поля style, определяющего стиль строки меню, вы можете использовать одну из следующих констант:

Константа Описание
MIS_SUBMENU Данная строка является меню следующего уровня
MIS_SEPARATOR Горизонтальная разделительная линия
MIS_BITMAP В строке меню отображается не текст, а графическое изображение
MIS_TEXT В строке меню отображается текстовая строка. Этот стиль используется по умолчанию
MIS_BUTTONSEPARATOR Разделитель меню
MIS_BREAK Начало нового столбца или строки меню
MIS_BREAKSEPARATOR Аналогично MIS_BREAK, однако дополнительно отображается разделитель. Этот стиль используется только для вложенных меню
MIS_SYSCOMMAND Если указан этот стиль, то при выборе пользователем строки из меню в родительское окно вместо сообщения с кодом WM_COMMAND передается сообщение с кодом WM_SYSCOMMAND
MIS_OWNERDRAW При использовании этого стиля родительское окно рисует строку меню самостоятельно. Для того чтобы функция родительского окна имела возможность определить видимые границы, занимаемые строкой, ей передается сообщение WM_MEASUREITEM . Рисование выполняется при получении родительским окном сообщения WM_DRAWITEM
MIS_HELP При выборе строки меню родительское окно вместо сообщения с кодом WM_COMMAND получает сообщение с кодом WM_HELP
MIS_STATIC Строка с таким стилем не может быть выбрана ни с помощью мыши, ни с помощью клавиатуры
В поле attribute оператора MENUITEM можно указывать одно из следующих значений, влияющих на внешний вид соовтетствующей строки меню:



Значение Описание
MIA_HILITED Этот атрибут устанавливается если пользователь выделил соответствующую строку меню
MIA_CHECKED Выделение строки символом "галочки"
MIA_DISABLED Строка заблокирована и не может быть выбрана
MIA_FRAMED Вокруг строки отображается рамка
MIA_NODISMISS Если указан этот атрибут, то сответстсвующее временное меню не исчезнет с экрана до тех пор, пока родительское окно не получит извещение о выборе строки, или пока пользователь не перейдет к работе с другим меню, или пока он не нажмет клавишу <Esc>
При необходимости приложение может изменять атрибуты строк меню, посылая ему сообщение MM_SETITEMATTR . Посылая меню сообщение MM_QUERYITEM ATTR можно узнать текущие атрибуты заданной строки.


Полоса просмотра


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

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

Для создания полосы просмотра вы можете воспользоваться функцией WinCreateWindow, как это мы делали для создания кнопок и переключателей в предыдущих приложениях. Однако если вам необходимо создать полосы просмотра в нижней или левой части окна, то это можно сделать проще: достаточно при создании окна указать функции WinCreateStdWindow флаги FCF_VERTSCROLL (вертикальная полоса проосмотра) и FCF_HORZSCROLL (горизонтальная полоса просмотра).

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

Прежде чем мы расскажем вам об этих сообщениях подробнее, необходимо рассмотреть структуру полос просмотра. Полоса просмотра состоит из нескольких объектов, имеющих различное назначение. На рис. 8.4 показано назначение объектов вертикальной полосы просмотра при ее использовании, например, для просмотра текстовых документов.

Рис. 8.4. Вертикальная полоса просмотра

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

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

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

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

Горизонтальная полоса просмотра состоит из тех же объектов, что и вертикальная. Она обеспечивает свертку документа в горизонтальном направлении.


Приложение BEEP


Для иллюстрации методов работы с таймером мы подготовили несложное приложение BEEP, исходный текст которого приведен в листинге 7.1.

Листинг 7.1. Файл beep\beep.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include "beep.h"

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

CHAR szAppTitle[] = "Timer Beep";

// ================================================= // Главная функция приложения 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[] = "TIMERBEEP";

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 (hab); return(-1); }

// Регистрация главного окна приложения fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================

MRESULT EXPENTRY WndProc( HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { switch (msg) { case WM_CREATE : { // Запускаем таймер с интервалом 1000 мс WinStartTimer (hab, hWnd, ID_APP_TIMER1, 1000);

// Запускаем таймер с интервалом 3000 мс WinStartTimer (hab, hWnd, ID_APP_TIMER2, 3000); return FALSE; }

case WM_DESTROY : { // Останавливаем таймеры WinStopTimer (hab, hWnd, ID_APP_TIMER1); WinStopTimer (hab, hWnd, ID_APP_TIMER2); return 0; }

case WM_TIMER : { // Для каждого таймера выдаем звуковой // сигнал своей частоты и длительности switch(SHORT1FROMMP (mp1)) { case ID_APP_TIMER1: { DosBeep(1000, 100); break; }

case ID_APP_TIMER2: { DosBeep(400, 300); break; }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } return 0; }

case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }


Приложение BUTTON


Приложение BUTTON создает в своем главном окне две кнопки (рис. 8.1). Если пользователь нажимает на одну из них, на экране появляется сообщение с названием нажатой кнопки.

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

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

Листинг 8.1. Файл button\button.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include "button.h"

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

// Идентификаторы кнопок HWND hWndButton1; HWND hWndButton2;

CHAR szAppTitle[] = "Button Demo";

// ================================================= // Главная функция приложения 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[] = "BTNDEMO";

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 (hab); return(-1); }

// Регистрация главного окна приложения fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================

MRESULT EXPENTRY WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { HPS hps; RECTL rec;

switch (msg) { // Создаем две кнопки case WM_CREATE : { hWndButton1 = WinCreateWindow (hWnd, WC_BUTTON , "Кнопка 1", WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 0, 0, hWnd, HWND_TOP , BTN1_ID, NULL, NULL);

hWndButton2 = WinCreateWindow (hWnd, WC_BUTTON , "Кнопка 2", WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 0, 0, hWnd, HWND_TOP , BTN2_ID, NULL, NULL);

return FALSE; }

// Изменяем размер и расположение кнопок case WM_SIZE : { WinSetWindowPos (hWndButton1, HWND_TOP , 10, 10, 200, 50, SWP _SIZE | SWP_MOVE | SWP_ZORDER );

WinSetWindowPos (hWndButton2, HWND_TOP , 10, 70, 200, 50, SWP _SIZE | SWP_MOVE | SWP_ZORDER );

// Перерисовываем окно приложения WinInvalidateRect (hWnd, NULL, TRUE); return 0; }

case WM_PAINT : { // Получаем пространство отображения hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

// Закрашиваем область, требующую обновления WinFillRect (hps, &rec, CLR_WHITE);

// Возвращаем пространство отображения WinEndPaint (hps); return 0; }

case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

// Это сообщение поступает в функцию окна, // когда пользователь нажимает одну из двух // кнопок case WM_COMMAND : { switch (COMMANDMSG(&msg) -> cmd) { case BTN1_ID: { WinMessageBox (HWND_DESKTOP, hWnd, "Нажата кнопка 1", "Сообщение", 0, MB_INFORMATION | MB_APPLMODAL | MB_MOVEABLE | MB_OK); break; }

case BTN2_ID: { WinMessageBox (HWND_DESKTOP, hWnd, "Нажата кнопка 2", "Сообщение", 0, MB_INFORMATION | MB_APPLMODAL | MB_MOVEABLE | MB_OK); break; } default: break; } return 0; } default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }


Приложение CHECK


Для демонстрации техники работы с переключателями мы подготовили исходные тексты приложения CHECK. В своем главном окне (рис. 8.3) это приложение создает кнопку и два переключателя. Первый из этих переключателей обычный, а второй - автоматический.

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

При помощи мыши вы можете изменять состояние переключателей с номерами 2 и 3 (первый номер присвоен кнопке с надписью "Переключатели"). Второй переключатель - автоматический и имеет стиль BS_AUTOCHECKBOX, третий - обычный со стилем BS_CHECKBOX.

Если после установки состояния переключателей нажать кнопку, расположенную в нижней части окна приложения, на экране появится диалоговая панель, отображающая текущее состояние переключателей. Эта панель показана на рис. 8.2.

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

Листинг 8.5. Файл check\check.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include <stdio.h> #include "check.h"

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

// Идентификаторы кнопок HWND hWndButton1; HWND hWndButton2; HWND hWndButton3;

// Состояние переключателей BOOL fButton2Checked = FALSE; BOOL fButton3Checked = FALSE;

CHAR szAppTitle[] = "CheckBox Demo";

// ================================================= // Главная функция приложения 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[] = "BTNDEMO";


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 (hab); return(-1); }

// Регистрация главного окна приложения fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================

MRESULT EXPENTRY WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { HPS hps; RECTL rec; CHAR szBuf[256];

switch (msg) { // Создаем кнопку и два переключателя case WM_CREATE : { hWndButton1 = WinCreateWindow (hWnd, WC_BUTTON , "Переключатели", WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 0, 0, hWnd, HWND_TOP , BTN1_ID, NULL, NULL);

// Автоматический переключатель hWndButton2 = WinCreateWindow (hWnd, WC_BUTTON , "(2) AutoCheckBox", WS_VISIBLE | BS_AUTOCHECKBOX, 0, 0, 0, 0, hWnd, HWND_TOP , BTN2_ID, NULL, NULL);



// Обычный переключатель hWndButton3 = WinCreateWindow (hWnd, WC_BUTTON , "(3) CheckBox", WS_VISIBLE | BS_CHECKBOX, 0, 0, 0, 0, hWnd, HWND_TOP , BTN3_ID, NULL, NULL); return FALSE; }

// Изменяем размер и расположение органов // управления case WM_SIZE : { WinSetWindowPos (hWndButton1, HWND_TOP , 10, 10, 200, 50, SWP _SIZE | SWP_MOVE | SWP_ZORDER );

WinSetWindowPos (hWndButton2, HWND_TOP , 10, 70, 200, 50, SWP _SIZE | SWP_MOVE | SWP_ZORDER );

WinSetWindowPos (hWndButton3, HWND_TOP , 10, 110, 200, 50, SWP _SIZE | SWP_MOVE | SWP_ZORDER );

// Перерисовываем окно приложения WinInvalidateRect (hWnd, NULL, TRUE); return 0; }

case WM_PAINT : { // Получаем пространство отображения hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

// Закрашиваем область, требующую обновление WinFillRect (hps, &rec, CLR_WHITE);

// Возвращаем пространство отображения WinEndPaint (hps); return 0; }

case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

// Это сообщение приходит при изменении // состояния переключателей case WM_CONTROL : { switch (SHORT1FROMMP (mp1)) { // Если сообщение пришло от автоматического // переключателя, получаем и сохраняем его // текущее состояние case BTN2_ID: { fButton2Checked = (BOOL) WinSendMsg (hWndButton2, BM_QUERYCHECK, MPFROMSHORT(0), NULL); break; }

// Если сообщение пришло от обычного // переключателя, изменяем его состояние // и сохраняем новое состояние // в переменной fButton3Checked case BTN3_ID: { if(fButton3Checked) WinSendMsg (hWndButton3, BM_SETCHECK, MPFROMSHORT(0), NULL);

else WinSendMsg (hWndButton3, BM_SETCHECK, MPFROMSHORT(1), NULL);

fButton3Checked = ~fButton3Checked; break; }

default: break; } return 0; }

// Когда пользователь нажимает кнопку, // определяем и сохраняем состояние // обоих переключателей case WM_COMMAND : { switch (COMMANDMSG(&msg) -> cmd) { case BTN1_ID: { sprintf (szBuf, "Состояние переключаталей:\n" "(3) третий - %d\n(2) второй - %d", fButton3Checked ? 1 : 0, fButton2Checked ? 1 : 0);

WinMessageBox (HWND_DESKTOP, hWnd, szBuf, "Состояние переключателей", 0, MB_INFORMATION | MB_APPLMODAL | MB_MOVEABLE | MB_OK); break; }

default: break; } return 0; }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }


Приложение CLOCK


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

Окно нашего приложения не имеет рамки и сразу после запуска приложения появляется в левом нижнем углу экрана. С помощью левой клавиши мыши вы можете перенести это окно в любое удобное для вас место экрана (рис. 7.1).

Рис. 7.1. Окно приложения CLOCK на поверхности рабочего стола

Исходный текст приложения CLOCK представлен в листинге 7.5.

Листинг 7.5. Файл clock\clock.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include <string.h> #include <stdio.h> #include "clock.h"

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

CHAR szAppTitle[] = "Clock Application";

// Координаты курсора мыши в момент нажатия // левой клавиши мыши SHORT cxPoint; SHORT cyPoint;

// Координаты курсора мыши в момент отпускания // левой клавиши мыши SHORT cxNewPoint; SHORT cyNewPoint;

// Признак начала процесса перемещения окна BOOL fDrag = FALSE;

// Идентификаторы указателей мыши HPOINTER hptr, hptr1;

// Размеры символов выбранного шрифта SHORT cxChar, cyChar, cyDesc;

// ================================================= // Главная функция приложения main // =================================================

int main () { HMQ hmq; QMSG qmsg; BOOL fRc; FONTMETRICS fm; HPS hps;

// Флаги для создания окна Frame Window . // Окно не имеет орагнов управления и // рамки для изменения размеров ULONG flFrameFlags = FCF_TASKLIST | FCF_ICON;

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

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 (hab); return(-1); }

// Регистрация главного окна приложения fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); }

// Получаем пространство отображения hps = WinGetPS (hWndFrame);

// Выбираем в пространство отображения шрифт // с фиксированной шириной символов SetCourierFont(hps);

// Определяем метрики шрифта GpiQueryFontMetrics(hps, (LONG)sizeof(fm), &fm);

cxChar = fm.lAveCharWidth; cyChar = fm.lMaxBaselineExt; cyDesc = fm.lMaxDescender;

// Устанавливаем шрифт, выбранный в пространство // отображения по умолчанию ResetFont(hps);

// Возвращаем пространство отображения WinReleasePS (hps);

// Устанавливаем новые размеры и расположение // главного окна приложения WinSetWindowPos (hWndFrame, HWND_TOP , 0, 0, 10 * cxChar, cyChar + cyDesc, SWP _SIZE | SWP_MOVE | SWP_ZORDER );

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

WinDestroyWindow(hWndFrame); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================



MRESULT EXPENTRY WndProc( HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { HPS hps; RECTL rec; SWP swp; CHAR pszBuf[256]; DATETIME dt;

switch (msg) { case WM_CREATE : { // Запускаем таймер с интервалом 1000 мс WinStartTimer (hab, hWnd, ID_APP_TIMER, 1000);

// Загружаем идентификаторы курсоров мыши hptr = WinLoadPointer(HWND_DESKTOP, NULLHANDLE, ID_APP_POINTER);

hptr1 = WinLoadPointer(HWND_DESKTOP, NULLHANDLE, ID_APP_POINTER1);

return FALSE; }

case WM_DESTROY : { // Останавливаем таймер WinStopTimer (hab, hWnd, ID_APP_TIMER);

// Удаляем курсоры мыши WinDestroyPointer (hptr); WinDestroyPointer (hptr1); return 0; }

// Когда приходит сообщение от таймера, // перерисовываем главное окно приложения case WM_TIMER : { WinInvalidateRect (hWnd, NULL, TRUE); return 0; }

case WM_PAINT : { // Получаем пространство отображения hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

// Определяем размеры главного окна WinQueryWindow Rect(hWnd, &rec);

// Определяем дату и время DosGetDateTime(&dt);

sprintf (pszBuf, "%02d:%02d:%02d", dt.hours, dt.minutes, dt.seconds);

// Устанавливаем шрифт для отображения SetCourierFont(hps);

// Выводим текст в центре окна WinDrawText (hps, -1, pszBuf, &rec, 0L, 0L, DT_WORDBREAK | DT_CENTER | DT_VCENTER | DT_TEXTATTRS | DT_ERASERECT);

// Восстанавливаем шрифт ResetFont(hps);

// Освобождаем пространство отображения WinEndPaint (hps); return 0; }

case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

// Когда пользователь нажимает левую клавишу // мыши, запоминаем координаты курсора и // выдвигаем окно приложения на передний план case WM_BUTTON1DOWN : { cxPoint = MOUSEMSG(&msg) -> x; cyPoint = MOUSEMSG(&msg) -> y;

// Изменяем расположение окна по оси Z WinSetWindowPos (hWndFrame, HWND_TOP , 0, 0, 0, 0, SWP _ZORDER );

// Устанавливаем признак перемещения // главного окна приложения fDrag = TRUE;

// Захватываем мышь WinSetCapture (HWND_DESKTOP, hWnd); return 0; }

// При отпускании левой клавиши мыши сбрасываем // признак перемещения окна case WM_BUTTON1UP : { // Сбрасываем признак перемещения // главного окна приложения fDrag = FALSE;



// Освобождаем мышь WinSetCapture (HWND_DESKTOP, NULLHANDLE); return 0; }

// Это сообщение приходит при перемещении курсора // мыши case WM_MOUSEMOVE : { // Если включен признак перемещения, определяем // новые координаты курсора и передвигаем окно if(fDrag) { // Выбираем указатель в виде закрытой руки WinSetPointer (HWND_DESKTOP, hptr1);

cxNewPoint = MOUSEMSG(&msg) -> x; cyNewPoint = MOUSEMSG(&msg) -> y;

// Определяем текущие координаты окна WinQueryWindow Pos(hWndFrame, &swp);

// Передвигаем окно WinSetWindowPos (hWndFrame, HWND_TOP , swp.x + (cxNewPoint - cxPoint), swp.y + (cyNewPoint - cyPoint), 0, 0, SWP _MOVE ); }

else // Выбираем указатель в виде открытой руки WinSetPointer (HWND_DESKTOP, hptr);

return (MRESULT)TRUE; }

// Если пользователь сделал двойной щелчок левой // клавише мыши, завершаем работу приложения case WM_BUTTON1DBLCLK : { WinPostMsg (hWnd, WM_QUIT , 0L, 0L); return 0; }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }

// ================================================= // Выбор шрифта с фиксированной шириной символов // =================================================

void SetCourierFont(HPS hps) { FATTRS fat;

// Заполняем структуру описанием нужного // нам шрифта fat.usRecordLength = sizeof(FATTRS); strcpy(fat.szFacename ,"Courier"); fat.fsSelection = 0; fat.lMatch = 0L; fat.idRegistry = 0; fat.usCodePage = 850; fat.lMaxBaselineExt = 12L; fat.lAveCharWidth = 12L; fat.fsType = 0; fat.fsFontUse = FATTR_FONTUSE_NOMIX;

// Создаем логический шрифт, имеющий идентификатор 1L GpiCreateLogFont(hps, NULL, 1L, &fat);

// Выбираем созданный шрифт в пространство отображения GpiSetCharSet (hps, 1L); }

// ================================================= // Установка шрифта, выбранного в пространство // отображения по умолчанию // ================================================= void ResetFont(HPS hps) { GpiSetCharSet (hps, LCID_DEFAULT); GpiDeleteSetId(hps, 1L); }


Приложение KBDMSG


Изучение параметров сообщения WM_CHAR удобно выполнять с помощью приложения KBDMSG, которое отображает эти параметры в своем окне (рис. 5.1).

Рис. 5.1. Просмотр клавиатурных сообщений в окне приложения KBDMSG

В столбце VKEY отображается виртуальный код нажатой клавиши, в столбце SCAN - аппаратный скан-код, в столбце FS - флаги. Далее в столбце REPT отображается счетчик повторений и в столбце CHAR - символ (только для символьных клавиш).

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

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

Листинг 5.1. Файл kbdmsg\kbdmsg.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include <stdio.h> #include <string.h> #include "kbdmsg.h"

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

CHAR szAppTitle[] = "Keyboard Messages";

// Размеры окна Client Window SHORT cxClient; SHORT cyClient;

// Размеры символов выбранного шрифта SHORT cxChar, cyChar, cyDesc;

// Заголовок столбцов CHAR szTitle[] = "VKEY SCAN FS REPT CHAR";

// ================================================= // Главная функция приложения 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[] = "KBDMSG";

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 (hab); return(-1); }

// Регистрация главного окна приложения fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================

MRESULT EXPENTRY WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { CHAR szMsg[100]; int nMsgSize; HPS hps; POINTL ptl; RECTL rec;

CHRMSG cm; FONTMETRICS fm;

switch (msg) { // При создании главного окна приложения // определяем и сохраняем метрики шрифта // с фиксированной шириной символов case WM_CREATE : { // Получаем пространство отображения hps = WinGetPS (hWnd);

// Выбираем в пространство отображения шрифт // с фиксированной шириной символов SetCourierFont(hps);

// Определяем метрики шрифта GpiQueryFontMetrics(hps, (LONG)sizeof(fm), &fm);

cxChar = fm.lAveCharWidth; cyChar = fm.lMaxBaselineExt; cyDesc = fm.lMaxDescender;

// Устанавливаем шрифт, выбранный в пространство // отображения по умолчанию ResetFont(hps);



// Возвращаем пространство отображения WinReleasePS (hps); return FALSE; }

// Во время перерисовки стираем содержимое // окна и выводим строку заголовка таблицы case WM_PAINT : { // Получаем пространство отображения hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

// Закрашиваем область, требующую обновление WinFillRect (hps, &rec, CLR_WHITE);

// Выбираем в пространство отображения шрифт // с фиксированной шириной символов SetCourierFont(hps);

// Рисуем заголовок таблицы в нижней части окна ptl.x = cxChar; ptl.y = cyDesc + cyChar; GpiCharString At (hps, &ptl, strlen(szTitle), szTitle);

// Устанавливаем шрифт, выбранный в пространство // отображения по умолчанию ResetFont(hps);

// Возвращаем пространство отображения WinEndPaint (hps); return 0; }

case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

// При изменении размеров окна сохраняем новые // размеры и перерисовываем окно case WM_SIZE : { cxClient = SHORT1FROMMP (mp2); cyClient = SHORT2FROMMP (mp2);

// Все окно требует перерисовки WinInvalidateRect (hWnd, NULL, TRUE); return 0; }

// Это сообщение появляется, когда пользователь // нажимает или отжимает клавишу case WM_CHAR : { // Получаем пространство отображения hps = WinGetPS (hWnd);

// Выбираем в пространство отображения шрифт // с фиксированной шириной символов SetCourierFont(hps);

// Выделяем параметры клавиатурного сообщения // и сохраняем их в структуре cm cm.chr = CHARMSG(&msg) ->chr; cm.vkey = CHARMSG(&msg) ->vkey; cm.scancode = CHARMSG(&msg) ->scancode; cm.cRepeat = CHARMSG(&msg) ->cRepeat; cm.fs = CHARMSG(&msg) ->fs;

// Готовим строку для отображения параметров // клавиатурного сообщения sprintf (szMsg, "%04x %04x %04x %d %c", cm.vkey, cm.scancode, cm.fs, cm.cRepeat, cm.fs & KC_CHAR ? cm.chr : ' ');

// Отображаем строку над заголовком таблицы // в нижней части окна ptl.x = cxChar; ptl.y = 2 * cyChar + cyDesc;

nMsgSize = strlen(szMsg); GpiCharString At (hps, &ptl, nMsgSize, szMsg);



// Устанавливаем шрифт, выбранный в пространство // отображения по умолчанию ResetFont(hps);

// Возвращаем пространство отображения WinReleasePS (hps);

// Устанавливаем границы сдвигаемой области окна WinSetRect (hab, &rec, 0, 2 * cyChar, cxClient, cyClient);

// Выполняем сдвиг верхней части окна WinScrollWindow (hWnd, 0, cyChar + cyDesc, &rec, NULL, NULLHANDLE, NULL, SW_INVALIDATERGN );

return 0; }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }

// ================================================= // Выбор шрифта с фиксированной шириной символов // =================================================

void SetCourierFont(HPS hps) { FATTRS fat;

// Заполняем структуру описанием нужного // нам шрифта

// Размер структуры fat.usRecordLength = sizeof(FATTRS);

// Название шрифта strcpy(fat.szFacename ,"Courier");

// Используем нормальный шрифт без выделений // наклоном, подчеркиванием и т. п. fat.fsSelection = 0;

// Указываем, что система Presentation Manager должна // подобрать шрифт, подходящий к нашему описанию fat.lMatch = 0L;

// Регистрационный номер, должен быть равен 0 fat.idRegistry = 0;

// Кодовая страница fat.usCodePage = 850;

// Высота шрифта fat.lMaxBaselineExt = 12L;

// Ширина шрифта fat.lAveCharWidth = 12L;

// Тип шрифта - обычный без использования кернинга, // двухбайтовых символов и т. д. fat.fsType = 0;

// Использование шрифта - шрифт, который отображается // без смешивания с графикой fat.fsFontUse = FATTR_FONTUSE_NOMIX;

// Создаем логический шрифт, имеющий идентификатор 1L GpiCreateLogFont(hps, NULL, 1L, &fat);

// Выбираем созданный шрифт в пространство // отображения GpiSetCharSet (hps, 1L); }

// ================================================= // Установка шрифта, выбранного в пространство // отображения по умолчанию // ================================================= void ResetFont(HPS hps) { // Выбираем шрифт по умолчанию GpiSetCharSet (hps, LCID_DEFAULT);

// Удаляем созданный ранее шрифт // с идентификатором 1L GpiDeleteSetId(hps, 1L); }


Приложение MENUAPP


В приложении MENUAPP мы создадим стандартное меню верхнего уровня, показанное на рис. 3.5.

Рис. 3.5. Стандартное меню верхнего уровня в приложении MENUAPP

Временное меню Options, показанное на рис. 3.6, демонстрирует использование стилей меню MIS_BREAKSEPARATOR и MIS_STATIC , а также атрибута строки меню MIA_FRAMED .

Рис. 3.6. Временное меню Options

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

Рис. 3.7. Плавающее меню, повторяющее временное меню File

Исходный текст приложения MENUAPP представлен в листинге 3.1.

Листинг 3.1. Файл menuapp\menuapp.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS

#include <os2.h> #include <stdio.h> #include "menuapp.h"

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

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

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

// Идентификатор окна Frame Window HWND hWndFrame;

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

// Заголовок приложения CHAR szAppTitle[] = "Menu Demo Application";

// Идентификатор временного меню HWND hwndPopupMenu;

// =================================================== // Главная функция приложения 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 | FCF_MENU;

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


// Инициализация приложения, необходимая для // использования функций 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 (hab); return(-1); }

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

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

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];

USHORT usItemId; // идентификатор меню HWND hwndMenu; // идентификатор окна меню

switch (msg) { case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));



// Выполняем инициализацию главного меню приложения case WM_INITMENU : { // Идентификатор меню usItemId = SHORT1FROMMP (mp1);

// Идентификатор окна меню hwndMenu = HWNDFROMMP(mp2);

// В меню File блокируем строки New и Open if(usItemId == IDM_FILE) { WinEnableMenuItem(hwndMenu, IDM_FILE_NEW, FALSE); WinEnableMenuItem(hwndMenu, IDM_FILE_OPEN, FALSE); }

// В меню Edit блокируем строки Undo и Redo else if(usItemId == IDM_EDIT) { WinEnableMenuItem(hwndMenu, IDM_EDIT_UNDO, FALSE); WinEnableMenuItem(hwndMenu, IDM_EDIT_REDO, FALSE); } return(WinDefWindowProc (hWnd, msg, mp1, mp2)); }

// Сообщение WM_COMMAND поступает в функцию окна, // кодгда пользователь выбирает одну из строк меню case WM_COMMAND : { // Анализируем код строки меню switch(LOUSHORT(mp1)) { case IDM_FILE_NEW: case IDM_FILE_OPEN: case IDM_FILE_SAVE: case IDM_FILE_SAVEAS:

case IDM_EDIT_UNDO: case IDM_EDIT_REDO: case IDM_EDIT_CUT: case IDM_EDIT_COPY: case IDM_EDIT_PASTE: case IDM_EDIT_CLEAR: case IDM_EDIT_DUPLICATE: case IDM_EDIT_SELECTALL:

case IDM_OPTIONS_FONT_NORMAL: case IDM_OPTIONS_FONT_BOLD: case IDM_OPTIONS_FONT_ITALIC: case IDM_OPTIONS_FONT_UNDERLINE: case IDM_OPTIONS_PARA_LEFT: case IDM_OPTIONS_PARA_CENTER: case IDM_OPTIONS_PARA_RIGHT: case IDM_OPTIONS_PARA_JUSTIFY:

case IDM_HELP_INDEX: case IDM_HELP_GENERAL: case IDM_HELP_USING: case IDM_HELP_KEYS: { WinMessageBox (HWND_DESKTOP, hWnd, "Функция не реализована", szAppTitle, 0, MB_INFORMATION | MB_OK); break; }

case IDM_HELP_ABOUT: { WinMessageBox (HWND_DESKTOP, hWnd, "Приложение MenuApp, (C) Frolov A., 1996", szAppTitle, 0, MB_INFORMATION | MB_OK); break; }

// Если из меню File выбрана строка Exit, // завершаем работу приложения case IDM_FILE_EXIT: { WinPostMsg (hWnd, WM_QUIT , 0L, 0L); break; } } return(WinDefWindowProc (hWnd, msg, mp1, mp2));

}

// В процессе инициализации главного окна // приложения загружаем временное меню и // определяем идентфикатор его окна case WM_CREATE : { hwndPopupMenu = WinLoadMenu (hWnd, NULLHANDLE, POPUP_MENU); return FALSE; }

// Перед уничтожением главного окна приложения // уничтожаем окно временного меню case WM_DESTROY : { WinDestroyWindow(hwndPopupMenu); break; }

// Если пользователь сделал в окне приложения // щелчок правой клавишей мыши, отображаем // временное меню case WM_BUTTON2DOWN : { WinPopupMenu (hWnd, hWnd, hwndPopupMenu, SHORT1FROMMP (mp1), SHORT2FROMMP (mp1), IDM_FILE_NEW, PU_POSITIONONITEM | PU_HCONSTRAIN | PU_VCONSTRAIN | PU_MOUSEBUTTON1 | PU_MOUSEBUTTON2 | PU_KEYBOARD);

return 0; }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } return(WinDefWindowProc (hWnd, msg, mp1, mp2)); }


Приложение MENUMOD


Приложение MENUMOD демонстрирует описанную выше методику изменения системного меню и динамического добавления временного меню в меню верхнего уровня.

На рис. 3.8 показано системное меню приложения, в которое были добавлены разделительная линия и строка Product Information.

Рис. 3.8. Измененное системное меню в приложении MENUMOD (приложение запущено в среде частично локализованной операционной системы, поэтому вы видите русские строки системного меню)

Кроме того, в приложении MENUMOD мы показали, как создаются вложенные меню (рис. 3.9).

Рис. 3.9. Использование вложенных меню в приложении MENUMOD

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

Листинг 3.5. Файл menumod\menumod.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS

#include <os2.h> #include <stdio.h> #include "menumod.h"

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

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

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

// Идентификатор окна Frame Window HWND hWndFrame;

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

// Заголовок приложения CHAR szAppTitle[] = "Menu Modification Demo";

// Идентификатор меню Edit HWND hwndEditMenu;

// Идентификатор меню верхнего уровня HWND hwndMenu;

// =================================================== // Главная функция приложения 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 | FCF_MENU;


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

// Инициализация приложения, необходимая для // использования функций 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 (hab); return(-1); }

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

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

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];

MENUITEM mi;



switch (msg) { case WM_CREATE : { HWND hwndSystemMenu; HWND hwndSystemSubMenu; SHORT sSysMeniID;

// --------------------------------------------- // Добавляем строку к системному меню // ---------------------------------------------

// Определяем идентификатор окна системного меню hwndSystemMenu = WinWindowFromID ( WinQueryWindow (hWnd, QW_PARENT ), FID_SYSMENU );

// Определяем идентификатор системного меню sSysMeniID = (SHORT)WinSendMsg (hwndSystemMenu, MM_ITEMIDFROMPOSITION , MPFROMSHORT(0), NULL);

// Определяем характеристики системного меню WinSendMsg (hwndSystemMenu, MM_QUERYITEM , MPFROMSHORT(sSysMeniID), MPFROMP(&mi));

// Идентификатор окна временного системного меню hwndSystemSubMenu = mi.hwndSubMenu;

// Заполняем структуру для разделительной линии mi.afStyle = MIS_SEPARATOR; mi.afAttribute = 0; mi.hwndSubMenu = 0; mi.hItem = 0; mi.id = -1; mi.iPosition = MIT_END;

// Добавляем разделительную линию WinSendMsg (hwndSystemSubMenu, MM_INSERTITEM , MPFROMP(&mi), NULL);

// Заполняем структуру для добавляемой строки mi.afStyle = MIS_TEXT; mi.id = IDM_HELP_ABOUT;

// Добавляем строку Product Information WinSendMsg (hwndSystemSubMenu, MM_INSERTITEM , MPFROMP(&mi), "Product Information...");

// --------------------------------------------- // Добавляем временное меню Edit // ---------------------------------------------

// Получаем идентификатор окна меню // верхнего уровня hwndMenu = WinWindowFromID ( WinQueryWindow (hWnd, QW_PARENT ), FID_MENU );

// Создаем пустое меню Edit hwndEditMenu = WinCreateMenu (HWND_OBJECT , NULL);

// Присваиваем меню Edit идентификатор IDM_EDIT WinSetWindowUShort(hwndEditMenu, QWS_ID, IDM_EDIT);

// Добавляем строки в меню mi.iPosition = MIT_END; mi.afStyle = MIS_TEXT; mi.afAttribute = 0; mi.hwndSubMenu = 0; mi.hItem = 0; mi.id = IDM_EDIT_UNDO;

WinSendMsg (hwndEditMenu, MM_INSERTITEM , MPFROMP(&mi), "~Undo");

mi.id = IDM_EDIT_REDO; WinSendMsg (hwndEditMenu, MM_INSERTITEM , MPFROMP(&mi), "~Redo");



mi.id = IDM_EDIT_CUT; WinSendMsg (hwndEditMenu, MM_INSERTITEM , MPFROMP(&mi), "Cu~t");

mi.id = IDM_EDIT_COPY; WinSendMsg (hwndEditMenu, MM_INSERTITEM , MPFROMP(&mi), "~Copy");

mi.id = IDM_EDIT_PASTE; WinSendMsg (hwndEditMenu, MM_INSERTITEM , MPFROMP(&mi), "~Paste");

mi.id = IDM_EDIT_CLEAR; WinSendMsg (hwndEditMenu, MM_INSERTITEM , MPFROMP(&mi), "C~lear");

// Добавляем разделительную линию mi.afStyle = MIS_SEPARATOR; mi.id = -1; WinSendMsg (hwndEditMenu, MM_INSERTITEM , MPFROMP(&mi), "~Duplicate");

// Добавляем еще две строки mi.afStyle = MIS_TEXT; mi.id = IDM_EDIT_DUPLICATE; WinSendMsg (hwndEditMenu, MM_INSERTITEM , MPFROMP(&mi), "~Duplicate");

mi.id = IDM_EDIT_SELECTALL; WinSendMsg (hwndEditMenu, MM_INSERTITEM , MPFROMP(&mi), "~Select all");

// Добавляем меню Edit в меню верхнего уровня mi.iPosition = 1; mi.afStyle = MIS_TEXT | MIS_SUBMENU; mi.afAttribute = 0; mi.id = IDM_EDIT; mi.hwndSubMenu = hwndEditMenu;

WinSendMsg (hwndMenu, MM_INSERTITEM , MPFROMP(&mi), "~Edit");

return FALSE; }

case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

// Сообщение WM_COMMAND поступает в функцию окна, // кодгда пользователь выбирает одну из строк меню case WM_COMMAND : { // Анализируем код строки меню switch(LOUSHORT(mp1)) { case IDM_FILE_NEW: case IDM_FILE_OPEN: case IDM_FILE_SAVE: case IDM_FILE_SAVEAS:

case IDM_EDIT_UNDO: case IDM_EDIT_REDO: case IDM_EDIT_CUT: case IDM_EDIT_COPY: case IDM_EDIT_PASTE: case IDM_EDIT_CLEAR: case IDM_EDIT_DUPLICATE: case IDM_EDIT_SELECTALL:

case IDM_OPTIONS_FONT_NORMAL: case IDM_OPTIONS_FONT_BOLD: case IDM_OPTIONS_FONT_ITALIC: case IDM_OPTIONS_FONT_UNDERLINE: case IDM_OPTIONS_PARA_LEFT: case IDM_OPTIONS_PARA_CENTER: case IDM_OPTIONS_PARA_RIGHT: case IDM_OPTIONS_PARA_JUSTIFY:

case IDM_HELP_INDEX: case IDM_HELP_GENERAL: case IDM_HELP_USING: case IDM_HELP_KEYS: { WinMessageBox (HWND_DESKTOP, hWnd, "Функция не реализована", szAppTitle, 0, MB_INFORMATION | MB_OK); break; }

case IDM_HELP_ABOUT: { WinMessageBox (HWND_DESKTOP, hWnd, "Приложение MenuMod, (C) Frolov A., 1996", szAppTitle, 0, MB_INFORMATION | MB_OK); break; }

// Если из меню File выбрана строка Exit, // завершаем работу приложения case IDM_FILE_EXIT: { WinPostMsg (hWnd, WM_QUIT , 0L, 0L); break; } } return(WinDefWindowProc (hWnd, msg, mp1, mp2)); }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }


Приложение MOUSEMOV


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

Рис. 6.1. Окно приложения MOUSEMOV не имеет заголовка, но его можно перемещать при помощи мыши

Если в окне нажать левую клавишу мыши, то обрабатывая сообщение WM_MOUSEMOVE , приложение отслеживает текущие координаты мыши, передвигая соответствующим образом главное окно приложения Frame Window .

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

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

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

Листинг 6.1. Файл mousemov\mousemov.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include <stdio.h> #include "mousemov.h"

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

CHAR szAppTitle[] = "Mouse Mover";

// Координаты курсора мыши в момент нажатия // левой клавиши мыши SHORT cxPoint; SHORT cyPoint;

// Координаты курсора мыши в момент отпускания // левой клавиши мыши SHORT cxNewPoint; SHORT cyNewPoint;

// Признак начала процесса перемещения окна BOOL fDrag = FALSE;

// Текст, который будет отображаться в окне CHAR pszText[] = "Перемещайте окно при помощи левой клавиши мыши";


// Идентификаторы указателей мыши HPOINTER hptr, hptr1;

// ================================================= // Главная функция приложения main // =================================================

int main () { HMQ hmq; QMSG qmsg; BOOL fRc;

// Флаги для создания окна Frame Window // Мы не создаем окна заголовка, системного меню, // а также окна кнопок минимизации и максимизации ULONG flFrameFlags = FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST | FCF_ICON;

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

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 (hab); return(-1); }

// Регистрация главного окна приложения fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================



MRESULT EXPENTRY WndProc( HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { HPS hps; POINTL ptl; RECTL rec; SWP swp;

switch (msg) { // При создании окна загружаем указатели мыши case WM_CREATE : { hptr = WinLoadPointer(HWND_DESKTOP, NULLHANDLE, ID_APP_POINTER); hptr1 = WinLoadPointer(HWND_DESKTOP, NULLHANDLE, ID_APP_POINTER1); return FALSE; }

// При уничтожении окна удаляем указатели мыши case WM_DESTROY : { WinDestroyPointer (hptr); WinDestroyPointer (hptr1); return 0; }

// Когда пользователь нажимает левую клавишу // мыши, запоминаем координаты курсора и // выдвигаем окно приложения на передний план case WM_BUTTON1DOWN : { cxPoint = MOUSEMSG(&msg) -> x; cyPoint = MOUSEMSG(&msg) -> y;

// Изменяем расположение окна по оси Z WinSetWindowPos (hWndFrame, HWND_TOP , 0, 0, 0, 0, SWP _ZORDER );

// Устанавливаем признак перемещения // главного окна приложения fDrag = TRUE; return 0; }

// При отпускании левой клавиши мыши сбрасываем // признак перемещения окна case WM_BUTTON1UP : { // Сбрасываем признак перемещения // главного окна приложения fDrag = FALSE; return 0; }

// Это сообщение приходит при перемещении курсора // мыши case WM_MOUSEMOVE : { // Если включен признак перемещения, определяем // новые координаты курсора и передвигаем окно if(fDrag) { // Выбираем указатель в виде закрытой руки WinSetPointer (HWND_DESKTOP, hptr1);

cxNewPoint = MOUSEMSG(&msg) -> x; cyNewPoint = MOUSEMSG(&msg) -> y;

// Определяем текущие координаты окна WinQueryWindow Pos(hWndFrame, &swp);

// Передвигаем окно WinSetWindowPos (hWndFrame, HWND_TOP , swp.x + (cxNewPoint - cxPoint), swp.y + (cyNewPoint - cyPoint), 0, 0, SWP _MOVE ); }

else // Выбираем указатель в виде открытой руки WinSetPointer (HWND_DESKTOP, hptr);

return (MRESULT)TRUE; }

// В ответ на сообщение WM_PAINT выводим // в центре окна строку текста case WM_PAINT : { // Получаем пространство отображения hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

// Определяем размеры главного окна WinQueryWindow Rect(hWnd, &rec);

// Выводим текст в центре окна WinDrawText (hps, -1, pszText, &rec, 0L, 0L, DT_WORDBREAK | DT_CENTER | DT_VCENTER | DT_TEXTATTRS | DT_ERASERECT);

// Освобождаем пространство отображения WinEndPaint (hps); return 0; }

case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

// При изменении размеров окна сохраняем новые // размеры и перерисовываем окно case WM_SIZE : { // При изменении размеров окна выполняем его // перерисовку WinInvalidateRect (hWnd, NULL, TRUE); return 0; }

// Если пользователь сделал двойной щелчок левой // клавише мыши, завершаем работу приложения case WM_BUTTON1DBLCLK : { WinPostMsg (hWnd, WM_QUIT , 0L, 0L); return 0; }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }


Приложение 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


Приложение POINTER


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

Внешний вид главного окна приложения показан на рис. 6.2.

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

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

Листинг 6.5. Файл pointer\pointer.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include <stdio.h> #include <stdlib.h> #include "pointer.h"

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

CHAR szAppTitle[] = "Mouse Poiter";

// Размеры пиктограммы курсора мыши LONG cxPointer, cyPointer;

// Размеры окна Client Window LONG cxClient, cyClient;

// ================================================= // Главная функция приложения 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[] = "MOUSEPOINTER";

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 (hab); return(-1); }


// Регистрация главного окна приложения fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); }

// Устанавливаем новую пиктограмму // для главного окна WinSendMsg (hWndFrame, WM_SETICON , (MPARAM)WinQuerySysPointer (HWND_DESKTOP, SPTR_ICONINFORMATION, FALSE), NULL);

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

WinDestroyWindow(hWndFrame); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================

MRESULT EXPENTRY WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { HPS hps; RECTL rec; POINTL ptl; LONG x, y; HPOINTER hptr; LONG xMouse, yMouse;

switch (msg) { case WM_CREATE : { // Определяем и сохраняем размеры пиктограмм cxPointer = WinQuerySysValue (HWND_DESKTOP, SV_CXPOINTER);

cyPointer = WinQuerySysValue (HWND_DESKTOP, SV_CYPOINTER);

// Расстояние между пиктограммами cxPointer += 5; return FALSE; }

case WM_PAINT : { // Получаем пространство отображения hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

// Закрашиваем область, требующую обновления WinFillRect (hps, &rec, CLR_WHITE);

// Рисуем стандартные изображения курсора мыши x = 0; y = cyClient - cyPointer; DrawMousePtr(hps, x, y, SPTR_ARROW);

x += cxPointer; DrawMousePtr(hps, x, y, SPTR_TEXT); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_WAIT); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_MOVE); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_SIZENWSE); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_SIZENESW); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_SIZEWE); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_SIZENS);



// Рисуем стандартные пиктограммы x = 0; y = cyClient - 3*cyPointer; DrawMousePtr(hps, x, y, SPTR_APPICON); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_ICONINFORMATION); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_ICONQUESTION); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_ICONERROR); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_ICONWARNING); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_ILLEGAL); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_FILE); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_MULTFILE); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_FOLDER); x += cxPointer; DrawMousePtr(hps, x, y, SPTR_PROGRAM);

// Освобождаем пространство отображения WinEndPaint (hps); return 0; }

case WM_MOUSEMOVE : { // Получаем идентификатор стандартной // пиктограммы SPTR_ICONINFORMATION hptr = WinQuerySysPointer (HWND_DESKTOP, SPTR_ICONINFORMATION, FALSE);

// Устанавливаем курсор мыши так, чтобы он // имел форму пиктограммы SPTR_ICONINFORMATION WinSetPointer (HWND_DESKTOP, hptr); return (MRESULT)TRUE; }

case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

// При изменении размеров окна приложения // перерисовываем его содержимое case WM_SIZE : { // Сохраняем размеры окна Client Window cxClient = SHORT1FROMMP (mp2); cyClient = SHORT2FROMMP (mp2);

// Обновляем окно Client Window WinInvalidateRect (hWnd, NULL, TRUE); return 0; }

// Выполняем эмуляцию мыши с помощью клавиатуры case WM_CHAR : { // Фильтруем сообщения, соответствующие отжатию // клавиш, а также сообщения от обычных клавиш if((CHARMSG(&msg) ->fs & KC_KEYUP) | (!(CHARMSG(&msg) ->fs & KC_VIRTUALKEY))) return 0;

// Определяем текущую позицию курсора мыши // в экранных координатах WinQueryPointerPos (HWND_DESKTOP, &ptl);

// Преобразуем экранные координаты в оконные WinMapWindowPoints (HWND_DESKTOP, hWnd, &ptl, 1);

// Сохраняем текущие оконные координаты курсора xMouse = ptl.x; yMouse = ptl.y;

// Изменяем координаты в соответствии с // виртуальным кодом нажатой клавиши switch(CHARMSG(&msg) -> vkey) { case VK_LEFT: { xMouse -= cxPointer; break; } case VK_RIGHT: { xMouse += cxPointer; break; } case VK_DOWN: { yMouse -= cyPointer; break; } case VK_UP: { yMouse += cyPointer; break; } case VK_HOME: { xMouse = cxPointer; yMouse = cyClient - cyPointer; break; } case VK_END: { xMouse = cxClient - cxPointer; yMouse = cyPointer; break; } default: break; }

// Не допускаем выхода курсора мыши за пределы // окна приложения ptl.x = max(min(xMouse, cxClient - 1), 0); ptl.y = max(min(yMouse, cyClient - 1), 0);

// Преобразуем оконные координаты в экранные WinMapWindowPoints (hWnd, HWND_DESKTOP, &ptl, 1);

// Устанавливаем новую позицию курсора мыши WinSetPointer Pos (HWND_DESKTOP, (SHORT) ptl.x, (SHORT) ptl.y);

return 0; }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }

// ================================================= // Функция рисования курсора мыши // =================================================

void DrawMousePtr(HPS hps, LONG x, LONG y, LONG lPtrId) { HPOINTER hptr;

// Получаем идентификатор стандартного курсора мыши hptr = WinQuerySysPointer (HWND_DESKTOP, lPtrId, FALSE);

// Рисуем изображение курсора мыши WinDrawPointer(hps, x, y, hptr, DP_NORMAL); }


Приложение SCROLL


В приложении SCROLL мы создаем в главном окне приложения две полосы просмотра - вертикальную и горизонтальную, указывая соответствующие флаги при вызове функции WinCreateStdWindow. Эти полосы используются для просмотра метрик шрифта с названием Courier (рис. 8.5).

Рис. 8.5. Просмотра метрик шрифта Courier в окне приложения SCROLL

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

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

Листинг 8.9. Файл scroll\scroll.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include <string.h> #include <stdio.h> #include "scroll.h"

#define YSIZE 50 #define XSIZE 50

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

CHAR szAppTitle[] = "Scroll Demo";

// Размеры окна Client Window SHORT cxClient; SHORT cyClient;

// Размеры символов выбранного шрифта SHORT cxChar, cyChar, cyDesc;

// Структура для записи метрик шрифта FONTMETRICS fm;

// Идентификаторы полос просмотра HWND hwndXScroll; HWND hwndYScroll;

// Текущие координаты движков INT nXScrollPos; INT nYScrollPos;

// Текущие координаты для вывода текста LONG cxCurrentPosition; LONG cyCurrentPosition;

// ================================================= // Главная функция приложения main // =================================================

int main() { HMQ hmq; QMSG qmsg; BOOL fRc; HPS hps;

// Флаги для создания окна Frame Window // Добавляем флаги FCF_VERTSCROLL и FCF_HORZSCROLL , // в результате чего в главном окне будут созданы // вертикальная и горизонтальная полосы просмотра ULONG flFrameFlags = FCF_SYSMENU | FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST | FCF_ICON | FCF_VERTSCROLL | FCF_HORZSCROLL ;


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

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(hab); return(-1); }

// Регистрация главного окна приложения fRc = WinRegisterClass(hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox(HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); }

// Получаем пространство отображения hps = WinGetPS(hWndFrame);

// Выбираем в пространство отображения шрифт // с фиксированной шириной символов SetCourierFont(hps);

// Определяем метрики шрифта GpiQueryFontMetrics(hps, (LONG)sizeof(fm), &fm);

cxChar = fm.lAveCharWidth; cyChar = fm.lMaxBaselineExt; cyDesc = fm.lMaxDescender;

// Устанавливаем шрифт, выбранный в пространство // отображения по умолчанию ResetFont(hps);

// Возвращаем пространство отображения WinReleasePS(hps);

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

WinDestroyWindow(hWndFrame); WinDestroyMsgQueue(hmq); WinTerminate(hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================



MRESULT EXPENTRY WndProc( HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { HPS hps; RECTL rec;

switch (msg) { case WM_CREATE: { // Начальные координаты движков nYScrollPos = 0; nXScrollPos = 0;

// Начальные координаты для вывода текста cyCurrentPosition = cyClient; cxCurrentPosition = nXScrollPos * cxChar + cxChar;

// Определяем идентификатор окна для // вертикальной полосы просмотра hwndYScroll = WinWindowFromID( WinQueryWindow(hWnd, QW_PARENT), FID_VERTSCROLL),

// Устанавливаем диапазон изменений координат // движка и начальное положение для // вертикальной полосы просмотра WinSendMsg(hwndYScroll, SBM_SETSCROLLBAR, (MPARAM)0, MPFROM2SHORT(0, YSIZE));

// Выполняем аналогичные действия для // горизонтальной полосы просмотра hwndXScroll = WinWindowFromID( WinQueryWindow(hWnd, QW_PARENT), FID_HORZSCROLL),

WinSendMsg(hwndXScroll, SBM_SETSCROLLBAR, (MPARAM)0, MPFROM2SHORT(0, XSIZE));

return FALSE; }

case WM_SIZE: { // получаем и сохраняем размеры главного окна cxClient = SHORT1FROMMP(mp2); cyClient = SHORT2FROMMP(mp2);

// Перерисовываем окно приложения WinInvalidateRect(hWnd, NULL, TRUE); return 0; }

case WM_PAINT: { // Получаем пространство отображения hps = WinBeginPaint(hWnd, NULLHANDLE, &rec);

// Закрашиваем область, требующую обновление WinFillRect(hps, &rec, CLR_WHITE);

// Выбираем в пространство отображения шрифт // с фиксированной шириной символов SetCourierFont(hps);

// Устанавливаем начальные координаты для // вывода текста cxCurrentPosition = -nXScrollPos * cxChar + cxChar; cyCurrentPosition = cyClient;

// Выводим метрики шрифта PrintString(hps, fm.szFamilyname, "szFamilyname"); PrintString(hps, fm.szFacename, "szFacename");

PrintLong(hps, fm.idRegistry, "idRegistry"); PrintLong(hps, fm.usCodePage, "usCodePage"); PrintLong(hps, fm.lEmHeight, "lEmHeight"); PrintLong(hps, fm.lXHeight, "lXHeight"); PrintLong(hps, fm.lMaxAscender, "lMaxAscender"); PrintLong(hps, fm.lMaxDescender, "lMaxDescender"); PrintLong(hps, fm.lLowerCaseAscent, "lLowerCaseAscent"); PrintLong(hps, fm.lLowerCaseDescent, "lLowerCaseDescent"); PrintLong(hps, fm.lInternalLeading, "lInternalLeading"); PrintLong(hps, fm.lExternalLeading, "lExternalLeading"); PrintLong(hps, fm.lAveCharWidth, "lAveCharWidth"); PrintLong(hps, fm.lMaxCharInc, "lMaxCharInc"); PrintLong(hps, fm.lEmInc, "lEmInc"); PrintLong(hps, fm.lMaxBaselineExt, "lMaxBaselineExt"); PrintLong(hps, fm.sCharSlope, "sCharSlope"); PrintLong(hps, fm.sInlineDir, "sInlineDir"); PrintLong(hps, fm.sCharRot, "sCharRot"); PrintLong(hps, fm.usWeightClass, "usWeightClass"); PrintLong(hps, fm.usWidthClass, "usWidthClass"); PrintLong(hps, fm.sXDeviceRes, "sXDeviceRes"); PrintLong(hps, fm.sYDeviceRes, "sYDeviceRes"); PrintLong(hps, fm.sFirstChar, "sFirstChar"); PrintLong(hps, fm.sLastChar, "sLastChar"); PrintLong(hps, fm.sDefaultChar, "sDefaultChar"); PrintLong(hps, fm.sBreakChar, "sBreakChar"); PrintLong(hps, fm.sNominalPointSize, "sNominalPointSize"); PrintLong(hps, fm.sMinimumPointSize, "sMinimumPointSize"); PrintLong(hps, fm.sMaximumPointSize, "sMaximumPointSize"); PrintLong(hps, fm.fsType, "fsType"); PrintLong(hps, fm.fsDefn, "fsDefn"); PrintLong(hps, fm.fsSelection, "fsSelection"); PrintLong(hps, fm.fsCapabilities, "fsCapabilities"); PrintLong(hps, fm.lSubscriptXSize, "lSubscriptXSize"); PrintLong(hps, fm.lSubscriptYSize, "lSubscriptYSize"); PrintLong(hps, fm.lSubscriptXOffset, "lSubscriptXOffset"); PrintLong(hps, fm.lSubscriptYOffset, "lSubscriptYOffset"); PrintLong(hps, fm.lSuperscriptXSize, "lSuperscriptXSize"); PrintLong(hps, fm.lSuperscriptYSize, "lSuperscriptYSize"); PrintLong(hps, fm.lSuperscriptXOffset, "lSuperscriptXOffset"); PrintLong(hps, fm.lSuperscriptYOffset, "lSuperscriptYOffset"); PrintLong(hps, fm.lUnderscoreSize, "lUnderscoreSize"); PrintLong(hps, fm.lUnderscorePosition, "lUnderscorePosition"); PrintLong(hps, fm.lStrikeoutSize, "lStrikeoutSize"); PrintLong(hps, fm.lStrikeoutPosition, "lStrikeoutPosition"); PrintLong(hps, fm.sKerningPairs, "sKerningPairs"); PrintLong(hps, fm.sFamilyClass, "sFamilyClass"); PrintLong(hps, fm.lMatch, "lMatch"); PrintLong(hps, fm.FamilyNameAtom, "FamilyNameAtom"); PrintLong(hps, fm.FaceNameAtom, "FaceNameAtom");



// Устанавливаем шрифт, выбранный в пространство // отображения по умолчанию ResetFont(hps);

// Возвращаем пространство отображения WinEndPaint(hps); return 0; }

case WM_ERASEBACKGROUND: return(MRFROMLONG(1L));

// Это сообщение приходит от вертикальной // полосы просмотра case WM_VSCROLL: { // В зависимости от кода извещения // изменяем содержимое переменной, в которой // хранится координата движка switch (SHORT2FROMMP(mp2)) { case SB_LINEDOWN: { nYScrollPos += 1; break; } case SB_LINEUP: { nYScrollPos -= 1; break; } case SB_PAGEDOWN: { nYScrollPos += cyClient / cyChar; break; } case SB_PAGEUP: { nYScrollPos -= cyClient / cyChar; break; } case SB_SLIDERTRACK: { nYScrollPos = SHORT1FROMMP(mp2); break; }

default: break; }

// Ограничиваем диапазон изменения // координаты движка if(nYScrollPos > YSIZE) nYScrollPos = YSIZE; if(nYScrollPos < 0) nYScrollPos = 0;

// Устанавливаем новую позицию движка WinSendMsg(hwndYScroll, SBM_SETPOS, (MPARAM)nYScrollPos, NULL);

// Перерисовываем окно приложения WinInvalidateRect(hWnd, NULL, TRUE);

return 0; }

// Это сообщение приходит от горизонтальной // полосы просмотра case WM_HSCROLL: { // В зависимости от кода извещения // изменяем содержимое переменной, в которой // хранится координата движка switch (SHORT2FROMMP(mp2)) { case SB_LINELEFT: { nXScrollPos -= 1; break; } case SB_LINERIGHT: { nXScrollPos += 1; break; } case SB_PAGERIGHT: { nXScrollPos += 10; break; } case SB_PAGELEFT: { nXScrollPos -= 10; break; } case SB_SLIDERTRACK: { nXScrollPos = SHORT1FROMMP(mp2); break; }

default: break; }

// Ограничиваем диапазон изменения // координаты движка if(nXScrollPos < 0) nXScrollPos = 0;

// Устанавливаем новую позицию движка WinSendMsg(hwndXScroll, SBM_SETPOS, (MPARAM)nXScrollPos, NULL);

// Перерисовываем окно приложения WinInvalidateRect(hWnd, NULL, TRUE);

return 0; }

// Это сообщение появляется, когда пользователь // нажимает или отжимает клавишу case WM_CHAR: { // Пропускаем только виртуальные клавиши if(!(CHARMSG(&msg) ->fs & KC_VIRTUALKEY)) return 0;



// Фильтруем сообщения, поступающие при // отжатии клавиш if(!(CHARMSG(&msg) ->fs & KC_KEYUP)) return 0;

// В зависимости от виртуального кода клавиши // посылаем окну нашего прилоджения // сообщения WM_HSCROLL или WM_HSCROLL для // работы с полосами просмотра при помощи // клавиатуры switch(CHARMSG(&msg) -> vkey) { case VK_LEFT: { WinSendMsg(hWnd, WM_HSCROLL, NULL, MPFROM2SHORT(0, SB_LINELEFT)); break; } case VK_RIGHT: { WinSendMsg(hWnd, WM_HSCROLL, NULL, MPFROM2SHORT(0, SB_LINERIGHT)); break; } case VK_UP: { WinSendMsg(hWnd, WM_VSCROLL, NULL, MPFROM2SHORT(0, SB_LINEUP)); break; } case VK_DOWN: { WinSendMsg(hWnd, WM_VSCROLL, NULL, MPFROM2SHORT(0, SB_LINEDOWN)); break; } case VK_PAGEUP: { WinSendMsg(hWnd, WM_VSCROLL, NULL, MPFROM2SHORT(0, SB_PAGEUP)); break; } case VK_PAGEDOWN: { WinSendMsg(hWnd, WM_VSCROLL, NULL, MPFROM2SHORT(0, SB_PAGEDOWN)); break; } default: break; }

return 0; }

default: return(WinDefWindowProc(hWnd, msg, mp1, mp2)); } }

// ================================================= // Выбор шрифта с фиксированной шириной символов // =================================================

void SetCourierFont(HPS hps) { FATTRS fat;

// Заполняем структуру описанием нужного // нам шрифта fat.usRecordLength = sizeof(FATTRS); strcpy(fat.szFacename ,"Courier"); fat.fsSelection = 0; fat.lMatch = 0L; fat.idRegistry = 0; fat.usCodePage = 850; fat.lMaxBaselineExt = 12L; fat.lAveCharWidth = 12L; fat.fsType = 0; fat.fsFontUse = FATTR_FONTUSE_NOMIX;

// Создаем логический шрифт, имеющий идентификатор 1L GpiCreateLogFont(hps, NULL, 1L, &fat);

// Выбираем созданный шрифт // в пространство отображения GpiSetCharSet(hps, 1L); }

// ================================================= // Установка шрифта, выбранного в пространство // отображения по умолчанию // ================================================= void ResetFont(HPS hps) { GpiSetCharSet(hps, LCID_DEFAULT); GpiDeleteSetId(hps, 1L); }

// ================================================= // Вывод в окне значения строчной переменной // =================================================



void PrintString( HPS hps, PSZ pszValue, PSZ pszName) { int i; CHAR szBuf[80]; POINTL ptl;

// Выводим название поля структуры sprintf(szBuf, "%s", pszName); i = strlen(szBuf);

ptl.x = cxCurrentPosition; ptl.y = cyCurrentPosition - cyChar * (1 - nYScrollPos); GpiCharStringAt(hps, &ptl, i, szBuf);

// Выводим значение, записанное в этом поле sprintf(szBuf, "= %s", pszValue); i = strlen(szBuf);

ptl.x = cxCurrentPosition + 20 * cxChar; ptl.y = cyCurrentPosition - cyChar * (1 - nYScrollPos); GpiCharStringAt(hps, &ptl, i, szBuf);

// Изменяем текущую позицию для вывода cyCurrentPosition -= cyChar; }

// ================================================= // Вывод в окне значения переменной типа LONG // =================================================

void PrintLong(HPS hps, LONG lValue, PSZ pszName) { int i; CHAR szBuf[80]; POINTL ptl;

sprintf(szBuf, "%s", pszName); i = strlen(szBuf);

ptl.x = cxCurrentPosition; ptl.y = cyCurrentPosition - cyChar * (1 - nYScrollPos); GpiCharStringAt(hps, &ptl, i, szBuf);

sprintf(szBuf, "= %ld", lValue); i = strlen(szBuf);

ptl.x = cxCurrentPosition + 20 * cxChar; ptl.y = cyCurrentPosition - cyChar * (1 - nYScrollPos); GpiCharStringAt(hps, &ptl, i, szBuf);

cyCurrentPosition -= cyChar; }


Приложение SLIDER


В приложении SLIDER мы создаем три круглых регулятора (рис. 8.7), с помощью которых можно изменять цвет окна приложения. Каждый из этих регуляторов изменяет значение цветовой компоненты (красной, зеленой или синей) в диапазоне от 0 до 255 (максимально возможное значение). Устанавливая значения компонент, вы можете выбрать любой цвет окна.

Рис. 8.7. Главное окно приложения SLIDER с регуляторами для изменения компонент цвета окна

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

Листинг 8.13. Файл slider\slider.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include "slider.h"

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

// Идентификаторы регуляторов HWND hWndCirc1; HWND hWndCirc2; HWND hWndCirc3;

// Компоненты цвета SHORT sColorR = 255, sColorG = 255, sColorB = 255;

CHAR szAppTitle[] = "Slider Demo";

// ================================================= // Главная функция приложения 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[] = "SLIDERDEMO";

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(hab); return(-1); }


// Регистрация главного окна приложения fRc = WinRegisterClass(hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox(HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); WinDestroyMsgQueue(hmq); WinTerminate(hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================

MRESULT EXPENTRY WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { HPS hps; RECTL rec;

switch (msg) { case WM_CREATE: { // Создаем регулятор красного цвета hWndCirc1 = WinCreateWindow(hWnd, WC_CIRCULARSLIDER, "Красный", WS_VISIBLE, 0, 0, 0, 0, hWnd, HWND_TOP, CIRCSLD1_ID, NULL, NULL);

// Устанавливаем диапазон изменения значений WinSendMsg(hWndCirc1, CSM_SETRANGE, MPFROMLONG(0L), MPFROMLONG(255L));

// Устанавливаем шаг для изменения позиции // и шаг размещения меток WinSendMsg(hWndCirc1, CSM_SETINCREMENT, MPFROMLONG(10L), MPFROMLONG(2L));

// Устанавливаем начальное значение WinSendMsg(hWndCirc1, CSM_SETVALUE, MPFROMLONG(255L), NULL);

// Создаем регулятор зеленого цвета // и настраиваем его параметры hWndCirc2 = WinCreateWindow(hWnd, WC_CIRCULARSLIDER, "Зеленый", WS_VISIBLE, 0, 0, 0, 0, hWnd, HWND_TOP, CIRCSLD2_ID, NULL, NULL);

WinSendMsg(hWndCirc2, CSM_SETRANGE, MPFROMLONG(0L), MPFROMLONG(255L));



WinSendMsg(hWndCirc2, CSM_SETINCREMENT, MPFROMLONG(10L), MPFROMLONG(2L));

WinSendMsg(hWndCirc2, CSM_SETVALUE, MPFROMLONG(255L), NULL);

// Создаем регулятор синего цвета // и настраиваем его параметры hWndCirc3 = WinCreateWindow(hWnd, WC_CIRCULARSLIDER, "Синий", WS_VISIBLE, 0, 0, 0, 0, hWnd, HWND_TOP, CIRCSLD3_ID, NULL, NULL);

WinSendMsg(hWndCirc3, CSM_SETRANGE, MPFROMLONG(0L), MPFROMLONG(255L));

WinSendMsg(hWndCirc3, CSM_SETINCREMENT, MPFROMLONG(10L), MPFROMLONG(2L));

WinSendMsg(hWndCirc3, CSM_SETVALUE, MPFROMLONG(255L), NULL);

return FALSE; }

// Изменяем размер и расположение регуляторов case WM_SIZE: { WinSetWindowPos(hWndCirc1, HWND_TOP, 0, 0, 150, 150, SWP_SIZE | SWP_MOVE | SWP_ZORDER);

WinSetWindowPos(hWndCirc2, HWND_TOP, 150, 0, 150, 150, SWP_SIZE | SWP_MOVE | SWP_ZORDER);

WinSetWindowPos(hWndCirc3, HWND_TOP, 300, 0, 150, 150, SWP_SIZE | SWP_MOVE | SWP_ZORDER);

// Перерисовываем окно приложения WinInvalidateRect(hWnd, NULL, TRUE); return 0; }

case WM_PAINT: { // Получаем пространство отображения hps = WinBeginPaint(hWnd, NULLHANDLE, &rec);

// Переключаем таблицу цветов в режим RGB GpiCreateLogColorTable(hps, LCOL_RESET, LCOLF_RGB, 0L, 0L, NULL);

// Закрашиваем область, требующую обновление WinFillRect(hps, &rec, (ULONG)sColorR << 16 | (ULONG)sColorG << 8 | (ULONG)sColorB);

// Возвращаем пространство отображения WinEndPaint(hps); return 0; }

case WM_ERASEBACKGROUND: return(MRFROMLONG(1L));

case WM_CONTROL: { // Сообщение пришло от красного регулятора if(SHORT1FROMMP(mp1) == CIRCSLD1_ID) { // Выделяем код извещения if(SHORT2FROMMP(mp1) == CSN_CHANGED) { // Устанавливаем новое значение цвета sColorR = (SHORT)mp2;

// Перерисовываем окно WinInvalidateRect(hWnd, NULL, TRUE); } }

// Сообщение от зеленого регулятора else if(SHORT1FROMMP(mp1) == CIRCSLD2_ID) { if(SHORT2FROMMP(mp1) == CSN_CHANGED) { sColorG = (SHORT)mp2; WinInvalidateRect(hWnd, NULL, TRUE); } }

// Сообщение от синего регулятора else if(SHORT1FROMMP(mp1) == CIRCSLD3_ID) { if(SHORT2FROMMP(mp1) == CSN_CHANGED) { sColorB = (SHORT)mp2; WinInvalidateRect(hWnd, NULL, TRUE); } } return 0; } default: return(WinDefWindowProc(hWnd, msg, mp1, mp2)); } }


Приложение TEXTOUT


Для иллюстрации всего сказанного выше в этой главе мы приведем исходные тексты приложения TEXTOUT. В своем главном окне это приложение отображает строку Hello, PM!. Кроме того, если делать щелчки левой клавишей мыши в окне приложения, то в месте расположения курсора мыши будут отображаться его координаты (рис. 4.1).

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

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

Листинг 4.1. Файл textout\textout.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include <stdio.h> #include <string.h> #include "textout.h"

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

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

HAB hab; HWND hWndFrame; HWND hWndClient;

CHAR szAppTitle[] = "TextOut 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[] = "TEXTOUT";

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 (hab); return(-1); }

// Регистрация главного окна приложения fRc = WinRegisterClass (hab, szWndClass, (PFNWP)WndProc, 0, 0); if(fRc == FALSE) { WinMessageBox (HWND_DESKTOP, HWND_DESKTOP, "Ошибка при регистрации класса главного окна", "Ошибка", 0, MB_ICONHAND | MB_OK); 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); WinDestroyMsgQueue (hmq); WinTerminate (hab); return(0); }

// ================================================= // Функция главного окна приложения // =================================================

MRESULT EXPENTRY WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { CHAR szMsg[100]; int nMsgSize; HPS hps; POINTL ptl; RECTL rec;

switch (msg) { // В ответ на это сообщение рисуем в // окне Client Window case WM_PAINT : { // Получаем пространство отображения hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

// Закрашиваем область, требующую обновления WinFillRect (hps, &rec, CLR_WHITE);

// Рисуем текстовую строку, начиная с точки, // имеющей координаты (10, 10) ptl.x = 10L; ptl.y = 10L; GpiCharString At (hps, &ptl, 10, "Hello, PM!");

// Освобождаем пространство отображения WinEndPaint (hps); return 0; }

// Позволяем окну Frame Window стереть содержимое // окна Client Window case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

// При изменении размеров окна приложения // перерисовываем его содержимое case WM_SIZE : { WinInvalidateRect (hWnd, NULL, TRUE); return 0; }

// Если пользователь сделал щелчок левой клавишей // мыши в окне приложения, в месте расположения // курсора мыши отображаем координаты этого курсора case WM_BUTTON1DOWN : { // Получаем пространство отображения hps = WinGetPS (hWnd);

// Определяем координаты курсора мыши и // записываем их в виде текстовой строки // во временный буфер sprintf (szMsg, "(x, y) = (%ld, %ld)", SHORT1FROMMP (mp1), SHORT2FROMMP (mp1));

// Определяем координаты точки для вывода текста ptl.x = SHORT1FROMMP (mp1); ptl.y = SHORT2FROMMP (mp1);

// Определяем размер текстовой строки nMsgSize = strlen(szMsg);

// Рисуем текстовую строку GpiCharString At (hps, &ptl, nMsgSize, szMsg);

// Освобождаем пространство отображения WinReleasePS (hps); return 0; }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }


Приложение WINTREE


Наше следующее приложение называется WINTREE. Оно создает два окна верхнего уровня и одно дочернее окно (рис. 2.1), демонстрируя древовидную структуру родительских отношений между окнами.

Рис. 2.1. Окна, создаваемые приложением WINTREE

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

Исходный текст приложения WINTREE показан в листинге 2.1.

Листинг 2.1. Файл wintree\wintree.c

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

#define INCL_WIN #define INCL_GPI #define INCL_WINDIALOGS #include <os2.h> #include <stdio.h> #include "wintree.h"

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

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

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

// Идентификатор первого и второго окна Frame Window HWND hWndFrame1; HWND hWndFrame2;

// Идентификатор дочернего окна HWND hWndChildFrame;

// Идентификатор первого и второго окна Client Window HWND hWndClient1; HWND hWndClient2;

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

// Заголовки окон CHAR szAppTitle1[] = "Windows Tree Demo 1"; CHAR szAppTitle2[] = "Windows Tree Demo 2"; CHAR szChildTitle[] = "Child Window";

// =================================================== // Главная функция приложения 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;


// Флаги для создания дочернего окна ULONG flFrameChildFlags = FCF_SYSMENU | FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER;

// Имена классов для создаваемых окон CHAR szWndClass1[] = "WINTREE1"; CHAR szWndClass2[] = "WINTREE2"; CHAR szWndClassChild[] = "WINTREECHILD";

// Инициализация приложения 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 (hab); return(-1); }

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

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

return(-1); }

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

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

return(-1); }

// Регистрация класса дочернего окна fRc = WinRegisterClass (hab, szWndClassChild, (PFNWP)WndProcChild, 0, 0);

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

return(-1); }

// Создаем главное окно приложения hWndFrame1 = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE , &flFrameFlags, szWndClass1, szAppTitle1, 0, 0, ID_APP_FRAMEWND, &hWndClient1);

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



WinDestroyMsgQueue (hmq); WinTerminate (hab);

return(-1); }

// Создаем второе окно hWndFrame2 = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE , &flFrameFlags, szWndClass2, szAppTitle2, 0, 0, ID_APP_FRAMEWND, &hWndClient1);

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

WinDestroyWindow(hWndFrame1); WinDestroyMsgQueue (hmq); WinTerminate (hab);

return(-1); }

// Создаем дочернее окно hWndChildFrame = WinCreateStdWindow (hWndFrame2, WS_VISIBLE , &flFrameChildFlags, szWndClassChild, szChildTitle, 0, 0, ID_CHILDWND, &hWndChildClient);

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

WinDestroyWindow(hWndFrame2); WinDestroyWindow(hWndFrame1); WinDestroyMsgQueue (hmq); WinTerminate (hab);

return(-1); }

// Устанавливаем начальные размеры и // расположение дочернего окна WinSetWindowPos (hWndChildFrame, HWND_TOP , 10, 10, 200, 200, SWP _ACTIVATE | SWP_SIZE | SWP_SHOW | SWP_MOVE );

// Устанавливаем пиктограмму для дочернего окна WinSendMsg (hWndChildFrame, WM_SETICON , (MPARAM)WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE), NULL);

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

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

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

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

// ================================================== // Функция главного окна приложения // ==================================================

MRESULT EXPENTRY WndProc1(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { CHAR szMsg[100];

switch (msg) { case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

case WM_BUTTON1DOWN : { sprintf (szMsg, "(x, y) = (%ld, %ld)", SHORT1FROMMP (mp1), SHORT2FROMMP (mp1));



WinMessageBox (HWND_DESKTOP, hWnd, szMsg, "Координаты курсора мыши (окно 1)", 0, MB_INFORMATION | MB_OK); }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }

// ================================================== // Функция второго окна приложения // ==================================================

MRESULT EXPENTRY WndProc2( HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { CHAR szMsg[100];

switch (msg) { case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

case WM_BUTTON1DOWN : { sprintf (szMsg, "(x, y) = (%ld, %ld)", SHORT1FROMMP (mp1), SHORT2FROMMP (mp1));

WinMessageBox (HWND_DESKTOP, hWnd, szMsg, "Координаты курсора мыши (окно 2)", 0, MB_INFORMATION | MB_OK); }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }

// ================================================== // Функция дочернего окна // ==================================================

MRESULT EXPENTRY WndProcChild(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2) { CHAR szMsg[100];

switch (msg) { case WM_ERASEBACKGROUND : return(MRFROMLONG(1L));

case WM_BUTTON1DOWN : { sprintf (szMsg, "(x, y) = (%ld, %ld)", SHORT1FROMMP (mp1), SHORT2FROMMP (mp1));

WinMessageBox (HWND_DESKTOP, hWnd, szMsg, "Координаты курсора мыши (дочернее окно)", 0, MB_INFORMATION | MB_OK); }

default: return(WinDefWindowProc (hWnd, msg, mp1, mp2)); } }


Пространство отображения


Прежде чем приступать к рисованию, приложение должно получить в свое распоряжение пространство отображения , которое называется Presentation Space (PS ).

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

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

Одним из важных атрибутов пространства отображения является размер области отображения. Например, если приложение получило пространство отображения для окна Client Window , оно может рисовать только во внутренней области этого онка. Если же получено пространство отображения для окна Frame Window , область рисования будет шире.

Подробным изучением атрибутов пространства отображения мы займемся позже, а пока расскажем о двух способах получения идентификатора пространства отображения.

Первый способ используется только при обработке сообщения WM_PAINT . Он заключается в вызыве функции WinBeginPaint :

HPS hps; RECTL rec; . . . case WM_PAINT : { hps = WinBeginPaint (hWnd, NULLHANDLE, &rec); . . . // Вызов функций рисования . . . WinEndPaint (hps); return 0; }

Функция WinBeginPaint возвращает идентификатор пространства отображения, который в дальнейшем будет использоваться всеми функциями рисования.

Заметим, что перед тем как вернуть управление, обработчик сообщения WM_PAINT должен освободить полученное пространство отображения при помощи фукнции WinEndPaint .

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

HPS WinBeginPaint ( HWND hwnd, // идентификатор окна HPS hps, // идентификатор пространства отображения PRECTL prclPaint); // область ограничения

Через параметр hwnd функции WinBeginPaint передается идентификатор окна, для которого необходимо получить пространство отображения.
Вы можете указать идентификатор любого окна, созданного вашим приложением, или значение HWND_DESKTOP . В последнем случае будет получено пространство отображения, пригодное для рисования во всей области рабочего стола Workplace Shell.

Для получения так называемого кэшированного пространства отображения (которое мы будем использовать в приложениях, исходные тексты которых приведены в этой книге) для параметра hps необходимо указать значение NULLHANDLE. Подробнее о типах пространств отображения вы узнаете позже.

Что же касается параметра prclPaint, то вы можете указать его как NULL либо передать через этот параметр указатель на структуру типа RECTL, описывающую координаты прямоугольной области.

Если этот параметр указан как NULL, от функции WinBeginPaint будет получено пространство отображения, пригодное для рисования в любом месте окна, заданного параметром hwnd. В противном случае в структуру типа RECTL записываются размеры области, которая будет перерисована.

Структура RECTL определена следующим образом:

typedef struct _RECTL { LONG xLeft; // координата X левой границы // прямоугольной области LONG yBottom; // координата Y нижней границы области LONG xRight; // координата X правой границы области LONG yTop; // координата Y верхней границы области } RECTL; typedef RECTL *PRECTL;

В случае успеха функция WinBeginPaint возвращает идентификатор полученного пространства отображения. При ошибке возвращается значение NULLHANDLE.

Рассмотрим теперь второй способ получения пространства отображения, который должен использоваться во всех случаях, кроме обработки сообщения WM_PAINT . Этот способ основан на использовании функции WinGetPS :

HPS hps; . . . case WM_BUTTON1DOWN : { hps = WinGetPS (hWnd); . . . // Вызов функций рисования . . . WinReleasePS (hps); return 0; }

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

Отметим, что после завершения процедур рисования вы должны освободить пространство отображения, полученное от функции WinGetPS , при помощи функции WinReleasePS (но не в коем случае не при помощи функции WinEndPaint , которая используется только в паре с функцией WinBeginPaint ).


Радиус регулятора


Вы можете определить радиус регулятора, посылая его окну сообщение CSM_QUERYRADIUS . Через параметр mp1 при этом необходимо передать указатель на переменную типа USHORT, в которую будет записан радиус.



Регистрация класса главного окна приложения


Напомним, что окно в системе 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 . Если вы этого не сделаете, система может оказаться в неустойчивом состоянии.


Регуляторы


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

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

Рис. 8.6. Регулятор круглой формы

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

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



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


Создавая программы для операционной системы 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 и есть на дискете, которую вы можете купить вместе с книгой (на этой дискете есть исходные тексты всех программ, приведенных в нашей книге).



Родительские и дочерние окна


Каждое окно, создаваемое приложением, имеет родительское окно . При этом само оно по отношению к родительскому является дочерним.

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

На рис. 2.1 показана ситуация, когда одно родительское окно имеет три дочерних окна.

Рис. 2.1. Родительское и дочерние окна

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

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

В том случае, когда в одном родительском окне создано несколько дочерних окон, они могут перекрывать друг друга.

Если пользователь перемещает родительское окно, то дочернее окно будет перемещаться вместе с ним.

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

При уничтожении родительского окна все его дочерние окна уничтожаются автоматически, поэтому вам не нужно об этом беспокоиться.



Счетчик повторений


Одинаковые клавиатурные сообщения могут комбинироваться системой Presentation Manager в одно клавиатурное сообщение (например, когда влключается режим автоповтора). Количество скомбинированных таким образом сообщений записывается в поле счетчика повторений, который располагается в битах 16 - 23 параметра mp1. Его можно извлечь при помощи макрокоманды CHAR3FROMMP :

nRepeatCount = CHAR3FROMMP(mp1);



изменения позиции и шаг размещения меток


Оба эти параметра устанавливаются одновременно при помощи сообщения CSM_SETINCREMENT :

WinSendMsg(hWndCirc1, CSM_SETINCREMENT, MPFROMLONG(10L), MPFROMLONG(2L));

Через параметр mp1 передается значение шага изменения позиции, а через параметр mp2 - значение шага размещения меток. Чем больше шаг изменения позиции, тем грубее регулировка.


Для определения шага изменения позиции и шага размещения меток окну регулятора необходимо послать сообщение CSM_QUERYINCREMENT . Через параметры mp1 и mp2 необходимо передать указатели на переменные типа USHORT, в первую из которых будет записано значение шага изменения позиции, а во вторую - значение шага размещения меток.



Система координат


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

Для рисования графических изображений программа MS-DOS переключала видеоадаптер в один из графических режимов, например, в режим VGA с разрешением 640 х 480 пикселов. Если нужно было выводить текст в графическом режиме, для каждого видеорежима приходилось использовать отдельный набор шрифтов.

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

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

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

Новые размеры окна Client Window , которые мы будем обозначать cxClient и cyClient, передаются через малдшее и старшее слово парметра mp2, соответственно. Вы можете определить размеры окна следующим образом:

case WM_SIZE : { cxClient = SHORT1FROMMP (mp2); cyClient = SHORT2FROMMP (mp2); return 0; }

Другой способ определения размеров окна основан на вызове функции WinQueryWindow Rect, прототип которой приведен ниже:

BOOL WinQueryWindow Rect( HWND hwnd, // идентификатор окна PRECTL prclDest); // указатель на структуру RECTL

Эта функция запишет размеры окна, идентификатор которого передается через параметр hwnd, в структуру типа RECTL с адресом prclDest. При успешном завершении функция возвращает значение TRUE, при ошибке (например, если был задан неправильный идентификатор окна) - значение FALSE.

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

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