Pascal 7 & Objects

         

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


-----------------------------------------------------------------

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

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

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

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



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


-----------------------------------------------------------------

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


Знакомство с Windows


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

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

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

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



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


-----------------------------------------------------------------

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 "Объекты приложения".



Описатели


-----------------------------------------------------------------

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

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

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

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



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


-----------------------------------------------------------------

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

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

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

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



Переопределение 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 с переопределенным поведением окна.



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


-----------------------------------------------------------------

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

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

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

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



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


-----------------------------------------------------------------

Скорейший способ сделать оконный объект полезным - это зас- тавить его отвечать на некоторые сообщения 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 реагирует на пользовательское собы- тие.


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


-----------------------------------------------------------------

+-----------------------+ |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".



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


-----------------------------------------------------------------

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



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


-----------------------------------------------------------------

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

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

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



Буксировка линии


-----------------------------------------------------------------

Мы уже видели, что щелчок левой кнопкой "мыши" дает в ре- зультате сообщение wm_LButtonDown и вызывает метод WMLButtonDown. В шаге 1 ваша программа отвечала на щелчки левой кнопкой "мыши", выводя окна сообщений. Вы могли также видеть, что щелчок правой кнопкой "мыши" давал в результате сообщение wm_RButtonDown и вы- зывал метод WMRButtonDown. На нажатие правой кнопки "мыши" прог- рамма отвечала очисткой окна.

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



Что такое контекст дисплея?


-----------------------------------------------------------------

Контекст дисплея имеет три основных функции отображения:

* Он обеспечивает, что текст и графика не выводятся вне по- верхности окна.

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

* Он обеспечивает независимость от устройства. Для вывода в контексте дисплея ваша программа использует стандартные функции API Windows. В шаге 9 мы покажем как можно исполь- зовать одни и те же команды для отображения в окне и на принтере.

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

Поскольку, чтобы рисовать в окне при буксировке "мыши", вам необходим контекст дисплея, создайте в объекте основного окна но- вое поле с именем DragDC, которое будет содержать описатель кон- текста дисплея. DragDC имеет тип HDC, который эквивалентен типу Word.

Чтобы использовать контекст дисплея, ваша программа должна:

* получить контекст дисплея;

* нарисовать в нем;

* освободить контекст дисплея.



Заполнение окна


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



Использование контекста дисплея


-----------------------------------------------------------------

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

TextOut(DragDC, 20, 20, 'Пример текста', 11); LineTo(DragDC, 30, 45);



Изменение размера пера


-----------------------------------------------------------------

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

Для реализации механизма выбора пользователем размера пера мы используем диалоговое окно (типа TInputDialog). Это окно вида:

+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | | | Введите новую толщину линии: | | +----------------------------------------------+ | | |@1@ | | | +----------------------------------------------+ | | +-----------+ +-----------+ | | |####OK#####| |##Cancel###| | | +-----------+ +-----------+ | | | +---------------------------------------------------------------+

Рис. 2.2 Задание новой толщины линии с помощью диалогового окна ввода.



Изображение точек и линий


-----------------------------------------------------------------

В API Windows имеются графические функции MoveTo и LineTo, которые, соответственно, перемещают текущую позицию рисования и рисуют линию до текущей позиции. Для правильной работы функций требуется указание описателя контекста дисплея DragDC. Нужно пом- нить о том, что вы рисуете не непосредственно в окне, а в его контексте дисплея.



Координаты Windows


-----------------------------------------------------------------

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

При отображении вас касаются только координаты в контексте дисплея. Windows обеспечивает, чтобы контекст дисплея попадал в область клиента окна.

Примечание: Область клиента - это часть окна внутри рамки.

На этом шаге Step отобразит текст, показывающий координаты той точки в окне, где вы щелкнули кнопкой "мыши". Например, '(20, 30)' - это точка, отстоящая на 20 элементов изображения вправо и на 30 элементов изображения вниз от верхнего левого угла поверх- ности отображения. Вы можете отображать прямо в той точке, где щелкнули "мышью". Это показано на Рис. 2.1.

+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| |(3,7) (483,7) | | (385,31) | | | | (60,42) | | (217,52) | | | | (302,110) (444,110) | | | | | | (109,141) | | | | (52,182) | | (239,187) (385,181) | | | |(4,288) (474,220)| +---------------------------------------------------------------+

Рис. 2.1 Отображение текста в точке нажатия кнопки "мыши".



Очистка окна


-----------------------------------------------------------------

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

Чтобы очистить окно в ответ на щелчок правой кнопкой "мыши", переопределите метод WMRButtonDown и вызовите в нем процедуру InvalidateRect, которая приводит к повторному отображению всего окна. Так как ваша программа пока не знает, как повторно вывести изображение, она просто очистит область клиента:

Procedure TStepWindow.WMRButtonDown(var Msg: TMessage); begin InvelidateRect(HWindow, nil, Trut); end;

Текущий исходный код вы можете найти в файле STEP02.PAS.

------------------------------------------------------------------------



Освобождение контекста дисплея


-----------------------------------------------------------------

После отображения текста или графики вы должны освободить контекст дисплея (как только закончите отображение).

ReleaseDC(HWindow, DragDC);

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

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

Примечание: GDI и вопросы использования памяти освеща- ются в Главе 17 "Интерфейс с графическим устройством".



Отслеживание размера пера


-----------------------------------------------------------------

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

Изобразительные средства

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

Примечание: В шаге 6 мы создадим объект, инкапсулирую- щий одно инструментальное средство - перо.

