2. Элементарная теория окон

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

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

2.1. Иерархия окон и родственные связи

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

Теперь мы рассмотрим иерархию и взаимосвязь окон Presentation Manager более детально.

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

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

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

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

IMG00003.GIF (2472 bytes)

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

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

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

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

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

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

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

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

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

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

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

IMG00004.GIF (2630 bytes)

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

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

hwndDesktopWnd = WinQueryDesktopWindow(hab, NULL);

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

hwndObjectWnd = WinQueryObjectWindow (HWND_DESKTOP);

Окно 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 имеются функции, позволяющие проследить "семейные отношения" между окнами. При помощи этих функций зная идентификатор дочернего окна, вы можете определить идентификатор родительского окна и наоборот, зная идентификатор родительского окна, определить идентификаторы всех его дочерних окон.

Например, функция WinQueryWindow позволяет определить идентификатор родительского окна, если идентификатор дочернего окна передается ей в качестве первого параметра, а константа QW_PARENT - в качестве второго.

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

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

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

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

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

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

Удочерение окна

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

2.2. Изменение размеров и расположения окна

С помощью функции WinSetWindowPos приложение может изменить расположение или размеры созданного ранее окна. Прототип этой функции приведен ниже:

BOOL WinSetWindowPos (
  HWND  hwnd,  // идентификатор окна
  HWND  hwndInsertBehind,// относительный
                         // порядок расположения
  LONG  x,     // координата по оси X
  LONG  y,     // координата по оси Y
  LONG  cx,    // ширина окна
  LONG  cy,    // высота окна
  ULONG fl);   // индикатор изменения позиции

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

Параметр hwndInsertBehind задает новое расположение окна по оси Z (если в параметре fl, описанном ниже, указан флаг SWP _ZORDER ).

Сделаем пояснение относительно оси Z .

Ось Z направлена перпендикулярно к плоскости экрана в направлении от экрана к глазам пользователя.

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

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

Что касается параметров x и y, то они задают новые значения для координат дочернего окна по соответствующим координатным осям, связанным с родительским окном (если в параметре fl указан флаг SWP _MOVE ). По умолчанию начало системы координат находится в левом нижнем углу окна, ось X направлена слева направо, а ось Y - снизу вверх. В качестве единицы измерения, опять же по умолчанию, используется пиксел - минимальный элемент изображения при выбранном видеорежиме.

Параметры cx и cy задают, соответственно, новые значения для ширины и высоты окна в пикселах (если в параметре fl указан флаг SWP _SIZE ).

Параметр fl указывается как набор флагов, объединенных при помощи логической операции ИЛИ. Он определяет, какие операции выполняет функция WinSetWindowPos . Ниже мы привели возможные значения флагов с кратким описанием.

Флаг Описание
SWP _SIZE Изменение размеров окна
SWP _MOVE Изменение расположения окна по осям X и Y
SWP _ZORDER Изменение расположения окна по оси Z
SWP _SHOW Отображение окна
SWP _HIDE Скрытие окна
SWP _NOREDRAW Если указан этот флаг, не выполняется перерисовка изменившихся областей окна
SWP _NOADJUST Если указан этот флаг, перед перемещением окна или перед изменением его размеров в функцию окна не передается сообщение WM_ADJUSTWINDOWPOS
SWP _ACTIVATE Активизация окна (действует только для окна Frame Window ). Если не указан флаг SWP _ZORDER и параметр hwndInsertBehind не равен HWND_BOTTOM , активизированное окно "всплывает" наверх по оси Z
SWP _DEACTIVATE Блокирование окна (действует только для окна Frame Window ). Если не указан флаг SWP _ZORDER и параметр hwndInsertBehind не равен HWND_TOP , активизированное окно отодвигается на задний план по оси Z
SWP _MINIMIZE Минимизация окна. Этот флаг несовместим с флагами SWP _MAXIMIZE и SWP_RESTORE
SWP _MAXIMIZE Максимизация окна. Этот флаг несовместим с флагами SWP _MINIMIZE и SWP_RESTORE
SWP _RESTORE Восстановление размеров окна. Этот флаг несовместим с флагами SWP _MINIMIZE и SWP_MAXIMIZE

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

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

typedef struct _SWP  
{
  ULONG   fl;
  LONG    cy;
  LONG    cx;
  LONG    y;
  LONG    x;
  HWND    hwndInsertBehind;
  HWND    hwnd;
  ULONG   ulReserved1; // зарезервировано
  ULONG   ulReserved2; // зарезервировано
} SWP ;
typedef SWP  *PSWP;

Обработчик сообщения может изменить поля x, y, cx, cy или hwndInsertBehind, внеся коррективы в изменения размеров или расположения окна.

2.3. Передача сообщений функции окна

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

Существует два принципиально разных способа передачи сообщений - передача через очередь приложения и непосредственная передача.

Передача сообщений через очередь приложения выполняется функцией WinPostMsg , прототип которой приведен ниже:

