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


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

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

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

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


Step 1Basic AppБазовая программа
Step 2TextТекст
Step 3LinesСтроки
Step 4MenuМеню
Step 5About BoxОб окне
Step 6PensПерья
Step 7PaintingРисование
Step 8StreamsПотоки
Step 9PrintingПечать
Step 10PaletteПалитра
Step 11BWCCУправляющие элементы окна
Step 12Custom 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.


Рис. 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.


Рис. 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.