Изобразительное средство можно рассматривать как кисть ху- дожника, а контекст дисплея - как холст. Художник сначала создает изобразительные средства (кисти) и получает контекст дисплея (холст). Затем художник выбирает соответствующее изобразительное средство, используя в каждый момент одну из кистей. Аналогично, программа Windows должна выбирать изобразительные средства в кон- тексте дисплея.

Используемые по умолчанию изобразительные средства

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



Параметры сообщений


-----------------------------------------------------------------

Щелчок левой кнопкой "мыши" генерирует сообщение wm_LButtonDown, который вы перехватываете с помощью метода реак- ции на сообщение WMLButtonDown.

Параметр Msg метода реакции на сообщение несет информацию о породившем сообщение событии (такую как координаты точки, где пользователь щелкнул кнопкой "мыши"). Msg - это запись TMessage, поля которой содержат параметр lParam типа Longint и параметр wParam типа Word. Идентификаторы lParam и wParam соответствуют полям в структуре сообщения Windows TMsg.

TMessage определяют также вариантные поля, содержащие подпо- ля lParam и wParam. Например, Msg.lParamLo содержит младшее слово lParam, а Msg.lParamHi - старшее слово. Чаще всего используются поля wParam, lParamLo и lParamHi.

В случае WMLButtonDown Msg.lParamLo содержит x-координату точки нажатия кнопки "мыши", а Msg.lParamHi - y-координату этой точки. Таким образом, чтобы переписать WMLButtonDown для отобра- жения координат точки нажатия кнопки, нужно преобразовать Msg.lParamLo и Msg.lParamHi в строки и, чтобы они приняли вид '(25,21)', конкатенировать их с запятой. В примере для форматиро- вания строки используется функция Windows WVSPrintF.

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

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

procedure TStepWindow.WMLButtonDown(var Msg: TMessage);

var S: array[09] of Char; begin WVSPrint(S, '(%d,%d)', Msg.LParam); DragDC := GetDC(HWindow); TextOut(DragDc, Msg.LParamLo, Msg.LParamHi, S, StrLen(S)); ReleaseDC(HWindow, DragDC); end;

Примечание: Windows ожидает получения строк с заверша- ющим нулем (конечным нулевым байтом). Подробнее эти строки описываются в Главе 18 "Руководства по языку".



Перехват "мыши"


-----------------------------------------------------------------

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

Нужно изменить определение объекта для TStepWindow с заго- ловками метода для WMMouseMove и WMLButtonUp:

procedure WMLButtonUp(var Msg: TMessage); virtual wm_First + wm_LButtonUp; procedure WMLMouseMove(var Msg: TMessage); virtual wm_First + wm_LMouseMove;

Пример полученного исходного кода вы найдете в файле STEP03A.PAS.



Получение контекста дисплея


-----------------------------------------------------------------

Чтобы отобразить что-то в окне, вы должны сначала получить контекст дисплея. Это можно сделать, вызвав в одном из методов типа непосредственно перед отображением на экране функцию Windows GetDC:

DragDC := GetDC(HWindow);



Получение пера нового размера


-----------------------------------------------------------------

Сначала нужно обеспечить способ выбора нового размера пера. В простейшем случае это можно сделать с помощью диалогового окна ввода модуля OStdDlgs. Добавьте модуль OStdDlgs в оператор uses программы. Чтобы использовать совместимые с Windows функции рабо- ты со строками, укажите также модуль Strings. Начало программного файла должно выглядеть таким образом:

program Steps;

uses Strings, WinTypes, WinProcs, OWindow, OStdDlgs; . . .

Выполнение диалогового окна ввода

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

Щелчок правой кнопкой "мыши" дает удобный способ вывода па- раметра для изменения толщины пера. Давайте переопределим метод WMRButtonDown для вывода нового диалогового окна ввода.

Так как диалоговое окно ввода появляется только на короткое время, а вся обработка выполняется одним методом, вам нет необхо- димости определять его как поле TStepWindows. Оно может существо- вать в виде локальной переменной метода WMRButtonDown. Все пост- роение и отмену объекта диалогового окна вы можете выполнять в рамках метода WMRButtonDowm.

Когда Init построит объект диалогового окна ввода, вы можете выполнить его как режимное диалоговое окно, вызвав ExecDialog. ExecDialog проверяет успешность выполнения конструктора Init и создает объект диалогового окна, соответствующий элементу экрана, выполняя затем диалоговое окно. Обработка для ExecDialog заверша- ется только после того как пользователь закрыл диалог, щелкнув "мышью" на командной кнопке OK (Подтверждение) или Cancel (Отме- на).

Если пользователь щелкнул "мышью" на командной кнопке OK, InputText заполняется полученным от пользователя текстом, вызывая метод GetText из TInputDialog. Так как вы запрашиваете номер тол- щины, возвращаемый текст нужно преобразовать в число и передать его в вызове SetPenSize. Таким образом, каждый раз, когда пользо- ватель выбирает новую толщину линии, старое перо удаляется и соз- дается новое.