BOOL WinPostMsg (
  HWND hwnd,         // идентификатор окна
  ULONG ulMsgid,     // код сообщения
  MPARAM mpParam1,   // первый параметр
  MPARAM mpParam2);  // второй параметр

Функция WinPostMsg посылает сообщение в функцию окна с идентификатором hwnd, причем код передаваемого сообщения определяется параметром ulMsgid, а первый и второй параметры сообщения - параметрами mpParam1 и mpParam2, соответственно.

Процедура посылки сообщения при помощи функции WinPostMsg выглядит следующим образом: функция записывает сообщение в очередь приложения и сразу же возвращает управление, не дожидаясь завершения обработки переданного сообщения. Если сообщение записано в очередь, функция WinPostMsg возвращает значение TRUE. В том случае, когда сообщение невозможно записать в очередь (например, по причине ее переполнения), функция возвращает значение FALSE.

Второй способ передачи сообщений основан на использовании функции WinSendMsg , прототип которой аналогичен прототипу функции WinPostMsg :

BOOL WinSendMsg (
  HWND hwnd,         // идентификатор окна
  ULONG ulMsgid,     // код сообщения
  MPARAM mpParam1,   // первый параметр
  MPARAM mpParam2);  // второй параметр

Вызов функции WinSendMsg приводит к тому, что Presentation Manager выполняет непосредственный вызов функции окна, идентификатор которого задан параметром hwnd. При этом функции окна передается сообщение с кодом ulMsgid и параметрами mpParam1, mpParam2.

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

Ниже мы привели фрагмент исходного текста приложения, в котором окну hWndChildFrame передается сообщение WM_SETICON :

WinSendMsg (hWndChildFrame, WM_SETICON ,
  (MPARAM)WinQuerySysPointer (HWND_DESKTOP,
  SPTR_APPICON, FALSE), NULL);

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

Функция WinBroadcastMsg позволяет передавать сообщения одним из двух описанных выше методов одновременно всем дочерним окнам любого родительского окна:

BOOL WinBroadcastMsg (
  HWND hwnd,     // идентификатор родительского окна
  ULONG ulMsgid,     // код сообщения
  MPARAM mpParam1,   // первый параметр
  MPARAM mpParam2,   // второй параметр
  ULONG  flCmd);     // команда

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

Значение Описание
BMSG_POST Передача сообщения через очередь сообщения. Этот параметр несовместим с параметрами BMSG_SEND и BMSG_POSTQUEUE
BMSG_SEND Непосредственная передача сообщения. Этот параметр несовместим с параметрами BMSG_POST и BMSG_POSTQUEUE
BMSG_POSTQUEUE Передача сообщения всем задачам процесса, имеющим очередь сообщений. Этот параметр несовместим с параметрами BMSG_POST и BMSG_SEND
BMSG_DESCENDANTS Сообщение передается всем дочерним окнам родительского окна hwnd
BMSG_FRAMEONLY Сообщение передается только окнам, имеющим стиль CS_FRAME (только окнам Frame Window )

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

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

BOOL WinPostQueueMsg (
  HMQ  hmq,          // идентификатор очереди сообщений
  ULONG ulMsgid,     // код сообщения
  MPARAM mpParam1,   // первый параметр
  MPARAM mpParam2);  // второй параметр

Обратите внимание, что этой функции не передается идентификатор окна.

2.4. Создание дочерних окон

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

Ниже регистрируется класс окна szWndClassChild, причем для дочернего окна будет использована функция окна с именем WndProcChild:

CHAR  szWndClassChild[] = "WINTREECHILD";
fRc = WinRegisterClass (hab, szWndClassChild,
    (PFNWP)WndProcChild, 0, 0);

После того как класса дочернего окна зарегистрирован, можно создавать дочернее окно:

hWndChildFrame = WinCreateStdWindow (hWndFrame, 
  WS_VISIBLE ,
  &flFrameChildFlags, szWndClassChild, szChildTitle,
  0, 0, ID_CHILDWND, &hWndChildClient);

Обратите внимание, что в качестве родительского используется окно с идентификатором hWndFrame. Кроме этого, дочернее окно имеет свой собственный идентификатор ID_CHILDWND.

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

WinSetWindowPos (hWndChildFrame, HWND_TOP , 
  10, 10, 200, 200,
  SWP _ACTIVATE  | SWP_SIZE  | SWP_SHOW  | SWP_MOVE );

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

2.5. Приложение 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));
  }
}

Файл wintree.h

Файл wintree.h, приведенный в листинге 2.2, содержит определение констант ID_APP_FRAMEWND и ID_CHILDWND (идентификаторы ресурсов для окон верхнего уровня и дочернего окна).

Листинг 2.2. Файл wintree\wintree.h

#define ID_APP_FRAMEWND 1
#define ID_CHILDWND     2

Файл wintree.rc

Файл описания ресурсов приложения (листиг 2.3) содержит определение пиктограммы.

Листинг 2.3. Файл wintree\wintree.rc

#include <os2.h>
#include "wintree.h"
ICON ID_APP_FRAMEWND WINTREE.ICO

