Часть 1. Изучение ObjectWindows


                          Глава 1. Знакомство с Windows

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

             Этот процесс разбит на следующие шаги:

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

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

                       Шаг 1: Создание базового приложения
        -----------------------------------------------------------------

             +-----------------------+
             |XStepX1:XBasicXAppXXXXX| Базовая программа
             | Step 2: Text          | Текст
             | Step 3: Lines         | Строки
             | Step 4: Menu          | Меню
             | Step 5: About Box     | Об окне
             | Step 6: Pens          | Перья
             | Step 7: Painting      | Рисование
             | Step 8: Streams       | Потоки
             | Step 9: Printing      | Печать
             | Step 10: Palette      | Палитра
             | Step 11: BWCC         | Управляющие элементы окна
             | Step 12: Custom ctrls | Специализированные элементы
             +-----------------------+

             Отправным пунктом для всех программ,  которые  вы  пишете  с
        применением ObjectWindows,  является  программа STEP01A.PAS.  Эта
        программа, которая называется Steps, создает основное окно прило-
        жения.

             Все программы   ObjectWindows   должны  использовать  модуль
        OWindows, которые  содержит  стандартные  объекты,   используемые
        ObjectWindows для приложений и окон. Большинство приложений вклю-
        чают в себя также диалоговые блоки и соответствующие  управляющие
        элементы. ObjectWindows  предусматривает для них объекты в модуле
        ODialogs. Объекты,  относящиеся  к  печати,  находятся  в  модуле
        OPrinter. Программам,  применяющим наборы и потоки, необходим мо-
        дуль Objects.

             Кроме модулей ObjectWindows большинству программ  необходимы
        также модули WinTypes и WinProcs.  Эти два модуля определяют типы
        и константы (WinTypes) и процедуры и функции (WinProcs), образую-
        щие прикладной программный интерфейс Windows (API).  Приложениям,
        использующим продвинутые средства Windows  (версий  старше  3.0),
        кроме данных двух нужны также другие модули.

                   Примечание: Обзор модулей ObjectWindows вы можете най-
              ти в Главе 7 "Иерархия ObjectWindows".

                             Требования к приложению
        -----------------------------------------------------------------

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

             Каждая программа ObjectWindows должна определять  новый  тип
        приложения, являющегося    наследником    предоставляемого   типа
        TApplication. В   программе    Steps    этот    тип    называется
        TMyApplication. Приведем основной блок программы Steps:

             var
               MyApp: TMyApplication;
             begin
               MyApp.Init('Steps');
               MyApp.Run;
               MyApp.Done;
             end.

             Init - это конструктор TMyApplication,  создающий новый объ-
        ект MyApp.  Он позволяет также задать имя приложения (поле объек-
        та) 'Steps'  и создает (и выводит) основное окно приложения.  Run
        запускает последовательность вызовов  методов,  составляющих  ход
        выполнения приложения    Windows.    Done    -   это   деструктор
        TMyApplication.

                           Определение типа приложения
        -----------------------------------------------------------------

             Ваша прикладная  программа  должна  создавать  новый  тип из
        стандартного типа ObjectWindows TApplication (или  некоторых  ти-
        пов,  производных от TApplication). Этот новый тип должен переоп-
        ределять  по  крайней   мере   один   метод   -   InitMainWindow.
        TApplication.InitMainWindow вызывается ObjectWindows автоматичес-
        ки для установки  основного  окна  программы.  Каждое  приложение
        ObjectWindows должно строить свое основное окно.

                   Примечание: Объекты  приложения подробно описываются в
              Главе 8.

             Определение TMyApplication имеет следующий вид:

             type
               TMyApplication = object(TApplication)
                  procedure InitMainWindow; virtual;
             end;

                          Инициализация основного окна
        -----------------------------------------------------------------

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

             precedure TMyApplication.InitMainWindow;
             begin
               MainWindow := New(PWindow, Init(nil, 'Steps'));
             end;

             Обычно метод  InitMainWindow модифицируется для создания но-
        вого типа основного окна.  Указанный метод  использует  экземпляр
        объекта TWindow - предоставляемый ObjectWindows тип окна, который
        определяет наиболее общее окно.  На шаге 2 мы заменим  его  более
        интересным оконным типом.

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

             program Steps;

             uses OWindows;

             type
               TMyApplication = object(TApplication)
                  procedure InitMainWindow; virtual;
               end;

             procedure TMyApplication.InitMainWindow;
             begin
               MainWindows := New(PWindow, Init(nil, 'Steps'));
             end;

             var MyApp: TMyApplication;
             begin
               MyApp.Init('Steps');
               MyApp.Run;
               MyApp.Done;
             end.


                              Объект основного окна
        -----------------------------------------------------------------

             Пока программа Steps состоит из двух объектов - объекта при-
        ложения и  объекта  окна.  Объект приложения (MyApp) является эк-
        земпляром TMyApplication - типом,  производным  от  TApplication.
        Оконный  объект,  который  содержится  в  поле MainWindow объекта
        MyApp,  является экземпляром TWindow (общее окно  ObjectWindows).
        Во  всех программах,  кроме простейших,  вам нужно определить тип
        своего основного окна,  соответствующий поведению  приложения.  В
        данном разделе  мы  выведем на экран основное окно,  тип которого
        является производным от TWindow.

                   Приложения: Более подробно об основном окне  рассказы-
              вается в Главе 8 "Объекты приложения".

                             Что такое объект окна?
        -----------------------------------------------------------------

             Объект приложения инкапсулирует стандартное поведение прило-
        жения Windows,   включая   построение   основного    окна.    Тип
        TApplication обеспечивает  фундаментальное  поведение для каждого
        создаваемого вами приложения.

             Аналогично, объект окна инкапсулирует поведение, реализуемое
        приложениями ObjectWindows, включая их основные окна. Это поведе-
        ние включает в себя вывод на экран, изменение размера и закрытие;
        ответ на пользовательские события,  такие как щелчок кнопкой "мы-
        ши",  буксировку и выбор пунктов меню;  вывод управляющих элемен-
        тов, таких как блоки списка и командные кнопки. Тип TWindow и его
        предок  TWindowsObject предусматривают для данного базового пове-
        дения методы и поля.

                   Примечание: Объекты окна подробно описываются в  Главе
              10 "Объекты окна".

             Чтобы сделать ваши программы полезными  и  интересными,  вам
        нужно создать новый тип, производный от TWindow. Новый тип насле-
        дует поля и методы TWindow и добавляет часть своих.  В общем слу-
        чае объектно-ориентированный подход позволяет вам не "изобретать"
        каждый раз окно.


                                    Описатели
        -----------------------------------------------------------------

             Объект окна имеет по крайней мере три поля:  HWindow. Parent
        и ChildList.  HWindow  содержит описатель окна.  Описатель окна -
        это уникальное число,  которое связывает интерфейсный объект (та-
        кой как окно, диалоговый блок или объект управляющего элемента) с
        соответствующим элементом экрана.

                   Примечание: Подробно  об описателях окна  их использо-
              вании рассказывается в Главе 10 "Объекты окна".

             Таким образом,  HWindow содержит целое значение, идентифици-
        рующее соответствующий  элемент  экрана.  Это напоминает бирку на
        связке ключей.  Аналогично тому как вы выбираете ключ, чтобы дос-
        тать из шкафа пальто,  вы выбираете описатель для получения окна.
        В большинстве случаев вы работаете с объектами окна,  и у вас нет
        необходимости манипулировать описателем окна непосредственно,  но
        они используются при вызове функций Windows.  Например, на данном
        шаге вы вызываете функцию MessageBox.  Эта функция требует указа-
        ния параметра,  идентифицирующего порождающее окно сообщений.  Вы
        указываете основное  окно,  описатель которого записан в его поле
        HWindow:

             MessageBox(MainWindow^.HWindow, 'Хотите сохранить?',
                        'Файл не изменен', mb_YesNo or mb_IconQuestion);

                           Порождающие и дочерние окна
        -----------------------------------------------------------------

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

                   Примечание: Взаимодействие  этих окон подробнее описы-
              вается в Главе 9.

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

             Третье поле оконного объекта - это поле ChildList,  содержа-
        щее связанный список дочерних окон.

                            Создание нового типа окна
        -----------------------------------------------------------------

             Теперь у вас есть некоторое представление о том,  что содер-
        жит оконный объект, и вы можете создать новый оконный тип, произ-
        водный от  TWindow,  используя  его  как  основное окно программы
        Step. Сначала  измените   определения   и   задайте   новый   тип
        TStepWindow. Не  забудьте также определить новый указатель на тип
        TStepWindow - PStepWindow, который будет полезен при создании эк-
        земпляров объектов TStepWindow.

             type
               PStepWindow = ^TStepWindow;
               TStepWindow = object(TWindow)
             end;

             Затем измените TMyApplication.InitMainWindow,  чтобы создать
        в качестве основного окна вместо TWindow TStepWindow.

             procedure TMyApplication.InitMainWindow;
             begin
               Main := New(PStepWindow, Init(nil, 'Step'));
             end;

             Определение нового  типа  и  создание   его   экземпляра   в
        InitMainWindow -  это  все,  что требуется для определения нового
        типа основного окна для TMyProgram.  Объект  приложения  вызывает
        методы для создания интерфейсного элемента окна (Create) и вывода
        его на экран (Show).  Вам почти никогда не потребуется  использо-
        вать эти методы непосредственно. Обычно они вызываются при вызове
        метода MakeWindow объекта приложения.

                   Примечание: MAkeWindow поясняется в  Главе  9  "Интер-
              фейсные объекты".

             Однако TStepWindow не определяет новых видов поведения,  от-
        личных от тех,  которые наследуются от TWindow  и  TWindowObject.
        Другими словами,  программа  Step не становится более интересной.
        Такие виды поведения будут добавлены в следующем разделе.

                              Реакция на сообщения
        -----------------------------------------------------------------

             Скорейший способ  сделать оконный объект полезным - это зас-
        тавить его отвечать на  некоторые  сообщения  Windows.  Например,
        когда вы щелкаете "мышью" в основном окне программы Step, Windows
        посылает окну сообщение wm_LButtonDown,  которое  перехватывается
        ObjectWindows и посылается затем соответствующему оконному объек-
        ту.  Это указывает оконному объекту,  что пользователь щелкнул  в
        нем  кнопкой "мыши".  При этом передаются также координаты точки,
        где пользователь нажал кнопку.  (Эту информацию мы  используем  в
        шаге 2.)

                   Примечание: Сообщения   Windows  определены  в  модуле
              WinTypes.

             Аналогично, когда пользователь щелкает правой  кнопкой  "мы-
        ши", основной  оконный  объект получает сообщение wm_RButtonDown,
        переданное Windows. На следующем шаге мы узнаем, как сделать так,
        чтобы основное окно (экземпляр TStepWindow) отвечало на эти сооб-
        щения и делало что-нибудь полезное.

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

             type
               TStepWindow = object(TWindow)
                  procedure WMLButtonDown(var Msg: TMessage); virtual
                     vm_First + wm_LButtonDown;
               end;

                   Примечание: Все программы и  модули,  переопределяющие
              методы ответа на сообщение, должны использовать WinTypes.

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

             Чтобы облегчить для вас эту задачу, ObjectWindows определяет
        для каждого вида  сообщений  константы:  wm_First  для  сообщений
        окон, cm_First  для командных сообщений и nf_First для уведомляю-
        щих сообщений. Подробнее об этих константах рассказывается в Гла-
        ве 7,  но сейчас нужно только помнить,  что когда вы пишете метод
        реакции на сообщение,  начинающееся с  wm_,  к  нему  добавляется
        wm_First.

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

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

             procedure TStepWindow.WMLButtonDown(var Msg: TMessage);
             begin
               MessageBox(HWindow, 'Вы нажали левую кнопку мыши',
                   'Диспетчеризуемое сообщение', mb_OK);
             end;

                   Примечание: Программы, которые вызывают MessageBox или
              другие функции  API  Windows,  должны  использовать  модуль
              WinProcs.

        +-----------------------------------------------------------+-+-+
        |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v|
        +-----------------------------------------------------------+-+-|
        |                                                               |
        |      +----------------------------------------------+         |
        |      |@=@#########Диспетчеризуемое сообщение########|         |
        |      +----------------------------------------------|         |
        |      |                                              |         |
        |      |       Вы нажали левую кнопку мыши            |         |
        |      |                +-----------+                 |         |
        |      |                |####OK#####|                 |         |
        |      |                +-----------+                 |         |
        |      +----------------------------------------------+         |
        |                                                               |
        +---------------------------------------------------------------+

             Рис. 1.2 Программа Steps реагирует на пользовательское собы-
        тие.

                         Завершение прикладной программы
        -----------------------------------------------------------------

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

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

             Когда пользователь      пытается      закрыть     приложение
        ObjectWindows, Windows   посылает   основному   окну    сообщение
        wm_Close, которое вызывает метод CanClose приложения.  CanClose -
        это  булевская функция, указывающая, можно ли завершить (OK) при-
        ложение (True). По умолчанию метод CanClose наследуется из вызова
        TApplication метода CanClose основного оконного объекта.  В боль-
        шинстве случаев  решение о закрытии (OK) принимается объектом ос-
        новного окна.


                            Переопределение CanClose
        -----------------------------------------------------------------

             Тип основного  окна  TStepWindow наследует метод CanClose от
        TWindowObject, которые вызывает методы CanClose каждого из  своих
        дочерних окон  (если они имеются).  Если дочерних окон нет (как в
        данном случае),  CanClose просто возвращает значение True.  Чтобы
        модифицировать поведение приложения при закрытии, вы можете пере-
        определить метод CanClose для объектного  типа  своего  основного
        окна:

             function TStepWindow.CanClose: Boolean;
             var Reply: Integer;
             begin
                CanClose := True;
                Reply := MessageBox(HWindow, 'Хотите сохранить?',
                           'Графическое изображение изменено',
                           mb_YesNo or mb_IconQuestion);
                if Reply = id_Yes then CanClose := False;
             end;


             Теперь когда пользователи попытаются закрыть Step, они полу-
        чат окно сообщений с запросом "Хотите сохранить".  Щелчок "мышью"
        на командной кнопке Yes (Да) приводит к тому,  что CanClose возв-
        ращает значение False и предотвращает закрытие основного  окна  и
        приложения. Щелчок "мышью" на No (Нет) возвращает True,  и прило-
        жение завершает работу.  На шаге 8 это окно сообщений получит не-
        который смысл.  Модифицированная программа Steps показана на Рис.
        1.3.

        +-----------------------------------------------------------+-+-+
        |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v|
        +-----------------------------------------------------------+-+-|
        |                                                               |
        |                                                               |
        |      +----------------------------------------------+         |
        |      |@=@#########Изображение изменилось############|         |
        |      +----------------------------------------------|         |
        |      |  @@@                                         |         |
        |      |  @?@  Хотите сохранить?                      |         |
        |      |  @@@  +-----------+      +-----------+       |         |
        |      |       |###Yes#####|      |####No#####|       |         |
        |      |       +-----------+      +-----------+       |         |
        |      +----------------------------------------------+         |
        |                                                               |
        +---------------------------------------------------------------+

             Рис. 1.3 Программа Steps с переопределенным поведением окна.

                          Дальнейшее изменение закрытия
        -----------------------------------------------------------------

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

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

             type
               PStepWindow = ^TStepWindow;
               TStepWindow = object(TWindow)
                  HasGhanged: Boolean;
                  constructor Init(AParent: PWindowsObject: ATitle:
                                   PChar);
                   .
                   .
                   .
                end;

             constructor TStepWindow.Init(AParent: PWindowsObject:
                                   ATitle: PChar);
             begin
               inherited Init(AParent, ATitle);
               HasChanged := False;
             end;

             Далее измените метод CanClose для проверки перед выводом ок-
        на сообщения HasChanged:

             function TStepWindow.CanClose: Boolean;
             var Reply: Integer;
             begin
               CanClose := True;
               if HasChanged then
               begin
                 Reply := MessageBox(HWindow, 'Хотите сохранить?',
                                     'Изображение изменилось',
                                      mb_YesNo or mb_IconQuestion);
                 if Reply = id_Yes then CanClose := False;
               end;
             end;

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

             program Steps;

             uses WinTypes, WinProcs, OWindows;

             type
               TMyApplication = object(TApplication)
                  procedure InitMainWindow; virtual;
               end;

             type
               PStepWindow = ^TStepWindow;
               TStepWindow = object(TWindow)
                  Haschanged: Boolean;
                  constructio Init(AParent: PWindowsObject; ATitle:
                                    PChar);
                  function CanClose: Boolean; virtual;
                  procedure CanClose: Boolean; virtual;
                  procedure WMLButtonDown(var Msg: TMessage);
                     virtual wm_First + wm_LButtonDown;
                  procedure WMRButtonDown(var Msg: TMessage);
                     virtual sm_First +? wm_RButtonDown;
             end;

             constructor TStepWindow.Init(AParent: PWindowsObject;
                                          ATitle: PChar);
             begin
               inherited Init(AParent, ATitle);
               HasChanged := False;
             end;

             function TStepWindow.CanClose: Boolean;
             var Reply: Integer;
             begin
                if HasChanged then
                begin
                  CanClose := True;
                  Reply := MessageBox(HWindow, 'Хотите сохранить?',
                                      'Изображение изменилось',
                                       mb_YesNo or mb_IconQuestion);
                 if Reply = id_Yes then CanClose := False;
               end;
             end;

             procedure TStepWindow.WMLButtonDown(var Msg: TMessage);
             begin
               MessageBox(HWindow, 'Вы нажали левую кнопку мыши',
                   'Диспетчеризуемое сообщение', mb_OK);
             end;

             procedure TStepWindow.WMRButtonDown(var Msg: TMessage);
             begin
               MessageBox(HWindow, 'Вы нажали правую кнопку мыши',
                   'Диспетчеризуемое сообщение', mb_OK);
             end;

             procedure TMyApplication.InitMainWindow;
             begin
               MainWindows := New(PStepWindow, Init(nil, 'Steps'));
             end;

             var MyApp: TMyApplication;
             begin
               MyApp.Init('Steps');
               MyApp.Run;
               MyApp.Done;
             end.
                              Назад | Содержание | Вперед