procedure TStepWindow.WMLButtonDown(var Msg: TMessage); var InputText: array[09] of Char; NewSize, ErrorPos: Integer; begin if not ButtonDown then begin Str(PenSize, InputText); if Application^.ExecDialog(New(PInputDialog, Init(@Self, 'Толщина линии', 'Введите новую толщину:', InputText, SizeOf(InputText))) = id_Ok then begin Val(InputText, NewSize, ErrorPos); if ErrorPos = 0 then SetPenSize(NewSize); end; end; end.

Добавление полей объекта

Далее добавим в TStepWindow новое поле для хранения описате- ля пера, которое вы будете использовать для рисования графики. В данной программе в каждый момент времени вы можете рисовать и вы- водить на экран линии только одной толщины. Соответствующее этой толщине перо хранится в новом поле TStepWindow с именем ThePen. Вы напишете также метод SetPenSize, создающий новое перо и удаля- ющий старое. Теперь описание объекта TStepWindow должно принять следующий вид:

type PStepWindow = ^TStepWindow; TStepWindow = object(TWindow) DragDC: HDC; ButtonDown, HasChanged: Boolean; ThePen: HPen; PenSize: Integer; constructor Init(AParent: PWindowsObject; ATitle: PChar); destructor Done; virtual; function CanClopse: Boolean: virtual; procedure WMLButtonDown(var Msg: TMessage); virtual wm_First + wm_LButtonDown; procedure WMLButtonUp(var Msg: TMessage); virtual wm_First + wm_LButtonUp; procedure WMMouseMove(var Msg: TMessage); virtual wm_First + wm_LMouseMove; procedure WMRButtonDown(var Msg: TMessage); virtual wm_First + wm_RButtonDown; procedure SetPenSize(NewSize: Integer); virtual; end;

Инициализация полей

Чтобы инициализировать новые поля, вам нужно модифицировать конструктор Init для установки пера и переопределить деструктор Done для его отмены. Не забудьте вызвать в новых методах наследу- емые методы:

constructor TStepWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin Inherited Init(AParent, ATitle); ButtonDown := False; HasChanged := False; PenSize := 1; ThePen := CreatePen(ps_Solid, Pensize, 0); end;

destructor TStepWindow.Done; begin DeleteObject(ThePen); inherited Done; end;



Изображение линий

Теперь изменим метод WMLButtonDown для выбора текущего пера (ThePen) во вновь полученном контексте дисплея. Аналогично MoveTo и MessageBox, SelectObject является функцией API Windows.

procedure TStepWindow.WMLButtonDown(var Msg: TMessage); begin if not ButtonDown then begin ButtonDown := True; SetCapture"(HWindow); DragDC := GetDC(HWindow); SelectObject(DragDC, ThePen); MoveTo(DragDC, Msg.lParamLo, Msg.lParamHi); end; end;

Указанные методы выбирают в контексте дисплея уже созданное перо. Однако для создания пера нужно написать следующий вызывае- мый WMRButtonDown метод SetPenSize:

procedure TStepWindow.SetPenSize(NewSize: Integer); begin DeleteObject(ThePen); ThePen := Create(ps_Solid, NewSize, 0); PenSize := NewSize; end;

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

На шаге 5 и 6 вы создадите собственное диалоговое окно и объект пера и используете их для более эффективного графического отображения.


Реакция на сообщения буксировки


-----------------------------------------------------------------

Нужно помнить о том, что после wm_LButtonDown всегда следует сообщение wm_LButtonUp (с промежуточными сообщениями wm_MouseMove или без них). Таким образом, каждый раз, когда вы получаете контекст дисплея, вы можете позднее освободить его.

Для правильного функционирования программы Windows очень важным является освобождение каждого получаемого вами контекста дисплея. Однако вы можете добавить еще одно более надежное средс- тво. Определите в TStepWindow новое булевское поле - тип основно- го окна с именем ButtonDown и обеспечьте его инициализацию в TStepWindow.Unit значением False. Затем вы можете проверять перед получением и освобождением контекста дисплея значение ButtonDown.

Приведем три метода обработки буксировки "мыши":

procedure TStepWindow.WMLButtonDown(var Msg: TMessage); begin InvalidateRect(HWindow, nil, True); if not ButtonDown then begin ButtonDown := True; SetCapture(HWindow); DragDC := GetDC(HWindow); MoveTo(DragDC, Msg.lParamLo, Msg.lParamHi); end; end;

procedure TStepWindow.WMMouseMove(var Msg: TMessage); begin if ButtonDown then LineTo(DragDC, Msg.lParamLo, MsglParamHi); end;

procedure TStepWindow.WMLButtonUp(var Msg: TMessage); begin if ButtonDown then begin ButtonDown := False; ReleaseCapture; ReleaseDC(HWindow, DragDC); end; end;



Отображение текста в окне


-----------------------------------------------------------------

+-----------------------+ | Step 1: Basic App | |XStepX2:XTextXXXXXXXXXX| | 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 | +-----------------------+

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

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



Изображение линий в окне


-----------------------------------------------------------------

+-----------------------+ | Step 1: Basic App | | Step 2: Tex | |XStepX3:XLinesXXXXXXXXX| | 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 | +-----------------------+

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

На шаге 3 мы добавим следующее поведение:

* Щелчок левой кнопкой "мыши" и буксировка соединяют началь- ную и конечную точки отображая в результате линию.

* Щелчок правой кнопкой "мыши" выводит диалоговое окно вво- да, позволяющее пользователю изменить толщину линии.

Чтобы выполнить эти шаги, изучим сначала схему буксировки Windows, а затем реализуем простую графическую программу.



Сообщения wm_MouseMove


-----------------------------------------------------------------

Сделать это можно путем реакции еще на несколько сообщений. Когда пользователь буксирует "мышь" в новую точку окна, Windows посылает сообщение wm_MouseMove, а когда пользователь отпускает левую кнопку "мыши" - сообщение wm_LButtonUp. Обычно окно получа- ет одно сообщение wm_LButtonDown, за которым следует последова- тельность сообщений wm_MouseMove (по одному на каждую промежуточ- ную точку буксировки) и одно сообщение wm_LButtonUp.

Типичная графическая программа Windows реагирует на сообще- ние wm_LButtonDown инициализацией процесса рисования (получая, кроме всего прочего, контекст дисплея). На сообщение wm_LButtonUp она реагирует завершением процесса рисования (освобождая контекст дисплея).



Вывод в контексте дисплея


-----------------------------------------------------------------

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

Примечание: Подробно о контексте дисплея рассказывает- ся в Главе 17 "Интерфейс с графическими устройствами".



Добавление диалогового блока


-----------------------------------------------------------------

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

Файловое диалоговое окно, как одно из диалоговых окон ObjectWindows, определено с типом TFileDialog. Файловое диалого- вое окно полезно использовать в любой ситуации, когда вы запраши- ваете у пользователя для сохранения и загрузки выбор файла на диске. Например, редактор текстов может использовать диалоговое окно для открытия и сохранения документов.

Вы будете выводить файловое диалоговое окно в ответ на выбор пользователем команды File|Open или File|Save As. Файловое диало- говое окно заменяет окно сообщения "Средство не реализовано". В шаге 8 оно будет приспособлено для некоторых реальных файлов, а также сохранения и открытия их для записи и считывания реальных данных. Пока просто выведем диалоговые окна. Вид файлового диало- гового окна показан на Рис. 3.3.

+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | File Options Palette | +---------------------------------------------------------------| | +----------------------------------------------+ | | |@=@##############Открытие файла###############| | | +----------------------------------------------| | | | +------------------+ +----------+| | | | Имя файла: | *.pts | |####OK#### | | | +------------------+ +----------+| | | | +----------+| | | | Каталог: a:\ |##Cancel## | | | +----------+| | | | Файлы: Каталоги: | | | | +----------------+ +-------------+-+ | | | | | | |[-a] |^| | | | | | | |[-b] +-| | | | | | | |[-c] |#| | | | | | | |[-d] |#| | | | | | | |[-e] |#| | | | | | | |[-f] |#| | | | | | | |[-g] |#| | | | | | | |[-h] +-| | | | | | | |[-i] |v| | | | | +----------------+ +-------------+-+ | | | +----------------------------------------------+ | +---------------------------------------------------------------+

Рис. 3.3 Программа Steps с диалоговым блоком File Open.

Добавление к программе Steps файлового диалогового блока требует трех шагов:

* Добавление поля объекта, содержащего имя файла. * Модификация конструктора объекта для инициализации файла. * Выполнение диалогового блока.



Добавление поля объекта


-----------------------------------------------------------------

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

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

Параметр шаблона ресурса определяет, будет ли файловый диа- логовый блок использоваться для открытия или для сохранения фай- ла. Если диалоговый ресурс имеет блок списка файлов с идентифика- тором управляющего элемента id_FList, диалоговый блок использует- ся для открытия файлов; отсутствие такого блока списка указывает на диалоговое окно для сохранения файлов.

Определение типа TStepsWindow должно теперь выглядеть следу- ющим образом:

TStepWindow = object(TWindow) . . . FileName: array[0.fsPathName] of Char; . . .

Примечание: Для работы с константой fsPathName нужно использовать модуль WinDos.

Модификация конструктора

Для создания экземпляра объекта справочного окна вы можете использовать конструктор Init типа TStepWindow. Теперь вам потре- буется добавить к нему код для инициализации FileName:


StrCopy(FileName, '*.PTS');

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

Выполнение диалогового блока

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

CMFileOpen и CMFileSaveAs следует переписать следующим обра- зом:

procedure TStepWindow.CMFileOpen(var Msg: TMessage); begin if Application^.ExecDialog(New(PFileDialog, Init(@Self, PChar(sd_FileOpen), FileName))) = id_Ok then MessageBox(HWindow, FileName, 'Открыть файл:', mb_Ok); end;

procedure TStepWindow.CMFileSaveAs(var Msg: TMessage); begin if Application^.ExecDialog(New(PFileDialog, Init(@Self, PChar(sd_FileSave), FileName))) = id_Ok then MessageBox(HWindow, FileName, 'Сохранить файл:', mb_Ok); end;

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

Полный исходный код программы Steps для данного шага вы мо- жете найти в файле STEP04B.PAS.

------------------------------------------------------------------------


Меню и диалоговые ресурсы


Большинство приложений Windows имеют в своих основных окнах меню, которые предоставляют пользователю возможность выбора - например, команды File|Save, File|Open и Help. В шаге 4 мы доба- вим в программу Steps строку меню. Ввод пользователем данных и выбор параметров в программах Windows часто происходит в диалого- вых блоках. В шаге 5 в программу Steps будет добавлен диалоговый блок.

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

Ресурсы для программы проще всего создавать с помощью редак- торов ресурсов, таких как Resource Workshop (пакет разработчика ресурсов) фирмы Borland. Подробно о создании и редактировании ре- сурсов рассказывается в "Руководстве пользователя по пакету раз- работчика ресурсов".



Идентификаторы управляющих элементов


-----------------------------------------------------------------

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

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



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


-----------------------------------------------------------------

Теперь вы можете определить все методы реакции на команды:

procedure CMFileNew(var Msg: TMessage); virtual cm_First + cm_FileNew; procedure CMFileOpen(var Msg: TMessage); virtual cm_First + cm_FileOpen; procedure CMFileSave(var Msg: TMessage); virtual cm_First + cm_FileSave; procedure CMFileSaveAs(var Msg: TMessage); virtual cm_First + cm_FileSaveAs; procedure CMFilePrint(var Msg: TMessage); virtual cm_First + cm_FilePrint; procedure CMFileSetup(var Msg: TMessage); virtual cm_First + cm_FileSetup;

Определять процедуру CMExit не требуется, поскольку TWindowsObject уже определяет завершающую программу процедуру, которая вызывается при поступлении в основное окно сообщения cm_Exit.



Перехват сообщений меню


-----------------------------------------------------------------

Когда пользователь выбирает элемент меню, окно, к которому присоединено меню, получает командное сообщение Windows. ObjectWindows обрабатывает и диспетчеризует эти сообщения wm_Command аналогично другим сообщениям, но облегчает для вас ра- боту со специальными командами.

Одним из параметров сообщения wm_Command является сама ко- манда (номер, соответствующий идентификатору меню выбранного эле- мента). Вместо вызова метода WMCommand и возложения на вас реше- ния, что делать с каждой возможной командой, ObjectWindows вызы- вает основанные на конкретных командах методы. Чтобы обработать эти сообщения, вы можете определить методы для объектного типа TStepWindow, используя специальное расширение:

procedure CMFileNew(var Msg: TMessage); virtual cm_First + cm_FileNew;

где cm_First - это константа ObjectWindows, определяющая начало диапазона констант для команд, а cm_FileNew - это желаемая коман- да меню. Это означает, что все элементы меню должны иметь уни- кальные идентификаторы (если только не предполагается реагировать на них одинаковым образом).

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

Не путайте основанный на cm_First динамический индекс метода с индексом, соответствующим поступающему сообщению Windows (осно- ванному на wm_First). cm_First - это специальное смещение, ис- пользуемое только для определения методов реакции для команд меню и командных клавиш.



Построение объекта диалогового блока


-----------------------------------------------------------------

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

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

Файл ресурса для программы Steps определяет диалоговый блок с именем 'ABOUTBOX', которое вы можете использовать в качестве окна About box, показанного на Рис. 3.4. Построение объекта диа- логового блока из данного ресурса выглядит следующим образом:

New(PDialog, Init(@Self, 'ABOUTBOX'));

+-------------------------------------------------+ |#=#XXXXXXXXXXXXXXXXXAbout StepsXXXXXXXXXXXXXXXXXX| +-------------------------------------------------| | | | +----------------------------------------+ | | | | | | | @@@@ ObjectWindows tutorial program | | | | @@@@ | | | | Copiright (C) 1992 | | | | Borland International Inc. | | | | All Rights Reserved | | | +----------------------------------------+ | | | | +-----------+ | | |####OK#####| | | +-----------+ | +-------------------------------------------------+

Рис. 3.4 Окно About Box для программы Steps.



Реакция на команды меню


-----------------------------------------------------------------

Теперь для каждого выбора в меню у вас есть метод, который будет вызываться в ответ на соответствующую команду. Выбор коман- ды File|Print вызывает ваш метод CMFilePrint. Пока вызовем просто окно сообщения:

procedure TStepWindow.CMFilePrint(var sg: TMessage); begin Message(HWindow, 'Средство не реализовано', 'Печать файла', mb_Ok); end;

На Рис. 3.2 показана реакция программы Steps на выбор коман- ды File|Print.

+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | File Options Palette | +---------------------------------------------------------------| | | | | | +----------------------------------------------+ | | |@=@##############Печать файла#################| | | +----------------------------------------------| | | | | | | | Средство не реализовано | | | | | | | | +-----------+ | | | | |####OK#####| | | | | +-----------+ | | | +----------------------------------------------+ | | | | | | | | | +---------------------------------------------------------------+

Рис. 3.2 Программа Steps реагирует на команду File|Print.

Для CMFileOpen, CMFileSave, CMFileSaveAs и CMFileSetup напи- шите фиктивные методы, аналогичные CMFilePrint. Позднее вы пере- пишете данные методы для выполнения осмысленных действий.

Теперь, очистив окно, вы можете реагировать на выбор команды меню File|New более интересным образом. Добавьте следующий метод CMFileNew:

procedure TStepWindow.CMFileNew(var Msg: TMessage); begin InvalidateRect(HWindow, nil, True); end;

InvalidateRect выполняет принудительное повторное отображе- ние окна. Полный исходный код программы Steps для данного этапа содержится в файле STEP04A.PAS.



Ресурсы меню


-----------------------------------------------------------------

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

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

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

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

Чтобы сделать программу более читаемой, замените идентифика- торы меню константами, определяемыми во включаемом файле. При создании своего ресурса меню с помощью Resource Workshop или ком- пилятора ресурсов вы можете включить те же константы и использо- вать те же идентификаторы, которые вы используете для доступа к ресурсу к своей программе. Константы меню для программы Steps оп- ределены в файле STEPS.INC:

const cm_FilePrint = 105; cm_FileSetup = 107; cm_Pen = 200; cm_About = 201; cm_PalShow = 301; cm_PalHide = 302;

Заметим, что число элементов меню в файле STEPS.INC не опре- делено. Это связано с тем, что ObjectWindows в файле IWINDOWS.INC определяет для вас некоторые общие команды меню, включая cm_FileOpen, cm_FileNew, cm_FileSave и cm_FileSaveAs.

Включение файлов ресурсов

Чтобы продолжить работу с программой Steps, используйте па- кет разработчика ресурсов или компилятор ресурсов для создания ресурса меню и сохраните его в файле с расширением .RES - STEPS.RES. Формат файла ресурса в исходном виде вы можете посмот- реть в файле STEPS.RC. Вы можете также использовать файл STEPS.RES, который можно найти на дистрибутивных дисках. Имея файл STEPS.RES, вы можете включить его с помощью директивы компи- лятора $R:


{$R STEPS.RES}

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

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

На Рис. 3.1 показан внешний вид этого меню (идентификатор ресурса 100). Оно включает в себя пункты File (Файл), Options (Параметры) и Palette (Палитра), а меню File содержит элементы New (Новый), Open (Открытие), Save (Сохранение), Save As (Сохра- нение под именем), Print (Печать), Printer Setup (Установка прин- тера) и Exit (Выход). Элементы верхнего уровня, у которых есть подэлементы, не имеют идентификаторов меню, а их вывод не вызыва- ет никаких действий кроме вывода подэлементов.

Примечание: Не путайте идентификатор ресурса меню с идентификаторами меню отдельных элементов (пунктов) меню.

+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| |XFileX Options Palette | +------------------+--------------------------------------------| |XNewXXXXXXXXXXXXXX| | | Open. | | | Save | | | Save as. | | +------------------| | | Print. | | | Printer Setup. | | +------------------| | | Exit | | +------------------+ | | | | | | | | | | | +---------------------------------------------------------------+

Рис. 3.1 Программа Steps с ресурсом меню.


Режимные и безрежимные диалоговые блоки


-----------------------------------------------------------------

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

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



Добавление строки меню


-----------------------------------------------------------------

+-----------------------+ | Step 1: Basic App | | Step 2: Text | | Step 3: Lines | |XStepX4:XMenuXXXXXXXXXX| | 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 | +-----------------------+

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

* Проектирование меню как ресурса меню.

* Определение констант меню во включаемом файле.

* Загрузка файла ресурса из программы.

* Загрузка ресурса меню в объект основного окна.

* Определение реакции на выбор в меню.

Меню прикладной программы - это не отдельный объект, а атри- бут основного окна. Все оконные объекты имеют набор атрибутов, записанных в поле записи Attr объекта. В поле Menu записи Attr хранится не описатель меню, а меню. Чтобы установить атрибут ме- ню, вы должны переопределить конструктор своего типа окна TStepWindow.



Добавление диалогового блока


-----------------------------------------------------------------

+-----------------------+ | Step 1: Basic App | | Step 2: Text | | Step 3: Lines | | Step 4: Menu | |XStepX5:XAboutXBoxXXXXX| | Step 6: Pens | | Step 7: Painting | | Step 8: Streams | | Step 9: Printing | | Step 10: Palette | | Step 11: BWCC | | Step 12: Custom ctrls | +-----------------------+

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

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

Создание диалогового блока из ресурса требует следующих ша- гов:

* Создание ресурса диалогового блока. * Построение объекта диалогового блока. * Выполнение диалогового блока.



Создание ресурсов диалогового блока


-----------------------------------------------------------------

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

Примечание: Не забывайте, что ресурс - это просто не- кое описание того, что будет создавать ваша программа.



Связывание клавиш с командами


-----------------------------------------------------------------

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

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

Каждая прикладная программа может иметь только один набор командных клавиш. Чтобы загрузить в программу ресурс командных клавиш, переопределите метод InitInstance:

procedure TMyApplication.InitInstance; begin inherited InitInstance; HaccTable := LoadAccelerators(HInstance, 'ShortCuts'); end;

Командные клавиши 'ShortCuts' в STEPS.RES связывают знакомые вам по IDE функциональные клавиши с аналогичными функциями прог- раммы Steps. Например, клавиша F3 генерирует команду cm_FileOpen.



Выполнение диалогового блока


-----------------------------------------------------------------

Чтобы выполнить специализированный диалоговый блок, исполь- зуйте тот же метод ExecDialog, который вы уже использовали для других диалоговых блоков:

Application^.ExecDialog(New(PDialog,Init(@Self,'ABOUTBOX')));

Естественно, нужно определить команду для вывода диалогового блока About box; Steps использует сообщение cm_About, генерируе- мое выбором меню Optrions|About. Теперь такой вид реакции на ко- манду должен быть вам достаточно знаком (см. файл STEP05.PAS):

type TStepWindow = object(TWindow) . . . procedure CMAbout(var Msg: TMessage); virtual cm_First + cm_About; end;

procedure TStepWindow.CMAbout(var Msg: TMessage); begin Application^.ExecDialog(New(PDialog, Init(@Self, 'ABOUTBOX'))); end;

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



Загрузка ресурса меню


-----------------------------------------------------------------

Получить ресурс меню можно с помощью вызова функции Windows LoadMenu:

LoadMenu(HInstance, MakeIntResource(100));

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

Примечание: Для использования типа PChar требуется ус- тановка $X+ (по умолчанию).

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

LoadMenu(HInstance, 'SAMPLE_MENU');

Вот как это делает TStepWindow.Init (заметим, что первое, что он делает - это вызов конструктора Init, наследуемого из TWindow, для выполнения инициализации, необходимой для всех окон- ных объектов):

constructor TStepWindow(AParent: PWindowObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, MakeIntResource(100)); BottomDown := False; HasChanged := False; end;

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



Чтение возвращаемых значений


-----------------------------------------------------------------

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

function GetStyle(ARec: TPenDate): Longint; var i: Integer; begin for i := 0 to 5 do if ARec.StyleArray[i] = bf_Cheched then GetStyle := i; end;

Если пользователь отменяет диалоговый блок, то вас, конечно, не должно беспокоить считывание значений: они совпадают с пере- данными значениями. Обычно когда вы выполняете диалоговый блок с помощью ExecDialog, то чтобы определить, возвратил ли диалоговый блок какие-либо полезные данные, проверяется возвращаемое значе- ние (id_Ok, если пользователь щелкнул "мышью" на командной кнопке OK, в противном случае id_Cancel).

if Application^.ExecDialog(PenDlg) <> id_Cancel then begin Val(PenDate.XWith, TempWith, ErrorPos); SetAttributes(GetStyle(PenData), TempWidth, GetColorAttr(PenData)); end;



Работа с диалоговым блоком


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

В данной главе описаны следующие шаги:

* Определение объекта пера.

* Создание сложного диалогового блока.

* Добавление управляющих объектов.

* Создание буфера передачи.

* Выполнение диалогового блока.

* Чтение результатов.



Использование интерфейсных объектов


-----------------------------------------------------------------

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

Примечание: Интерфейсные объекты описываются в Главе 9, а управляющие объекты описываются, в частности, в Главе 12.

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

Связь объекта с созданными из ресурса управляющим элементом достаточно проста: внутри конструктора объекта диалогового блока вы строите объекты для любых управляющих элементов, которыми хо- тите манипулировать. Однако вместо использования для построения управляющих объектов конструктора Init применяется InitResource.



Конструктор InitResource


-----------------------------------------------------------------

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

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

Как показано в приведенном выше примере, диалог пера модуля Pen связывает объекты с их управляющими элементами редактирования (для задания размера пера) и обоими наборами кнопок с зависимой фиксацией (для задания цвета и стиля).

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

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



Передача данных


-----------------------------------------------------------------

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

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

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



Изменение атрибутов пера


-----------------------------------------------------------------

+-----------------------+ | Step 1: Basic App | | Step 2: Text | | Step 3: Lines | | Step 4: Menu | | Step 5: About Box | |XStepX6:XPensXXXXXXXXXX| | Step 7: Painting | | Step 8: Streams | | Step 9: Printing | | Step 10: Palette | | Step 11: BWCC | | Step 12: Custom ctrls | +-----------------------+

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

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



Создание буфера передачи


-----------------------------------------------------------------

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

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

type TPenData = record XWidth: array[06] of Char; ColorArray: arra[07] of Word; StyleArray: array[05] of Word; end;

Вы можете также управлять кнопками с независимой фиксацией, используя 14 отдельных полей или один массив из 14 значений типа Word; передаваемые данные будут теми же. Однако, так как ваша прикладная программа будет интерпретировать их как две группы из 8 и 6 кнопок соответственно, удобно задать поле для каждой груп- пы.

Присваивание буфера

Каждый потомок TWindowsObject имеет поле TransferBuffer. Когда вы хотите передать данные в диалоговое окно, нужно задать объект TransferBuffer диалогового блока, указывающий на запись передачи:

PenDlg := New(PPenDialog, Init(Application^.MainWindow, 'PenDlg')); PenDlg^.TransferBuffer := @PenData;

Если ваши программы создают объекты диалогового окна динами- чески, убедитесь, что они каждый раз назначают буфер передачи. TransferBuffer по умолчанию имеет значение nil. Это означает, что данные не переданы.

Заполнение буфера

Перед фактической передачей данных в диалоговое окно, вам нужно установить значение полей в буфере передачи. Перед выводом диалогового окна пера это делает TPen.ChangePen:

procedure TPen.ChangePen; var PenDlg: PPenDialog; TempWidth, ErrorPos: Integer; begin SetColorAttr(PenDate, Color); SetStyle(PenDate, Style); wvsprintf(PenDialog, Init(Application^.MainWindows, 'PenDlg')); PenDlg^.TransferBuffer := @PenData; if Application^.ExecDialog(PenDlg) <> id_Cancel then begin Val(PenData.XWidth, TempWidth, ErrorPos); if ErrorPos = 0 then SetAttributes(SetStyle(PenData), TempWidth, GetColorAttr(PenData)); end; end;

SetColorAttr и SetStyle используют то преимущество, что бу- фер передачи задает кнопки с зависимой фиксацией в виде массива значений Word. SetStyle, например, выглядит следующим образом:

procedure SetStyle(var ARec: TPenData; AStyle: Integer); var i: Integer; begin for i := 0 to 5 do if = AStyle then ARec.StyleArray[i] := bf_Checked else ARec.StyleArray[i] := bf_Unchecked; end;

Примечание: SetColorAttr выполняет то же назначение, что и ColorArray. bf_Checked и bf_Unchecled - это константы ObjectWindows.



Создание объекта пера


-----------------------------------------------------------------

Хотя Windows ссылается на свои изобразительные средства как на "объекты" (отсюда и имена типа SelectObject и DeleteObject), они не являются объектами в истинном объектно-ориентированном смысле, так как не используют наследование и полиморфизм. Перо на самом деле представляет собой просто группу из трех характеристик отображения, на которые Windows ссылается при изображении линии. Эти характеристики являются просто свойствами контекста дисплея, но полезно рассматривать их, как встроенные в перо.

Характеристики пера

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

type PPen = ^TPen; TPen = object(TObject) Width, Style: Integer; Color: Longint; constructor Init(AStyle, AWidth: Integer; AColor: Longint); constructor Load(var S: TStream); procedure ChangePen; procedure Delete; procedure Select(ADC: HDC); procedure SetAttributes(AStyle, AWidth: Integer; AColor: Longint); procedure Store(var S: TStream); private PenHandle, OldPen: HPen; TheDC: HDC; PenData: TPenData; end;

Примечание: Большую часть исходного кода из данной главы вы можете найти в файле PEN.PAS. Для использования модуля Pen в STEP06A.PAS и STEP06B.PAS нужно внести мини- мальные изменения.

Примечание: Тип TPen определен в модуле Pen.

Конструктор Init создает новый объект пера с заданным сти- лем, размером и цветом. SetAttributes изменяет атрибуты уже соз- данного объекта пера. ChangePen выводит диалоговое окно, позволя- ющее пользователю задать атрибуты пера. Load и Store позволяют сохранять объекты пера в потоке.

Выбор и удаление объектов пера

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


Метод Delete отменяет описатель пера, освобождая ресурс для Windows. Select проверяет, имеется ли уже выделенное перо, и пе- ред созданием и выбором нового отменяет существующее перо. Это полезно использовать, если это же перо предполагается применять повторно, так что вам не понадобиться вызывать Delete при каждом использовании пера. С другой стороны, в шаге 7 вы увидите, как можно сохранять нарисованные линии, и каждая линия будет иметь свой собственный объект пера. Если бы каждый объект пера созда- вался и сохранялся в пере Windows, Windows скоро исчерпала бы ре- сурсы. Поэтому важно непосредственно после использования пера вы- зывать для его отмены метод Delete.

Основное достоинство TPen в том, что вам не нужно больше беспокоиться о получении, сохранении и удалении объекта пера. TPen имеет два частных поля, в одном их которых записывается опи- сатель пера. Объект пера отслеживает описатель и взаимодействия с Windows, а ваша программа просто имеет дело с объектом. Другое частное поле, PenData, содержит используемый на этом шаге буфер передачи.

Файл STEP06A.PAS содержит код программы Steps, модифициро- ванный для использования объекта TPen в модуле Pen. В основном изменения невелики (например, поле ThePen изменяет тип с HPen на PPen, а метод SetPenSize заменяется вызовом метода SetPenAttributes объекта пера, поскольку объект пера может управ- лять цветом и стилем наряду с размером).


Создание сложного диалогового блока


-----------------------------------------------------------------

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

В модуле Pen определяется более сложный ресурс диалогового блока с именем 'PenDlg', который дает вам возможность изменения атрибутов только что определенного объекта пера. Этот диалоговый блок показан на Рис. 4.1.

+---------------------------------------------------+ |#=#XXXXXXXXXXSet Pen AttributesXXXXXXXXXXXXXXXXXXXX| +---------------------------------------------------| | +-Color----------+ +-Style-----------+ | | | (*) Black | | (*) Solid | | | | ( ) Purple | | ( ) Dash | | | | ( ) Blue | | ( ) Dot | | | | ( ) Cyan | | ( ) DashDot | | | | ( ) Green | | ( ) DasDotDot | | | | ( ) Yellow | | ( ) Null | | | | ( ) Red | +-----------------| | | | ( ) White | | Width: #1# | | | +----------------+ +-----------------+ | +---------------------------------------------------| | | | +------------+ +------------+ | | |####OK######| |##Cancel####| | | +------------+ +------------+ | | | +---------------------------------------------------+

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

Set Pen Attributes - установка атрибутов пера; Color - цвет; Black - черный; Purple - фиолетовый; Blue - голубой; Cyan - бирю- зовый; Green - зеленый; Yellow - желтый; Red - красный; White - белый; Style - стиль; Solid - непрерывная линия; Dash - пунктир; Dot - точки; DashDot - точки и тире; DasDotDot - тире и две точ- ки; Null - пусто; Width - ширина; OK - подтверждение; Cancel - отмена.

Построение объекта из ресурса 'PenDlg' выполняется также, как это делается для окна About Box (за исключением порождающего окна). Поскольку диалоговый блок атрибута пера выполняется из объекта TPen, а не из оконного объекта, вы не можете в качестве порождающего окна использовать @Self. Вместо этого TPen присоеди- няет диалоговый блок к одному из окон, о присутствии которых из- вестно заранее - основному окну приложения:


procedure TPent.ChangePen; var PenDlg: PPenDialog; begin . . . PenDlg := New(PPenDialog, Init(Application^.MainWindow, 'PenDlg')); . . . end;

Другим важным отличием является то, что на этот раз вы имее- те новый производный объектный тип TPenDialog. Так как окно About box не использует ничего, кроме назначенного по умолчанию поведе- ния диалогового окна, инкапсулированного в TDialog, вам не требу- ется создавать для него новый объектный тип. Однако диалог атри- бутов пера отличается более сложным поведением и требует настрой- ки объекта.

Приведем определение TPenDialog из модуля Pen:

type PPenDialog = ^TPenDialog; TPenDialog = object(TDialog); constructor Init(AParent: PWindowsObject; AName; PChar); end;

constructor TPenDialog.Init(AParent: PWindowsObject; AName: PChar; var AControl: PRadioButton; i: Integer; begin inherited Init(AParent, AName); AControl := New(PRadioButton, InitResource(@Self, 1100 + i)); for i := 0 to 5 do AControl := New(PRadioButton, InitResource(@Self, 1200 + i)); end;

Построенные в TPenDialog управляющие объекты поясняются в следующем разделе.


Управляющие объекты


-----------------------------------------------------------------

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



Вызов диалогового блока пера


-----------------------------------------------------------------

Чтобы вывести диалоговый блок пера, вызовите его метод ChangePen. Программа STEP06B.PAS делает это в ответ на команду cm_Pen, генерируемую выбором пункта меню Options|Pen и щелчком правой кнопкой "мыши".

procedure TStepWindow.CMPen(var Msg: TMessage); begin CurrentPen^.ChangePen; { CurrentPen - это объект блока пера } end;

procedure TStepWindow.WMRButtonDown(var Msg: TMessage); begin if not ButtonDown then CurrentPen^.ChangePen; end;

Примечание: Данные методы можно найти в файле STEP06B.PAS.