Файл wintree.def

В файле определения модуля (листинг 2.4) в секции EXPORTS перечислены имена всех функций обратного вызова. В роли этих функций выступают функции окон верхнего уровня WndProc1 и WndProc2, а также функция дочернего окна WndProcChild.

Листинг 2.4. Файл wintree\wintree.def

NAME        WINTREE   WINDOWAPI
DESCRIPTION 'WinTree Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc1
            WndProc2
            WndProcChild

Определения

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

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

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

Функция main

В функции main определены флаги, которые используются для создания окон верхнего уровня (переменная flFrameFlags) и для окна дочернего уровня (переменная flFrameChildFlags).

Обратите внимание, что для дочернего окна мы не используем флаги FCF_ICON , FCF_SHELLPOSITION и FCF_TASKLIST . Для установки пиктограммы, отображемой в верхнем левом углу дочернего окна, мы посылаем этому окну сообщение WM_SETICON . Размеры и расположение дочернего окна будут установлены явням образом при помощи функции WinSetWindowPos .

Каждое из трех окон нашего приложения имеет свою функцию окна и потому создается на базе своего класса окна. Имена этих классов хранятся в переменных szWndClass1, szWndClass2 и szWndClassChild.

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

После этого для регистрации классов окна три раза вызывается функция WinRegisterClass . При этом определяется, что для окон, создаваемых на базе класса szWndClass1, будет использоваться функция окна WndProc1, для окон, создаваемых на базе класса szWndClass2 - функция окна WndProc2, а для окон, создаваемых на базе класса szWndClassChild - функция окна WndProcChild.

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

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

На следующем этапе наше приложение создает окно, которое является дочерним по отношению к второму окну верхнего уровня, имеющему идентификатор hWndFrame2. Для этого используется знакомая вам функция WinCreateStdWindow :

hWndChildFrame = WinCreateStdWindow (hWndFrame2, 
  WS_VISIBLE ,
  &flFrameChildFlags, szWndClassChild, szChildTitle,
  0, 0, ID_CHILDWND, &hWndChildClient);

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

После создания дочернего окна функция main устанавливает его размеры и расположение, вызывая для этого рассмотренную нами ранее функцию WinSetWindowPos :

WinSetWindowPos (hWndChildFrame, HWND_TOP , 
  10, 10, 200, 200,
  SWP _ACTIVATE  | SWP_SIZE  | SWP_SHOW  | SWP_MOVE );

Далее для изменения пиктограммы системного меню при помощи функции WinSendMsg в функцию дочернего окна посылается сообщение WM_SETICON :

WinSendMsg (hWndChildFrame, WM_SETICON ,
  (MPARAM)WinQuerySysPointer (HWND_DESKTOP,
  SPTR_APPICON, FALSE), NULL);

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

Функция WinQuerySysPointer предназначена для получения идентификатора ресурса системного курсора или системной пиктограммы, заданной своим идентификатором. Прототип этой функции приведен ниже:

HPOINTER WinQuerySysPointer (
  HWND hwndDeskTop, // идентификатор окна Desktop Window 
  LONG lIdentifier, // идентификатор системного курсора
                    // или пиктограммы
  BOOL fCopy);      // признак копирования

В качестве значения для параметра hwndDeskTop можно использовать константу HWND_DESKTOP. Что же касается идентификатора lIdentifier, то для него следует указывать одно из приведенных ниже значений.

Значение Описание
SPTR_ARROW Курсор в виде стрелки
SPTR_TEXT Текстовый курсор в виде буквы I
SPTR_WAIT Курсор в виде песочных часов
SPTR_SIZE Курсор для изменения размера
SPTR_MOVE Курсор для перемещения объектов
SPTR_SIZENWSE Курсор для изменения размера, направленный вниз
SPTR_SIZENESW Курсор для изменения размера, направленный вверх
SPTR_SIZEWE Горизонтальный курсор для изменения размера
SPTR_SIZENS Вертикальный курсор для изменения размера
SPTR_APPICON Стандартная пиктограмма приложения
SPTR_ICONINFORMATION Пиктограмма для информационного сообщения
SPTR_ICONQUESICON Пиктограмма со знаком вопроса
SPTR_ICONERROR Пиктограмма для сообщения об ошибке
SPTR_ICONWARNING Пиктограмма для предупреждающего сообщения
SPTR_ILLEGAL Пиктограмма для сообщения о неправильной операции
SPTR_FILE Пиктограмма для обозначения одного файла
SPTR_MULTFILE Пиктограмма для обозначения группы файлов
SPTR_FOLDER Пиктограмма папки
SPTR_PROGRAM Пиктограмма приложения

Признак копирования fCopy может принимать значения TRUE или FALSE. В первом случае создается копия указанного системного ресурса, которая при необходимости может быть модифицирована. Ресурс, созданный таким образом, должен быть удален после использования при помощи функции WinDestroyPointer . Эта функция имеет единественный параметр - идентификатор уничтожаемого ресурса.

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

Функции окон

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