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.
Инициализация основного окна
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) принимается объектом ос- новного окна.
Pascal 7 & Objects
Буксировка линии
Мы уже видели, что щелчок левой кнопкой "мыши" дает в ре- зультате сообщение wm_LButtonDown и вызывает метод WMLButtonDown. В шаге 1 ваша программа отвечала на щелчки левой кнопкой "мыши", выводя окна сообщений. Вы могли также видеть, что щелчок правой кнопкой "мыши" давал в результате сообщение wm_RButtonDown и вы- зывал метод WMRButtonDown. На нажатие правой кнопки "мыши" прог- рамма отвечала очисткой окна.Но это предусматривает реакцию только на щелчки кнопкой "мы- ши". Многие программы Windows требуют от пользователя нажатия кнопки "мыши" и перемещения ее указателя по экрану (буксировка). При этом рисуются линии или прямоугольники, либо графическое изображение помещается в точку с конкретными координатами. Для программ графического отображения желателен перехват событий бук- сировки и реакция на них путем изображения линий.
Что такое контекст дисплея?
Контекст дисплея имеет три основных функции отображения:* Он обеспечивает, что текст и графика не выводятся вне по- верхности окна.
* Он управляет выбором и отменой инструментальных средств отображения: перьев, кистей и шрифтов. В шаге 3 показан пример выбора нового пера, но мы начнем сначала с вывода текста.
* Он обеспечивает независимость от устройства. Для вывода в контексте дисплея ваша программа использует стандартные функции API Windows. В шаге 9 мы покажем как можно исполь- зовать одни и те же команды для отображения в окне и на принтере.
Windows управляет контекстом дисплея в своем собственном пространстве памяти, но ваша прикладная программа может отслежи- вать контекст дисплея с помощью описателей. Как и описатель окна, описатель контекста дисплея - это число, идентифицирующее кор- ректный контекст дисплея Windows.
Поскольку, чтобы рисовать в окне при буксировке "мыши", вам необходим контекст дисплея, создайте в объекте основного окна но- вое поле с именем DragDC, которое будет содержать описатель кон- текста дисплея. DragDC имеет тип HDC, который эквивалентен типу Word.
Чтобы использовать контекст дисплея, ваша программа должна:
* получить контекст дисплея;
* нарисовать в нем;
* освободить контекст дисплея.
Использование контекста дисплея
Теперь вы можете использовать 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 "Интерфейс с графическими устройствами".
Pascal 7 & Objects
Добавление диалогового блока
Диалоговый блок аналогичен всплывающему окну, но обычно оно сохраняется на экране в течении короткого периода и выполняет од- ну конкретную задачу, связанную с вводом-выводом, такую как выбор принтера или настройка страницы документа. Здесь мы добавим в программу 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.
------------------------------------------------------------------------
Идентификаторы управляющих элементов
Аналогично элементам в ресурсе меню, каждый управляющий эле- мент в ресурсе диалогового блока имеет идентификатор, который прикладная программа использует для определения того, с каким уп- равляющим элементом нужно взаимодействовать. Для статических эле- ментов, с которыми ваша прикладная программа не взаимодействует (статический текст или битовые массивы) уникальные идентификаторы не требуются, поэтому они обычно имеют идентификатор -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. Однако, чтобы при выборе элементов меню вы- полнялись какие-либо действия, вы должны перехватывать сообщения меню и реагировать на них. Если вы не определили реакцию на ко- манду меню, то можете выбирать элемент меню, но при этом ничего не происходит.
Pascal 7 & Objects
Чтение возвращаемых значений
Считывание значений обратно в буфер передачи - это обратный процесс по отношению к заполнению буфера перед заполнением диало- гового окна. В модуле 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;
Использование интерфейсных объектов
При "обычном" программировании в 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.
Pascal 7 & Objects
Добавление поля объекта
Чтобы сохранить рисунок в виде набора линий, добавьте в TStepWindow поле с именем Drawing. В любой момент Drawing содер- жит текущий рисунок в виде набора объектов линий. Когда требуется отобразить окно, оно использует для изображения линии данные, за- писанные в Drawing.Изменение методов работы с "мышью"
Чтобы сохранять линии в виде объектов, вы должны изменить EMLButtonDown и WMMouseMove, чтобы не только рисовать линии, но также сохранять точки в наборе линий. Поскольку текущую линию придется обновлять не только одному методу, добавьте в TStepWindow еще одно поле типа PLine с именем CurrentLine:type TStepWindow = object(TWindow); CurrentLine: PLine; . . . end;
Кроме добавления изображения линии WMLButttonDown создает при каждом вызове новый объект линии и добавляет его в набор в Drawing. WMMouseMove просто добавляет новую точку в конец объекта текущей линии и изображает в окне линейные сегменты. Сохраняя все точки всех линий, ваше окно будет записывать информацию, необхо- димую для точного воспроизведения картинки.
procedure TStepWindow.WMLButtonDown(var Msg: TMessage); begin if not ButtonDown then begin ButtonDown := True; SetCapture(HWindow); DragDC := GetDC(HWindow); CommonPen^.Select(DragDC); MoveTo(DragDC, Msg.lParamLo, Msg.lParamHi); CurrentLine := New(PLine, Init(CommonPen)); Drawing^.Insert(CurrentLine); end; end.
procedure TStepWindow.WMMouseMove(var Msg: TMessage); begin if ButtonDown then begin LineTo(DragDC, Msg.lParamLo, Msg.lParamHi); CurrentLine^.AddPoint(Msg.LParamLo, Msg.LParamHi); end; end;
Примечание: Уничтожать устаревшие CurrentLine не тре- буется, поскольку они записаны в наборе Drawing. Все объек- ты линий уничтожаются при уничтожении Drawing.
WMLButtonUp модификации не требует. Вам не нужно уничтожать все объекты линий при очистке отображаемого окна, поэтому добавь- те в CMFileNew вызов метода FreeAll:
procedure TStepWindow.CMFileNew(var Msg: TMessage); begin Drawing^.FreeAll; InvalidateRect(HWindow, nil, True); end;
Изображение и рисование
Существует два способа получения в окне графического образа. С рисованием вы уже знакомы по предыдущим примерам. При рисовании вы создаете графический образ в реальном времени в ответ на ввод данных пользователем. Но окно не может требовать от пользователя воссоздания графики при обновлении экрана.Окна должны иметь возможность воссоздавать по запросу свои графические образы. Windows сообщает своим оконным объектам, ког- да они требуют изображения или обновления. При этом окно должно каким-то образом генерировать образ экрана. В ответ на необходи- мость изображения. ObjectWindows автоматически вызывает метод Paint вашего окна. Наследуемый и TWindow метод Paint не выполняет никаких функций. В Paint вы должны поместить код для передачи со- держимого окна. Фактически Paint вызывается при первом выводе ок- на. Paint отвечает за обновление (при необходимости) изображения текущим содержимым.
Существует еще одно важное отличие между отображением графи- ки в методе Paint и другим ее отображением (например, в ответ на действия "мышью"). Содержимое экрана, которое должно использо- ваться для отображения, передается в параметре PaintDC, так что вашей программе не требуется получать или освобождать его. Однако вам потребуется вновь выбрать для PaintDC изобразительные средс- тва.
Чтобы отобразить содержимое окна, вместо повторения тех действий, которые привели к первоначальному изображению (DragDC), вы используете PaintDC. Визуальный эффект будет тот же, что и при первоначальном рисовании пользователем (аналогично проигрыванию аудиозаписи концерта). Но чтобы "проигрывать" ее в методе Paint, сначала вам нужно сохранить графику в виде объектов.
Определение объекта линии
Далее нужно ответить на вопрос, что такое линия. В шаге 4 вы видели, что изображаемая линия представляет собой просто набор точек, передаваемых из Windows в программу через сообщение wm_MouseMove. Для представления линий и точек вам необходимы объ- ектные типы. Поскольку эффективный объект изображения линии дол- жен быть повторно используемой частью, создайте отдельный модуль, определяющий объекты линий и точек.TLine содержит всю информацию, необходимую для изображения данной линии: перо и набор точек.
type PLine = ^TLine; TLine = object(TObject) Points: PCollection; LinePen: PPen; constructor Init(APen: PPen); constructor Load(var S: TStream); destructor Done; virtual; procedure AddPoint(AX, AY: Word); procedure Draw(ADC: HDC); procedure Store(var S: TStream); end;
LinePen просто указывает на объект TPen, а Point - это набор объектов точек. TLine и TLinePoint содержат методы Load и Store, преимущества использования которых для записи картинок на диск вы увидите в шаге 8. В отличие от них объект TLine весьма прост: конструктор и деструктор создают и уничтожают LinePen, AddPoint включает объект точки в Points, а Draw рисует линии между точками Points.
Объект TLinePoint еще проще:
type PLinePoint = ^TLinePoint; TLinePoint = object(TObject) X, Y: Integer; constructor Init(AX, AY: Integer); constructor Load(var S: TStream); procedure Store(var S: TStream); end;
constructor TLinePoint.Init(AX, AY: Integer); begin X := AX; Y := AY; end;
TLinePoint не определяет никакого нового поведения - это просто объект данных, который должен использоваться в TLine. Но позднее (в шаге 8) он понадобиться как объект для записи в поток. Не забудьте построить в TStepWindow.Init Drawing и уничтожить его в TStepWindow.Done:
constructor TStepWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherites Init(AParent, ATitle); ButtonDown := False; HasChanged := False; CommonPen := New(PPen, Init(ps_Solid, 1, 0)); Drawing := New(PCollection, Init(50, 50)); end;
destructor TStepWindow.Done; begin Dispose(CommonPen, Done); Dispose(Drawing, Done); inherited Done; end;
Основное окно программы Steps содержит набор в своем поле Drawing набор линий. Когда пользователь рисует линии, вы должны преобразовывать их в объекты и добавлять в Drawing. Затем, когда потребуется отобразить окно, путем итерации Drawing нужно отобра- зить каждую его точку.
Отслеживание состояния
Требуется отслеживать две характеристики рисунка. Изменение файла мы уже отслеживали (в шаге 1 было добавлено поле HasChanged), но теперь нужно знать, загружен ли файл в данный мо- мент. Как и HasChanged, IsNewFile - это атрибут TStepWindow типа Boolean, поэтому его также следует сделать полем:TStepWindow = object(TWindow) ButtonDown, HasChanged, IsNewFile: Boolean; . . . end.
Поле HasChanged принимает значение True, если текущий рису- нок модифицирован. Модификация означает, что рисунок был изменен с момента последнего сохранения или не сохранялся вовсе. Вы уже устанавливаете поле HasChanged в True, когда пользователь начина- ет рисовать, и в False, когда окно очищается. Когда пользователь открывает новый файл или сохраняет существующий, HasChanged сле- дует установить в False.
IsNewFile указывает, что рисунок не взят из файла, поэтому сохранение рисунка потребует от пользователя задать имя файла. IsNeFile имеет значение True только при первоначальном запуске приложения и после выбора пользователем команды меню File|New (Файл|Новый). Это поле устанавливается в False, когда файл откры- вается или сохраняется. Фактически, FileSave использует IsNewFile, чтобы увидеть, можно ли сохранить файл немедленно, или пользователю требуется выбрать файл из файлового диалога.
Приведем методы сохранения и загрузки файла. На данный мо- мент они выполняют только сохранение и загрузку файлов. Сохране- ние файла сконцентрировано в одном новом методе, который называ- ется WriteFile, а открытие файла выполняет метод ReadFile.
procedure TStepWindow.CMFileNew(var Msg: TMessage); begin if CanClose then begin Drawing^.FreeAll; InvalidateRect(HWindow, nil, True); HasChanged := False; IsNewFile := True; end; end;
procedure TStepWindow.CMFileOpen(var Msg: TMessage); begin if CanClose then if Application^.ExecDialog(New(PFileDialog, Init(@Self, PChar(sd_FileOpen), StrCopy(FileName, '*.PTS')))) = id_Ok then ReadFile; end;
procedure TStepWindow.CMFileSave(var Msg: TMessage); begin if IsNewFile then CMFileSaveAs(Msg) else WriteFile; end;
procedure TStepWindow.CMFileSaceAs(var Msg: TMessage); begin if IsNewFile then StrCopy(FileName, ''); if Application^.ExecDialog(New(PFileDialog, Init(@Self, PChar(sd_FileSave), FileName))) = id_Ok then WriteFile; end;
procedure TStepWindow.ReadFile; begin MessageBox(HWindow, @FileName, 'Загрузить файл:', mb_Ok); HasChanged := False; IsNewFile := False; end;
procedure TStepWindow.WriteFile; begin MessageBox(HWindow, @FileName, 'Сохранить файл:', mb_Ok); HasChanged := False; IsNewFile := False; end;
Примечание: Данный текст программы можно найти в файле STEP08A.PAS.
Построение объекта принтера
Любая программа ObjectWindows может получить доступ к прин- теру с помощью объекта типа TPrinter. В этом случае основное окно вашего приложения должно построить объект принтера и сохранить его в объектном поле с именем Printer:constructor TStepWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); . . . Printer := New(PPrinter, Init); end;
Примечание: Тип TPrinter определен в модуле OPrinter, поэтому не забудьте добавить OPrinter в свой оператор uses.
Это все, что обычно приходится делать для инициализации объ- екта принтера. По умолчанию TPrinter использует назначенный по умолчанию принтер, заданный в файле WIN.INI. TPrinter предусмат- ривает также механизм для выбора альтернативных принтеров.
Вывод на экран графики
+-----------------------+ | Step 1: Basic App | | Step 2: Text | | Step 3: Lines | | Step 4: Menu | | Step 5: About Box | | Step 6: Pens | |XStepX7:XPaintingXXXXXX| | Step 8: Streams | | Step 9: Printing | | Step 10: Palette | | Step 11: BWCC | | Step 12: Custom ctrls | +-----------------------+Возможно, вы удивитесь, узнав, что графика и текст, которые вы рисуете в окне с помощью функций Windows типа TextOut или LineTo, исчезают при изменении размера окна или повторного вывода его на экран. После того, как графические данные переданы Windows, через вызовы функций Windows вы никогда не получаете их обратно для повторного отображения.
Чтобы в окне повторно отображалась графика, вы должны сохра- нить эту графику (или данные для регенерации графики) в структуре некоторого типа, такой как объект. С помощью объектов вы можете полиморфически хранить простую или сложную графику, а также хра- нить объекты и поля ваших оконных объектов.
Сохранение рисунка в файле
+-----------------------+ | Step 1: Basic App | | Step 2: Text | | Step 3: Lines | | Step 4: Menu | | Step 5: About Box | | Step 6: Pens | | Step 7: Painting | |XStep 8:XStreamsXXXXXXX| | Step 9: Printing | | Step 10: Palette | | Step 11: BWCC | | Step 12: Custom ctrls | +-----------------------+Теперь, когда вы сохранили представление данных в виде ри- сунка как часть оконного объекта, можно легко записать эти данные в файл (фактически, в буферизованный поток DOS) и считать его об- ратно.
Данный шаг посвящен добавлению полей объектов для записи состояния сохраняемой информации, модификации сохраняемой в файле информации и методам открытия. Используя предусмотренные в ObjectWindows потоковые объекты, вы убедитесь в удобстве их при- менения для сохранения данных в файле.
Печать графического образа
+-----------------------+ | Step 1: Basic App | | Step 2: Text | | Step 3: Lines | | Step 4: Menu | | Step 5: About Box | | Step 6: Pens | | Step 7: Painting | | Step 8: Streams | |XStepX9:XPrintingXXXXXX| | Step 10: Palette | | Step 11: BWCC | | Step 12: Custom ctrls | +-----------------------+Печать из Windows может представлять собой сложную задачу, но ObjectWindows предоставляет простой механизм добавления средств печати в вашу прикладную программу.
Добавление этих средств предусматривает следующие три шага:
* Построение объекта принтера.
* Создание объекта распечатки.
* Печать объекта распечатки.
Сохранение графики в объектах
Созданные программой Steps изображения на самом деле просто представляют наборы различного числа линий. При каждой буксировке "мыши" вы добавляете другую линию. А каждая линия - это на самом деле определенный набор точек, соединенных линейными сегментами. Чтобы сохранить и воспроизвести такие изображения, вам необходим гибкий и расширяемый тип данныхДля размещения неизвестного числа линий или точек прекрасно подходит определенный в модуле Objects тип TCollection. Это набор объектов, который может динамически расширяться, когда вы добав- ляете другие элементы. Этим наборам не важно, что они включают, поэтому вы можете использовать один и тот же механизм и для ри- сунка (набора линий), и для линии (набора точек).
Примечание: О наборах рассказывается в Главе 19 "Набо- ры".
Концептуально вам нужно просто сделать так, чтобы окно знало о своем содержимом, так что оно сможет обновить изображение. Окно содержит рисунок, представляющий собой набор линий. Таким обра- зом, вам нужно:
* Передать окну объект или поле, содержащее набор линий.
* Определить объект линии, который может отображаться.
* В ответ на сообщения "мыши" добавлять к сохраненным линиям точки.
Сохранение и загрузка файлов
Теперь, когда вы создали основную схему для построения и загрузки файлов, осталось только выполнить фактическую загрузку и сохранение в файле наборов точек. Для этого можно использовать потоковый механизм автоматического сохранения объекта. Сначала вы научитесь сохранять и загружать сами объекты точек и линий (как это сделать для наборов вы уже знаете). Затем методы WriteFile и FileOpen будут модифицированы для использования потоков.Примечание: Подробнее об использовании потоков с объ- ектами рассказывается в Главе 20 "Потоки".
Ниже приведен исходный код, показывающий как сохранять и загружать сами объекты TLine и TLinePoint:
const RLinePoint: TStreamRec = ( ObjType: 200; VmtLink: Ofs(TypeOf(TLinePoint)^); Load: @TLinePoint.Load; Store: @TLinePoint.Store);
RLine: TStreamRec = ( ObjType: 201; VmtLink: Ofs(TypeOf(TLine)^); Load: @TLine.Load; Store: @TLine.Store);
constructor TLinePoint.Load(var S: TStream); begin S.Read(X, SizeOf(X)); S.Read(Y, SizeOf(Y)); end;
procedure TLinePoint.Store(var S: TStream); begin S.Write(X, SizeOf(X)); S.Write(Y, SizeOf(Y)); end;
constructor TLine.Load(var S: TStream); begin Points := PCollection(S.Get); LinePen := PPen(S.Get); end;
procedure TLine.Store(var S: TStream); begin S.Put(Points); S.Put(LinePen); end;
procedure StreamRegistration; begin RegisterType(RCollection); end;
Для регистрации TCollection при запуске прикладной программы вы должны вызывать StreamRegistration (который находится в Steps). Вы можете поместить это вызов в метод TStepWindow.Init. Модуль DrawLine регистрирует в своем коде инициализации TLinePoint и TLine, поэтому линии и точки регистрируются простым включением DrawLine в оператор uses.
Заключительным шагом изменения методов WriteFile и ReadFile будет фактическая запись в потоки и чтение из них (см. STEP08B.PAS):
procedure TStepWindow.ReadFile; var TempColl: PCollection; TheFile: TDosStream; begin TheFile.Init(FileName, stOpen); TempColl: := PCollection(TheFile.Get); TheFile.Done; if TempColl <> nil then begin Dispose(Drawing, Done); Drawing := TempColl; InvalidateRect(HWindow, nil, True); end; HasChanged := False; IsNewFile := False; end;
procedure TStepWindow.WriteFile; var TheFile: TDosStream; begin TheFile.Init(FileName, stCreate); TheFile.Put(Drawng); TheFile.Done; IsNewFile := False; HasChanged := False; end;
------------------------------------------------------------------------
Создание объекта распечатки
ObjectWindows управляет выводимыми на печать данными точно также, как выводом на экран. То есть вместо записи непосредствен- но на устройство вывода (или даже непосредственно в Windows) вы направляете свой вывод на объект, который знает, как представлять информацию на устройстве вывода. Для вывода на печать используйте объекты, полученные из абстрактного типа TPrintout и метода с именем PrintPage.Существует два основных случая, с которыми вы будете иметь дело при генерации из приложения Windows данных для печати: пе- чать документов и печать содержимого окна. Печать содержимого ок- на проще, поскольку окна уже "знают" о своем представлении. Как оказывается на самом деле, это просто особый случай печати доку- мента.
Примечание: О печати документов рассказывается в Главе 15.
Создание распечатки окна
TWindowPrint - это специальный потомок TPrintout, используе- мый для печати содержимого окна. Печать содержимого окна не представляет сложности по двум причинам: вы имеете дело только с одной страницей, и объект окна уже знает, как отображать свой об- раз.Обычно при печати документа вашей прикладной программе нужно выполнять итерации процесса печати для каждой страницы документа. Так как окно имеет только один образ, при печати окна это не яв- ляется необходимым. Таким образом, печать также проста, как ориентация метода Paint объекта окна на вывод вместо окна в контекст устройства, подходящий для принтера:
procedure TWindowPrint.PrintPage(DC: HDC; Page: Word; Size: TPoint; var Rect: TRect; Flags: Word); var PS: TPaintStruct; begin Window^.Paint(DC, PS); end;
Поскольку переданный PrintPage параметр DC уже указывает на подходящий для принтера контекст устройства, в простейшем случае PrintPage должен только сообщить объекту окна на вывод его содер- жимого в этот контекст устройства. Теперь ваш объект распечатки также знает о своем представлении.
Выбор другого принтера
Одним из средств TPrinter является возможность изменения ус- тановки принтера. TPrinter определяет метод Setup, который выво- дит диалоговое окно установки принтера, позволяющее пользователю выбрать принтер из числа установленных в Windows и обеспечивает доступ к диалоговому окну конфигурации устройства.Чтобы вывести диалоговое окно установки принтера, ваша прик- ладная программа вызывает метод Setup объекта принтера. Steps де- лает это в ответ на команду cm_FileSetup (см. STEP09.PAS):
procedure TStepWindow.CMFileSetup(var Msg: TMessage); begin Printer^.Setup(@Self); end;
Диалоговое окно установки принтера является экземпляром типа TPrinterSetupDlg (см. Рис. 5.1).
+---------------------------------------------------------------+ |#=#XXXXXXXXXXXXXXXXXXXXSelect PrinterXXXXXXXXXXXXXXXXXXXXXXXXXX| +---------------------------------------------------------------| | | | Принтер и порт | | +--------------------------------------------+-+ | | |PostScript Printer on LPT1:#################|v| | | +--------------------------------------------+-+ | | +-----------+ +-----------+ +-----------+ | | |####OK#####| |##Setup####| |##Cancel###| | | +-----------+ +-----------+ +-----------+ | | | +---------------------------------------------------------------+
Рис. 5.1 Диалоговое окно установки принтера.
В комбинированном блоке диалогового окна выводятся принтеры, заданные в WIN.INI. Это дает пользователю возможность доступа ко всем установленным принтерам.
Вывод распечатки
Наличие объекта распечатки, которому известно о своем предс- тавлении, это все, что нужно для передачи распечатки на принтер. Программа Steps делает это в ответ на команду cm_FilePrint, гене- рируемую командой Print меню File:procedure TStepWindow.CMFilePrint(var Msg: TMessage); var P: PPrintout; begin if IsNewFile then StrCopy(FileName, 'Untitled'); P := New(PWindowPrint, Init(FileName, @Self)); Printer^.Print(@Self, P); Dispose(P, Done); end;
CMFilePrint очень просто строит объект распечатки, озаглав- ленный заданным именем (имя файла точек или 'Untitled') и запол- няет его своим содержимым (так как это единственное окно в при- ложении).
При наличии объекта распечатки CMFilePrint сообщает объекту принтера, что его нужно напечатать, добавив какие-либо сообщения об ошибках или диалоговые окна (отсюда параметр @Self). Когда пе- чать закончится, CMFilePrint уничтожает объект распечатки.
Вывод сохраненной графики
Теперь, когда TStepWindow сохраняет свою текущую строку, вы должны научить его по команде (этой командой является Paint) ри- совать ее. Давайте напишем для TStepWindow метод Paint, который повторяет действия WMLButtonDown, WMMouseMove и WMLButtonUp. Пу- тем итерации по набору линий Paint воссоздает картинку аналогично тому, как это делаете вы. Метод Paint имеет следующий вид (см. файл STEP07.PAS):procedure TStepWindow.Paint(PaintDC: HDC; var PaintInfo: TPintStruct);
procedure DrawLine(P: PLine); far; begin P^.Draw(PaintDC); end;
begin Drawing^.ForEach(@DrawLine); end;
Примечание: Итерация методов описывается в Главе 19 "Наборы".
Метод Draw объекта линии для изображения каждой линии между точками также использует итератор ForEach:
procedure TLine.Draw(ADC: HDC); var First: Boolean;
procedure DrawLine(P: PLinePoint); far; begin if First then MoveTo(ADC, P^.X, P^.Y) else LineTo(ADC, P^.X, P^.Y); First := False; end;
begin First := True; LinePen^.Select(ADC); Points^.ForEach(@DrawLine); LinePen^.Delete; end; ------------------------------------------------------------------------
Запись в контекст устройства
До настоящего момента вы всегда записывали текст и графику в контекст дисплея (что является способом представления в Windows пользовательской области окон). Контекст дисплея - это просто специализированная версия контекста устройства - механизма, через который приложения Windows работают с различными устройствами, такими как экраны, принтеры или коммуникационные устройства.Запись в контекст одного устройства мало отличается от запи- си в контекст другого, поэтому, например, достаточно просто сооб- щить, например, оконному объекту, что для записи на принтер нужно использовать механизм Paint.
Pascal 7 & Objects
Динамическое изменение размеров палитры
Так как каждое перо, которое вы сохраняете в палитре, имеет один и тот же размер (40 элементов изображения высотой и 128 ши- риной), вам нужно убедиться, что окно палитры может увеличиваться и сжиматься на этот размер каждый раз, когда вы удаляете перо. Объект TPenPalette определяет два метода, которые позволяют это делать: Grow и Shrink.procedure TPenPalette.Grow var WindowRect: TRect; begin GetWindowRect(HWindow, WindowRect); with WindowRect do MoveWindow(HWindow, left, top, right - left, bottom - top + 40, True); end;
procedure TPenPalette.Shrink; var WindowRect: TRect; begin GetWindowRect(HWindow, WindowRect); with WindowRect do MoveWindow(HWindow, left, top, right - left, bottom - top - 40, True); end;
Оба метода находят координаты границ окна, модифицируют их и сообщают окну, что нужно использовать новые координаты границ. Функция API GetWindowRect возвращает структуру TRect, содержащую верхнюю, нижнюю, левую и правую координату. Grow добавляет в ниж- нюю область окна 40 элементов изображения, а Shink вычитает тот же объем.
В следующем разделе вы узнаете, как вызывать методы Grow и Shrink в ответ на нажатие командных кнопок Add Pen и Del Pen.
Добавление и удаление перьев
В последнем разделе вы отвечали на сообщения от Add Pen (До- бавить перо) и Del Pen (Удалить перо) изменением размера окна па- литры. Теперь настало время изменить эту реакцию и фактически до- бавлять или удалять перья из палитры, что в свою очередь указыва- ет окну палитры на необходимость изменения размера. Вместо вызова собственных методов Grow и Shrink методы объекта палитры AddPen и DeletePen должны вызывать соответственно, методы IDAdd и IDDel в TPenPalette.procedure TPenPalette.IDAdd(var Msg: TMessage); begin Pens^.AddPen(CommonPen); end;
procedure TPenPalette.IDDel(var Msg: TMessage); begin Pens^.DeletePen; end;
Метод AddPen воспринимает передаваемое перо, копирует его в набор и отмечает перо, как текущее выбранное. Затем он разрешает кнопку Del Pen в окне палитры пера, запрещает кнопку Add Pen, ес- ли набор полон, и сообщает порождающему окну о необходимости уве- личения размера, чтобы поместить новое перо.
procedure TPenPic.AddPen(APen: PPen); begin CurrentPen := PenSet^.Count; with APen^ do PenSet^.Insert(New(PPen, Init(Style, With, Color))); with PPenPalette(Parent)^ do begin DelBtn^.Enable; if PenSet^.Count >= MaxPens tnen AddBtn^.Disable; Grow; end; end;
Примечание: Чтобы использовать преимущества средств, специфических для TPenPAlette, TPenPic может выполнять при- ведение типа поля Parent. Большинство оконных объектов не связаны так жестко с конкретным порождающим типом, и поэто- му не должны делать никаких предположений относительно типа порождающих их окон.
Метод DeletePen по существу изменяет действия AddPen на об- ратные. При наличии выбранного в палитре пера оно удаляется из набора, а набор уплотняется таким образом, чтобы перья размеща- лись непрерывно. Затем он указывает, что в данный момент выбран- ных перьев нет (поскольку выбранное перо только что удалено) и запрещает командную кнопку Del Pen, так как Del Pen работает с выбранным пером. Далее он разрешает кнопку Add Pen, поскольку удаление пера автоматически освобождает место для по крайней мере еще одного пера. Наконец, он сообщает порождающему окну на необ- ходимость уменьшения его размера (так выводить теперь нужно мень- ше перьев).
procedure TPenPic.DeletePen; begin if CurrentPen > -1 then begin PenSet^.AtFree(CurrentPen); PenSet^.Pack; CurrentPen := -1; with PPenPelette(Parent)^ do begin AddBtn^.Enable; DelBtn^.Disable; Shrink; end; end; end;
Заметим, что AddPen и DeletePen используют преимущества того факта, что окна палитры пера для упрощения связи с методами имеет указатели на свои командные кнопки. Если бы TPenPalette не имел полей AddBtn и DelBtn, то объекту палитры пришлось бы искать их по идентификаторам и посылать им сообщения, либо нужно было бы послать сообщение порождающему окну, которое в свою очередь долж- но каким-то образом связываться с командными кнопками.
Добавление к окну дочернего окна
Режимными дочерними окнами, которые вы до сих пор использо- вали, управлять легко. Вы можете их создать, использовать и отме- нить. Но здесь нам нужно работать с диалоговым окном, которое не является режимным. Например, оно должно закрываться, если закры- вается приложение, и становиться скрытым, если основное окно ми- нимизируется.В основном поведение диалоговых окон (такое как закрытие или когда окно временно становится скрытым) автоматическое. Единс- твенный случай, когда вам нужно делать что-то особенное - это когда вы хотите сделать дочернее окно независимым от его порожда- ющего окна. Например, окно палитры, которое вы собираетесь выво- дить, сможет скрываться и выводиться, пока основное окно остается видимым. Окна, которые могут перемещаться или выводиться незави- симо от своих порождающих окон, называются независимыми дочерними окнами. На следующем шаге вы будете создавать зависимые окна, присоединенные к независимому окну палитры.
Так как основное окно должно посылать команды окну палитры, потребуется указатель на это окно, поэтому добавьте в TStepWindow его палитры пера. TStepWindow содержит теперь следующие поля:
TStepWindow = object(TWindow) DragDC: DHC; ButtonDown: Boolean; FileName: array[0fsPathName] of Char; HasChanged, IsNewFile: Boolean; Drawing: PCollection; Printer: PPrinter; PenPalette: PPenPalette; { окно палитры } . . . end;
Осталось только построить объект дочернего окна и присвоить его PenPalette. Этому посвящен следующий раздел.
Добавление к палитре командных кнопок
Хотя они ведут себя идентично, между управляющими кнопками диалоговых блоков (таких как файловое диалоговое окно) и управля- ющими элементами окон (таких как окно палитры) имеется существен- ное различие. Управляющие элементы диалогового блока вы можете задать в ресурсе диалогового блока. Они не являются объектами, и диалоговый блок, которому они принадлежат, полностью отвечает за управление этими элементами. В Главе 11 показано, как создать из диалоговых ресурсов свои собственные диалоговые блоки и работать с их управляющими элементами.Управляющие элементы окон задаются определением объекта. По- рождающее окно управляет их поведением через методы, определенные объектами управляющих элементов ObjectWindows. Например, чтобы получить следующий элемент, который пользователь выбрал в блоке списка, вызовите метод GetSelString объекта блока. Аналогично оконному объекту или объекту диалогового блока, объект управляю- щего элемента имеет соответствующий визуальный элемент.
Объект управляющего элемента и его управляющий элемент свя- заны через поле идентификатора объекта управляющего элемента. Каждый управляющий элемент имеет уникальный идентификатор, кото- рый используется его порождающим окном для идентификации управля- ющего элемента при маршрутизации управляющих событий (таких как щелчок на элементе "мышью"). Для ясности для каждого идентифика- тора управляющего элемента следует определить следующие констан- ты:
const id_Add = 101; id_Del = 102; MaxPens = 9;
MaxPens задает максимальное число перьев, которые будет со- держать палитра. Значение 9 хорошо подходит для стандартного эк- рана VGA.
Добавление "кнопок" палитры
Теперь, когда у вас есть окно палитры, вам необходим просто способ вывода на экран и выбора перьев в палитре. Для этого вы можете использовать относительно простой потомок TWindow и набор объектов пера.В данном разделе вы сделаете следующее:
* определите объект палитры пера;
* выберете перья по щелчку кнопкой "мыши".
Имена методов реакции на сообщения управляющих элементов
Как и в случае методов реакции на сообщения, имена которым присваиваются по сообщениям, методы, основанные на дочерних иден- тификаторах, также должны именоваться по идентификаторам сообще- ний. Так как две командные кнопки, на которые вы хотите реагиро- вать, имеют идентификаторы id_Add и id_Del, TPenPalette нужны ме- тоды с именами IDAdd и IDDel.TPenPalette = object(TWindow) AddBtn, DelBtn: PBitButton; constructor Init(AParent: PWindowsObject; ATitle: PChar); procedure Grow; procedure SetupWindow; virtual; procedure Shrink; procedure IDAdd(var Msg: TMessage); virtual id_First + id_Add; procedure IDDel(var Msg: TMessage); virtual id_First + id_Del; end;
Теперь для выполнения соответствующих действий в ответ на командные кнопки осталось только определить методы IDAdd и IDDel. Пока что IDAdd должен просто вызывать увеличение окна, а IDDel - его сжатие
procedure TPenPalette.IDAdd(var Msg: TMessage); begin Grow; end;
procedure TPenPalette.IDDel(var Msg: TMessage); begin Shrink; end;
Примечание: Это дополняет содержимое файла STEP12A.PAS.
Многодокументальный интерфейс
TStepWindow может очень легко работать в качестве дочернего окна MDI. Фактически с небольшими изменениями программа Graffiti использует в качестве своих дочерних окон те же окна, что Steps использует для основного окна. Так как владельцем окна палитры пера является объект TStepWindow, каждый отдельный рисунок имеет свой собственный отличный набор перьев. О многодокументальном ин- терфейсе рассказывается в Главе 14 "Объекты MDI").Назначение порождающего окна
Порождающие окна автоматически управляют своими дочерними окнами, поэтому при создании дочернего окна ему передается указа- тель на объект порождающего окна. Поскольку порождающее окно обычно строит свои дочерние окна, указателем порождающего окна обычно является @Self.Важным исключением является основное окно приложения. Так как оно не имеет порождающего окна, конструктору основного окна передается nil.
Каждый оконный объект имеет список своих дочерних окон, обеспечивающий создание, вывод, сокрытие и закрытие окон в нужные моменты. Построение и уничтожение дочерних окон автоматически об- новляет список дочерних окон: построение дочернего окна добавляет его в список своего порождающего окна; уничтожение окна удаляет его из списка.
Нумерация ресурсов графических изображений
Единственная сложная часть в определении графических изобра- жений для командных кнопок - это присваивание идентификаторов ре- сурсов. Управляющие элементы BWCC знают о том, какое графическое изображение использовать, основываясь на идентификаторе конкрет- ного управляющего элемента. Для командных кнопок в системах с VGA для ресурсов используется 1000 + идентификатор для "верхнего" об- раза, 3000 + идентификатор для "нижнего" образа и 5000 + иденти- фикатор для образа в фокусе.Примечание: В системах с EGA используются, соответс- твенно, ресурсы 2000 + идентификатор, 4000 + идентификатор и 6000 + идентификатор.
Так как командная кнопка Add Pen имеет идентификатор 101 (id_Add), разрешение использования BWCC принимает вид ресурсов 1101, 3101 и 5101. В программе STEP11B.PAS, для доступа к специа- лизированными графическим изображениям, для командных кнопок Add Pen и Del Pen, используется директива:
{$R PENTAL.RES}
------------------------------------------------------------------------
Объекты управляющих элементов как поля
Как и в случае других дочерних окон, часто удобно хранить указатель объекта управляющего элемента в виде поля. Это необхо- димо только для дочерних окон, с которыми вы позднее сможете ра- ботать непосредственно, вызывая методы их объектов. TPenPalette записывает каждый из этих объектов управляющих элементов в от- дельном поле. Приведем часть описания объекта TPenPalette:TPenPalette = object(TWindow) AddBtn, DelBtn: PButton; . . . end;
После создания экземпляра этих дочерних объектов управляющих элементов вы можете манипулировать ими с помощью вызовов методов. Например, в соответствующие моменты можно разрешать или запрещать командные кнопки, вызывая их методы Enable и Disable. Используя метод ChildList порождающего окна, можно получить доступ к объек- там управляющих элементов дочерних окон, не записанных в полях, но гораздо удобнее делать это с помощью полей.
Определение объекта палитры
Так как окно палитры пера может изменять свой размер, палит- ра в окне может фактически оставаться фиксированной. Чтобы пока- зать только часть палитры, в которой отображаются перья, вы може- те использовать возможности отсечения Windows.Самой палитре необходимы только несколько полей данных: на- бор перьев, указание того, какое перо в данный момент выбрано, и описатели представляющих перья графических образов. Описатели графических изображений представляют собой частные поля не пото- му, что они должны быть секретными, а для предотвращения их неп- реднамеренного изменения другим кодом.
Приведем описание объекта палитры:
TPenPic = object(TWindow) PenSet: PCollection; CurrentPen: Integer; constructor Init(AParent: PWindowsObject); destructor Done: virtual; procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual; procedure AddPen(APen: PPen); procedure DeletePen; procedure SetupWindow; virtual; procedure WMLButtonDown(var Msg: TMessage); virtual wm_First + wm_LButtonDown; private UpPic, DownPic: HBitMap; end;
Объекту TPenPic не требуется очень много методов. Он имеет простой конструктор для создания набора перьев и деструктор для их уничтожения. Метод SetupWindow просто перемещает палитру внут- ри ее порождающего окна. AddPen и DeletePen включают перо в набор и удаляют перо из набора, а WMLButtonDown интерпретирует щелчки "мышью" для выбора перьев из палитры. Наконец, Paint рисует "кнопки", представляющие перья в наборе.
Отметим также, что TPenPic является потомком TWindow, а не TControl. Хотя поведение вашего нового объекта во многом напоми- нает поведение управляющего элемента окна, он должен быть произ- водным от TWindow, так как TControl работает только со стандарт- ными управляющими элементами, такими как "нажимаемые" командные кнопки и полосы прокрутки. При создании собственных управляющих элементов нужно начинать с TWindow.
Отмена
Поскольку рисунок состоит из наборов линий, для стирания последней нарисованной линии легко использовать методы наборов. Graffiti связывает элемент меню Edit|Undo (Редактирование|Отмена) с набором линий метода AtDelete, удаляя последнюю линию в наборе, которая является также последней нарисованной линией. Повторяя удаление последней линии в наборе, вы можно эффективно отменять все изображение.Хотя программа Graffiti этого не делает, вы можете добавить также функцию Redo (Возобновление), сохраняя удаленные линии в другом наборе, и перемещая их по одной в набор отображаемых ли- ний, либо даже в другой рисунок.
Отображение содержимого палитры
За все, что мы насоздавали в объекте палитры, приходится расплачиваться, когда приходит момент отображать ее на экране. Поскольку перья хранятся в наборе, для отображения каждого пера вы легко можете выполнять итерацию. В самом деле, метод Paint состоит только из инициализации локальной переменной-счетчика, а затем выполняет итерацию по набору с помощью ForEach:procedure TPenPic.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var PenCount: Integer;
procedure ShowPen(P: PPen); far; var MemDC: HDC; TBitmap: HBitmap; begin MemDC := CreateCompatibleDC(PaintDC); Inc(PenCount); if PenCount = CurrentPen then TheBitmap = DownPic; else TheBitmap := UpPic; SelectObject(MemDC, TheBitmap); BitBlt(PaintDC, 0, PenCount * 40, 128, 40, MemDC, 0, 0, SrcCopy); P^.Select(PaintDC); MoveTo(PaintDC, 15, PenCount * 40 + 20); LineTo(PaintDC, 115, PenCount * 40 + 20); P^.Delete; DeleteDC(MemDC); end;
begin PenCount := -1; PenSet^.ForEach(@ShowPen); end;
Наиболее интересная часть содержится не в Paint, а во вло- женной процедуре ShowPen, которая вызывается для каждого пера в палитре. На самом деле ShowPen состоит из двух различных частей. Первая рисует графическое изображение фона, а вторая (которая уже должна быть вам достаточно знакома) использует объект пера для изображения по этому фону образца линии.
Изображение графических образов предусматривает три шага: создание контекста устройства памяти, выбор в контексте устройс- тва графического образа и копирование образа в контекст экрана.
Как вы видели в шаге 8, для различных видов устройств су- ществует различные контексты устройства. Для работы с графически- ми образами (битовыми массивами) Windows позволяет создавать кон- текст устройства памяти. Фактически, вы можете только выбирать битовые массивы в контексте устройства памяти, хотя они могут ко- пироваться в другие контексты устройства.
Метод CreateMemoryDC создает пустой контекст устройства па- мяти, совместимый с текущим PaintDC. Затем, в зависимости от то- го, является ли данное конкретное перо выбранным, ShowPen выбира- ет в контексте устройства графические образы UpPic или DownPic. Заметим, что в контексте устройства памяти графический образ интерпретируется аналогично любому другому изобразительному средству. Наконец, функция BitBlt копирует заданную часть кон- текста устройства памяти в PaintDC. Заметим, что контекст уст- ройства памяти требуется уничтожать.
После того как фон будет на месте, ShowPen использует ShowTo и LineTo аналогично тому, как это делается при рисовании непос- редственно в окне.
Построение окна палитры
Объекты дочернего окна строятся обычно в конструкторах своих порождающих окон. Аналогично тому, как это происходит при инициа- лизации любого другого поля, вы присваиваете значения любому ука- зателю дочернего окна. В данном случае:constructor TStepWindows.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); . . . PenPalette := New(PPenPalette, Init(@Self, 'Pan Palette'); end;
Поведение палитры
Вы можете также отметить, что при щелчке "мышью" между па- литрой и основным окном, окно, где вы нажимаете кнопку, становит- ся активным (получает активную рамку), а другие окна становятся неактивными. Если вы часто перемещаетесь между двумя окнами (что может иметь место при работе с палитрой), это может показаться весьма раздражающим. Чтобы предотвратить это явление, вам нужно перехватывать передачу в окна сообщений sm_NCActivate, и когда параметр WParam сообщений равен 0 (попытка деактивизации рамки), вы можете изменить его на 1 (активизация рамки):procedure TPenPalette.WVNCActivate(var Msg: TMessage); begin if Msg.WParam = 0 then Msg.WParam := 1; DefWndProc(Msg); end;
Вызов DefWndProc обеспечивает, что сообщение обрабатывается как обычно, но теперь рамка палитры деактивизироваться не будет. Аналогичный перехват вы можете добавить в TStepWindow.
Прокрутка
Наконец, так как каждое дочернее окно MDI обычно невелико по размеру, Graffiti добавляет возможность автоматической прокрутки изображения при рисовании. В программе Steps, когда перо выходит за границы окна, линия продолжает рисоваться, хотя видеть ее вы больше не можете. Graffiti прокручивает изображение, так что вы будете видеть другие части рисунка.Работа с управляющими элементами
Любой тип окна, который имеет объекты управляющих элементов (или другое дочернее окно) должен определять для построения своих объектов управляющих элементов конструктор Init. Кроме того, для задания управляющих элементов перед выводом вы можете переопреде- лить SetupWindow. Порождающее окно (TPenPalette) автоматически создает и выводит все свои дочерние окна.Приведем в качестве примера метод Init палитры пера. Первое, что он делает - это установка собственного расположения и атрибу- тов размера. Так как метод Init окна отвечает за задание его ат- рибутов создания, и поскольку вместе с ним создаются управляющие элементы окна, вы должны также в методе Init окна построить уп- равляющие элементы. В каждом вызове конструктора первый параметр - это @Self (порождающее окно). За ним следует идентификатор уп- равляющего элемента.
constructor TPenPalette.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); with Attr do begin Style := Style or ws_Tiled or ws_SysMenu or ws_Visible; W := 133; H := GetSystemMetrics(sm_CYCaction) + 42; AddBtn := New(PButton, Init(@Self, id_Add, 'Добавить перо', 0, 0, 65, 40, True); DelBtn := New(PButton, Init(@Self, id_Del, 'Удалить перо', 0, 0, 65, 40, False); end;
После создания окна, чтобы задать управляющие элементы окна, вызывается виртуальный метод TPenPalette.SetupWindow. Поскольку здесь вы имеете дело только с командными кнопками, инициализация не требуется, но TPenPalette.SetupWindow первоначально запрещает одну из командных кнопок. Если бы вы использовали другой управля- ющий элемент (например, блок списка), то для инициализации объек- та управляющего элемента потребовалось бы вызывать SetupWindow.
Примечание: Когда вы переопределяете метод SetupWindow окна, не забудьте сначала вызывать наследуемый метод SetupWindow, так как он создает все дочерние управляющие элементы.
Вызов методов Init и SetupWindow вполне достаточен для пра- вильного вывода в окне палитры всех управляющих элементов. Коман- дные кнопки можно будет активизировать ("нажимать"), но без ка- ких-либо действий. В шаге 12 мы определим реакцию на события уп- равляющего элемента.
Размещение в порождающем окне
Как вы могли заметить, TPenPic не задает свою позицию в конструкторе так, как это делают большинство управляющих элемен- тов. Причина здесь в том, что вы не можете полагаться на коорди- наты окна, пока оно не будет реально существовать. TPenPalette обходит эту проблему, создавая свои объекты кнопок путем вызова для определения высоты заголовка окна функции API GetSystemMetrics. Хотя это будет работать, существует и другой подход.Вместо позиционирования палитры в конкретное место порождаю- щего окна вы можете поместить его в определенную позицию в порож- дающей области клиента. Таким образом, если вы добавляете меню или изменяете рамку, либо выполняете вне окна какое-либо другое изменение, ваш объект палитры все равно будет правильно позицио- нирован.
Изменение позиции окна выполняется в SetupWindow, так как окну палитры требуется использовать описатель своего порождающего окна, который до вызова собственного метода SetupWindow недосту- пен. При достижении SetupWindow дочернее окно может рассчитывать на допустимость описателя порождающего окна.
procedure TPenPic.SetupWindow; var ClientRect: TRect; begin inherited SetupWindow; GetClientRect(Parent^.HWindow, ClientRect); with ClientRect do MoveWindow(HWindow, 1, bottom - top + 1, 128, 40 * MaxPens, False; end;
Для возврата координат области клиента окна палитры метод TPicPen использует функцию API Windows GwetClientRect. Затем он перепозиционируется с помощью MoveWindow непосредственно под объ- екты кнопок, задавая высоту, достаточную для размещения всех перьев в наборе. Заметим, что последний параметр MoveWindow - это значение типа Boolean, указывающее, следует ли выполнять повтор- ное отображение окна после перемещения. Так как палитра на экран пока не выводилась, то заново отображать ее не имеет смысла, поэ- тому TPenPic.SetupWindow передает значение False.
Разрешение специализированных управляющих элементов
Как вы уже знаете, объекты управляющих элементов создают стандартные управляющие элементы Windows. Например, только что созданный вами объект TButton дает в результате в палитре окна стандартную серую кнопку. Однако IDE и диалоговые блоки, создан- ные вами из ресурсов, используют кнопки другого типа с располо- женными на них графическими изображениями. ObjectWindows предос- тавляет вам простой способ использования в программах командных кнопок такого вида.Использовать специализированные управляющие элементы Borland для Windows (BWCC) также просто, как использование модуля. Для этого нужно просто добавить BWCC в оператор uses основной прог- раммы. Это немедленно дает два эффекта. Первый состоит в том, что все стандартные диалоговые блоки (такое как файловое диалоговое окно, которое вы уже добавили в программу Steps) используют для таких общих элементов как кнопки OK или Cancel, а также кнопки с зависимой и независимой фиксацией, вместо стандартных управляющих элементов специализированные.
Примечание: Об использовании и проектировании специа- лизированных управляющих элементов Borland рассказывается в Главе 12.
Фактически, после добавления в оператор uses программы Steps BWCC вы можете перекомпилировать программу и получить доступ к диалоговым блокам. Без каких-либо других усилий вы существенно улучшите внешний вид программы и ее интерфейса.
Но как насчет кнопок в палитре пера? Они были созданы из уп- равляющих объектов с BWCC, используемых в программе, но выглядят как обычные командные кнопки. Ответ, конечно, состоит в том, что вы еще не определили для кнопок, графические изображения, так что по умолчанию они просто используют метки, переданные в конструк- торе Init. В следующем разделе вы увидите, как добавлять к специ- ализированным управляющим элементам графические изображения.
Реакция на события управляющих элементов
Основное различие между окном палитры и режимными диалоговы- ми окнами, которые вы использовали ранее, состоит в том, что ре- жимное диалоговое окно манипулирует управляющими элементами, а затем считываете результаты, если пользователь щелкает "мышью" на командной кнопке OK. В данном безрежимном окне палитры вы имеете дело с активной, динамической частью программы и можете активно отвечать на каждый используемый управляющий элемент.Пока командные кнопки выводятся в окне палитры, но щелчок кнопкой "мыши" не дает никакого эффекта. Щелчок и выбор "мышью" являются событиями управляющего элемента. Они аналогичны событиям меню, на которые вы отвечали в шаге 4.
Вы отвечали на события меню, определяя методы реакции на ко- манды. Что-то аналогичное нужно делать с сообщениями управляющих элементов. События управляющих элементов создают сообщения (на основе дочернего идентификатора), аналогичные командным сообщени- ям, но вместо идентификатора меню содержащие идентификатор управ- ляющего элемента. Для идентификации заголовка метода на основе дочернего идентификатора используйте сумму идентификаторов уп- равляющего элемента и констант id_First.
Примечание: Подробнее о командных сообщениях и уведом- ляющих сообщениях управляющих элементов рассказывается в Главе 16 "Сообщения окон".
Сглаживание линий
Для сложных рисунков со многими длинными линиями повторное отображение рисунка может потребовать много времени. Graffiti ис- пользует одно из преимуществ графический функций GDI - функцию PolyLine, которая рисует сегменты каждой линии сразу, а не по од- ному. Эффектом будет более быстрое и плавное отображение окна.Добавление всплывающего окна
+-----------------------+ | Step 1: Basic App | | 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 | |XStepX10:XPaletteXXXXXX| | Step 11: BWCC | | Step 12: Custom ctrls | +-----------------------+Создание и уничтожение окон и диалоговых блоков прекрасно подходит, когда они используются не часто. Но в некоторых случаях желательно иметь дочернее окно, доступное большую часть времени. Примером такого окна является инструментальная палитра.
В этом шаге вы будете делать следующее:
* Добавите к основному окну поле.
* Построите плавающую палитру пера.
* Выведете и скроете палитру пера.
Палитра пера, которая выводится при выборе пользователем ко- манды Palette|Show (Палитра|Вывод) показана на Рис. 6.1.
+---------------------------------------+ |#=#XXXXXXXXXXXXXPenPaletteXXXXXXXXXXXXX| +------------------+--------------------| | @@ Add | Delete | | @@@@@@ Pen | @@@@@@ Pen | | @@ | | +------------------+--------------------| | | | ----------------------------- | | | +---------------------------------------| | | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | +---------------------------------------| | | | XXXXXXXXXXXXXXXXXXXXXXXXXXXXX | | | +---------------------------------------+
Рис. 6.1 Палитра пера программы Steps с тремя перьями.
Поскольку это первое "новое" окно, которое вы создаете и ко- торое будет создаваться автоматически, неплохо рассмотреть, как создаются и выводятся на экран объекты и элементы окна.
добавление специализированных управляющих элементов
+-----------------------+ | Step 1: Basic App | | 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 | |XStepX11:XBWCCXXXXXXXXX| | Step 12: Custom ctrls | +-----------------------+В шаге 10 вы добавили к основному окну независимое дочернее окно. Теперь вы добавите зависимые дочерние окна, которые называ- ются управляющими элементами. Порождающим окном этих управляющих элементов является окно палитры пера. Нужно помнить о том, что окно палитры пера является независимым дочерним окном, для кото- рого порождающим окном является основное окно. Таким образом, па- литра пера является одновременно дочерним окном основного окна и порождающим окном для управляющих элементов.
В шаге 6 вы уже имели дело с управляющими элементами и объ- ектами управляющих элементов, но тогда вы просто связывали объек- ты с управляющими элементами, определенными в ресурсе. Построение управляющего элемента на этапе выполнения несколько сложнее, так как наряду с типом как вам требуется задать позицию и размер уп- равляющих элементов.
Палитра пера, показанная на Рис. 6.1, использует две специа- лизированные кнопки кисти и последовательность графических изоб- ражений, каждое из которых представляет перо, которое можно ис- пользовать для рисования. Эти перьевые "кнопки" фактически не яв- ляются управляющими элементами, а скорее представляют собой обра- зы в единственным дочернем окне, которое может их идентифициро- вать при щелчке "мышью".
В данном шаге вы добавите графические кнопки с помощью:
* добавления простых управляющих кнопок;
* реализации в программе специализированных управляющих эле- ментов;
* определения графических изображений для кнопок.
Для всех управляющих элементов, в целом, поведение задается типом ObjectWindows TControl и его типом-потомком, позволяющим работать с каждым типом управляющего элемента. Например, TListBox определяет объекты блока списка, а TEdit определяет каждый управ- ляющий объект редактирования. Вы должны также понимать, что TControl - это потомок TWindow.
Создание специализированного управляющего элемента окна
+-----------------------+ | Step 1: Basic App | | 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 | |XStepX12:XCustomXctrlsX| +-----------------------+Наиболее интересная часть создания палитры пера - это созда- ние вашего собственного специализированного окна палитры. Здесь вы можете, наконец, использовать возможности, предусмотренные стандартными инструментальными средствами окон, и что-то создать.
На этом шаге вы сделаете следующее:
* реализуете динамическое изменение размера окна палитры;
* зададите реакцию на уведомляющие сообщения от управляющих элементов;
* создадите объект палитры с несколькими областями.
Сокрытие вместо закрытия
Если вы дважды щелкните "мышью" в блоке системного меню па- литры пера, оно исчезнет. Выбор команды Palette|Show не может больше выводить палитру, так как объект и его экранные элементы уничтожены. Выводить нечего. Вы можете переопределить это, доба- вив метод CanClose, который скрывает окно, а затем запрещает его закрытие (см. STEP11A.PAS):function TPenPalette.CanClose: Boolean; begin Show(sw_Hide); CanClose := False; end;
Теперь двойной щелчок "мышью" в блоке системного меню скры- вает окно, но не закрывает его, так что позднее вы можете вывести его снова.
Обычно наличие дочернего окна, которое всегда возвращает из CanClose False, может предотвратить закрытие всего приложения. Но TStepWindow перед закрытием не проверяет своих дочерних окон, так как в шаге 1 вы переопределили его метод CanClose.
Создание для командных кнопок графических изображений
Хотя вы можете создавать графические изображения (битовые массивы) для командных кнопок на этапе выполнения, эту задачу следует решать с помощью ресурсов. Используя пакет разработчика ресурсов, вам нужно создать для каждой командной кнопки три раз- личных битовых массива: один для позиционирования сверху, один для позиционирования снизу и один для позиционирования в фокусе ввода.С этими графическими изображениями вы можете делать что угодно. Здесь ограничений нет. Вы можете изменять цвет образа в зависимости от состояния кнопки или перемещать образы (что обычно используется) при нажатии, а также добавлять вокруг текста кнопки линию из точек, когда она находится в фокусе (активна). На Рис. 6.2 показано три графических изображения для командной кнопки Add Pen палитры пера.
+------------------+ +------------------+ +------------------+ | |# | . | | . |# | @@ Add |# | @@ : Add : | | @@ : Add : |# | @@@@@@ Pen |# | @@@@@@ : Pen : | | @@@@@@ : Pen : |# | @@ |# | @@ . | | @@ . |# | |# | | | |# +------------------+# +------------------+ +------------------+# ################### ###################
Рис. 6.2 Графические изображения для специализированной кнопки.
Создание и уничтожение палитры
Построение и уничтожение объекта палитры выполняется доста- точно просто. Конструктор Init вызывает TWindow.Init, затем изме- няет стиль окна (чтобы оно стало видимым дочерним окном). PenSet инициализируется как набор фиксированного размера, достаточно большой, чтобы содержать максимальное число заданных константой MaxPens перьев, и не возрастающий. Для текущего выбранного пера CurrentPen устанавливается в -1. Это означает, что выбранного пе- ра нет.Наконец, Init загружает в UpPic и DownPic два графических образа. Они используются в качестве фона для каждого пера палит- ры. DownPic рисуется за выбранным пером, а UpPic - в качестве фо- на других перьев.
################### #+------------------+ +------------------+ #| | | |# #| | | |# #| | | |# +------------------+ +------------------+# ###################
Рис. 6.3 Фоновые образы палитры пера.
Деструктор Done перед вызовом наследуемого деструктора Done отменяет графические образы. Вызов DeleteObject для уничтожения графических образов имеет важное значение. Подобно контексту дисплея, графические образы являются ресурсами Windows, поддержи- ваемыми в ограниченной памяти Windows. Если размещаете их в памя- ти Windows и не удаляете, то ваша программа (и другие работающие параллельно с ней программы) потеряют доступ к этой памяти.
Init и Done объекта палитры выглядят следующим образом:
constructor TPenPic.Init(AParent: PWindowsObject); begin inherited Init(AParent, nil); AttrStyle := ws_Child or ws_Visible; PenSet := New(PCollection, Init(MaxPens, 0)); CurrentPen := -1; UpPic := LoadBitMap(HInstance, 'PAL_UP'); DownPic := LoadBitmap(HInstance, 'PAL_DOWN'); end;
destructor TPenPic.Done; begin DeleteObject(UpPic); DeleteObject(DownPic); Dispose(PenSet, Down); inherites Done; end;
Создание элементов экрана
При построении объекта дочернего окна, ObjectWindows берет на себя функции по работе с соответствующими объекту элементами экрана. Это обратно тому, что вы делали в шаге 6 с помощью InitResource. Тогда вы имели созданный из ресурса элемент экрана и связывали с ним объект, благодаря чему могли манипулировать элементом экрана. Теперь вы создали собственный объект, и вам нужно сообщить Windows о необходимости создания соответствующего экранного элемента.Когда вы в шаге 3 делали это для диалогового окна, то вызы- вали ExecDialog. Метод TApplication создает элемент экрана и вы- полняет режимное диалоговое окно. Соответствующим методом для не- режимных (или безрежимных) диалоговых окон является TApplication.MakeWindow. Основным отличием является то, что MakeWindow не выводит автоматически создаваемый элемент экрана и не переходит в режимное состояние.
Примечание: MakeWindow и создание элементов экрана подробно описываются в Главе 9 "Интерфейсные объекты".
Тогда процесс построения и вывода окна состоит из следующих трех этапов:
* Построение оконного объекта с помощью Init.
* Создание элемента экрана с помощью MakeWindow.
* Вывод окна с помощью Show.
К счастью, второй и третий шаги для основного окна приложе- ния выполняются автоматически. Кроме того, вызов для порождающего окна MakeWindow автоматически вызывает для любого окна в его списке дочерних окон MakeWindow, так что дочерние окна основного окна (такие как палитра пера) автоматически получают элементы эк- рана.
В следующем разделе мы выведем дочернее окно.
Выбор перьев с помощью "мыши"
Наконец. TPenPic обеспечивает реакцию на щелчок в нарисован- ной области кнопкой "мыши". Заметим, что хотя размер объекта па- литры никогда не изменяется, он получает сообщение от щелчков "мышью" в области, фактически показываемой на экране. Палитра от- секается рамкой окна палитры. Это означает, что вы можете щелкать кнопкой "мыши" только непосредственно в палитре.Поскольку каждый элемент в палитре имеет один и тот же раз- мер, WMLButton для определения того, в каком графическом образе была нажата кнопка "мыши", может просто разделить y-координату щелчка "мыши" (которая поступает в LParamHi) на 40 (размер каждо- го графического изображения). Затем он делает перо, на котором была нажата кнопка "мыши" текущим и задает в качестве пера для рисования копию выбранного пера палитры. Поскольку теперь есть выбранное перо, он разрешает кнопку Del Pen в окне палитры, затем запрещает палитру для обеспечения ее повторного отображения для того, чтобы показать новый выбор.
Код для WMLButtonDown имеет следующий вид (это дополняет текст STEP12B.PAS):
procedure TPenPic.WMLButtonDwon(var Msg: TMessage); begin CurrentPen := Msg.LParamHi div 40; if CurrentPen <> nil then Dispose(CurrentPen, Done); with PPen(PenSet^.At(CurrentPen))^ do CurrentPen := New(PPen, Init(Style, With, Color)); PPenPalette(Parent)^.DelBlt^.Enable; InvalidateRect(HWindow, nil, False); end;
Вывод и сокрытие палитры
Дочерние окна, отличные от тех, которые были созданы и выве- дены по умолчанию их порождающими окнами (этот процесс управляет- ся с помощью EnableAutoCreate и DisableAutoCreate) в каждом ин- терфейсном объекте. Но вы можете скрыть или вывести дочернее окно по команде. Обе функции метода Show наследуются из TWindowsObject.В зависимости от передаваемых параметров метод Show выводит либо скрывает окно. Параметр - это одна из констант sw_, опреде- ленная в Windows. В ответ на команды меню Palette|Show (Палит- ра|Вывод) или Palette|Hide (Палитра|Сокрытие), которые генериру- ют, соответственно, команды cm_PalShow и cm_PalHide, TStepWindow вызывает метод Show палитры пера (это дополняет STEP10.PAS):
procedure TStepWindow.CMPalShow(var Msg: TMessage); begin PenPalette^.Show(sw_ShowNA); end;
procedure TStepWindow.CMPalHide(var Msg: TMessage); begin PenPalette^.Show(sw_Hide); end;
Если у вас есть поле порождающего окна, указывающее на до- чернее окно, с которым необходимо работать, вы легко можете за- дать дочернее окно.
------------------------------------------------------------------------
Pascal 7 & Objects
Доступ к функциям API
Чтобы из приложения ObjectWindows обратиться непосредствен- но к функциям API, вы должны использовать модуль WinProcs. WinProcs определяет для каждой функции Windows заголовок процеду- ры или функции Паскаля, что позволяет вам вызывать функции Windows как любую подпрограмму на Паскале. Перечень заголовков этих функций вы можете найти в файле WINPROCS.PAS или в оператив- ном справочнике Help.Файлы ObjectWindows
Перечисленные выше типы ObjectWindows реализует в скомпили- рованных модулях. В данном разделе кратко описывается содержимое поставляемых модулей. В каждом приложении ObjectWindows требуется только модуль OWindows.В данной версии ObjectWindows различные части иерархии объ- ектов разбивают различные части иерархии объектов по отдельным моделям. Чтобы перекомпилировать код ObjectWindows более ранних версий, в общем случае нужно изменить все ссылки на модуль WObjects на OWindows и добавить к программам и модулям, использу- ющим наборы и потоки или диалоговые блоки, соответственно Objects и ODialogs.
В данную версию ObjectWindows добавлены также новые модули для печати, проверки данных, специализированных управляющих эле- ментов Borland и поддержки Windows 3.1.
В Таблице 7.1 перечислены модули, составляющие интерфейс ObjectWindows и AOPI Windows 3.0. Модули, поддерживающие расшире- ния Windows 3.1, представлены в Таблице 7.2.
Модули для ObjectWindows и API Windows Таблицы 7.1 +----------------+----------------------------------------------+ | Модуль | Содержимое | +----------------+----------------------------------------------| | Objects | Базовый объект TObject, наборы, потоки. | | OWindows | Приложения, окна, полосы прокрутки, окна MDI.| | ODialogs | Диалоговые блоки, диалоговые окна, управляю-| | | щие элементы. | | OPrinter | Печать, специальные распечатки. | | Validate | Проверка допустимости данных. | | BWCC | Специализированные управляющие элементы фирмы| | | Borland. | | OStdDlgs | Диалоговые блоки имен файлов, однострочный| | | ввод. | | OStdWnds | Окна текстового редактора, окна редактора| | | файлов. | | WinTypes | Все типы, используемые подпрограммами API| | | Windows 3.0, включая записи, стили, сообщений| | | и флаги. | | WinProcs | Описания процедур и функций для API Windows| | | 3.0. | +----------------+----------------------------------------------+
Файлы ресурсов
Модули OStdDlgs, OStdWnds и OPrinter имеют связанные с ними файлы ресурсов. Ресурс модуля находится в файле с именем, эквива- лентным имени модуля, и расширением .RES. Ресурсы автоматически включаются при использовании соответствующих модулей, так что программа, которая использует модуль OstdDlgs, будет автоматичес- ки иметь доступ к ресурсам в OSTDDLGS.RES.Кроме стандартных модулей API Windows
-----------------------------------------------------------------Кроме стандартных модулей API Windows 3.0, вы можете писать программы, использующие преимущества средств, добавленных в вер- сию 3.1 Windows. Каждая из 11 DLL Windows 3.1 имеет соответствую- щий модуль:
Модули для доступа к средствам Windows 3.1 Таблица 7.2 +----------------+----------------------------------------------+ | Модуль | Средство | +----------------+----------------------------------------------| | ComDlg | Общие диалоговые блоки. | | DDTML | Сообщения динамического обмена данными. | | Dlgs | Константы диалогового блока. | | LZExpand | Расширения файла LZ. | | MMSystem | Расширения мультимедиа. | | OLE | Компоновка и встраивание объектов (OLE). | | ShellAPI | Оболочка API Windows. | | Stress | Строгая проверка типов. | | ToolHelp | Отладка и другие инструментальные средства. | | Ver | Версии. | | Win31 | Расширения Windows 3.1. | +----------------+----------------------------------------------+
Функции API Windows
Функциональные возможности Windows заключены в ее около 600 функций. Каждая функция имеет имя. Взаимодействовать с операцион- ной средой Windows, модифицировать ее отображение или действие в ответ на ввод пользователя можно с помощью вызова функций API. Однако с помощью ObjectWindows вы можете создавать окна, выводить диалоговые блоки и манипулировать управляющими элементами, не вы- зывая функций Windows. Как все это работает?Функции системного вызова
Windows содержит ряд функций, которые позволяют вам прохо- дить в цикле (или перечислять) определенные типы элементов в сис- теме Windows. Например, перечислить шрифты системы и напечатать образец текста, используя каждый из них. Чтобы использовать такую функцию, вы должны передать указатель на нее (указатель системной функции), и Windows будет использовать его в процессе перечисле- ния. Данная функция при итерации по списку характеристик окна, окон, дочерних окон, шрифтов и т.д. вызывается непосредственно Windows.Функции Windows, требующие функций системного вызова (или повторного вызова), включают в себя: EnimChildWindows, EnumClipboardFormats, EnumFonts, EnumMetaFile, EnumObjects, EnumPops, EnumTaskWindows и EnumWindows.
Функция системного вызова должна быть обычной функцией, а не методов объекта. Указатель на эту функцию передается в качестве первого параметра (типа TFarProc) данных методов. Например, если вы определили в Паскале функцию системного вызова ActOnWindow следующим образом:
function ActOnWindow(TheHandle: HWnd; The Value: Longint): Integer; far; export;
то можете передать ее в качестве функции системного вызова при вызове функции Windows EnumWindows:
ReturnValue := EnumWindows(TFarProc(ActOnWindow), ALongint);
Функция системного вызова должна иметь тот же тип возвращае- мого значения, что и вызывающая ее функция Windows. Функция ActOnWindows будет выполнять некоторое действие с окном, заданным переданным указателем. Параметр TheValue - это любое значение, выбранное для передачи в вызывающей программе.
Директива компилятора {$K+} позволяет автоматически управ- лять функциями системного вызова. Если вы не выбираете {$K+}, то для возврата адреса, по которому будет выполнять вызов Windows, должны передавать свои функции системного вызова через функцию API MakeProcInstance.
Иерархия объектов
ObjectWindows - это иерархия объектных типов, которые вы мо- жете использовать для работы с большинством обычных задач в при- ложении ObjectWindows. Схема объектов пользовательского интерфей- са библиотеки показана на Рис. 7.1. На Рис. 7.2 представлены объ- екты иерархии, используемые для управления данными и проверки их допустимости.+----------------+ | TObject | +-------+--------+ +----+--------------+----------------------+------------+ +-------+--------+ +--------+-------+ +-------+--------+ | | TPrintout | | TPrinter | | TWindowsObject | | +-------+--------+ +----------------+ +-------+--------+ | +-------------------+ | | +-------+--------+ +--------+-------+ +-----------+ | | TEditPrintout | | TWindowPrintout| | +------------| +----------------+ +----------------+ | | | | +-------+--------+ | +--------------------+----------+ | TApplication | | | | +----------------+ | +------+---------+ +--------+-------+ +------------+ | TDialog | | TWindow | | +------+---------+ +--------+-------+ +-------+--------+ | | | TScroller | | | +----------------+ | +----------------------------+ +-------------------+-----------+ | +------+---------+ +-------+--------+ | | | TFileDialog | | TInputDialog | | | +----------------+ +----------------+ | | +----------------------+--------+ | +------+------------+ | | |TPrinterAbortDialog| +------------+ | +-------------------+ | | | | +--------+-------+ | +---------------------| | TPrintDialog | | +-------+--------+ | +----------------+ | |TPrinterSetupDlg| +------------+ | +----------------+ | | +--------+-------+ | | TDlgWindow | | +----------------+ |
| +------------------+------------------+----------| +-------+--------+ +-------+--------+ +-------+--------+ | | TControl | | TMDIClient | | TMDIWindow | | +-------+--------+ +----------------+ +----------------+ | +------------------+-----------+ +------+ +-------+--------+ +-------+--------+ | +-------+--------+ | TButton | | TScrollBar | | | TEditWindow | +-------+--------+ +----------------+ | +-------+--------+ +-------+--------+ | +-------+--------+ | TCheckBox | +-----------| | TFileWindow | +-------+--------+ | | +----------------+ +-------+--------+ +-------+--------+ +----------+ | TRadioButton | | TStatic | | | +----------------+ +-------+--------+ | | | | +-------+--------+ +-------+--------+ | | TListBox | | TEdit | | +-------+--------+ +----------------+ | | | +-------+--------+ +-----------+ | TComboBox | | +----------------+ +-------+--------+ | TGroupBox | +----------------+
Модуль OPRINTER: Модуль OWINDOWS: Модуль OSTDDLGS:
TPrinout TWindow TInputDialog TPtinter TAppication TInputDialog TEditPrintout TScroller TWindowPrintout TPrinterAbortDlg TPrintDialog TPrinterSetup
Модуль ODUIALOGS: Модуль OSTDWNDS:
TDialog TEditWindow TDlgWindow TFileWindow TButton TScrollBar TCheckBox TRadioButton TControl TStatic TEdit TListBox TComboBox TGroupBox Рис. 7.1. Иерархия объектных типов ObjectWindows.
+----------------+ | TObject | +-------+--------+ +----+--------------+--------------------+ +-------+--------+ +-------+--------+ +-------+--------+ | TValidator | | TCollection | | TStream | +-------+--------+ +----------------+ +---------+------+ +---------------------+------------------+ +--------+ +-------+-----------+ +-------+--------+ +-------+--------+ | |TPXPictureValidator| |TFilterValidator| |TLookupValidator| | +-------------------+ +-------+--------+ +-------+--------+ | +-------+---------+ | | | TRangeValidator | +--+ | +-----------------+ | | +----------+-----------+ | |TStringLookupValidator| | +----------------------+ | +-------------------+-------------------+-----------+ +-------+--------+ +-------+--------+ +-------+--------+ | TMemoryStream | | TDosStream | | TEmsStream | +----------------+ +-------+--------+ +----------------+ +-------+--------+ | TBufStream | +----------------+
Рис. 7.2 Иерархия наборов, потоков и проверки допустимости.
Модуль OBJECTS: Модуль VALIDATE:
TObject TValidator TCollection TPXPictureValidator TSortedCollection TFilterValidator TStrCollection TRangeValidator TStringCollection TLookupValidator TStream TStrongLookupValidator TMemoryStream TEmsStream TDosStream TBufStream
Базовый объект
TObject - это базовый объектный тип, общий предок всех объ- ектов ObjectWindows. Он определяет рудиментарный конструктор и деструктор. Потоки ObjectWindows требует, чтобы записанные объек- ты были потомками TObject.
TApplication
Этот тип определяет поведение, необходимое для всех приложе- ний ObjectWindows. Каждое приложение ObjectWindows, которое вы пишете, будет определять объектный тип приложения, производный от TApplication. Объекты приложения подробно описываются в Главе 8 "Объекты приложения".
Интерфейсные объекты
Остальные объекты в иерархии ObjectWindows классифицируются в общем случае как интерфейсные объекты. Они являются интерфейс- ными в том смысле, что представляют элементы и пользовательском интерфейсе Windows и служат интерфейсом между кодом вашей прик- ладной программы и операционной средой Windows. Интерфейсные объ- екты описываются в Главе 9.
Объекты Windows
Объекты Windows представляются не только знакомыми окнами среды Windows, но и большинством визуальных инструментов операци- онной среды, такими как управляющие элементы.
Объекты диалоговых блоков
Объекты диалоговых блоков (окон) обеспечивают временные ок- на для обслуживания специальных функций ввода и вывода. В общем случае они включат в себя текст и управляющие элемента, такие как командные кнопки, блоки списка и полосы прокрутки. Об объектах диалоговых блоков подробно рассказывается в Главе 11.
Объекты управляющих элементов
В диалоговых блоках и некоторых окнах управляющие элементы позволяют пользователям вводить данные и выбирать параметры. Объ- екты управляющих элементов обеспечивают согласованные и простые средства для работы со всеми типами управляющих элементов, опре- деленных в Windows. Объекты управляющих элементов подробно описы- ваются в Главе 12.
Объекты MDI
Windows реализует стандарт для работы с несколькими докумен- тами в рамках одного окна, которое называется множественным доку- ментальным интерфейсом (MDI). ObjectWindows обеспечивает средства для установки окон MDI и работы с ними. Объекты MDI подробно опи- сываются в Главе 14.
Объекты проверки допустимости
ObjectWindows содержит полный набор объектов проверки допус- тимости данных, которые позволяют проверять допустимость данных, введенных пользователем, при выходе пользователя из поля или ког- да пользователь завершает работу с окном. Проверка допустимости данных описывается в Главе 13.
Объекты принтера
Для работы с печатью документов или печати содержимого окна ObjectWindows предусматривает соответствующие объекты. О том, как использовать эти объекты, рассказывается в Главе 15.
Объекты наборов и потоков
Модуль Object включает в себя многочисленные объекты, реали- зующие гибкие структуры данных и потоки, позволяющие считывать и записывать объекты. Наборы описываются в Главе 19, а потоки - в Главе 20.
Имена методов
Методы реакции на сообщения называются по именам сообщений, на которые они отвечают, но без подчеркиваний. Например, метод, отвечающий на сообщение wm_KeyDown будет называться WMKeyDown.Имена объектов
Имена всех объектный типов, предусмотренных в ObjectWindows, начинаются с буквы T. Например, объекты диалоговых окон имеют тип TDialog. Для каждого определения объектного типа имеется соот- ветствующий ссылочный тип, начинающийся с P. Например, указатель на TDialogh имеет тип PDialog. В примерах данного руководства бу- дут создаваться динамические экземпляры объектов, например, с по- мощью PDialog позволяет разместить объекты TDialog в динамически распределяемой области памяти.Комбинирование констант стилей
Функции Windows, позволяющие получить интерфейсные элементы, требуют обычно некоторого параметра типа Word или Longint. Иден- тификаторы констант стилей состоят из двухбуквенного мнемоничес- кого префикса, за которым следует подчеркивание и описательное имя. Например, ws_Popup - это константа стиля окна (ws_ означает стиль окна - window style").Примечание: В Windows определены сотни констант сти- лей, которые перечислены в Главе 21 "Справочник по ObjectWindows".
Часто эти стили комбинируются для получения другого сти- ля. Например, в случае функции MessageBox вы можете передать в качестве параметра стиля mb_YesNo или mb_IconQuestion. Этот стиль дает окно сообщений с двумя командными кнопками Yes и No и пик- торгаммой вопросительного знака. Поразрядная операция or факти- чески комбинирует две константы бит за битом. Полученный в ре- зультате стиль представляет собой комбинацию обоих стилей.
Имейте в виду, что некоторые стили взаимноисключающие. Их комбинирование может дать непредвиденные или нежелательные ре- зультаты.
Константы Windows
Функции Windows требуют от вас передачи в качестве аргумен- тов разнообразных констант типа Word или Longint. Эти константы представляют стили окна, диалогового блока или управляющего эле- мента, а также возвращаемые значение и др. Если в программе ис- пользуются данные константы, она становится более читаемой, обс- луживаемой и будет более независимой от изменений в последующих версиях Windows, чем программы, использующие числа. Определенные в модуле WinTypes константы описываются в Главе 21 "Справочник по ObjectWindows".Обзор объектов
Соглашения Windows
Соглашения по именам ObjectWindows обеспечивают ясность и содержательность имен.Типы функций Windows
Ниже перечислены виды функций Windows, которые вы можете ис- пользовать в своих программах ObjectWindows.Функции интерфейса с администратором Windows
Эти функции выполняют обработку сообщений, манипулируют ок- нами и диалоговыми блоками и создают системный вывод. Данная ка- тегория включает в себя функции для меню, курсоров и буфера выре- занного изображения.
Функции интерфейса с графическими устройствами (GDI)
Эти функции выводят на разнообразные устройства (включая эк- ран и принтер) текст, графику и битовые массивы. Они не привязаны к конкретному устройству и являются независимыми от устройства.
Функции интерфейса со служебными функциями системы
Эти функции работают с широким диапазоном системных уст- ройств, включая управление памятью, взаимодействие с операционной системой, администрирование ресурсов и коммуникации.
Вызов в ObjectWindows функций API
Методы ObjectWindows вызывают функции API. Но ObjectWindows - это не дублируемые функциональные возможности; она предоставля- ет в новом пакете объектно-ориентированной библиотеки функции Windows, ее прикладной программный интерфейс (API). Кроме того, ObjectWindows значительно упрощает задачи спецификации многочис- ленных параметров, требуемых в функциях Windows. Часто ObjectWin- dows автоматически подставляет параметры, такие как описатели окон и идентификаторы дочерних окон, которые хранятся в интер- фейсных объектах в виде полей.Например, многие функции Windows для задания окна, с которым они должны работать, используют описатели окна, и эти функции вы- зываются обычно из методов оконного объекта. Объекты содержат в поле HWindow описатель соответствующего окна и может передавать его, освобождая вас от необходимости каждый раз задавать описа- тель. Таким образом, объектные типы ObjectWindows служат объект- но-ориентированным слоем, реализованным с помощью вызовов необъ- ектно-ориентированных функций API.
Взаимодействие с Windows
ObjectWindows избавляет вас от многих утомительных и запу- танных частей Windows, но иногда возникает необходимость в непос- редственном взаимодействии с Windows (например, когда вы хотите переопределить некоторое заданное в Windows поведение или выйти за рамки того, что инкапсулировано в ObjectWindows).Существует два способа, с помощью которых вы можете взаимо- действовать с ObjectWindows: вызов ее функций API и получение со- общений. В данном разделе описываются функции API. Об обработке сообщений рассказывается в Главе 16 "Сообщения Windows".
Записи данных Windows
Некоторые функции Windows требуют более сложных структур данных, например, шрифтов (TLongFont) или классов окон (TWndClass). Windows и ObjectWindows определяют эти и другие структуры данных. Перечень доступных структур вы можете найти в оперативном справочнике или в файле WINTYPES.PAS. Структуры, не- посредственно используемые в ObjectWindows, вы можете найти в Главе 21 "Справочник по ObjectWindows".При использовании ObjectWindows все функции Windows доступны также непосредственно и могут вызываться в вашей программе (если в ее операторе uses указывается модуль WinProcs). Например, сле- дующий код для получения окна сообщений вызывает функцию Windows MessageBox:
Reply := MessageBox(HWindow, 'Хотите сохранить?', 'Файл изменен', mb_YesNo or mb_IconQuestion);
MessageBox возвращает целочисленное значение, указывающее, какое действие выбрал пользователь для закрытия окна сообщения. Если пользователь щелкнул "мышью" на командной кнопке Yes (Да), то результат равен определенной в Windows целочисленной константе id_Yes. Если пользователь щелкнул "мышью" на командной кнопке No (Нет), то результат равен id_No.
Pascal 7 & Objects
Деструктор Done
Done - это деструктор объекта приложения. Перед завершением работы приложения он освобождает память объекта приложения.+----------------+ +-----------------+ | Init +--+->| InitApplication | +----------------+ | +-----------------+ | | +-----------------+ +----------------+ +->| InitInstance +--->| InitMainWindow | +-----------------+ +----------------+
+----------------+ +-----------------+ | Run +---->| MessageLoop | +----------------+ +-----------------+
+----------------+ | Done | +----------------+
Рис. 8.1 Вызовы методов, управляющие работой приложения.
Инициализация каждого экземпляра
Пользователь может одновременно выполнять несколько экземп- ляров ObjectWindows. Метод InitInstance инициализирует каждый эк- земпляр приложения. Он должен инициализировать только само прило- жение, а не его основное окно. Основное окно инициализируйте в InitMaionWindow.InitInstance вызывает InitMainWindow, а затем создает и вы- водит основное окно. Для модификации стандартной инициализации приложения (например, для загрузки таблицы оперативных клавиш) вам нужно только переопределить InitInstance. Если вы переопреде- ляете для своего приложения InitInstance, убедитесь сначала, что он вызывает метод InitInstance, наследуемый из TApplication.
Приведем метод InitInstance, который перед выполнением при- ложения загружает метод InitInstance. 'MeHotKeys' - это идентифи- катор ресурса таблицы оперативных клавиш, определенный в файле ресурса:
procedure TEditApplication.InitInstance; begin inherited InitInstance; HAccTable := LoadAccelerators(HInstance, 'MyHotKeys'); end;
Вы можете также использовать InitInstance для регистрации экземпляра приложения с внешней DLL (типа Paradox Engine).
Инициализация основного окна
Вы должны определить метод InitMainWindow, который строит и инициализирует объект основного окна и сохраняет его в поле MainWindow объекта приложения. Ниже показан пример описания объ- екта приложения и метода InitMainWindow.Данный метод создает новый экземпляр типа TWindow ObjectWindows (PWindow - это указатель на тип TWindow). Обычно ваша программа будет определять для своего основного окна новый оконный тип, а InuitMainWindow будет использовать этот тип вмес- то TWindow.
Примечание: Объекты окон подробно описываются в Главе 10.
Следующее простое приложение ObjectWindows объединяет в себе новый тип TMyApplication и старое приложение MinApp. Оно отлича- ется от MinApp только тем, что основное окно имеет заголовок:
program TestApp; uses OWindows;
type TMyApplication = object(TApplication) procedure InitMainWindow; virtual; end;
procedure TMyApplication.InitMainWindow; begin MainWindow := New(PWindow, Init(nil, 'Основное окно')); end;
var MyApp: TApplication; begin MyApp.Init('TestApp'); MyApp.Run; MyApp.Done; end;
Программа TestApp выводит окно с заголовком 'Основное окно'. Вы можете легко перемещать это окно и изменять его размер, мини- мизировать его, восстанавливать или максимизировать. Закрытие ок- на завершает приложение. Короче, TestApp - это полнофункциональ- ный "скелет" приложения, оснащенный только простейшим основным окном.
Инициализация первого экземпляра
Если пользователь запускает приложение в отсутствии уже ра- ботающих других экземпляров этого приложения, вы можете опреде- лить для такого первоначального запуска некоторую обработку. Эта обработка называется инициализацией первого экземпляра. Если пользователь начинает и завершает ваше приложение, затем запуска- ет его снова и т.д., каждый экземпляр будет первым экземпляром (поскольку другие экземпляры в этот момент не выполняются).Если текущим экземпляром является первый экземпляр, то конс- труктор Init вызывает InitApplication. TApplication определяет заместитель метода InitApplication, который вы можете для выпол- нения специальной инициализации первого экземпляра переопреде- лить.
Например, вы можете модифицировать TestApp таким образом, чтобы о первом экземпляре сообщалось в заголовке основного окна. Для этого добавьте в тип приложения TMyApplication булевское поле с именем FirstApp, затем переопределите метод InitApplication, чтобы он устанавливал FirstApp в True. Наконец, модифицируйте InitMainWindow для проверки FirstApp и вывода в основном окне приложения соответствующего заголовка (см. Рис. 8.2).
################################################################# #+----------------------------------------------------+-+-+###### #|#=###############Первый экземпляр###################|^|v|###### #+----------------------------------------------------+-+-|###### #| |###### #| +----------------------------------------------------+-+-+### #| |#=###############Дополнительный экземпляр###########|^|v|### #| +----------------------------------------------------+-+-|### #| | |### #| | +----------------------------------------------------+-+-+ #| | |#=#XXXXXXXXXXXXДополнительный экземплярXXXXXXXXXXXXX|^|v| #| | +----------------------------------------------------+-+-| #| | | | #| | | | #+--| | | ####| | | ####+--| | #######| | #######+--------------------------------------------------------+
Рис. 8.2 Новая инициализация приложения.
program TestApp; uses OWindows;
type TMyApplication = object(TApplication) FirstApp: Boolean; procedure InitMainWindow; virtual; procedure InitApplication; virtual; end;
procedure TMyApplication.InitMainWindow; begin if FirstApp then MainWindow := New(PWindow, Init(nil, 'Первый экземпляр')) else MainWindow := New(PWindow, Init(nil, 'Дополнительный экземпляр')); end;
procedure TMyApplication.InitApplication; begin FirstApp := True; end;
var MyApp; TMyApplication; begin MyApp.Init('TestApp'); MyApp.Run; MyApp.Done; end.
Инициализация приложения
Выполнение методов, инициализирующих объект приложения, поз- воляет вам настроить части процесса путем переопределения отдель- ных методов. Один из методов, требующий переопределения, чтобы приложения получило конкретный смысл - это InitMainWindow. Вы мо- ете также переопределить InitInstance и InitApplication.Конструктор Init
Первый оператор - это вызов конструктора Init приложения. Этот вызов делает следующее:* Строит объект.
* Инициализирует поля данных объекта.
* Устанавливает глобальную переменную Application на объект (@Self).
* Выполняет два вида инициализации:
- Вызывает InitApplication при отсутствии других выполняе- мых экземпляров данного приложения.
- Всегда вызывает InitInstance, устанавливая вызовом InitMainWindow основное окно.
Когда Init завершает работу, основное окно вашего приложения находится на экране. В большинстве случаев вам нужно только пере- определить InitMainWindow.
Метод Run
Второй оператор вызывает метод Run приложения, который реа- лизует работу приложения, вызывая MessageLoop. MessageLoop обра- батывает сообщения от Windows и выполняет инструкции, которые уп- равляют работой приложения. MessageLoop - это цикл, который про- должает работу до закрытия приложения.MessageLoop вызывает несколько методов, обрабатывающих конк- ретные поступающие сообщения (см. далее).
Методы Init, Run и Done
Так как в основном блоке программы ObjectWindows каждый раз используются одни и те же операторы, вы должны понимать, что де- лает каждый из них.Минимальное приложение
Приведем пример минимального приложения ObjectWindows:program MinApp; uses OWindows; var MyApp: TApplication; begin MyApp.Init('TtstApp'); MyApp.Run; MyApp.Done; end;
MinApp - это абсолютный минимум приложения ObjectWindows. Эта программа не требует определения других объектов. Обычно вы определяете новый объектные типы как минимум для приложения и ос- новного окна.
Минимальные требования
Чтобы ваше программа ObjectWindows стала рабочим приложением Windows, она должна в своем основном блоке beginend делать сле- дующее:- выполнять инициализацию;
- обрабатывать сообщения;
- по требованию завершать работу.
Используемый по умолчанию объект выполняет эти задачи путем вызова трех его методов: Init, Run и Done. Основной блок любой программы ObjectWindows содержит обычно эти три метода. Чтобы из- менить поведение по умолчанию, методы нужно переопределить.
Модификация поведения при закрытии
Все оконные объекты, наследуют булевский метод CanClose, возвращающий по умолчанию True (что указывает на подтверждение закрытия, то есть TestApp закрывается немедленно). Для изменения поведения при закрытии вы можете переопределить методы CanClose приложения или основного типа окна. Если какие-либо объекты возв- ращают из CanClose значение False, то приложение завершиться не может. Обычно вы можете изменить поведение при закрытии объектно- го типа основного окна. Например, перед завершением можно убе- диться, что приложение сохранило файлы.Механизм CanClose
Механизм CanClose дает основному окну, объекту приложения и другим окнам возможность подготовиться с закрытию или предотвра- тить его. В итоге объект приложения должен разрешить закрытие приложения. По умолчанию он проверяет основное окно. Обычная пос- ледовательность закрытия выглядит следующим образом:
1. Windows посылает основному окну приложения сообщение wm_Close.
2. Объект основного окна вызывает метод CanClose объекта приложения.
3. Объект приложения вызывает метод CanClose.
4. Объект основного окна вызывает метод CanClose для каждого из дочерних окон и возвращает True только в том случае, если методы CanClose дочерних окон возвращают True.
Модификация CanClose
Методы CanClose редко возвращают значение False. Вместо это- го они должны выполнять некоторые действия, позволяющие им возв- ращать True. Метод CanClose должен возвращать False только в том случае, если он не может выполнить действий, необходимых для нор- мального завершения, или пользователь показывает, что он хочет продолжать выполнение программы.
Например, метод CanClose окна редактора может проверять из- менение редактируемого текста, а затем выводить диалоговое окно с запросом, нужно ли сохранить текст перед закрытием, и восприни- мать ответ Yes (Да), No (Нет) или Cancel (Отмена). Cancel будет указывать, что пользователь пока не хочет закрывать приложение, так что CanClose должен возвращать False. CanClose следует также возвращать False при обнаружении ошибки в сохранении текста, пре- доставляя пользователю другую возможность сохранения данных перед закрытием.
Если метод CanClose не переопределяется, тип основного окна наследует его от TWindowsObject, где CanClose возвращает True после вызовов методов CanClose дочерних окон. Чтобы модифициро- вать поведение при закрытии основного окна, переопределите метод CanClose. Например, для проверки того, что пользователь собирает- ся закрыть окно, он может проверять открытые файлы.
Поиск объекта приложения
Когда программа выполняется, ObjectWindows поддерживает гло- бальную переменную Application - указатель на объект приложения. Этот указатель позволяет подпрограммам вне объекта приложения об- ращаться к его полям и методам. По умолчанию Application устанав- ливается в @Self конструктором объекта приложения и в nil дест- руктором объекта.Специальный вывод основного окна
Начальный вывод основного окна управляется переменной CmdShow модуля System. CmdShow содержит одну из констант sw_ и передается в качестве параметра функции API Windows ShowWindow, когда приложение создает свое основное окно.С помощью CmdShow вы легко можете вывести основное окно в нормальном виде (по умолчанию), распахнутым до размеров полного экрана, минимизированным в пиктограмму или скрытым. Лучшим местом присваивания значения CmdShow является конструктор объекта основ- ного окна. Это обеспечивает передачу выбранного значения при соз- дании элемента экрана.
Выполнение приложений
Метод Run вашего приложения вызывает метод MessageLoop, ко- торый вызывает цикл обработки сообщений вашей программы. Во время выполнения вашей программы в цикле обработки сообщений обрабаты- ваются поступающие от Windows сообщения. Программы ObjectWindows наследуют цикл MessageLoop, который работает автоматически. До- полняйте цикл обработки сообщений только специальными диалогами, оперативными клавишами или обработкой MDI.Метод MessageLoop для обработки сообщений Windows вызывает три метода. ProcessDlgMsg работает с безрежимными диалоговыми ок- нами, ProcessAccels - обрабатывает оперативные клавиши, а ProcessMDIAccels - оперативные клавиши для приложений MDI. Для приложений, не использующих командные клавиши или безрежимные ди- алоговые окна или не являющихся приложениями MDI MessageLoop мож- но несколько упростить. См. методы TApplication в Главе 21.
Закрытие приложений
Вызов деструктора Done в основной программе вашего приложе- ния уничтожает объект приложения. Однако, перед тем как это про- исходит, программа должна прервать цикл обработки сообщения. Это может произойти, когда пользователь пытается закрыть основное ок- но приложения. Мы говорим, что это может произойти, так как ObjectWindows предусматривает механизм изменения поведения прило- жения при закрытии: вы можете задавать условия закрытия.Pascal 7 & Objects
Что делают интерфейсные объекты?
Все интерфейсные объекты ObjectWindows наследуют из единс- твенного абстрактного объектного типа TWindowsObject, который определяет поведение, общее для окна, диалога и объектов управля- ющих элементов, видоизменяемых и специализируемых в производных объектных типах TDialog, TWindow и TControl.В качестве общего предка всех интерфейсных объектов методы TWindowsObject обеспечивают единообразный способ поддержки взаи- мосвязи между объектами и элементами экрана (включая создание и уничтожение объектов), обслуживают соотношения "родитель-потомок" между интерфейсными объектами и регистрируют новый классы Windows (см. Главу 10).
Новые типы, производные от TWindowsObject, определяются ред- ко, но он служит основой объектно-ориентированной модели окон. Он определяет большинство наследуемых объектами функциональных воз- можностей, когда вы получаете из TWindow и TDialog новые типы.
Для чего нужны интерфейсные объекты?
Для чего нужны интерфейсные объекты, если в Windows уже есть окна, диалоговые блоки и управляющие элементы?Одна из основных трудностей программирования в Windows - это достаточно сложная работа с визуальными элементами. Иногда вам нужно послать в окно сообщение, в другой раз - вызвать функцию API Windows. Для разных типов элементов экрана соглашения будут различными.
ObjectWindows уменьшает большую часть этих сложностей, пре- доставляя объекты, инкапсулирующие элементы экрана и избавляющие вас от необходимости иметь дело непосредственно с Windows. К тому же они обеспечивают более удобный интерфейс.
Допустимость описателя окна
Обычно в Windows вновь созданный интерфейсный элемент полу- чает (от Windows) сообщение wm_Create, на которое требуется отве- тить инициализацией. Интерфейсный объект ObjectWindows не будет получать сообщений wm_Create, поэтому не забудьте определить для инициализации метод SetupWindow.Если инициализация интерфейсного объекта требует описателя элемента экрана (например, для вызова функции API Windows), то она не должна вызываться раньше SetupWindow. То есть, перед вызо- вом SetupWindow поле HWindow интерфейсного объекта не является допустимым и использоваться не должно. Если вы хотите вызывать функцию API или нечто требующее описателя окна, не вызывайте их в конструкторе Init. Поместите такие вызовы в метод SetupWindow.
|<----HWindow допустим--->| | | |<-------------------интерфейсный объект допустим---------->| --+-----------------------------------------------------------+--> ^ ^ ^ ^ ^ | | | | | Init вызывает | | | | наследуемый Init | | | | | | Done | SetupWindow вызывает наследуемый | SetupWindow | Done вызывает наследуемый | метод Done | Наследуемый SetupWindow вызывает Create
Рис. 9.1 Когда окно имеет допустимый описатель.
Итерация дочерних окон
Иногда желательно написать методы, для реализации функции выполняющие итерации по каждому дочернему окну данного окна. Нап- ример, можно проверить в окне все кнопки с независимой фиксацией. В этом случае используйте метод TWindowsObject.ForEach:procedure TMyWindow.CheckAllBoxes;
procedure CheckTheBox(ABox: PWindowsObject); far; begin PCheckBox(ABox)^.Check; end;
begin ForEach(@CheckTheBox); end;
Использование метода ForEach (и аналогичных методов FirstThat и LastThat) похоже на применение методов с аналогичными названиями в TCollection. Хотя ObjectWindows не использует наборы для обслуживания дочерних окон, методы итерации работают анало- гично.
Поиск определенного дочернего окна
Иногда желательно иметь методы, выполняющие итерацию по списку окон в поиске конкретного окна. Например, в окне с нес- колькими кнопками вам может потребоваться найти первую установ- ленную кнопку с независимой фиксацией. В этом случае метод TWindowsObject.FirstThat можно записать так:function TMyWindow.GetFirstChecked: PWindowsObject;
function IsThisOneChecked(ABox: PWindowsObject): Boolean; far; begin IsThisOneChecked := (ABox^.GetCheck = bf_Checked); end;
begin GetFirstChecked := FirstThat(@IsThisOneChecked); end;
Построение дочерних окон
Как и в случае интерфейсных объектов, объекты дочерних окон создаются в два этапа (построение объекта и создание элемента эк- рана). Объекты порожденного окна следует строить с помощью конс- труктора Init порождающего окна. Например, объект окна, наследую- щий из TWindow и содержащий командную кнопку должен иметь пример- но следующий вид:constructor TMyWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); TheButton := New(PButton, Init(@Self, id_TheButton, 'Текст кнопки', 20, 10, 100, 25, True)); end;
Обратите внимание на использование указателя Self для связи дочернего объекта (TheButton) с порождающим (экземпляром TMyWindow). Конструктор интерфейсного объекта автоматически до- бавляет к своему списку дочерних окон новые объекты.
Создание дочерних элементов экрана
Когда построен список дочерних элементов интерфейсного объ- екта, создание элементов экрана для дочерних окон выполняется ав- томатически. Создание родительского окна (через вызов MakeWindow) включает в себя вызов метода SetupWindow порождающего окна. Одним из наследуемых действий SetupWindow является вызов для каждого из окон в списке дочерних окон методов SetupWindow.Примечание: Автоматическое создание можно запретить. См. ниже раздел "Запрещение автоматического создания".
При создании нового производного объектного типа нужно пом- нить об инициализации объекта в SetupWindow после вызова наследу- емого метода SetupWindow, например:
procedure TMyCheckBox.SetupWindow; begin inherited SetupWindow; { сначала по умолчанию } . . . { выполнить инициализацию объекта } end;
Создание интерфейсных объектов
Задание полного интерфейсного объекта с соответствующими ин- терфейсными элементами требует двух шагов:* Построения объекта.
* Создания элемента экрана.
Первым шагом является вызов конструктора Init, который стро- ит интерфейсный объект и устанавливает его атрибуты, такие как стиль и меню.
Второй шаг заключается в вызове метода создания окна объекта приложения, MakeWindow, связывающего интерфейсный объект с новым элементом экрана. Эта связь поддерживается полем HWindow экрана (описателем окна).
Примечание: Об описателях окон рассказывается в Главе 7 "Иерархия ObjectWindows".
MakeWindow вызывает метод Create объекта, который всегда со- общает Windows о необходимости создания элемента на экране. Create создает также метод SetupWindow, который инициализирует интерфейсный объект путем создания, например, дочерних окон.
Список дочерних окон
Когда вы строите объект дочернего окна, то можете в качестве параметра конструктора Init можете задать порождающее окно (при- мер вы можете найти в Главе 10). Объект дочернего окна отслежива- ет свое порождающее окно через указатель на его поле Parent. Он отслеживает также объекты его дочерних окон, сохраненные в поле ChildList. Дочернее окно, на которое в данный момент установлен ChildList, является последним созданным дочерним окном.Связь порождающего и дочернего объектов
В приложении Windows совместная работа элементов экрана (окон, диалоговых блоков и управляющих элементов) обеспечивается с помощью связей "родитель-потомок". Порождающие окна управляют своими дочерними окнами, а Windows отслеживает эти связи. ObjectWindows поддерживает параллельный набор связей между соот- ветствующими интерфейсными объектами.Дочернее окно - это элемент экрана (оно не обязано быть ок- ном), который управляется другим элементом экрана. Например, бло- ки списка обслуживаются окном или диалоговым блоком, в котором они выводятся. Они выводятся на экран только при выводе их порож- дающих окон. Диалоговые блоки, в свою очередь, являются дочерними окнами, управляемыми порождающими их окнами.
Когда вы перемещаете или закрываете порождающее окно, дочер- ние окна автоматически закрываются, и в некоторых случаях переме- щаются в нем. Конечным предком всех дочерних интерфейсных элемен- тов является основное окно, хотя вы можете иметь окна и диалоги без порождающих окон.
Порождающими окнами могут быть только диалоговые блоки и ок- на, но не порождающие элементы. Дочерним окном может быть любой интерфейсный элемент.
Уничтожение дочерних окон
Вызов деструктора порождающего окна приводит к вызову дест- рукторов всех его дочерних окон, так что вашей программе не нужно явно вызывать деструкторы дочернего окна. Это же справедливо для метода CanClose, который возвращает True только после вызова CanClose для всех его дочерних окон.Уничтожение интерфейсных объектов
Как и в случае создания интерфейсный объектов, их уничтоже- ние предполагает выполнение двух шагов:* Уничтожение визуального интерфейсного элемента (Destroy).
* Уничтожение интерфейсного объекта (Dispose).
Уничтожением экранного элемента занимается метод Destroy ин- терфейсного объекта, который делает следующее: он вызывает функ- цию Windows DestroyWindow, чтобы избавиться от элемента экрана, и устанавливает поле HWindow объекта в 0. Таким образом, проверив указатель, вы можете сообщить, связан ли еще объект с элементом экрана.
Уничтожить элемент экрана вы можете без уничтожения объекта (если хотите создавать и выводить его снова).
Примечание: Уничтожение самого окна обычно не требует- ся. Это делается автоматически при закрытии окна.
Когда пользователь закрывает на экране окно, ObjectWindows обнаруживает, что данный элемент экрана уничтожен, устанавливает поле HWindow соответствующего объекта в 0 и вызывает деструктор объекта Done.
Видимость на экране
Создание интерфейсного объекта и соответствующего визуально- го элемента не обязательно означает, что вы что-то видите на эк- ране. Когда метод Create указывает Windows на создание элемента экрана, Windows проверяет, включает ли стиль окна ws_Visible. Ес- ли да, то интерфейсный элемент будет выводиться. В противном слу- чае он будет скрытым.ws_Visible и другие стили окна обычно устанавливаются или сбрасываются конструктором Init в поле Attr.Style объекта.
В любой момент после создания элемента экрана вы можете вы- вести или скрыть его, вызвав метод Show интерфейсного объекта.
Запрещение автоматического создания
Чтобы явно исключить дочернее окно из механизма автоматичес- кого создания и вывода, вызовите после его создания метод DisableAutoCreate. Чтобы явно добавить в механизм создания и вы- вода дочернее окно (такое как диалоговый блок, который при нор- мальном выводе будет исключен), вызовите после построения его ме- тод EnableAutoCreate.Pascal 7 & Objects
Атрибуты порожденного окна
Конструктор TWindowType отвечает за построение его дочерних объектов, таких как всплывающие окна и блоки списка. Тип порож- денного окна, в свою очередь, может устанавливать атрибуты в сво- ем собственном конструкторе Init:constructor TChilwWindowType.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); with Attr do begin Style := Style or ws_PopupWindow or ws_Caption; X := 100; Y := 100; W := 300; H := 300; end; end;
В качестве альтернативы вы можете не определять потомка типа окна, а сначала построить объект окна, а затем переустано- вить его атрибуты (все это в конструкторе Init порождающего ок- на):
constructor TWindowType.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, 'TheMenu'); AChildWindow := New(PChildWindowType, Init(@Self, 'Заголовок дочернего окна')); with Attr do begin Style := Style or ws_PopupWindow or ws_Caption; X := 100; Y := 100; W := 300; H := 300; end; . . . end;
Что такое объект прокрутки?
TScroller содержит значения, определяющие, насколько должно прокручивается окно. Эти значения записываются в полях XUnit, YUnit, XLine, YLine, XRange, YRange, XPage и YPage объекта TScroller. Поля, начинающиеся с буквы X, представляют горизон- тальные значения, а начинающиеся с буквы Y - вертикальные.Единицы прокрутки
Единица прокрутки определяет минимальную величину прокрутки. Она задается в наименьших единицах устройства (обычно в элементах изображения, но может зависеть от текущего режима отображения), на которые вы можете выполнять прокрутку в горизонтальном или вертикальном направлении. Это значение обычно основывается на ви- де выводимой на экран информации.
Например, если вы выводите текст с шириной символа 8 элемен- тов изображения и высотой 15, то в качестве значений XUnit и YUnit полезно задать, соответственно, 8 и 15.
Строки, страницы и диапазон
Другие атрибуты прокрутки - строка, страница и диапазон - выражаются в единицах прокрутки. Значения Line (строка) и Page (страница) - это число единиц, на которые выполняется прокрутка в ответ на запрос пользователя. Запрос может иметь форму щелчка кнопкой "мыши" на концах полосы прокрутки (построчная прокрутка). Щелчок "мышью" в самой полосе прокрутки (но не на маркере полосы прокрутки) позволяет выполнять постраничную прокрутку. Атрибуты диапазона (XRange, YRange) представляют общее число единиц, на которое можно выполнять прокрутку. Обычно этот диапазон определя- ется на основе размера редактируемого документа.
Типичный объект прокрутки
В качестве примера рассмотрим текстовое окно редактирования. Если вы хотите вывести на экран текстовый файл, имеющий 400 строк текста с границей 80 символов и 50 строками на странице, то можно выбрать следующие значения:
Типичные значения для окна редактирования Таблица 10.4 +-----------------+-------------+-------------------------------+ | Поле | Значение | Смысл | +-----------------+-------------+-------------------------------| | XUnit | 8 | ширина символа | | YUnit | 15 | высота символа | | XLine, YLine | 1 | 1 единица на строку | | XPage | 40 | 40 символов по горизонтали на| | | | страницу | | YPage | 50 | 50 символов по вертикали на| | | | страницу | | XRange | 80 | максимальный горизонтальный| | | | диапазон | | YRange | 400 | максимальный вертикальный ди-| | | | апазон | +-----------------+-------------+-------------------------------+
Объект TScroller с данными значениями позволяет выполнять построчную или постраничную прокрутку. С помощью полос прокрутки или автоматической прокрутки выполняется просмотр всего файла.
Значения по умолчанию
По умолчанию XLine и YLine имеют значение 1, так что без яв- ной необходимости устанавливать их в другие значения не нужно. Для установки значений прокрутки на страницу также существует ис- пользуемая по умолчанию схема, согласно которой страница прокрут- ки будет соответствовать текущей высоте или ширине области клиен- та окна (в зависимости от направлений прокрутки). Если вы не хо- тите переопределить данный механизм, переустанавливать эти значе- ния не требуется.
Что такое объекты окон?
Термин "объект окна" относится к любому интерфейсному объек- ту, представляющему окно, а в Windows это почти все, что выводит- ся на экран. В качестве шаблона определения большей части фунда- ментального поведения основного окна и любого всплывающего окна приложения ObjectWindows использует тип TWindow.Где найти объекты окон
Каждое приложение Windows имеет основное окно. Это окно мо- жет выводиться в виде пиктограммы или не выводиться снова (скры- тое окно), но существует всегда. Приложения ObjectWindows не яв- ляются исключением: они должны иметь основное окно, представлен- ное оконным объектом.Примером минимальной программы ObjectWindows ("скелета" программы) является TestApp в Главе 8. Основное окно программы ObjectWindows является обычно экземпляром TWindow или определяе- мого в программе наследующего типа. Многие приложения имеют дру- гие окна, которые обычно являются дочерними окнами основного ок- на. Эти дополнительные окна также являются экземплярами TWindow или одного из его потомков.
Например, графическая программа может определять для своего основного окна тип TPaintWindow, а для окна, показывающего графи- ческий рисунок - тип TZoomWindow. В этом случае TPaintWindow и TZoomWindow являются наследниками TWindow.
Инициализация объектов окон
Оконные объекты представляют элементы окна, связанные через описатели, сохраненные в наследуемом из TWindowsObject поле HWindow. Так как объект окна имеет две части, его создание требу- ет двух шагов: инициализации объекта и создания визуального эле- мента.Инициализация окна - это процесс создания оконного объекта ObjectWindows путем вызова конструктора Init:
Window1 := New(PWindow,Init(nil, 'Заголовок окна 1')); Window2 := New(PNewWindowType,Init(nil,'Заголовок окна 2'));
Init создает новый оконный объект и устанавливает поле Title в Attr в передаваемый аргумент PChar. Первый аргумент вызова Init - это оконный объект порождающего окна. Если окно является основ- ным окном (не имеющим порождающего окна), то это nil.
Использование файловых окон
Файловое окно - это окно редактирования с дополнительными возможностями, позволяющими считывать и записывать данные в файл. TFileWindow.Init воспринимает в качестве аргумента заголовок окна и устанавливает поле FileDialog таким образом, чтобы оно указыва- ло на файловый диалоговый объект.Для работы с файлами TFileWindow имеет четыре метода. Методы Open, Save и SaveAs для вывода пользователю подсказки с именем файла используют поле TFileWindow.FileDialog (см. Главу 11). Ме- тод New дает пользователю возможность отмены, если редактирование нового файла приведет к потере изменений текущего текста. Чтобы дать пользователю возможность доступа к этим методам, создайте свое меню со следующими идентификаторами меню:
Методы и идентификаторы меню файлового окна Таблица 10.3 +---------------------+-----------------------------------------+ | Метод | Идентификатор меню для вызова | +---------------------+-----------------------------------------| | New | cm_FileNew | | Open | cm_FileOpen | | Save | cm_FileSave | | SaveAs | cm_FileSaveAs | +---------------------+-----------------------------------------+
Вы можете использовать файловые окна без модификации как простые автономные текстовые редакторы. Однако, иногда желательно создать производные от TFileWindow типы и обеспечить дополнитель- ные функциональные возможности. Например, можно предусмотреть средство поиска. Помните, что вы все равно будете иметь доступ к управляющему элементу редактирования TFileWindow.Editor.
Использование окон редактирования
Окно редактирования - это окно с управляющим элементом ре- дактирования, заполняющим его область клиента. TEditWindow.Init инициализирует поле Editor окна редактирования, чтобы оно указы- вало на управляющий элемент объекта редактирования. TEditWindow.SetupWindow устанавливает размеры управляющего эле- мента редактирования в соответствии с областью клиента окна и создает экранный управляющий элемент редактирования.Метод WMSize обеспечивает изменение размера управляющего элемента редактирования при изменении размера его окна. Метод WMSetFocus обеспечивает, что управляющий элемент редактирования получает фокус ввода при получении окном сообщения wm_SetFocus.
Показанная ниже программа EditWindowTester использует окно редактирования, чтобы пользователь мог редактировать текст для простой (нефункциональной) электронной почты.
+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXEdit Window TesterXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | Edit Text | +---------------------------------------------------------------| | Кого это может касаться: | | | | Я хотел бы зарегистрировать жалобу по поводу попугая, которого| | я купил в вашем магазине полгода назад. Он умер. | | +-----------------------------------+ | | Брюс |#=#@@@@@@Передано сообщение@@@@@@@@| | | +-----------------------------------| | | | | | | | 6 строк послано | | | | +------------+ | | | | |####OK######| | | | | +------------+ | | | +-----------------------------------+ | | | +---------------------------------------------------------------+
Рис. 10.2 Окно редактирования.
program EditWindowTester; {$R EWNDTEST.RES} uses ODialogs, WinTypes, WinProcs, Strings, OStdWnds; const cm_sendText = 399; type TestApplication = object(TApplication) procedure InitMainWindow; virtual; end;
PMyEditWindow = ^MyEditWindow; MyEditWindow = object(TEditWindow) constructor Init(AParent: PWindowsObject; ATitle: PChar); procedure CMSendText(var Msg: TMessage); virtual cm_First + cm_SendText; end;
constructor MyEditWindow.Init(AParent: PWindowsObject; Atitle: PChar); begin inherited Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, MakeIntResource(102)); end
procedure MyEditWindows.CMSendText(var Msg: TMessage); var Lines: Integer; TestString: string[3]; Text: array[020] of Char; begin Lines := Editor^.GetNumLines; Str(Lines, TextString); StrCat(Text, ' строк послано'); MessageBox(HWindow, @Text, 'Передано сообщение', mb_Ok); end;
procedure TestApplication.InitMainWindow; begin MainWindow := New(PMyEditWindow, Init(nil, 'Окно редактирования - попробуйте набор и редактирование')); end;
var TestApp: TestApplication; begin TestApp.Init('EditWindowTester'); TestApp.Run; TestApp.Done; end.
Использование специализированных окон
ObjectWindows предусматривает два потомка TWindow, являющих- ся специализированными окнами для редактирования текста. Объект- ный тип TEditWindow обеспечивает простой текстовый редактор, не обладающий возможностями чтения из файла или записи в него. Тип TFileWindow, наследующий из TEditWindow, обеспечивает текстовый редактор с возможностями чтения/записи файлов.Эти объекты можно использовать непосредственно как стандарт- ные компоненты ваших приложений. Вы можете также построить произ- водные от них типы и создать свои собственные специализированные редакторы. Программы или модули, использующие окна редактирования или файловые окна, должны включать в свой оператор uses модуль OStdWnds.
Используемые по умолчанию атрибуты окна
По умолчанию TWindow.Init устанавливает Attr.Style в ws_Visible. Если окно является основным окном приложения, то Style равно ws_OverlappedWindow or ws_Visible.Menu по умолчанию устанавливается в 0. Это означает, что ме- ню не определено.
X, Y, W и H устанавливаются в cw_UseDefault, что дает в ре- зультате перекрывающееся окно удовлетворительного размера. Когда создается окно, не являющееся основным, значения X, Y, W и H вы обычно устанавливаете сами.
Используемые по умолчанию атрибуты регистрации
Тип TWindow определяет класс окна 'TurboWindow' с пустой пиктограммой, курсором-стрелкой и стандартным цветом окна. Ис- пользуемый по умолчанию класс ObjectWindows (TurboWindow) имеет следующие атрибуты:* стиль: cs_HRedraw or cs_VRedraw (повторное отображение после каждого изменения размера);
* пиктограмма: idi_Application (пустой прямоугольник);
* курсор: idc_Arrow (стандартная стрелка Windows);
* фоновый цвет: HBrush(color_Window + 1);
* меню по умолчанию: nil.
Изменение имени класса
GetClassName - это функция, которая возвращает имя (PChar) класса окна. TWindow.GetClassName возвращает 'TurboWindow', имя используемого по умолчанию класса окна. TWindow.GetClassName возвращает 'TurboWindow' - имя используемого по умолчанию класса окна:function TWindow.GetClassName: PChar; begin GetClassName := 'TurboWindow'; end;
Чтобы определить тип объекта окна с именем IBeamWindow, ко- торый использует вместо стандартной стрелки I-образный курсор, переопределите наследуемый метод следующим образом:
function TBeamWindow.GetClassName: PChar; begin GetClassName := 'IBeamWindow'; end;
Примечание: Имя класса не обязательно должно соответс- твовать имени объектного типа.
Имя класса должно быть уникальным.
Изменение позиции прокрутки
Windows с помощью методов ScrollTo и ScrollBy может выпол- нять принудительную прокрутку. Каждый из них воспринимает два це- лочисленных аргумента в терминах горизонтальных и вертикальных единиц прокрутки. Например, если нужно переместиться к левому верхнему углу картинки, то используется ScrollTo:Scroller^.ScrollTo(0, 0);
Приведем другой пример. Если картинка имеет длину 400 единиц в вертикальном направлении, то позицию прокрутки можно перемес- тить к середине картинки следующим образом:
Scroller^.ScrollTo(0, 200);
Метод ScrollBy может перемещать позицию просмотра на задан- ное число единиц вверх, вниз, влево или вправо. Отрицательные значения осуществляют сдвиг к левому верхнему углу, а положитель- ные - к правому нижнему. Если нужно сместиться на 10 единиц впра- во и на 20 единиц вниз, то это можно сделать командой:
Scroller^.ScrollBy(10, 20);
Классы окон
С каждым типом оконного объекта связан список атрибутов ре- гистрации, которые называются классом окна. Список атрибутов ре- гистрации во многом напоминает список атрибутов создания, запи- санных в поле записи Attr объекта окна. Однако, атрибуты регист- рации сохраняются в записи с именем TWndClass, который определя- ется и поддерживается Windows.Процесс связи класса окна с типом оконного объекта называет- ся регистрацией класса окна. ObjectWindows автоматизирует процесс регистрации. Таким образом, если вы хотите изменить какую-либо из используемых по умолчанию характеристик объекта, то можете не беспокоиться о классе регистрации окна.
Поля записи TWndClass и их типы перечислены в следующей таб- лице:
Атрибуты регистрации окна Таблица 10.2 +-------------------------+-------------------+-----------------+ | Характеристика | Поле | Тип | +-------------------------+-------------------+-----------------| | стиль класса | style | Word | | пиктограмма | hIcon | HIcon | | курсор | hCursor | HCursor | | фоновый цвет | hbrBackground | HBrush | | меню по умолчанию | lpszMenuName | PChar | +-------------------------+-------------------+-----------------+
Поля стиля класса
Это поле стиля отличается от атрибута стиля окна (ws_), за- даваемого при инициализации окна, поскольку задает поведение, присущее операциям окна (в отличие от их визуального представле- ния). Это поле может заполняться комбинацией констант стиля (cs_).
Например, cs_HRedraw приводит к повторному отображению окна при изменении его размера по горизонтали; cs_DoubleClk позволяет окну получать сообщения о двойном нажатии кнопки "мыши"; cs_NoClose предотвращает выбор параметра Close меню Control, а cs_ParentDC дает окну контекст дисплея порождающего окна.
Поле пиктограммы
Это поле содержит описатель пиктограммы, которое использует- ся для представления окна в его минимизированном состоянии. Обыч- но для представления основного окна программы выбирается ресурс пиктограммы.
Поле курсора
Поле hCursor содержит описатель курсора, который использует- ся для представления указателя "мыши" при позиционировании его в окне.
Поле фонового цвета
Это поле задает фоновый цвет окна. Для большинства приложе- ний используется стандартный назначаемый по умолчанию цвет окна, который может устанавливаться пользователем в управляющей панели. Однако вы можете путем установки этого поля в описатель физичес- кой кисти подставить конкретный цвет. Либо вы можете установить любое из значений цветов Windows, такие как color_ActiveCaption. К любому значению цвета всегда добавляйте 1.
Поле используемого по умолчанию меню
Это поле указывает на имя ресурса меню, которое служит ис- пользуемым по умолчанию меню для данного класса. Например, если вы определите тип EditWindow, который всегда имеет стандартное меню редактирования, то можете задать здесь это меню. Это устра- нит необходимость задания меню в методе Init. Если данный ресурс меню имеет идентификатор 'MyMenu', вы можете установить это поле следующим образом:
AWndClass.IpszMenuName := 'MyMenu';
Модификация единиц прокрутки и диапазона
В приведенных выше примерах мы предполагали, что к моменту построения TScroller известны значения единиц и диапазонов. Во многих случаях эта информация неизвестна или может меняться при изменении размеров отображаемой информации. В этом случае может потребоваться установить или изменить значения диапазона (а может быть и единиц) позднее. Если значения заранее неизвестны, то их можно задать как 0 в конструкторе TScroller.Изменение диапазона
Метод SetRange воспринимает два целочисленных аргумента - число горизонтальных и вертикальных единиц, которые определяют общий диапазон прокрутки. Метод SetRange должен использоваться при изменении размеров картинки. Например, при подготовке изобра- жения картинки шириной 1 0 единиц и высотой 300, данная команда установит диапазон прокрутки надлежащим образом:
Scroller^.setRange(100, 300);
Изменение единиц прокрутки
Если при инициализации объекта TScroller единицы неизвестны, то их значения могут быть установлены непосредственно перед прок- руткой. Например, они могут быть установлены методом окна SetupWindow:
procedure ScrollWindow.SetupWindow; begin TWindow.SetupWindow; Scroller^.XUnit:=10; Scroller^.YUnit:=20; end;
Окна, которые не являются окнами
TWindow имеет три типа-потомка: TMDIWindow, TControl и TEditWindow, так что все они также являются оконными объектами, хотя на самом деле это не окна в полном смысле слова. Типы MDI используются в приложениях ObjectWindows, которые соответствуют стандарту многодокументального интерфейса Windows. Об MDI и этих типах рассказывается в Главе 14. TControl определяет управляющие элементы, такие как командные кнопки и блоки списков (см. Главу 12). Чаще всего новые оконные типы являются производными от TWindow.Эта глава охватывает типы TWindow и TEditWindow и содержит примеры регистрации новых классов окон.
Определение новых атрибутов регистрации
Чтобы отклониться от стандартных характеристик, вы должны заполнить поля записи TWndClass с различными данными в методе GetWindowClass.GetWindowClass воспринимает в качестве аргумента-переменной запись TWndClass и заполняет ее поля новыми атрибутами регистра- ции. Когда вы определяете новый метод GetWindowClass, вам следует всегда сначала для установки значений по умолчанию вызывать нас- ледуемый метод TWindow.GetWindowClass, а затем устанавливать по- ля, которые вы хотите изменить.
Например, в поле hCursor хранится описатель ресурса курсора. Для IBeamWindow определяется метод GetWindowClass:
procedure IBeamWindow.GetWindowClass(var AWndClass: TWndClass); begin inherited GetWindowClass(AWndClass); AWndClass.hCursor := LoadCursor(0, idc_IBeam); end;
Примечание: idc_Beam - это константа, представляющая один из курсоров Windows.
Кроме окон, диалоговым окнам (не диалоговым блокам) необхо- димо регистрировать классы окна (см. Главу 11). Диалоговым блокам и управляющим элементам классы окон не требуются.
Оптимизация методов Paint для прокрутки
В приведенном выше примере рисуется 50 прямоугольников, но не делается даже попытки определить, все ли прямоугольники видны в области клиента окна. Это может привести к излишним усилиям на дорисовку невидимых изображений. Для оптимизации рисования в окне методом Paint можно использовать метод TScroller.IsVisibleRect.Приведенный ниже метод ScrollWindow.Paint использует IsVisibleRect для определения, нужно ли вызывать функцию Windows Rectange. Rectange воспринимает аргументы в единицах устройства, а VisibleRect в единицах прокрутки. С этой целью вершина прямоу- гольника X1 Y1 и ширина прямоугольника (X2-X1) и его высота (Y2-Y1) должны быть разделены на соответствующее число единиц до вызова IsVisibleRect:
procedure TScrollWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var X1, Y1, X2, Y2, I: Integer; begin for I:=0 to 49 do begin X1 := 10 + I * 8; Y1 := 30 + I * 5; X2 := X1 + X1; Y2 := X1 + Y1 * 2; if Scroller^.IsVisibleRect(X1 div 8, Y1 div 15, (X2-X1) div 8, (Y2-Y1) div 15) then Rectangle(PaintDC, X1, Y1, X2, Y2); end; end;
Отслеживание полос прокрутки
В дополнение к автоматической прокрутке, приведенный выше пример программы будет отслеживать запросы на прокрутку, сдвигая при нажатой кнопке "мыши" маркер полосы прокрутки. Другими слова- ми картинка сдвигается уже при нажатой кнопке. Эта особенность дает действительную обратную связь, и пользователь может сдвигать нужную часть изображения не отпуская кнопку "мыши".Однако, в некоторых случаях этот эффект нежелателен. Напри- мер, если вы просматриваете большой текстовый файл, такое отсле- живание может замедлить работу, поскольку возникает необходимость постоянно считывать информацию с диска и отображать порцию текста для каждого движения "мыши". В такой ситуации лучше отменить этот эффект:
Scroller^.TrackMode:=False;
Теперь никакой прокрутки не происходит до момента отпускания кнопки на мыши, и в области клиента будет лишь однократно показа- на нужная часть картинки.
Переопределение используемых по умолчанию атрибутов
При создании новых оконных типов, производных от TWindow, вы обычно определяете новый конструктор Init (особенно если хотите получить атрибут создания, отличных от используемого по умолча- нию). Если вы хотите переопределить Init, то можете заново задать атрибуты объекта, непосредственно изменяя поле Attr после вызова Init.Если вы переопределили Init, убедитесь, что первое, что он делает - это вызов наследуемого метода TWindow.Init, устанавлива- ющего используемые по умолчанию атрибуты. Затем вы можете изме- нить по своему выбору любой из атрибутов. Например, типичное окно может определять конструктор Init, который устанавливает атрибут Menu:
constructor TWindowType.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, 'TheMenu'); AChildWindow := New(PChildWindowType, Init(@Self, 'Заголовок дочернего окна')); List1 := New(PListBox, Init(@Self, id_ListBox, 201, 20, 20, 180, 80)); . . . end;
Пример прокрутки
Scroll - это полное приложение с графическим выводом, допус- кающим прокрутку. Показанная ниже программа рисует последователь- ность прямоугольников, затем увеличивает их размер, так что вся картинка не умещается в область клиента окна, отображенного на обычном экране VGA. С помощью полос прокрутки вы можете просмат- ривать различные части рисунка или автоматически прокручивать картинку, удерживая нажатой левую кнопку "мыши" и перемещая ее из области клиента.program Scroll;
uses Strings, WinTypes, WinProcs, OWindows;
type TScrollApp = object(TApplication) procedure InitMainWindow; virtual; end;
PScrollWindow = ^TScrollWindow; TScrollWindow = object(TWindow) constructor Init(ATitle: PChar); procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual; end;
procedure TScrollApp.InitMainWindow; begin MainWindow := New(PScrollWindow, Init('Boxes')); end;
constructor TScrollWindow.Init(ATitle: PChar); begin inherited Init(nil, ATitle); Attr.Style := Attr.Style or ws_VScroll or ws_HScroll; Scroller := New(PScroller, Init(@Self, 8, 15, 80, 60)); end;
procedure TScrollWindow.PAint(PaintDC: HDC; var PaintInfo: TPaintStruct); var X1, Y1, I: Integer; begin for I := 0 to 49 do begin X1 := 10 + I*8; Y1 := 30 + I*5; Rectangle(PaintDC, X1, Y1, X1 + X1, X1 + Y1 * 2); end; end;
var ScrollApp: TScrollApp;
begin ScrollApp.Init('ScrollApp'); ScrollApp.Run; ScrollApp.Done: end.
Прокрутка содержимого окон
В большинстве случаев для прокрутки текущей области просмот- ра пользователи используют полосы прокрутки вдоль края окна. В отличие от стандартных управляющих элементов типа полос прокрутки полосы прокрутки окна являются частью самого окна.ObjectWindows управляет прокруткой окна, предоставляя каждо- му оконному объекту поле Scroller, которое может указывать на объект TScroller. Объект прокрутки TScroller обеспечивает автома- тизированный способ прокрутки в окнах текста и графики. Кроме то- го, TScroller может прокручивать окна, когда пользователь переме- щает "мышь" за область клиента окна (это называется автоматичес- кой прокруткой и действует даже для окон, которые не имеют полос прокрутки).
Регистрация нового класса
Чтобы изменить атрибут регистрации, такой как курсор или пиктограмму, вам нужно написать два метода - GetClassName и GetWindowClass - и определить новый класс окна. Каждый раз, когда вы изменяете атрибуты регистрации, вам нужно изменить имя класса. Если класс регистрации с данным именем уже зарегистрирован в Windows, другие классы с тем же именем класса регистрироваться не будут - они получат атрибуты уже зарегистрированного класса.Создание элементов окна
После построения оконного объекта вам нужно сообщить Windows, что требуется создать связанные с объектом элементы эк- рана. Это делается с помощью вызова MakeWindow объекта приложения и передачи ему в качестве параметра указателя на объект окна.if Application^.MakeWindow(AWindow) <> nil then { успешное создание } else { неуспешное создание }
MakeWindow вызывает два важных метода: ValidWindow и Create. ValidWindow проверяет успешность построение объекта окна, прове- ряя поле Status. Если по каким-либо причинам конструктор завер- шился неуспешно, то MakeWindow возвращает nil. При успешном вы- полнении конструктора MakeWindow переходит на метод Create окон- ного объекта.
Create - это метод, который фактически сообщает Windows о создании элемента экрана. Если Create завершается неудачно, MakeWindow возвращает nil. В противном случае возвращается указа- тель на оконный объект. Для работы с элементом экрана Create так- же устанавливает поле HWindow.
Хотя этот метод фактически создает элемент экрана, вы обычно не можете вызывать Create явно. Основное окно приложения автома- тически создается при запуске программы методом TApplication.InitInstance.
Все прочие окна приложения являются дочерними окнами, прямо или косвенно порождаемыми основным окном, а дочерние окна созда- ются обычно в методе SetupWindow или в его порождающих оконных объектах, либо с помощью MakeWindow динамически на этапе выполне- ния.
Примечание: Дочерние окна и SetupWindow описываются в Главе 9 "Интерфейсный объекты".
В общем случае порождающие окна обычно вызывают для своих дочерних окон методы Init и MakeWindow. Атрибуты оконного объекта обычно устанавливаются их методами объекта порождающего окна. Поскольку основное окно приложения не имеет порождающего окна, объект приложения строит и создает его при запуске приложения.
Установка атрибутов создания
Типичное приложение Windows имеет много различных типов окон: перекрывающиеся или всплывающие, окна с рамкой, прокручива- емые окна, окна с заголовком и др. Эти атрибуты стилей, а также заголовок и меню окна задаются при инициализации оконного объекта и используются при создании элементов окна.Атрибуты создания оконного объекта, такие как стиль, заголо- вок и меню, записываются в поле Attr объекта - записи типа TWindowAttr. TWindowAttr содержит следующие поля:
Атрибуты создания окна Таблица 10.1 +------------+--------------+-----------------------------------+ | Поле | Тип | Использование | +------------+--------------+-----------------------------------| | Title | PChar | Строка заголовка. | | | | | | Style | Longint | Комбинированная константа стиля. | | | | | | Menu | HMenu | Описатель ресурса меню. | | | | | | X | Integer | Горизонтальная координата экрана| | | | верхнего левого угла окна. | | | | | | Y | Integer | Вертикальная координата экрана| | | | верхнего левого угла окна. | | | | | | W | Integer | Начальная ширина окна в координа-| | | | тах экрана. | | | | | | H | Integer | Начальная высота окна в координа-| | | | тах экрана. | | | | | +------------+--------------+-----------------------------------+
^################################################################ #(0,0)########################################################### #####(X,Y)####################################################### #####v########################################################### #####+---------------------------------------+--+--+--########### #####| XXXXXXXXXXXXXXXX Title XXXXXXXXXXXXXX| | | ^########### #####+---------------------------------------+--+--| |########### #####| Menu | |########### #####+---------------------------------------------| |########### #####| | |########### #####| | |########### #####| | H########### #####| | |########### #####| | |########### #####| | |########### #####| | |########### #####| | |########### #####| | v########### #####+---------------------------------------------+--########### #####|<------------------W------------------------>|############# ################################################################# #################################################################
Рис. 10.1 Атрибуты окна.
Установка размеров страницы
По умолчанию размер страницы (XPage и YPage) устанавливается в соответствии с размером области клиента окна. При изменении размеров окна механизм прокрутки учитывает эту информацию. Метод окна WMSize вызывает метод прокрутки SetPageSize, который уста- навливает поля объекта XPage и YPage на основании текущих разме- ров области клиента окна и значений XUnit и YUnit. Для отмены этого механизма и непосредственной установки размеров страницы вы должны переписать унаследованный метод объекта окна WMSize и не вызывать SetPageSize:procedure TTestWindow.WMSize(var Msg: TMessage); begin DefWndProc(Msg); end;
Затем вы можете непосредственно установить XPage и YPage в конструкторе окна (или в производном конструкторе TScroller):
constructor ScrollWindow.Init(AParent:PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Attr.Style:=Attr.Style or ws_VScroll or ws_HScroll; Scroller:=New(PScroller, Init(@Self, 8, 15, 80, 400)); Scroller^.XPage:=40; Scroller^.YPage:=100; end;
Задание атрибутов регистрации
В ходе инициализации оконного объекта путем заполнения поля объекта Attr вы можете установить несколько атрибутов окна, такие как его стиль, расположение и меню. Эти атрибуты используются для создания соответствующего оконного элемента, поэтому они называ- ются атрибутами создания.Другие атрибуты, включая фоновый цвет, пиктограмму представ- ления и курсора "мыши", более тесно связаны с данным типом окон- ного объекта и не могут изменяться в ходе работы программы. Эти присущие окну атрибуты называются атрибутами регистрации, так как они устанавливаются при регистрации класса окна в Windows.
Задание для окна объекта прокрутки
Чтобы задать для окна объект прокрутки, постройте в конс- трукторе своего оконного объекта объект TScroller и присвойте его полю Scroller. Вам нужно установить начальный размер единицы и диапазона, но позднее вы можете их изменить.При использовании объекта прокрутки для автоматической прок- рутки полосы прокрутки не требуются, но многие прокручиваемые окна их имеют. Чтобы добавить в окно полосы прокрутки, добавьте в поле Attr.Style ws_VScroll, ws_HScroll (или то и другое).
Приведем пример конструктора для текстового окна редактиро- вания:
constructor TTextWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Attr.Style := Attr.Style or ws_VScroll or ws_YScroll; Scroller := New(PScroller, Init(@Self, 8, 15, 80, 400)); end;
В качестве аргументов TScroller воспринимает прокручиваемое окно и начальные значения для полей XUnit, YUnit, XRange и YRange соответственно. Атрибуты строки и страницы получают значения по умолчанию.
После вывода окна на экран содержимое его области клиента можно прокручивать вертикально и горизонтально, используя для этого полосу прокрутки или автоматическую прокрутку. Метод Pant окна просто рисует на экране графическую информацию, необходимую для уведомления о прокрутке. Как описывается в конце этого разде- ла, метод Paint можно оптимизировать для вывода только части ри- сунка.
Запрещение автоматической прокрутки
Объект TScroller может по умолчанию выполнять автоматическую прокрутку, но установка поля AutoMode TScroller в значение False отключает это средство. Окно-владелец может сделать это в конс- трукторе после построения объекта TScroller:Scroller := New(PScroller, Init(@Self, 8, 15, 80, 60)); Scroller^.AutoMode :=False;
Если AutoMode равно False, то прокрутка может выполняться только с помощью полос прокрутки. Полезная особенность автомати- ческой прокрутки состоит в том, что чем дальше вы сдвинете "мышь" от области клиента окна, тем быстрее будет происходить прокрутка окна. В зависимости от удаления мыши приращение прокрутки будет обратно пропорционально значению параметра строк и прямо пропор- ционально значению параметра страницы.
Pascal 7 & Objects
Ассоциирование объектов управляющих элементов
До этого момента мы имели дело с реакцией блоков диалога на управляющие информационные сообщения, которая использовала методы реакции, основанные на дочерних идентификаторах. Однако, иногда более предпочтительно, чтобы управляющий элемент сам реагировал на сообщение. Например, вам может потребоваться управляющий эле- мент редактирования, который позволяет вводить только цифры, или командная кнопка, которая меняет стиль при своем "нажатии". Это можно реализовать с помощью объектов управляющих элементов в ок- нах (см. Главу 12). Однако, чтобы это имело место для управляющих элементов диалога, созданного с файлом ресурса, вам нужно исполь- зовать для конструирования объекта другой конструктор.При организации связей вы создаете объект управляющего эле- мента для представления управляющего объекта диалога. Этот объект управления дает вам гибкость в реакции на управляющие сообщения. Он дает вам возможность использования набор методов объектов уп- равляющих элементов, описанных в Главе 12.
Для связи объекта с управляющим элементом определите сначала объект управляющего элемента. Он должен быть создан в конструкто- ре диалога. Однако, вместо того, чтобы использовать конструктор Init, как это показано в Главе 12, следует использовать InitResource, который берет в качестве параметров порождающее ок- но и идентификатор управляющего элемента (из ресурса диалога). Это приводит к вызову методов реакции на сообщения объектов уп- равляющих элементов вместо обработки элементов по умолчанию. Для этого нужно определить новый тип объекта, производный от предус- мотренного типа управляющего элемента.
Обратите внимание, что в отличие от задания оконного объек- та, которое предполагает два шага (Init и MakeWindow), поскольку управляющий элемент уже существует, связь объекта с управляющим элементов выполняется за один шаг: он загружается из диалогового ресурса. Вам нужно только сообщить InitResource, какой управляю- щий элемент из ресурса вы хотите связать с объектом, используя идентификатор управляющего элемента.
Файловые диалоговые блоки
Файловые диалоговые блоки являются другим типом диалогов, поставляемых с ObjectWindows в типе TFileDialog. Файловый диало- говый блок следует использовать каждый раз, когда вы желаете по- будить пользователя ввести имя файла, например в функциях File Open и File Save во всех приложениях. См. Рис. 11.1.+-----------------------------------------------------+ |#=#XXXXXXXXXXXXXXXXXXXXFile OpenXXXXXXXXXXXXXXXXXXXXX| +-----------------------------------------------------| | | | +------------------+ +----------+ | | Имя файла: | *.pas | |####OK####| | | +------------------+ +----------+ | | +----------+ | | Каталог: a:\ |##Cancel##| | | +----------+ | | Файлы: Каталоги: | | +--------------+-+ +--------------+-+ | | |collect3.pas |^| |[-a-] |^| | | |collect4.pas +-| |[-c-] +-| | | |diatest.pas |#| |[-f-] |X| | | |edittest.pas |X| |[-g-] |#| | | |ewndtest.pas |#| |[-h-] |#| | | |helpwind.pas |#| |[-i-] |#| | | |lboxtest.pas |#| |[-j-] |#| | | |mditest.pas +-| |[-k-] +-| | | |paltest.pas |v| |[-w-] |v| | | +--------------+-+ +--------------+-+ | | | +-----------------------------------------------------+
Рис. 11.1 Файловый диалоговый блок.
В большинстве случаев файловые диалоговые блоки выполняются как режимные. С объектом файлового диалога связан ресурс файлово- го диалогового блока, имеющийся в ObjectWindows в файле OSTDDLGS.RES. Использование модуля OStdDlgs автоматически включа- ет файл ресурса.
Инициализация файлового диалогового блока
TFileDialog определяет конструктор Init, который позволяет задать маску файла и буфер для считывания имени файла. Маска фай- ла (такая как '*.TXT') ограничивает файлы, перечисляемые в комби- нированном блока (аналогично тому, как это делается в команде DOS DIR *.TXT). Имя файла и маска передаются в записи типа TFileDlgRec. Приведем пример вызова файлового диалогового блока Init:var FileRec: TFileDlgRec; IsOpen: Boolean; begin StrCopy(FileRec.Name, 'TEST1.TXT'); StrCopy(FileRec.Mask, 'C:\*.TXT'); IsOpen := True; AFileDlg.Init(@Self, FileRec, IsOpen); . . . end;
Последний параметр указывает, будет ли диалог диалогом отк- рытия или сохранения (как описывается в следующем разделе).
Использование диалоговых блоков ввода
Диалоговые блоки ввода - это простые объекты диалоговых бло- ков, определяемых типом TInputDialog, которые выводят пользовате- лю подсказку со строкой текста.Вы можете запускать диалоги ввода как режимные или безрежим- ные диалоговые блоки, но обычно вы будете выполнять их как режим- ные. С объектом диалога ввода связан ресурс диалога ввода. Он на- ходится в файле ObjectWindows OSTDDLGS.RES.
Примечание: Использование модуля StdDlgs автоматически включает ресурсы в OSTDDLGS.RES.
Каждый раз при конструировании диалога ввода с использовани- ем метода Init, вы задаете для диалога заголовок, подсказку и текст по умолчанию. Покажем вызов конструктора Init объекта диа- лога ввода:
var SomeText: array[079] of Char; begin AnInputDlg.Init(@Self, 'Caption', 'Prompt', SomeText, SizeOf(SomeText)) . . . end;
В данном примере EditText - это текстовый буфер, который за- полняется вводом пользователя, когда он "нажимает" кнопку OK. Когда пользователь "нажимает" кнопку OK или клавишу Enter, строка введенного в диалоге ввода текста автоматически передается в мас- сив символов, который хранит текст по умолчанию. В данном примере конструируется и отображается блок диалога и считывается текст:
procedure TSampleWindow.Test(var Msg: TMessage); var EditText: array[0255] of Char; begin EditText:='Frank Borland'; if ExecDialog(New(PInputDialog, Init(@Self, 'Data Entry', 'Введите имя:', EditText, SizeOf(EditText)))) = id_OK then MessageBox(HWindow, EditText, 'Имя =', mb_OK); else MessageBox(HWindow, EditText, 'Имя пока имеет значение:',mb_OK); end;
Использование диалоговых окон
Основная разница между диалоговыми блоками и окнами состоит в том, что диалог имеет соответствующий ресурс и задает тип и расположение своих управляющих элементов. Но окно также может иметь управляющие элементы.Одним из подходов размещения управляющих элементов в окне является использование объектов управляющих элементов (как пока- зано в Главе 12). Другой подход - это слияние возможностей диало- говых блоков и окон, как это делается в объектном типе TDlgWindow, что позволяет получить гибридный объект, называемый диалоговым окном. Второй подход предусматривает более удобный способ построения и управления многими управляющими элементами в окне. Кроме того, он предлагает для диалоговых блоков более гиб- кие средства окон.
TDglWindow является потомком TDialog и наследует его методы, такие как Execute, Create, Ok и EndDlg. Как и диалоговые блоки, диалоговые окна имеют соответствующий ресурс диалогового блока. С другой стороны, как и окна, диалоговые окна имеют соответствующий класс окон, определяющий среди всего прочего пиктограмму, курсор и меню. Из-за связи с оконным классом в потомке TDlgWindow следу- ет переопределять методы GetClassName и GetWindowClass. Этот класс должен быть тем же, что и перечисленный в диалоговом ресур- се.
В большинстве случаев вы будете выполнять диалоговые окна как и другие окна или безрежимные диалоговые окна с помощью мето- дов Create и Show, а не метода Execute.
В тех случаях, когда основное окно должно содержать много сложных управляющих элементов, хорошим использованием диалоговых окон является основное окно приложения. Например, программа-каль- кулятор может иметь в качестве основного окна диалоговое окно, где кнопки калькулятора заданы как управляющие элементы диалого- вого ресурса. Это позволило бы вывести в основном окне также ме- ню, пиктограмму и курсор.
Использование объектов диалоговых блоков
Использование объектов диалоговых блоков аналогично исполь- зованию объектов всплывающего окна. Диалоги являются дочерними окнами своего порождающего окна. Для простых диалоговых блоков, которые появляются на короткое время, вся обработка диалога может быть выполнена одним методом объекта порождающего окна. Диалог может быть сконструирован, выполнен и удален в одном методе, и нет необходимости хранить диалог в поле объекта. Для более слож- ных диалогов может потребоваться записать диалоговый блок в поле оконного объекта вашего диалогового блока.Подобно всплывающим окнам и управляющим элементам, диалого- вые блоки являются дочерними окнами и при конструировании добав- ляются к списку ChildList порождающих окон.
Использование объекта диалогового блока предусматривает сле- дующие шаги:
* Построение объекта.
* Выполнение диалогового окна.
* Закрытие диалогового окна.
Использование предопределенных диалоговых окон
Для выполнения двух типов общих функций ObjectWindows пре- дусматривает стандартные диалоговые блоки. Одно их них, окно диа- логового блока, выводит пользователю однострочную подсказку. Дру- гое, файловое диалоговое окно, позволяет пользователю задать имя файла и каталог для открытия или сохранения файла.Построение объекта
Диалоговые блоки конструируются и специфицируются с помощью описания ресурса, создаваемого вне программы. Ресурс диалогового окна описывает внешний вид и размещение управляющих элементов, таких как кнопок, блоков списка, областей редактирования и текс- товых строк. Он описывает только внешний вид диалогового блока и не касается его поведения - за это отвечает прикладная программа.Каждый ресурс диалогового блока имеет идентификатор, который может быть номером идентификатора (Word) или строкой (PChar). Этот идентификатор позволяет объекту диалогового блока задавать, какой ресурс используется для определения его внешнего вида.
Пример связи
----------------------------------------------------------------- В файле с текстом программы DIALTEST.PAS, основное окно име- ет режимный диалог, определенный типом диалога TTestDialog. Эта программа обеспечивает двухстороннюю связь между объектом диалога и его управляющими элементами. Два метода - IDBN1 и IDLB1 - явля- ются методами реакции, основанными на дочерних идентификаторах, и вызываются при выборе пользователем управляющих элементов (дочер- них окон). Например, при выборе пользователем кнопки диалога BN1 ('Fill List Box') вызывается метод IDBN1. Аналогично, когда поль- зователь делает выбор в блоке списка, вызывается IDLB1. С другой стороны, для заполнения блока списка элементами текста код метода IDBN1 посылает в диалог управляющее сообщение, lb_AddString, ис- пользуя метод диалога SendDlgItemMsg,Эта программа также показывает как путем создания нового ти- па диалога и связывания его с ресурсом диалога в вызове конструк- тора Init метода TestWindow.RunDialog создаются новые диалоги. Полный текст программы вы можете найти на дистрибутивных дисках.
Работа с безрежимными диалоговыми блоками
Диалоговые блоки отличаются от других дочерних окон, таких как всплывающие окна и управляющие элементы, тем, что они за вре- мя существования своего порождающего окна создаются и уничтожают- ся многократно и редко выводятся на экран и уничтожаются вместе с порождающим окном. Обычно программа создает диалоговый блок в от- вет на выбор меню, щелчок кнопкой "мыши", ошибку или другое собы- тие.Таким образом, нужно убедиться, что вы не строите объекты диалоговых блоков снова и снова, не уничтожая их. Помните о том, что все построенные диалоговые объекты автоматически включаются в списки дочерних окон их порождающих окон.
Примечание: К режимным диалоговым блокам это не отно- сится, так как они автоматически уничтожаются при закрытии.
Работа с управляющими элементами
Все блоки диалога, кроме самых простейших, имеют (как дочер- ние окна) несколько управляющих элементов (например, управления редактированием, блоки списка и командные кнопки). Обратите вни- мание на то, что эти управляющие элементы являются не объектами управляющих элементов, а только управляющими интерфейсными эле- ментами, без методов и полей объекта. Эта глава кроме того пока- зывает альтернативные методы, позволяющие связывать объекты уп- равления с элементами управления диалоговых блоков с использова- нием InitResource.Примечание: Использование управляющих объектов в окне (но не блоков диалога) показано в Главе 12.
Между объектом диалога и его элементами управления имеется двухсторонняя связь (можно сказать диалог). С одной стороны диа- логу нужно манипулировать его управляющими элементами, например, для заполнения блока списка. С другой стороны ему нужно обрабаты- вать и реагировать на сгенерированные сообщения управляющих собы- тий, например, когда пользователь выбирает элемент блока списка.
Режимные и безрежимные диалоговые блоки
Режимные диалоговые блоки являются наиболее общими блоками диалога. Аналогично генерируемым функцией MessageBox блокам сооб- щений, режимные диалоги отображаются для специфических целей на короткий отрезок времени. Слово "режимный" означает, что пока отображается диалог, пользователь не может выбрать или использо- вать его порождающее окно. Пользователь должен воспользоваться диалогом и выбрать командную кнопку OK или Cancel для прекращения диалога и возвращению к работе с программой. Режимный диалог как бы "замораживает" выполнение оставшейся части программы.Безрежимный диалоговый блок не приостанавливает выполнения программы. Как и оконный объект, он может создаваться и выпол- няться в одном шаге с помощью MakeWindow:
Application^.MakeWindow(ADlg);
В любое момент вы можете считать данные из диалогового окна (если объект диалогового блока еще существует). Чаще всего это выполняется в методе OK, который вызывается при активизации поль- зователем командной кнопки OK.
Выполнение безрежимных диалоговых блоков
Безрежимные диалоги похожи на всплывающие окна и управляющие элементы. Основная причина, по которой вы не можете удалять без- режимными диалогами сразу же после их отработки (в отличие от ре- жимных), состоит в том, что вы заранее не знаете, когда пользова- тель закроет блок диалога. (Помните о том, что в режимных диало- гах метод ExecDialog не возвращает значения до закрытия диалога.) Следовательно лучше всего конструировать безрежимные диалоги в конструкторе его порождающего окна и хранить в поле порождающего объекта.В отличие от окон и объектов управления, используемых в ка- честве дочерних окон, диалоги автоматически не отображаются при выводе их порождающих окон.
constructor ParentWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin TWindow.Init(AParent, ATitle); ADlg := New(PSampleDialog, Init(@Self, 'EMPLOYEEINFO')); end;
Затем, каждый раз, когда вы хотите отобразить диалог, соз- дайте и выведите его:
begin Application^.MakeWindow(ADlg) end;
И, наконец, отдельный объект диалога будет автоматически удаляться при закрытии его порождающего окна.
Выполнение диалоговых блоков
Выполнение диалогов аналогично созданию и отображению окна. Однако, поскольку диалоги обычно появляются на более короткий от- резок времени, некоторые этапы могут быть сокращены. Это в значи- тельной степени зависит от того, будет ли диалоговый блок отобра- жаться как режимный или безрежимный.Выполнение файловых диалоговых блоков
Существует два вида файловых диалоговых блоков: диалоговый блок открытия файла и диалоговый блок сохранения файла. Они раз- личаются текстом кнопки в правом верхнем углу диалогового окна. В зависимости от того, запрашивает пользователь открытие или сохра- нение файла, на командной кнопке будет написано Open или Save. Когда вы вызовите ExecDialog, то получите тип диалогового блока, заданных в конструкторе IsOpen параметром типа Boolean. Если фай- ловый диалоговый блок строится с IsOpen, установленным в True, то диалоговый блок будет работать как диалоговый блок открытия фай- ла. Если он строится с IsOpen, установленным в False, то файловый диалоговый блок будет блоком сохранения файла.Дополнительным средством файлового диалогового блока ObjectWindows является то, что он выводит пользователю подсказку, хочет ли пользователь сохранить файл с именем уже существующего файла (см. Рис. 11.2). В другой раз вы можете запросить пользова- теля, хочет ли он открыть новый файл или очистить текущий текст без сохранения. Поскольку это должно происходить перед выводом файлового диалогового блока, то не является частью поведения это- го блока. В примере программы Steps в первой части данного руко- водства перед загрузкой рисунка из файла проверяется метод CanClose его основного окна.
+-------------------------------------------+ |#=#XXXXXXFile exists! Overwrite it?XXXXXXXX| +-------------------------------------------| | | | C:\TEMP\NICELINE.PTS | | | | +----------+ +----------+ | | |###Yes####| |###No#####| | | +----------+ +----------+ | | | +-------------------------------------------+
Рис. 11.2 Предупреждение пользователя о перезаписи существу- ющих файлов.
File exists! Overwrite it? - файл существует, затереть его?
Приведем пример типичного использования диалогового окна:
procedure TMyWindow.OpenSelectedFile; var FileRec: TFileDlgRec; begin StrCopy(FileRec.Name, 'HEDGEHOG.PAS'); StrCopy(FileRec.Mask, '*.PAS'); if ExecDialog(New(PFileDialog, Init(@Self, FileRec, True))) = id_Ok then begin Assign(AFile, StrPas(FileRec.Name)); . . . end; end;
Выполнения режимных диалоговых блоков
В случае режимных диалоговых блоков лучше всего, вероятно, строить, выполнять и уничтожать все объекты в одном методе (как показано в примерах данной главы). Таким образом, при каждом вы- воде диалогового блока это будет новый объект.Объекты приложения имеют режимный эквивалент MakeWindow, ко- торый называется ExecDialog. Аналогично MakeWindows, ExecDialog проверяет допустимость передаваемого объекта диалогового блока (то есть успешность выполнения конструктора объекта и отсутствие ситуации нехватки памяти), а затем выполняет диалоговый блок, де- лая его модальным.
ExecDialog возвращает целочисленное значение, указывающее, что пользователь закрывает диалоговое окно. Возвращаемое значение - это идентификатор задействованного пользователем управляющего элемента, такой как id_Ok для командной кнопки OK или id_Cancel для командной кнопки Cancel. После завершения выполнения диалого- вого окна ExecDialog уничтожает объект диалогового окна.
Таким образом, с помощью одного вызова метода ExecDialog вы можете создать, вывести на экран и завершить диалоговый блок.
ADlg := New(PSampleDialog, Init(@Self, 'RESOURCEID')); ReturnValue := Application^.ExecDialog(ADlg); if ReturnValue = id_OK then { кодирование для выборки данных и обработки диалога } else if ReturnValue = id_Cancel then { нажата Cancel }
Вызов конструктора
Чтобы построить объект диалогового блока, вызовите конструк- тор Init. Init воспринимает в качестве своих параметров указа- тель на порождающее окно и параметр типа PChar, представляющий имя ресурса диалога:ADlg:=New(PSampleDialog, Init(@Self, 'EMPLOYEEINFO'));
Если идентификатор задается номером, его требуется привести с помощью MakeIntResource к PChar:
Dlg := New(PSampleDialog, Init(@Self, PChar(120)));
Так как диалоговые блоки обычно строятся внутри метода окон- ного объекта, порождающее окно почти всегда задается как Self. Объекты диалоговых блоков, не создаваемые оконными объектами, должны иметь в качестве порождающего Applicartion^.MainWindow (поскольку это единственный оконный объект, всегда присутствующий в каждой программе ObjectWindows).
Взаимодействие с управляющим элементом
Windows определят набор сообщений управляющих элементов, ко- торые посылаются от приложения к Windows. Например, имеются сле- дующие сообщения блока списка: lb_GetText, lb_GetCurSel и lb_AddString. Сообщения управляющих элементов задают специфичес- кое управление и несут с собой информацию в аргументах wParam и lParam. Каждый управляющий элемент в ресурсе диалога имеет номер идентификатора, который вы используете для задания управляющего элемента, принимающего сообщение. Для посылки сообщения управляю- щему элементу нужно вызвать метод TDialg SendDlgItemMsg. Напри- мер, данный метод заполнит блок списка диалога элементами текста путем посылки сообщения lb_AddString:procedure TestDialog.FillListBox(var Msg: TMessage); var TextItem: PChar; begin TextItem := 'Item 1'; SendDlgItemMsg(id_LB1, lb_AddString, 0, Longint(TextItem)); end;
где id_LB1 есть константа, равная ID блока списка.
Если вам потребуется описатель одного из управляющих элемен- тов диалога, его можно получить методом GetItemHandle:
GetItemHandle(id_LB1);
Когда пользователь выбирает управляющий элемент, например, "нажимает" командную кнопку или делает выбор в блоке списка, диа- логовым блоком управляющего элемента порождающего окна принимает- ся специальное сообщение, основанное на дочернем окне и называе- мое управляющим сообщением (сообщение управляющего элемента). Оп- ределим метод реакции на сообщение, основанное на дочернем иден- тификаторе, в порождающем типе диалога для каждого дочернего уп- равляющего элемента:
TTestDialog = object(TDialog) procedure HandleBN1Msg(var Msg: TMessage); virtual id_First + id_BN1; procedure HandleListBox(var Msg: TMessage); virtual id_First + id_LB1; end;
В данном примере id_BN1 - это идентификатор кнопки управляю- щего элемента, а id_LB1 - это идентификатор блока списка. Щелчок "мышью" на командной кнопке даст сообщение, посылаемое в диалого- вый блок. Объект диалогового блока реагирует через динамический метод с индексом, основанным на идентификаторе кнопки IDBN1.
Завершение диалогов
Каждый блок диалога должен иметь способ его закрытия пользо- вателем. Чаще всего это кнопки OK и/или Cancel. Потомки TDialog автоматически отреагируют на нажатие одной из этих кнопок вызовом метода EndDlg, который заканчивает диалог. Вы можете разработать новые средства завершения диалога, если только они приводят к вы- зову EndDlg. Для изменения поведения при закрытии вы можете пере- определить методы OK и Cancel.Например, вы можете переопределить метод OK таким образом, что введенные данные будут копироваться в буфер, который находит- ся вне объекта блока диалога. Если ввод был осуществлен некор- ректно, вы можете вывести блок сообщения или сгенерировать звуко- вой сигнал. Если ввод был сделан верно, вы можете вызвать EdnDlg. Переданное в EndDlg значение становится возвращаемым значением ExecDialog.
Как и в случае оконных объектов, объект диалога вызывает CanClose до закрытия блока диалога, как это имело место для объ- ектов окна. Вы можете переписать CanClose для учета условий зак- рытия, как для блока диалога, который проверяет ввод пользовате- ля. При переписывании CanClose нужно быть уверенным в том, что вызывается унаследованный от него метод, т.к. он вызывает методы CanClose дочерних окон.
Pascal 7 & Objects
Что такое объекты управляющих элементов?
Для Windows управляющие элементы - это просто специализиро- ванные виды окон. В ObjectWindows тип TControl является потомком типа TWindow, так что большинство объектов управляющих элементов можно использовать как все другие оконные объекты. Объекты управ- ляющих элементов по способу их создания и уничтожения и способу их поведения (как дочерние окна) аналогичны оконным объектам. Од- нако они отличаются от других окон способом реакции на сообщения. Например, методы Paint объектов управляющих элементов запрещены. Windows берет на себя функции по отображению своих стандартных управляющих элементов.Может оказаться, что перечисленные в приведенной выше табли- це управляющие элементы отвечают всем потребностям вашего прило- жения. Однако могут возникать случаи, когда требуется определить наследующие типы управляющих элементов. Например, вы можете соз- дать специализированный блок списка TFontListBox, производный от TListBox, содержащий имена всех доступных вашему приложению шриф- тов и автоматически выводящих их при создании нового экземпляра объекта.
Тип TControl, как и TWindowsObject, является абстрактным объектным типом. Вы можете создать экземпляры его потомков - TListBox, TButton и другие - но не можете создать экземпляр TControl.
Заметим, что вам, возможно, никогда не потребуется создавать новый объектный тип, наследующий непосредственно из TControl. TControl инкапсулирует свойства и стандартные управляющие элемен- ты, о которых уже знает Windows. Создание специализированных уп- равляющих элементов описывается в данной главе ниже.
Действие, аналогичное диалоговому блоку
Диалоговый блок с управляющими элементами позволяет пользо- вателю с помощью клавиши Tab циклически перемещаться по всем эле- ментам диалогового блока. Он может также использовать клавиши стрелок для выбора в групповом блоке кнопок с зависимой фиксаци- ей. Чтобы эмулировать этот клавиатурный интерфейс для окон с уп- равляющими элементами, вызовите для оконного объекта в его конс- трукторе метод EnableKBHandler объекта TWindowsObject.Для чего используется буфер передачи?
В качестве альтернативы вы можете не определять производный объект, а определить соответствующую запись, представляющую сос- тояние управляющих элементов окна или диалога. Эта запись называ- ется буфером передачи, поскольку легко передает информацию о сос- тоянии между буфером и набором управляющих элементов.Например, ваша программа может иметь режимный блок диалога и после его закрытия, выделить информацию из буфера передачи от- носительно состояния каждого из его управляющих элементов. Следо- вательно, при повторном вызове пользователем блока диалога, его управляющие элементы будут выведены в соответствии с их состояни- ем перед последним закрытием диалога. Кроме того, вы можете уста- новить начальное состояние каждого из управляющих элементов и на основании данных буфера передачи. Вы можете явно передавать дан- ные в любом направлении в любой момент времени, например, устано- вить значения управлений равными их предыдущим значениям. Окно или безрежимный блок диалога с управляющими элементами также мо- гут использовать механизм передачи для установки или выяснения информации о состоянии в любой момент времени.
Механизм передачи требует для представления управляющих элементов, для которых вы будете передавать данные, использования объектов ObjectWindows. Это означает, что вы должны использовать InitResource для связывания объектов с управляющими элементами в блоках и окнах диалога.
Примечания: Связь управляющих элементов с управляющими объектами описывается в Главе 11 "Объекты диалоговых бло- ков".
Чтобы использовать механизм передачи, вы можете сделать сле- дующее:
* Определить буфер передачи. * Определить соответствующее окно. * Передать данные.
Где можно использовать объекты управляющих элементов?
Управляющие элементы - это специализированные окна, которые позволяют пользователю предопределенным образом задавать или вы- бирать данные. Чаще всего управляющие элементы содержатся в диа- логовом блоке. Диалоговый блок управляет их определением с по- мощью ресурса диалогового блока, так что вам не потребуется часто использовать в них объекты. Режимные диалоговые блоки не распола- гают способами взаимодействия с управляющим объектом, поэтому в диалоговых блоках объекты управляющих элементов используются обычно для установки и считывания значений управляющих элементов с помощью передачи. Передача для объектов управляющих элементов в диалоговых блоках и в окнах выполняется одинаково (как описывает- ся ниже в этой главе).Примечание: Диалоговые блоки и их управляющие элементы описываются в Главе 11 "Объекты диалоговых блоков".
Возможно вы захотите использовать управляющие элементы в ок- нах, поэтому в данной главе описывается использование управляющих элементов вне диалоговых блоков. Следующая таблица описывает уп- равляющие элементы Windows, поддерживаемые типами объектов ObjectWindows:
Управляющие элементы Windows, поддерживаемые в ObjectWindows Таблица 12.1 +---------------+------------+----------------------------------+ | Управляющий | Тип объекта| Использование | | элемент | | | +---------------+------------+----------------------------------| | блок списка |TListBox |Прокручиваемый список элементов,| | | |из которых можно сделать выбор. | +---------------+------------+----------------------------------| | полоса |TScrollBar |Полоса прокрутки, аналогичная| | прокрутки | |тем, которые выводятся в прокру-| | | |чиваемых окнах и блоках списка. | +---------------+------------+----------------------------------| | "нажимаемая" |TButton |Кнопка для "нажатия" со связанным| | кнопка | |с ней текстом. | +---------------+------------+----------------------------------| | кнопка с |TCheckBox |Состоящая из блока кнопка, которая| | независимой | |может выбираться или нет, со свя-| | фиксацией | |занным текстом. | +---------------+------------+----------------------------------| | кнопка с |TRadioButton|Кнопка, которая может выбираться| | зависимой | |или нет. Обычно используется| | фиксацией | |во взаимоисключающих группах. | +---------------+------------+----------------------------------| | блок группы |TGroupBox |Статический прямоугольник с текс-| | | |том в левом верхнем углу. | +---------------+------------+----------------------------------| | управляющий |TEdit |Поле для ввода текста пользовате-| | элемент | |лем. | | редактирования| | | +---------------+------------+----------------------------------| | статический |TStatic |Фрагмент отображаемого текста | | управляющий | |который не может быть изменен | | элемент | |пользователем. | +---------------+------------+----------------------------------| | Комбиниро- |TComboBox |Комбинация блока списка и управля-| | ванный блок | |ющего элемента редактирования. | +---------------+------------+----------------------------------+
+----------------------------------+ Командная строка: | | +----------------------------------+ ^ + редактируемый упрвляющий элемент
+----------+ +----------+ |###OK#####| |##Cancel##| <- командные кнопки +----------+ +----------+
+---+-----------------------------------------------+---+ | < |########################X######################| > | +---+-----------------------------------------------+---+ ^ полоса прокрутки -+
+--------------+-+ |collect3.pas |^| <- блок списка |collect4.pas +-| |diatest.pas |#| |edittest.pas |X| +----------------+ |ewndtest.pas |#| | *.txt | |helpwind.pas |#| +-+--------------+-+ |lboxtest.pas |#| |netlect3.txt |^| |mditest.pas +-| |netlect4.txt +-| |paltest.pas |v| |diatext.txt |#| +--------------+-+ |readtxt.txt |X| |vwndtext.txt |#| комбинированный блок -> |whelpwnd.txt |#| |wboxtext.txt |#| |ydrtest.txt +-| |xaltesx.txt |v| +--------------+-+
Рис. 12. 1 Стандартные управляющие элементы Windows.
Группирование управляющих элементов
Поскольку блок группы визуально связывает группу других уп- равляющих элементов, он может логически связывать группу блоков выбора (кнопок с зависимой и независимой фиксацией). Логическая группа автоматически отменяет выбор характеристик блоков выбора "автоматического" стиля.Для добавления в группу, нужно при конструировании блока вы- бора указать указатель на блок группы. Например, чтобы добавить в окно группу кнопок с независимой фиксацией, в оконный объект и его конструктор можно включить следующее:
type TSomeWindow = object(TWindow) Group: PGroupBox; FirstCheck, SecondCheck: PCheckBox: constructor Init(AParent: PWindowsObject, ATitle: PChar); end;
constructor TSomeWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Group := New(PCheckBox, Init(@Self, id_TheGroup, 'Various boxes', 10, 01, 100, 50)); FirstCheck := New(PCheckBox, Init(@Self, id_FirstCheck, 'One', 15, 20, 90, 10, Group)); SecondCheck := New(PCheckBox, Init(@Self, id_SecondCheck, 'Two', 15, 20, 90, 10, Group)); end;
Заметим, что передаваемый блоку выбора параметр группы - это указатель на объект блока группы, а не идентификатор группового управляющего элемента (как в API Windows). Использование указате- ля позволяет вам строить объекты перед созданием методом SetupWindows порождающего окна экранных элементов.
Инициализация управляющего элемента
Экранный элемент управляющего объекта автоматически создает- ся методом SetupWindow, наследуемым объектом порождающего окна. Убедитесь, что при создании новых производных типов окон вы вызы- ваете наследуемый метод SetupWindow перед любой другой инициали- зацией окна.При необходимости методом SetupWindow порождающего окна так- же устанавливаются и заполняются управляющие элементы. Приведем пример типичного метода SetupWindow:
procedure TSampleWindows.SetupWindow; begin inherited SetupWindow; { создает дочерние управляющие элементы } { добавляет элементы в список } TheList^.AddString('Элемент 1'); TheList^.AddString('Элемент 2'); end;
Заметим, что инициализация управляющего элемента, такая как добавление строк в блок списка, должна выполняться в SetupWindow, а не в конструкторе. Вызов такого метода как TListBox.AddString приводит к передаче сообщений экранному управляющему элементу. Так как экранный элемент до вызова наследуемого метода SetupWindow не создается, попытки инициализации управляющих эле- ментов до этого момента завершится неудачно.
Использование блока списка
Использование блока списка - это простейший способ запросить пользователя программы Windows выбрать что-либо из списка. Блоки списка инкапсулируются объектным типом TListBox. TListBox опреде- ляет методы для создания блоков списка, модификации списка эле- ментов, запроса состояния списка элементов и поиска выбранного пользователем элемента.Использование блоков выбора
Тип TCheckBox наследует от TButton, а тип TRadioButton - от TCheckBox. Кнопки с зависимой и независимой фиксацией мы будем иногда кратко называть блоками выбора.Кнопки с независимой фиксацией обычно используются для пре- доставления пользователю выбора одного из двух возможных вариан- тов. Пользователь может установить или не установить управляющий элемент, или оставить его так, как он установлен. Если имеется группа кнопок с независимой фиксацией, то может устанавливаться не одна кнопка, а несколько. Например, вы можете использовать кнопку с независимой фиксацией для выбора трех шрифтов для их загрузки в приложение.
Кнопки с зависимой фиксацией используются для выбора одного из нескольких взаимоисключающих вариантов. Например, кнопка с за- висимой фиксацией может использоваться для выбора шрифта для конкретного символа. Когда пользователь щелкает "мышью" на кнопке с зависимой фиксацией, это событие приводит к генерации сообщения Windows. Как и в случае других управляющих элементов, порождающее окно кнопки с независимой фиксацией обычно перехватывает эти со- общения и выполняет по ним действия.
Однако, вы можете для выполнения ими некоторых действий при нажатии создать типы, производные от TCheckBox и TRadioButton. Если ваш тип определяет метод для nf_First + bn_Clicked, то он сначала должен вызывать метод реакции BNClicked, а уже затем вы- полнять любые дополнительные действия.
Использование буфера передачи с диалоговым блоком
Для случая окон с управляющими элементами объекты управляю- щих элементов конструируются с использованием Init. Для диалогов и окон диалогов нужно использовать конструктор InitResource. Нап- ример (используется определенный ранее тип TSampleRecord):type TSampleTransferRecord = record . . . PParentWindow = ^TParentWindow; TParentWindow = object(TWindow) TheDialog: PDialog; TheBuffer: SampleTransferRecord; . . . . constructor TParentWindow.Init(AParent: PWindowsObject; ATitle: PChar); var Stat1: PStatic; Edit1: PEdit; List1: PListBox; Combo1: PComboBox; Check1: PCheckBox; Radio1: PRadioButton; Scroll1: PScrollBar; begin TWindow.Init(AParent, ATitle); TheDialog^.Init(@Self, PChar(101)); New(Stat1, InitResource(TheDialog, id_Stat1)); New(Edit1, InitResource(TheDialog, id_Edit1)); New(List1, InitResource(TheDialog, id_List1)); New(Combo1, InitResource(TheDialog, id_Combo1)); New(Check1, InitResource(TheDialog, id_Check1)); New(Radio1, InitResource(TheDialog, id_Radio1)); New(Scroll1, InitResource(TheDialog, id_Scroll1)); TheDialog^.TranssferBuffer:=@TheBuffer; end;
Для управляющих элементов, построенных с помощью InitResource, механизм передачи разрешается автоматически.
Использование буфера передачи с окном
Для случая окна с управляющими элементами используйте для конструирования объектов управления в надлежащей последователь- ности Init, а не InitResource. Другое отличие между диалогами и окнами состоит в том, что механизм передачи по умолчанию для уп- равляющих элементов окна запрещен. Для разрешения использования механизма вызывается EnableTransfer:constructor TSampleWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Edit1 := New(PEdit, Init(@Self, id_Edit1, '', 10, 10, 100, 30, 40, False)); Edit1^.EnableTransfer; end;
Чтобы явно исключить управляющий элемент из механизма пере- дачи, вызовите после его создания метод DisableTransfer.
Использование буфера вырезанного изображения и меню Edit
Вы можете передавать текст непосредственно между объектом управляющего элемента редактирования и буфером вырезанного изоб- ражения Windows, используя для этого вызовы методов. Часто вам бывает нужно предоставить пользователю доступ к этим методам че- рез меню редактирования. Объект управляющего элемента редактиро- вания автоматически отреагирует на выбор из меню таких вариантов, как Edit|Copy и Edit|Undo. TEdit определяет основанные на коман- дах методы (например, CMEditCopy и CMEditUndo), которые вызывают- ся в ответ на конкретный выбор (команду) меню в порождающем окне управляющего элемента редактирования. CMEditCopy вызывает Copy, а CMEditUndo вызывает Undo.Следующая таблица содержит список методов, которые вызывают- ся в ответ на выбор пункта меню:
Управляющие элементы редактирования и меню Edit Таблица 12.7 +-----------------------+-------------------+-------------------+ | Операция | Метод TEdt | Команда меню | +-----------------------+-------------------+-------------------| | Копирование текста в | Cut | cm_EditCut | | буфер вырезанного | | | | изображения. | | | +-----------------------+-------------------+-------------------| | Вырезание текста в | Copy | cm_EditCopy | | буфер вырезанного | | | | изображения. | | | +-----------------------+-------------------+-------------------| | Вставка текста из | Paste | cm_EditPaste | | буфера вырезанного | | | | изображения. | | | +-----------------------+-------------------+-------------------| | Очистка всего элемента| Clear | cm_EditClear | | редактирования. | | | +-----------------------+-------------------+-------------------| | Удаление выделенного | DeleteSelection | cm_EditDelete | | текста. | | | +-----------------------+-------------------+-------------------| | Отмена последнего | Undo | cm_EditUndo | | редактирования. | | | +-----------------------+-------------------+-------------------+
Чтобы добавить в окно меню редактирования, содержащее управ- ляющий элемент редактирования, определите для окна с помощью ко- манд, перечисленных в Таблице 12.7, ресурс меню. Никаких новых методов вам писать не нужно.
Имеется также один дополнительный метод в виде булевской функции CanUndo, который определяет, можно ли отменить последнюю операцию редактирования.
Использование групповых блоков
В своей простейшей форме блок группы представляет собой ста- тический прямоугольник с меткой, который обычно объединяет другие управляющие элементы.Использование командных кнопок
Управляющие элементы типа командных кнопок (которые иногда называют "нажимаемыми" кнопками) выполняют некоторое действие при "нажатии" такой кнопки. Есть два стиля командных кнопок, и оба они имеют тип TButton. Эти два стиля - bs_PushButton и DefPushButton. Используемые по умолчанию командные кнопки анало- гичны командным другим кнопкам, но имеют жирную рамку и обычно используются для указания реакции пользователя по умолчанию. На Рис. 12.3 показан пример программы Windows, в которой используют- ся обычные кнопки нажатия и командные кнопки по умолчанию.+---------------------------------+-+ |#=#XXXXXXXXXHex CalculatorXXXXXXX|v| +---------------------------------+-| | +-----------------+ | | | 0 | | | +-----------------+ | | | | +---+ +---+ +---+ +---+ +---+ | | D | E | | F | | + | | & | | | +---+ +---+ +---+ +---+ +---+ | | +---+ +---+ +---+ +---+ +---+ | | | A | | B | | C | | - | | | | | | +---+ +---+ +---+ +---+ +---+ | | +---+ +---+ +---+ +---+ +---+ | | | 7 | | 8 | | 9 | | * | | ^ | | | +---+ +---+ +---+ +---+ +---+ | | +---+ +---+ +---+ +---+ +---+ | | | 4 | | 5 | | 6 | | / | | < | | | +---+ +---+ +---+ +---+ +---+ | | +---+ +---+ +---+ +---+ +---+ | | | 1 | | 2 | | 3 | | % | | > | | | +---+ +---+ +---+ +---+ +---+ | | +---+ +---------+ +----------+ | | | 0 | | Back | | Equals | | | +---+ +---------+ +----------+ | +-----------------------------------+
Рис. 12.3 Программа Windows, использующая командные кнопки.
Использование комбинированных блоков
Управляющий элемент типа комбинированного блока является со- четанием двух других управляющих элементов: блока списка и управ- ляющего элемента редактирования. Он служит тем же целям, что и блок списка - позволяет пользователю выбрать один элемент списка из прокручиваемого списка элементов текста, нажимая на кнопку "мыши". Управление редактированием, вынесенное в верхнюю часть блока списка предоставляет иной механизм выбора, позволяя пользо- вателю ввести текст нужного элемента. Если отображается область списка комбинированного блока, то автоматически выбирается нужный элемент. Тип TComboBox является производным от типа TListBox и наследует его методы модификации, опроса и выбора элементов спис- ка. Кроме того, TComboBox предоставляет методы по манипулированию списком, находящемся в комбинированном блоке, который в некоторых случаях может раскрываться по запросу.Использование конкретных управляющих элементов
Каждый вид управляющих элементов работает в чем-то отлично от других. В данном разделе вы можете найти конкретную информацию о том, как использовать объекты для каждого из стандартных управ- ляющих элементов Windows.Использование полос прокрутки
Полосы прокрутки являются важнейшим механизмом изменения об- зора пользователем окна приложения, блока списка или комбиниро- ванного блока. Однако, может возникнуть ситуация, когда нужна от- дельная полоса прокрутки для выполнения некоторой специализиро- ванной задачи (например, управление температурой в программе тер- мостата или цветом в программе рисования). Когда нужна отдельная специализированная полоса прокрутки, используются объекты TScrollBar. Рис. 12.5 показывает типичное использование объекта TSсrollBar.+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXXXThermostatXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | | | 68 градусов | | | | | | | | +--+----------------------------------------------------+--+ | | |
Рис. 12.5 Объект полосы прокрутки.
Использование специализированных управляющих элементов
Windows обеспечивает механизм, позволяющий вам создавать свои собственные виды управляющих элементов, а ObjectWindows об- легчает создание объектов, использующих преимущества управляющих элементов. В данном разделе обсуждается использование специализи- рованных управляющих элементов Borland, которые придают своеоб- разный вид работающим в Windows приложениям Borland, а затем опи- сывается, как создавать свои собственные уникальные управляющие элементы.Использование стандартных BWCC
ObjectWindows позволяет легко добавлять BWCC в ваши приложе- ния Windows. Нужно просто добавить модуль BWCC в оператор uses программы:uses BWCC;
Использование BWCC автоматически позволяет вам делать следу- ющее:
* использовать загружаемые из ресурсов управляющие элементы BWCC;
* создавать в вашей программе BWCC.
Например, с помощью пакета разработчика ресурсов Resource WorkShop вы можете создать ресурсы диалоговых блоков, использую- щие специализированные управляющие элементы Borland. Включение в оператор uses программы модуля BWCC обеспечивает для вашей прог- раммы информацию о том, где искать динамически компонуемую библи- отеку (BWCC.DLL), содержащую код, который обеспечивает работу BWCC.
Кроме того, после добавления модуля BWCC любые создаваемые в программе объекты управляющих элементов будут иметь вид и харак- теристики управляющих элементов Borland.
Использование статических управляющих элементов
Статические управляющие элементы - это обычно неизменяемые модули текста или простые изображения, которые могут появляться на экране в окне или блоке диалога. Пользователь не взаимодейс- твует со статическими управляющими элементами, хотя программа и может изменять их текст. Рис. 12.2 показывает множество стилей статического управления и соответствующих констант стиля Windows.+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXStatic Control TesterXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | | | Default Static Sample Text | | | | ss_Simple Sample Text | | | | ss_Left Sample Text | | | | ss_Center Sample Text | | | | ss_Right Sample Text | | +------------------------------+ | | ss_BlackFrame | | | | +------------------------------+ | | +------------------------------+ | | ss_BlackRect |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | | +------------------------------+ | | +------------------------------+ | | ss_GrayFrame | | | | +------------------------------+ | | +------------------------------+ | | ss_GrayRect |##############################| | | +------------------------------+ | | | | ss_NoPrefix Sample & Text | | | +---------------------------------------------------------------+
Рис. 12.2 Стили статических управляющих элементов.
Использование управляющих элементов редактирования
Управляющие элементы редактирования могут быть описаны, как интерактивные статические управляющие элементы. Это прямоугольные области (с рамкой или без) на экране, которые пользователь прило- жения может заполнять текстом, изменять или удалять. Управляющий элемент редактирования наиболее удобен в качестве поля для ввода данных на экране. Они обеспечивают следующие операции:- Ввод текста пользователем. - Динамическое отображение текста приложением. - Вырезание, копирование и вставка в буфер вырезанного изоб- ражения. - Многострочное редактирование (удобно для текстовых редак- торов).
На Рис. 12.7 показано окно с двумя управляющими элементами редактирования.
+-----------------------------------------------------------+-+-+ |#=#@@@@@@@@@@@@@@@@@Edit Control Tester@@@@@@@@@@@@@@@@@@@@|^|v| +-----------------------------------------------------------+-+-| | | | Оригинал: Копия: | | +----------------------+ +---+ +----------------------+ | | |Default Text | |#>#| |DEFAULT.TEXT | | | +----------------------+ +---+ +----------------------+ | | | | | +---------------------------------------------------------------+
Рис. 12.7 Окно с управляющими элементами редактирования.
Изменение атрибутов объекта управляющего элемента
Все управляющие объекты, кроме TMDIClient, получает от вызо- ва TControl.Init используемые по умолчанию стили ws_Child и ws_Visible. Если вы хотите изменить стиль управляющего элемента, то можно изменить поле Attr.Style (как это описывается для окон в Главе 10). Каждый тип управляющего элемента имеет также другие стили, определяющие его конкретные характеристики.Модификация блоков списка
После создания блока списка вам нужно заполнить его элемен- тами списка, который должны представлять собой строки. Позднее вы можете добавить, вставить или удалить элементы, либо полностью очистить список. Указанные действия вы можете выполнить с помощью методов, приведенный в следующей таблице:Методы модификации блоков списка Таблица 12.2 +--------------------------------+------------------------------+ | Выполняемое действие | Метод | +--------------------------------+------------------------------| | Добавление элемента | AddString | | Вставка нового элемента | InsertString | | Добавление элемента | InsertString | | Удаление элемента | DeleteString | | Удаление каждого элемента | ClearList | | Выбор элемента | SetSellIndex или SetSelString| +--------------------------------+------------------------------+
Существует пять методов, которые вы можете вызвать для полу- чения информации о списке, содержащейся в объекте блока списка. Эти методы перечислены в следующей таблице:
Методы запроса блока списка Таблица 12.3 +-------------------------------------------+-------------------+ | Получаемая информация | Вызываемый метод | +-------------------------------------------+-------------------| | Число элементов в списке | GetCount | | Элемент с конкретным индексом | GetString | | Длина конкретного элемента | GetStringLen | | Выбранный элемент | GetSelString | | Индекс выбранного элемента | SEgSelIndex | +-------------------------------------------+-------------------+
Модификация блоков выбора
Модификация (выбор или отмена выбора) блоков выбора выглядит задачей пользователя программы, а не вашей. Но в некоторых случа- ях программе требуется явно управлять состоянием блоков выбора. Одним из таких случаев является вывод на экран параметров, кото- рые были выбраны и сохранены ранее. Для модификации состояния кнопки с независимой фиксацией TCheckBox определяет 4 метода:Методы модификации кнопок с независимой фиксацией Таблица 12.5 +---------------------------+-----------------------------------+ | Выполняемое действие | Вызов метода | +---------------------------+-----------------------------------| | Выбор кнопки | Check или SetCheck(bf_Chacked) | | Отмена выбора кнопки | Uncheck или SetCheck(bf_Unchecked)| | Переключение кнопки | Toggle | +---------------------------+-----------------------------------+
Когда вы используете данные методы с кнопками с зависимой фиксацией, ObjectWindows обеспечивает выбор в группе только одной кнопки с зависимой фиксацией.
Модификация комбинированных блоков
TComboBox определяет два метода для демонстрации и сокрытия области списка в раскрывающихся комбинированных блоках и раскры- вающихся комбинированных блоках списка: ShowList и HideList. Обе эти процедуры не нуждаются в аргументах. Вызывать эти методы для демонстрации или сокрытия списка, когда пользователь нажимает стрелку вниз справа от области редактирования, не нужно. В этом случае работает автоматический механизм комбинированных блоков. Эти методы полезны только для принудительного вывода или сокрытия списка.Модификация полос прокрутки
Модификация полос прокрутки - это скорее работа для пользо- вателя вашей программы, и часто это действительно так. Однако, ваша программа также может модифицировать полосу прокрутки. Ис- пользуемые для этого методы перечислены в следующей таблице.Методы модификации полос прокрутки Таблица 12.6 +--------------------------------------+------------------------+ | Выполняемое действие | Вызываемый метод | +--------------------------------------+------------------------| | Задание диапазона прокрутки | SetRange | | Установка позиции маркера | SetPosition | | Перемещение позиции маркера | DeltaPos | +--------------------------------------+------------------------+
SetRange - это процедура, которая воспринимает два целочис- ленных аргумента, наименьшую и наибольшую позицию диапазона. По умолчанию новая полоса прокрутки имеет диапазон от 1 до 100. Вы можете изменить этот диапазон для наилучшего расположения управ- ляющих элементов полос прокрутки. Например, полоса прокрутки в приложении для термостата может иметь диапазон от 32 до 120 гра- дусов Фаренгейта:
ThermScroll^.SetRange(32, 120);
SetPosition - это процедура, которая воспринимает один цело- численый аргумент - позицию, в которую нужно переместить указа- тель полосы прокрутки. В рассмотренном ранее приложении для тер- мостата, ваша программа может непосредственно установить темпера- туру 78 градусов:
ThermScroll^.SetPosition(78);
Третий метод DeltaPos передвигает позицию указателя полосы прокрутки вверх (налево) или вниз (направо) на величину, заданную целым аргументом. Положительная целая величина перемещает указа- тель вниз (направо). Отрицательная целая величина перемещает его вверх (налево). Например, для уменьшения температуры термостата на 5 градусов используется:
ThermScroll^.DeltaPos(-5);
Модификация управляющих элементов редактирования
Для традиционной программы ввода текста вам может не потре- боваться непосредственно модифицировать управляющий элемент ре- дактирования. Пользователь модифицирует текст, а программа считы- вает этот текст методом опроса. Однако, во многих других случаях использование управляющего элемента редактирования требует, чтобы ваше приложение явно заменяло, вставляло, удаляло и выбирало текст. ObjectWindows обеспечивает подобное поведение и кроме того предоставляет возможность использовать прокрутку управляющего элемента редактирования.Методы модификации управляющих элементов редактирования Таблица 12.9 +----------------------------------------+----------------------+ | Выполняемое действие | Вызываемый метод | +----------------------------------------+----------------------| | Удаление всего текста | Clear | | Удаление выделенного текста | DeleteSelection | | Удаление диапазона символов | DeleteSubText | | Удаление строки текста | DeleteLine | | Вставка текста | Insert | | Вставка текста из буфера | Paste | | вырезанного изображения | | | Замена всего текста | SetText | | Выделение диапазона текста | SelectRange | | Прокрутка текста | Scroll | +----------------------------------------+----------------------+
Определение буфера передачи
Буфер передачи - это запись с одним полем для каждого управ- ляющего элемента, участвующего в передаче. Окно или диалог могут также иметь управляющие элементы, значения которых не устанавли- ваются механизмом передачи. Например, командные кнопки, у которых нет состояния, не участвуют в передаче. Это же справедливо для групповых блоков.Для определения буфера передачи нужно определить поле для каждого участвующего управляющего элемента диалога или окна. Оп- ределять поля для каждого управления диалога или окна не требует- ся - нужно лишь определить поля для тех из них, которые будут по- лучать и принимать значения по вашему желанию. Этот буфер переда- чи хранит один из каждых типов управляющих элементов, кроме ко- мандной кнопки и блока группы:
type TSampleTransferRecord = record Stat1: array[0TextLen-1] of Char; { статический текст } Edit1: array[0TextLen-1] of Char; { текст управляющего элемента редактирования } List1Strings: PStrCollection; { строки блока списка } List1Selection: Integer; { индекс выбранных строк } ComboStrings: PStrCollection; { строки комбинированного блока } ComboSelection: array[0TextLen-1] of Char; { выбранные строки } Check1: Word; { проверка состояния блока} Radio1: Word; { состояние кнопки с независимой фиксацией } Scroll1: ScrollBarTransferRec; { диапазон полосы прокрутки и т.д. } end;
В каждом типе управляющего элемента хранится различная ин- формация. Буфер передачи для каждого стандартного управляющего элемента поясняется в следующей таблице:
Поля буфера передачи для каждого типа управляющего элемента Таблица 12.11 +-----------------------+---------------------------------------+ | Тип управляющего | Буфер передачи | | элемента | | +-----------------------+---------------------------------------| | Статический | Символьный массив размером до макси-| | | мальной длины текста, плюс завершающий| | | нулевой символ. | | | | +-----------------------+---------------------------------------| | Редактирование | Текстовый буфер управляющего элемента| | | редактирования размером до длины, оп-| | | ределенной в текстовом поле TextLen. | | | | +-----------------------+---------------------------------------| | Блок списка | | | одиночный выбор | Набор строк в списке, плюс целочислен-| | | ный индекс выделенной строки. | | | | | множественный выбор | Набор строк в списке, плюс запись, со-| | | держащая индексы всех выделенных эле-| | | ментов. | | | | +-----------------------+---------------------------------------| | Комбинированный блок | Набор строк в списке, плюс выбранная| | | строка. | | | | +-----------------------+---------------------------------------| | Кнопка с независимой | Значения Word с указывающими состояния| | фиксацией | bf_Unchecked, bf_Checked и bf_Grayed. | | | | +-----------------------+---------------------------------------| | Кнопка с зависимой | Значения Word с указывающими состояния| | фиксацией | bf_Unchecked, bf_Checked и bf_Grayed. | | | | +-----------------------+---------------------------------------| | Полоса прокрутки | Запись типа TScrollBarTransferRec, со-| | | храняющая диапазон полосы прокрутки и| | | позицию в ней. | | | | +-----------------------+---------------------------------------+
Тип TScrollBarTransferRec имеет вид:
TScrollBarTransferRec := record LowValue : Integer; HighValue: Integer; Position : Integer; end;
Определение окна
Окно или диалоговый блок, которые используют буфер передачи, должны создавать объекты участвующих управляющих элементов в той последовательности, в которой определяются их соответствующие по- ля буфера передачи. Для подключения механизма передачи к объекту окна или диалога нужно просто установить значение его поля TransferBuffer в указатель на определенный вами буфер передачи.Опрос блоков выбора
Опрос блока выбора - это один из способов выяснения его сос- тояния и организации реакции на него. Кнопки с зависимой и неза- висимой фиксацией имеют два состояния: выбранные и невыбранные. Для получения состояния блока выбора используется метод GetCheck типа TheckBox:MyState:=Check1^.GetCheck;
Для определения состояния блока возвращаемое GetCheck значе- ние можно сравнить с заданными константами bf_Unchecked, bf_Checked и bf_Grayed.
Примечание: Использование кнопок обоих видов показано в примере программы BtnTest на ваших дистрибутивных дисках.
Опрос полосы прокрутки
TScrollBar определяет два метода опроса полосы прокрутки: GetRange и GetPosition. Метод GetRange - это процедура, использу- ющая два целочисленных переменных аргумента. Процедура заносит в эти целые значения верхнюю и нижнюю позиции из диапазона полосы прокрутки. Этот метод очень удобен, когда нужно, чтобы ваша прог- рамма переместила указатель в его верхнюю или нижнюю позицию.GetPosition - это функция, которая возвращает в виде целой величины позицию указателя. Ваша программа очень часто будет зап- рашивать диапазон и позицию и сравнивать их.
Опрос управляющих элементов редактирования
Иногда нужно организовать опрос управляющих элементов редак- тирования для проверки допустимости введенного текста, записи ввода для его последующего использования или копирования ввода в другой управляющий элемент. TEdit поддерживает несколько методов опроса. Многие из опросов управляющих элементов редактирования и методов модификации возвращают или требуют от вас указать номер строки или позицию символа в строке. Все эти индексы начинаются с нуля. Другими словами, первая строка - это нулевая строка, а пер- вый символ в любой строке это нулевой символ. Самыми важными ме- тодами запроса являются GetText, GetLine, NumLines и LineLength.Методы опроса управляющих элементов редактирования Таблица12.8 +------------------------------------------+--------------------+ | Выполняемое действие | Вызываемый метод | +------------------------------------------+--------------------| | Определение изменения текста | IsModified | | Считывание всего текста | GetText | | Считывание строки | GetLine | | Получение числа строк | GetNumLines | | Получение длины данной строки | GetLineLength | | Получение индекса выделенного текста | GetSelection | | Получение диапазона символов | GetSubText | | Подсчет символов перед строкой | LineIndex | | Поиск строки, содержащей индекс | GetLineFromProc | +------------------------------------------+--------------------+
Вы можете заметить, что методы запросов TEdit, которые возв- ращают текст из управляющего элемента редактирования, сохраняют форматирование текста. Это важно только для многострочных управ- ляющих элементов редактирования, которые допускают появление нес- кольких строк текста. В этом случае возвращаемый текст, который занимает несколько строк в управляющем элемента редактирования содержит в конце каждой строки два дополнительных символа: возв- рат каретки (#13) и смена строки (#10). Если этот текст снова по- мещается в управляющий элемент редактирования, вставляется из бу- фера вырезанного изображения, записывается в файл или выводится на принтер, то строки разбиваются так, как это было в управляющем элемента редактирования.
Следовательно, при использовании метода запроса для получе- ния определенного числа символов, нужно учитывать эти два симво- ла, которые заканчивают строку. GetText ищет текст в управляющем элементе редактирования. Он заполняет строку, на которую указыва- ет переданный аргумент PChar, содержимым управляющего элемента редактирования, включая перевод строки. Общее число символов за- дается вторым параметром. Он возвращает значение False, если уп- равляющий элемент редактирования пуст, или содержит текста боль- ше, чем помещается в предоставленную строку. Следующая процедура считывает из управляющего элемента редактирования строку и выде- ленный текст:
procedure TTestWindow.ReturnText(RetText: PChar); var TheText: array[020] of Char; begin if EC1^.GetText(@TheText, 20) then RetText:=@TheText else RetText:=nil; end;
procedure TTestWindow.ReturnText(RetText: PChar); var TheText: array[020] of Char; begin RetText:=nil; with EC^ do if NumLines >= LineNum then if LineLength(LineNum) < 11 then if GetLine(@TheText, 20, LineNum) then RetText := @TheText; end;
procedure TestWindow.ReturnLineText(RetText: PChar; LineNum: Integer); var TheText: array[020] of Char; begin with EC1^ do begin GetSelection(SelStart, SelEnd); GetSubText(TheText, SelStart, SelEnd); end; RetText := TheText; end;
Передача данных
В большинстве случаев передача данных в окно выполняется автоматически, но в любой момент вы можете явно передать данные.Передача данных из диалогового окна
Когда режимное диалоговое окно получает командной сообщение с идентификатором управляющего элемента id_OK, оно автоматически передает данные из управляющего элемента в буфер передачи. Обычно ляет свой буфер передачи. Затем, если вы снова выполняете диалог, диалоговый блок передает текущие данные в управляющие элементы.Передача данных из окна
Однако, вы можете явно передавать данные в любом направлении в любой момент времени. Например, вы можете передать данные из управляющих элементов окна или безрежимного диалога. Вы также мо- жете сбросить состояния управляющих элементов, используя данные буфера передачи, в ответ на щелчок "мышью" на кнопке Reset (сброс). В обоих случаях используется метод TransferData. Конс- танта tf_SetData обозначает передачу данных из буфера в управляю- щий элемент, а константа tf_GetData - передачу в другом направле- нии. Например, вы можете вызвать TransferData в методе Destroy объекта окна:procedure TSampleWindow.Destroy; begin TransferData(tf_GetData); TWindow.Destroy; end;
Передача данных в окно
После создания окна или диалогового блока данные автомати- чески. Для создания экранного элемента, представляющего оконный объект, конструктор вызывает SetupWindow. Затем для загрузки дан- ных из буфера передачи вызывается TransferData. Окно SetupWindow интерактивно вызывает SetupWindow для каждого из дочерних окон, так что дочерние окна имеют возможность передать свои данные. Поскольку порождающее окно устанавливает свои дочерние окна в по- рядке их построения, данные в буфере передачи должны следовать в том же порядке.Если объект управляющего элемента был построен с помощью InitResource или если порождающее окно явным образом разрешило межанизм передачи путем вызова для управляющего элемента EnableTransfer, то методы управляющего объекта SetupWindow вызы- вают TransferData.
Поддержка передачи для специализированных управляющих элементов
Вы можете изменить способ передачи данных для конкретного управляющего элемента или включить новый управляющий элемент, оп- ределенный вами в механизме передачи. В обоих случаях вам просто нужно написать метод Transfer для вашего управляющего объекта, который если установлен флаг tf_GetData копирует данные из управ- ляющего элемента в место, задаваемое указателем. Если установлен флаг tf_SetData, то просто скопируйте данные по заданному указа- телю в управляющий элемент. Рассмотрим в качестве примера TStatic.Transfer:function TStatic.Transfer(DataPrt: Pointer; TransferFlag: Word): Word; begin if TransferFlag = tf_GetData then GetText(DataPrt, TextLen) else if TransferFlag = tf_SetData then SetText(DataPtr); Transfer:=TextLen; end;
Метод Transfer должен всегда возвращать число переданных байт информации.
Построение групповых блоков
Конструктор Init группового блока кроме обычных 6 параметров воспринимает текстовую строку метки группы.constructor TGroupBoxInit(AParent: PWindowsObject; AnID: Integer; AText: PChar; X, Y, W, H: Integer);
Типичное использование конструктора группового блока может быть следующим:
GroupBox1 := New(PGroupBox, Init(@Self, id_GB1, 'A Group Box', 38, 102, 176, 108));
Построение и уничтожение объектов управляющих элементов
Обычно в объекта порождающего окна для каждого из дочерних окон определяется поле данных. Независимо от того, какой вид объ- ектов вы используете, для каждого из них вы можете выполнить нес- колько задач:* Построение объекта управляющего элемента.
* Вывод управляющего элемента.
* Уничтожение управляющего элемента.
Построение кнопок с зависимой и независимой фиксацией
Кроме обычных 6 параметров, конструктор Init для кнопок с зависимой и независимой фиксацией воспринимает текстовую строку и указатель на объект группового блока (см. "Групповые блоки"), ко- торый логически и визуально выделяет кнопки. AGroup - это указа- тель на объект группового блока. Если AGroup имеет значение nil, то блок выбора не является частью какой-либо логической группы. Конструкторы описываются следующим образом:constructor Init(AParent: PWindowsObject; AnID: Integer; ATitle: PChar; X, Y, W, H: Integer; AGroup: PGroupBox);
Для обоих видов блоков выбора синтаксис идентичен. Конструк- торы различаются только присваиваемым стилем, используемым по умолчанию. Типичное использование конструкторов блока выбора име- ет вид:
GroupBox1 := New(PGroupBox, Init(@Self, id_GB1, 'A Group Box', 38, 102, 176, 108)); ChBox1 := New(PCheckBox, Init(@Self, id_Check1, 'Check Box Text', 235, 12, 150, 26, GroupBox1));
Кнопки с независимой фиксацией по умолчанию инициализируются со стилем bs_AutoCheckBox, а кнопки с независимой фиксацией име- ют стиль bs_AutoRadioButton. В соответствии с этими стилями в каждый момент времени может выбираться только одна клавиша выбора в группе. Если одна выбрана, то другие автоматически остаются не- выбранными.
Если вы переопределяете стили объектов кнопок с зависимой или независимой фиксацией как "неавтоматические", то тогда уже вы отвечаете за их выбор или не выбор в ответ на произведенные поль- зователем нажатия.
Построение командных кнопок
Кроме обычных 6 параметров, конструктор Init объекта TButton воспринимает текстовую строку типа PChar, AText и флаг типа Boolean IsDefaultButton, указывающий, должна ли кнопка быть ис- пользуемой по умолчанию или обычной командной кнопкой. Конструк- тор Init объекта TButton описывается следующим образом:constructor TButton.Init(AParent: PWindowsObject; AnID: Integer; AText: PChar; X, Y, W, H: Integer; IsDefault: Boolean);
Типичный конструктор для обычной кнопки выглядит так:
Push1 := New(PButton, Init(@Self, id_Push1, 'Test Button', 38, 48, 316, 24, False));
Построение комбинированных блоков
Кроме обычных 6 параметров объектов управляющих элементов конструктор Init для TComboBox воспринимает в качестве аргументов стиль и максимальную длину текста. Конструктор TComboBox описыва- ется следующим образом:constructor TComboBox.Init(AParent: PWindowsObject; AnID: Integer: X, Y, W, H: Integer; AStyle, ATextLen: Word);
Все комбинированные блоки, построенные с помощью Init, име- ют стили ws_Child, ws_Visible, cbs_AutoHScroll, cbs_Sort (отсор- тированный список), и VScroll (вертикальная полоса прокрутки). Параметр стиля - это один из стандартных стилей комбинированных блоков Windows: cbs_Simple, cbs_DropDown или cbs_DropDownList. Параметр длины текста работает подобно соответствующему параметру управляющего элемента редактирования, ограничивая число символов, которые можно ввести в область редактирования комбинированного блока.
Следующие строки приведут к созданию спускающегося комбини- рованного блока списка с неотсортированным списком:
CB3: = New(PComboBox, Init(@Self, id_CB3, 190, 160, 150, 100, cbs_DropDownList, 40)); CB3^.Attr.Style:=CB3^.Attr.Style and (not cbs_Sort);
Построение объекта управляющего элемента
Построение управляющих элементов отличается от построения любых других дочерних окон. Обычно конструктор порождающего окна вызывает конструкторы всех его дочерних окон. Однако в случае уп- равляющих элементов не просто создается связь "родитель - пото- мок", но устанавливается также связь "окно - управляющий эле- мент". Это очень важно, поскольку кроме обычных связей между предком и потомком управляющие элементы взаимодействуют с порож- дающими окнами особыми способами (через уведомления).Примечание: Уведомления описываются в Главе 16 "Сооб- щения Windows". Чтобы построить и инициализировать объект управляющего эле- мента, нужно сделать следующее:
* добавить в объект порождающего окна поле (не обязательно); * вызвать конструктор объекта управляющего элемента; * изменить атрибуты управляющего элемента; * инициализировать управляющий элемент в SetupWindows.
Построение объектов блока списка
Конструктор Init в TListBox воспринимает только шесть пара- метров, которые необходимы всем объектам управляющих элементов. Этими параметрами являются порождающее окно, идентификатор и раз- меры управляющего элемента X, Y, W и H:LB1 := New(PListBox, Init(@Self, id_LB1, 20, 20, 340, 100));
TListBox получает используемый по умолчанию стиль управляю- щего элемента ws_Child or ws_Visible, затем прибавляется lbs_Standard. lbs_Standard - это комбинация lbs_Notify (для полу- чения уведомляющих сообщений), ws_VScroll (для получения верти- кально полосы прокрутки), lbs_Sort (для сортировки списка элемен- тов в алфавитном порядке) и ws_Border (для вывода рамки). Если вы хотите получить другой стиль блока списка, то можете модифициро- вать поле Attr.Style в TListBox. Например, для блока списка, не сортирующего свои элементы, можно использовать следующее:
LB1 := New(PListBox, Init(@Self, id_LB1, 20, 20, 340, 100)); LB1^.Attr.Style := LB1^.Attr.Style and not lbs_Sort;
Построение полос прокрутки
Кроме обычных 6 параметров управляющего объекта, конструктор Init полосы прокрутки воспринимает флаг типа Boolean, указываю- щий, является ли полоса прокрутки горизонтальной. Приведем описа- ние конструктора полосы прокрутки:constructor TScrollBarInit(AParent: PWindowsObject; AnID: Integer; X, Y, W, H: Integer; IsHScrollBar: Boolean);
Если вы зададите нулевую ширину вертикальной полосы прокрут- ки, Windows присвоит ей стандартную ширину (аналогичную полосе прокрутки блока списка). То же самое касается задания нулевой вы- соты горизонтальной полосы прокрутки. Вызов:
ThermScroll := New(PScrollBar, Init(@Self, id_ThermScroll, 20, 170, 340, 0, True));
создает горизонтальную полосу прокрутки стандартной высоты, как это показано на Рис. 12.5. Init конструирует полосы прокрутки со стилями ws_Child, ws_Visible и sbs_Horz или sbs_Vert для горизон- тальной или вертикальной полосы прокрутки соответственно. Разно- образные полосы прокрутки показаны на Рис. 12.6.
+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXScroll Bar TesterXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | +--+ +-+ | | +--+--|/\|---------------------------+--+ |^| | | |/X|##+--|###########X###############|X\| +-| | | |\X|##|##|###########X###############|X/| |#| | | +--+--|XX|---------------------------+--+ |X| | | |##| |#| | | |##| |#| | | |##| +-| | | +--| |v| | | |\/| +-+ | | +--+ | | +--+----------------------------------------------------+--+ | | |
Рис. 12.6 Окно с разнообразными полосами прокрутки.
Построение статических управляющих элементов
Пользователь никогда не взаимодействует непосредственно со статическими управляющими элементами, поэтому приложение будет очень редко, если вообще будет, принимать уведомляющие сообщения управляющих элементов относительно статического управляющего эле- мента. Следовательно, большинcтво статических управляющих элемен- тов можно сконструировать с идентификатором ресурса -1 или неко- торым другим неиспользуемым числом.Конструктор Init в TStatic конструирует новый объект стати- ческого управления и описывается следующим образом:
constructor TStatic.Init(AParent: PWindowsObject; AnID: Integer; ATitle: PChar; X, Y, W, H: Integer; ATextLen: Word);
Кроме обычных параметров Init управляющего объекта, TStatic.Init имеет два дополнительных параметра - текстовую стро- ку ATitle и максимальную длину текста ATextLen. Текст должен за- канчиваться нулевым символом, поэтому в действительности число отображаемых символов на единицу меньше заданной в конструкторе длины текста. Типичный вызов для построения статического управля- ющего элемента может выглядеть так:
Stat1 := New(Static, Init(@Self, id_ST1, '&Text', 20, 50, 200, 24, 6));
После его создания обращаться к статическому управляющему объекту или манипулировать им требуется редко, поэтому поле, со- держащее статический управляющий объект, в общем случае присваи- вать не нужно.
Используемым по умолчанию стилем статического управляющего элемента является назначенный по умолчанию стиль управляющего элемента, то есть ws_Child or ws_Visible (выравнивание влево). Чтобы изменить стиль, модифицируйте поле Attr.Style. Например, чтобы центрировать текст управляющего элемента, сделайте следую- щее:
Stat1^.Attr.Style := Stat1^.Attr.Style and (not ss_Left) or ss_Center;
В статическом управляющем элементе есть возможность подчер- кивания одного или нескольких символов в строке текста. Реализа- ция и действие этого эффекта аналогичны подчеркиванию первого символа в выборе меню: Insert и & должны непосредственно пред- шествовать символу в строке, который будет подчеркиваться. Напри- мер, для подчеркивания T в слове 'Text' нужно в вызове Init стро- ку '&Text'. Если в строке вам нужно использовать &, применяйте статический стиль Windows ss_NoPrefix (см. Рис. 12.2). Для уточ- нения текущего текста, который хранится в статическом управляющем элементе, используется метод GetText.
Модификация статического управляющего элемента
Для изменения текста статического управляющего элемента TStatic имеет два метода. SetText устанавливает статический текст, передаваемый аргументом PChar. Clear удаляет статический текст. Однако, вы не можете сменить текст статического управляю- щего элемента, созданный со стилем ss_Simple.
Опрос статических управляющих элементов
Чтобы считать текст, содержащийся в статическом управляющем элементе, используйте метод GetText.
Построение управляющих элементов редактирования
Конструктор Init управляющего элемента редактирования анало- гичен конструктору статического управляющего элемента и восприни- мает 6 обычных параметров, плюс начальная текстовая строка, мак- симальная длина строки и флаг Multiline типа Boolean. Конструктор TEdit описывается следующим образом:constructor TEdit.Init(AParent: PWindowsObject; AnID: Integer; ATitle: PChar; X, Y, W, H, ATextLen: Integer; Multiline: Boolean);
По умолчанию управляющий элемент редактирования имеет стили ws_Child, ws_Visible, es_TabStop, es_Left и es_AutoHScroll. Так как управляющий элемент должен включать в себя завершающий нуле- вой символ, параметр длины текста на самом деле на 1 превышает максимальное число символов, допустимых в строке редактирования.
Если Multiline имеет значение True, то управление редактиро- ванием имеет стиль es_MultiLine, es_AutoVScroll, ws_VScroll и ws_HScroll. Приведем типичные конструкторы управляющих элементов редактирования (один для однострочного элемента, другой - для многострочного):
EC1 := New(PEdit, Init(@Self, id_EC1, 'Default Text', 20, 50, 150, 30, 40, False)); EC2 := New(PEdit, Init(@Self, id_EC2, '', 20, 20, 200, 150, 40, True));
Пример программы: BtnTest
BtnTest - это полная программа, которая создает окно с ко- мандной кнопкой, кнопками с зависимой и независимой фиксацией и блоком группы управляющих элементов. После запуска приложения по- является основное окно с управляющими элементами. Когда пользова- тель "нажимает" на управляющий элемент, приложение реагирует на это различными способами. См. Рис. 12.4.Примечание: Полный текст программы содержится в файле BTNTEST.PAS на ваших дистрибутивных дискетах.
+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXButton TesterXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | | | +---+ | | | X | Текст кнопки с независимой фиксацией | | +---+ | | +----------------------------------------------------------+ | | |###########Состояние кнопки с независимой фиксацией#######| | | +----------------------------------------------------------+ | | | | +-Групповой блок-------------------------------+ | | | ( ) Кнопка с зависимой фиксацией 1 | | | | (*) Кнопка с зависимой фиксацией 2 | | | +----------------------------------------------+ | +---------------------------------------------------------------+
Рис. 12.4 Окно с различными видами кнопок.
Пример программы: CBoxTest
Программа CBoxTest реализует приложение, показанное на Рис. 12.8. В нем использованы все три типа комбинированных блоков. CB1 - это простой комбинированный блок, CB2 это раскрывающийся комби- нированный блок, а CB3 - это раскрывающийся комбинированный блок списка. Нажатие кнопок Show и Hide выполняет принудительный вывод и сокрытие правого верхнего комбинированного блока, CB3, путем вызова методов ShowList и HideList.Примечание: Полный текст файла CBOXTEST.PAS содержится на ваших дистрибутивных дискетах.
Пример программы: EditTest
EditTest - это программа, которая помещает на экран основное окно, которое будет порождающим для двух управляющих элементов редактирования, двух статических управляющих элементов и кнопки. Данное окно показано на Рис. 12.7.Когда пользователь щелкает на командной кнопке кнопкой "мы- ши", текст из левого управляющего элемента редактирования (EC1) копируется в правый управляющий элемент редактирования (EC2). В EC2 текст преобразуется в буквы верхнего регистра, поскольку оно было построено со стилем es_UpperCase. Если в C1 никакой текст не выбран, то в EC2 копируется весь текст. Если в EC1 выбран некото- рый текст, то будет скопирован именно он. Меню редактирования обеспечивает функции редактирования независимо от того, с каким управляющим элементов редактирования идет работа. Полный файл EDITTEST.PAS и файл ресурса EDITTEST содержатся на ваших дистри- бутивных дискетах.
Пример программы: LBoxTest
Программа LBoxTest - это полная программа, которая создает окно с блоком списка. После запуска приложения появляется основ- ное окно с блоком списка. Когда пользователь выбирает элемент блока списка, появляется диалог с выбранным элементом. Обратите внимание на взаимоотношения между объектом окна и объектом блока списка. Блок списка - это не просто дочернее окно основного окна, основное окно владеет им как полем объекта. LB1 - это одно из по- лей объекта основного окна, и оно содержит объект блока списка. Полный текст программы содержится в файле LBOXTEST.PAS на ваших дистрибутивный дискетах.Пример программы: SBarTest
Программа SBarTest создает приложение для термостата, пока- занное на Рис. 12.5. Полный текст программы содержится в файле SBARTEST.PAS на ваших дистрибутивных дискетах.Пример программы StatTest
Программа StatTest создает статическое тестовое приложение, показанное на Рис.12.2. Обратите внимание на то, что метки ('Default Static' и 'ss_Simple') представляют собой статистичес- кие управляющие элементы, также как и 'Sample Text', черные и се- рые прямоугольники. Полный текст программы содержится в файле STATTEST.PAS на ваших дистрибутивных дискетах.Пример программы: TranTest
Основное окно программы TranTest воспроизводит режимный диа- лог с полями, в которые пользователь вводит данные об имени и ад- ресе. Буфер передачи используется для хранения этой информации и отображения ее в управляющих элементах диалога при повторном его выполнении. Обратите внимание на то, что нам не нужно определять новый тип объекта диалога для установки и поиска данных диалога. Также обратите ваше внимание на то, что мы непосредственно мани- пулируем данными буфера передачи, поэтому статическое управление при первом выводе диалога гласит "First Mailing Label" (первая почтовая этикетка), а при всех остальных появлениях "Subsequent Mailing Label" (следующая почтовая этикетка).Примечание: Полный текст программы содержится в файле TRANTEST.PAS на ваших дистрибутивных дискетах.
Присваивание полям объекта
----------------------------------------------------------------- Часто при построении управляющего элемента в окне желательно сохранять указатель на управляющий элемент в поле оконного объек- та. Например, чтобы добавить в окно, определенное типом TSampleWindow блок списка, вы можете задать в поле TSampleWindow поле TheList и присвоить ему блок списка:constructor SampleWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); TheList := New(PListBox, Init(@Self, id_LB1, 20, 20, 100, 80)); end;
Порождающие окна автоматически поддерживают список своих до- черних окон, включая управляющие элементы. Однако удобнее манипу- лировать управляющими объектами, когда имеются соответствующие поля объекта. Управляющие элементы, с которыми часто работать не требуется (такие как статический текст или групповые блоки) могут не иметь соответствующих полей.
При наличии поля объекта построение объекта управляющего элемента не представляет труда. Например, чтобы добавить в TSampleWindow групповой блок, нужно сделать следующее:
constructor SampleWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); TempGroupBox := New(PListBox, Init(@Self, id_LB1, 'Group name', 140, 20, 100, 80)); end;
Работа с управляющими элементами окна
Диалоговые окна работают с их управляющими элементами путем передачи им сообщений с помощью метода SendDlgItemMsg с констан- той управляющего сообщения (такой как lb_AddString) в качестве параметра (см. Главу 11). Объекты управляющих элементов сильно упрощают этот процесс путем использования методов (таких как TListBox.AddString) для непосредственной работы с управляющими элементами на экране.Когда объекты управляющих элементов окна имеют соответствую- щие поля объекта, то вызвать управляющие методы достаточно прос- то:
TheListBox^.AddString('Scotts Valley');
Расширение BWCC
BWCC обеспечивает кнопки с графическими изображениями для всех стандартных командных кнопок Windows. То есть, имеются гра- фические изображения, предусмотренные для командных кнопок, для которых Windows обеспечивает стандартный идентификатор: id_Abort, id_Cancel, id_Ignore, id_No, id_Ok, id_Retry и id_Yes.Создание кнопок с графическими изображениями
В своих приложениях вы можете обеспечить для командных кно- пок собственные графические образы. Все что нужно предусмотреть - это шесть ресурсов графических изображений (битовых массивов), пронумерованных относительно идентификатора управляющего элемента вашей командной кнопки. Например, если вы хотите создать графи- ческую командную кнопку с идентификатором id_MyButton, то создае- те ресурсы битовых массивов с идентификаторами ресурса 1000 + id_MyButton, 2000 + id_MyButton, 3000 + id_MyButton, 4000 + id_MyButton, 5000 + id_MyButton и 6000 + id_MyButton. Каждый представляемый битовый массив показан в следующей таблице:
Ресурсы битовых массивов для командных кнопок BWCC Таблица 12.12 +------------------------+-----------------+--------------------+ | Образ | Идентификатор | Идентификатор | | | ресурса VGA | ресурса VGA | +------------------------+-----------------+--------------------| | Командная кнопка | 1000 + идент. | 2000 + идент. | | в фокусе | | | | | | | +------------------------+-----------------+--------------------| | Нажатая командная | 3000 + идент. | 4000 + идент. | | кнопка | | | | | | | +------------------------+-----------------+--------------------| | Командная кнопка | 5000 + идент. | 6000 + идент. | | не в фокусе | | | | | | | +------------------------+-----------------+--------------------+
Графические образы командных кнопок VGA должны иметь ширину 63 и высоту 39 элементов изображения. Графические образы команд- ных кнопок EGA должны иметь ширину 63 и высоту 39 элементов изображения.
Для текста следует использовать шрифт Helvetica размером 8 пунктов, а вокруг образа кнопки, находящейся в фокусе, следует выводить рамку из точек. Набор графических изображений для ко- мандных кнопок с идентификатором 201 показан на следующем рисун- ке:
+------------------+ +------------------+ +------------------+ |##################| |##################| |##################| |###@@######Add####| |###@@######Add####| |###@@######Add####| |#@@@@@@####Pen####| |#@@@@@@####Pen####| |#@@@@@@####Pen####| |###@@#############| |###@@#############| |###@@#############| |##################| |##################| |##################| +------------------+ +------------------+ +------------------+ 1201 3201 5201
+------------------+ +------------------+ +------------------+ | |# | . | | . #| | @@ Add |# | @@ : Add : | | @@ : Add : #| | @@@@@@ Pen |# | @@@@@@ : Pen : | | @@@@@@ : Pen : #| | @@ |# | @@ . | | @@ . #| | |# | | | #################| +------------------+# +------------------+ +------------------+ ################### 2201 4201 6201
Рис. 12. 9 Графические ресурсы для командной кнопки BWCC с идентификатором 201.
Реакция на блок списка
Методы модификации и запроса блока списка позволяют вам ус- тановить значения или определять в каждый конкретный момент сос- тояние блока списка. Однако, чтобы знать, что делает пользователь в данный момент с блоком списка, вам нужно реагировать на уведом- ляющие сообщения управляющего элемента.Пользователь может выполнять с блоком списка только следую- щие действия: прокрутку списка, щелчок кнопкой "мыши" на элементе списка, двойной щелчок кнопкой "мыши" на элементе. Когда выполня- ется одно из этих действий, Windows посылает порождающему окну блока списка уведомляющее сообщение блока списка. Обычно метод реакции на уведомление для обработки сообщений для каждого управ- ляющего элемента порождающего объекта определяется в порождающем оконном объекте.
Каждое уведомляющее сообщение блока списка содержит в поле lParamHi параметра Msg код уведомления (константу lbn_), который специфицирует характер действия. Наиболее общие коды lbn перечис- лены в следующей таблице:
Информационные сообщения блока списка Таблица 12.2 +-------------+-------------------------------------------------+ | wParam | Действие | +-------------+-------------------------------------------------| |lbn_SelChange|Отдельным нажатием кнопки "мыши" был выбран| | |элемент. | +-------------+-------------------------------------------------| |lbn_DblClk |Элемент был выбран двойным щелчком кнопки "мыши".| +-------------+-------------------------------------------------| |lbn_SetFocus |Пользователь переместил фокус на блок списка| | |простым или двойным нажатием кнопки "мыши", либо| | |клавишей Tab. Предшествует lbn_SelChange. | +-------------+-------------------------------------------------+
Приведем пример метода порождающего окна по обработке сооб- щений блока списка:
procedure TLBoxWindow.HandleLB1Msg(var Msg: TMessage); var Idx: Integer; ItemText: string[10] begin if Msg.lParamHi=lbn_SelChange then begin Idx:=LB1^.GetSelIndex; if LB1^.GetStringLenIdx)<11 then begin LB1^.GetSelString(@ItemText, 10); MessageBox(HWindow, @ItemText, 'Вы выбрали:', mb_OK); end; end; else DefWndProc(Msg); end;
Пользователь делает выбор, если Msg.lParamHi совпадает с константой lbn_SelChange. Если это так, то берется длина выбран- ной строки, проверяется, что она помещается в строку из 10 симво- лов, и выбранная строка показывается в блоке сообщения.
Реакция на групповые блоки
Когда происходит событие, которое может изменить выбор блока группы (например, "нажатие" пользователем кнопки или вызов прог- раммой метода Check), порождающее окно блока группы принимает со- общение, основанное на дочернем идентификаторе. Порождающий объ- ект воспринимает сообщение, используя сумму id_First и идентифи- катора группового блока. Это позволяет вам определить методы для каждой группы вместо их задания для каждого блока выбора в груп- пе.Для определения управляющего элемента в группе, на который было оказано воздействие, вы можете прочитать текущее состояние каждого управляющего элемента.
Реакция на командные кнопки
Когда пользователь щелкает на командной кнопке "мышью", по- рождающее окно кнопки принимает уведомляющее сообщение. Если объ- ект порождающего окна перехватывает сообщение, он может отреаги- ровать на эти события выводом блока диалога, записью файла или другим контролируемым программой действием.Для организации реакции на сообщения кнопок нужно определить основанный на дочернем идентификаторе метод для обработки каждой кнопки. Например, следующий метод IDBut1 обрабатывает реакцию на "нажатие" пользователем кнопки. Единственный код уведомления, оп- ределенный в Windows для командных кнопок - это bn_Clicked, поэ- тому код уведомления не нужно проверять.
type TTestWindow = object(TWindow) But1: PButton; procedure IDBut1(var Msg: TMessage); virtual id_First + idBut1; . . . end;
procedure TestWindow.IDBut1(var Msg: TMessage); begin MessageBox(HWindow, 'Clicked', 'The Button was:' mb_OK) end;
Примечание: Пример использования командных кнопок по- казывает программа BtnTest, которую вы можете найти на дистрибутивных дисках.
Реакция на полосы прокрутки
При работе пользователя с полосой прокрутки ее порождающее окно получает от нее уведомляющие сообщения Windows. Если нужно, чтобы ваше окно реагировало на сообщения прокрутки, реакция на информационные сообщения должна быть обычной, путем определения методов реакции, основанных на дочерних идентификаторах.Однако, уведомляющие сообщения полосы прокрутки несколько отличаются от других уведомляющих сообщений элемента управления. Они основаны на сообщениях Windows wm_HScroll и wm_VScroll, а не wm_Command. Единственное отличие, на которое нужно обратить вни- мание состоит в том, что уведомляющие коды полосы прокрутки запи- саны в Msg.wParam, а не в Msg.lParamHi.
Чаще всего встречаются коды sb_LineUp, sb_LineDown, sb_PageUp, sb_PageDown, sb_ThumbPosition и sb_ThumbTrack. Наибо- лее часто вы будете реагировать на каждое событие проверкой новой позиции полосы прокрутки и организацией соответствующего дейс- твия. В данном случае вы можете игнорировать уведомляющий код. Например:
procedure TestWindow.HandleThermScrollMsg(var Msg: TMessage); var NewPos: Integer; begin NewPos:=ThermScroll^.GetPosition; { обработка с помощью NewPos } end;
Часто альтернатива состоит в том, чтобы не реагировать на перемещение указателя до тех пор, пока пользователь не выберет его нового местоположения. В этом случае нужно реагировать на со- общение с кодом sb_ThumbTrack.
procedure TestWindow.HandleThermScrollMsg(var Msg: TMessage); var NewPos: Integer; begin if Msg.wParam <> sb_ThumbTrack then begin NewPos:=ThermScroll^.GetPosition; { некоторая обработка на основе NewPos. } end; end;
Иногда может потребоваться, чтобы объекты полосы прокрутки сами реагировали на уведомляющие сообщения полосы прокрутки. При этом конкретная реакция поведения должна быть встроена в объект полосы прокрутки. Для программирования объекта полосы прокрутки, который непосредственно реагировал бы на его информационные сооб- щения, нужно определить для его типа метод реакции, основанный на информации. В качестве идентификатора заголовка метода нужно ис- пользовать сумму nf_First и информационного кода полосы прокрут- ки. Этот процесс описан в разделе "Уведомляющие сообщения управ- ляющих элементов" Главы 16.
Реакция на управляющие элементы
Реакция на взаимодействие с пользователем с помощью управля- ющих элементов несколько более сложна, чем просто вызов методов объектов управляющих элементов. Чтобы узнать, как отвечать на со- общения управляющих элементов, см. раздел "Команды, уведомления и идентификаторы управляющих элементов" в Главе 16.Сохранение управляющих элементов
Для вывода на экран управляющих элементов нет необходимости вызывать метод Show. Как дочерние окна, они автоматически выво- дятся на экран и повторно отображаются вместе с порождающим ок- ном. Однако, вы можете использовать Show для сокрытия или вывода управляющих элементов по запросу.Создание ваших собственных специализированных управляющих элементов
Простейший способ создания специализированного управляющего элемента состоит в фактическом создании окна, которое действует как управляющий элемент, но вовсе не является окном. Этот подход используется в программе Steps в Части 1 данного руководства. Тот же используемый в программе Steps метод применяется для ее объек- та палитры, который можно использовать, например, для создания объекта инструментальной полосы. Таким "управляющие элементы" яв- ляются наследниками TWindow, а не TControl, поскольку TControl имеет дело только со стандартными управляющими элементами Windows.Другим стандартным способом создания специализированного уп- равляющего элемента является построение в динамически компонуемой библиотеке нового класса окон. После этого вы можете создавать объекты ObjectWindows, использующие этот новый класс. Пакет раз- работчика ресурсов также может использовать специализированные управляющие элементы, созданные в DLL. Информацию об использова- нии специализированных управляющих элементов в ресурсах диалого- вых блоках вы можете найти в "Руководстве пользователя по пакету разработчика ресурсов".
Примечание: О классах окон рассказывается в Главе 10.
Специализированные управляющие элементы Borland для Windows
Специализированные управляющие элементы Borland для Windows (BWCC) обеспечивают выразительный внешний вид приложений Borland для Windows. Основными средствами BWCC являются:* командные кнопки с графическими изображениями;
* серый "рельефный" фон диалоговый блоков;
* трехмерные кнопки с зависимой и независимой фиксацией.
ObjectWindows дает вам возможность простого доступа к BWCC, так что вы можете придать своим приложениями стандартный для Borland вид.
Средства BWCC
BWCC добавляет к стандартным управляющим элементам в стиле Windows некоторые новые стили, но подчиняется также новым согла- шения и предположениям. Если вы создаете все свои новые управляю- щие элементы из ресурсов, то беспокоиться об этом вам не нужно. Однако при построении управляющих элементов в программном коде вам может потребоваться использовать некоторые новые стили и сле- довать соглашениям.Связь с управляющими элементами
Связь между оконным объектом и его управляющими объектами в некотором смысле аналогичны взаимодействию между объектом диало- гового блока и его управляющими элементами. Как и диалоговому блоку, окну требуется механизм для работы с его управляющими эле- ментами и для ответа на управляющие события, такие как выбор в блоке списка.Три типа комбинированных блоков
Имеются три типа комбинированных блоков: простые, раскрываю- щиеся и раскрывающиеся со списком. На Рис. 12.8 показан вывод трех типов комбинированных блоков с блоком списка.+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXStatic Control TesterXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | | | Блок списка Простой комбинированный блок | | +----------------+ +--------------------------+ | | |a | | | | | |b | ++-------------------------| | | |c | |a | | | |d | |b | | | |e | |c | | | |f | |d | | | +----------------+ +-------------------------+ | | | | Раскрывающийся комбинированный Комбинированный блок с | | блок раскрывающимся списком | | +-----------------------++---+ +----------------------+---+ | | | v | |c#####################| v | | | +-----------------------++---+ +----------------------+---+ | | | | | | | +---------------------------------------------------------------+
Рис. 12.8 Три типа комбинированных блоков и блок списка.
Перечень стилей комбинированного блока Таблица 12.10 +---------------------+---------------------+-------------------+ | Стиль |Возможность скрытого |Соответствие текста| | | списка | списку | +---------------------+---------------------+-------------------| | Простой | нет | нет | | Раскрывающийся | есть | нет | | Раскрывающийся со | есть | да | | списком | | | +---------------------+---------------------+-------------------+
С точки зрения пользователя между различными стилями комби- нированных блоков существуют следующие различия:
* Простые комбинированные блоки.
Простой комбинированный блок не может делать область спис- ка скрытой. Его область редактирования ведет себя анало- гично управляющему элементу редактирования. Пользователь может вводить и редактировать текст, и текст не обязан совпадать ни с одним из элементов в списке. При совпадении выбирается соответствующий элемент списка.
* Раскрывающиеся комбинированные блоки.
Раскрывающиеся комбинированные блоки ведут себя аналогично простым комбинированным блокам, но с одним исключением. В начальной стадии работы их область списка не отображается. Она появляется, когда пользователь нажимает стрелку вниз, расположенную справа от области редактирования. Раскрываю- щиеся комбинированные блоки и раскрывающиеся комбинирован- ные блоки списков очень удобны, когда нужно поместить большое число управляющих элементов в маленькую область. Когда они не используются, то занимают значительно меньшую площадь, чем простой комбинированный блок или блок списка.
* Раскрывающиеся комбинированные блоки списка.
Область списка в раскрывающемся комбинированном блоке списка ведет себя подобно области списка в спускающемся комбинированном блоке - появляется при необходимости и ис- чезает, когда не нужна. Эти два типа комбинированных бло- ков отличаются поведением их областей редактирования. Раскрывающиеся области редактирования ведут себя подобно обычным управляющим элементам редактирования. Раскрывающи- еся области редактирования списка ограничиваются только отображением одного элемента списка. Если редактируемый текст соответcтвует элементу списка, то никаких дополни- тельных символов ввести нельзя.
Уничтожение управляющих элементов
Порождающее окно отвечает за уничтожение управляющих элемен- тов. Экранный управляющий элемент автоматически уничтожается вместе с элементом порожденного окна, когда пользователь закрыва- ет окно или приложение. Деструктор порождающего окна автоматичес- ки уничтожает все дочерние объекты.Управление диапазоном полосы прокрутки
Один из атрибутов полосы прокрутки, инициализируемый при ее конструировании, это диапазон. Диапазон полосы прокрутки - это набор всевозможных положений указателя (маркера полосы прокрут- ки). Маркер полосы прокрутки - это подвижный прямоугольник, кото- рый пользователь может перемещать по ней. Каждой позиции соот- ветствует целое число. Порождающее окно использует эту целую ве- личину, позицию, для установки и запроса по полосе прокрутки. После конструирования объекта полосы прокрутки его диапазон уста- навливается от 1 до 100.Положению маркера в "самой верхней" позиции (вершина верти- кальной полосы прокрутки или крайнее левое положение горизонталь- ной полосы прокрутки) соответствует позиция 1. "Самой нижней" по- зиции маркера соответствует позиция 100. Для установки иного диа- пазона нужно использовать метод SetRange, описанный в разделе "Модификация полосы прокрутки".
Управление параметрами полосы прокрутки
Два других атрибута объекта полосы прокрутки - это его при- ращение по строкам и страницам. Приращение по строкам, установ- ленное в 1, это расстояние в единицах диапазона, на которое пере- местится указатель при нажатии пользователем стрелок на полосе прокрутки. Приращение по страницам, установленное в 10, это расс- тояние в единицах диапазона, на которое переместится указатель при нажатии пользователем в области прокрутки. Эти значения можно изменить непосредственной модификацией полей объекта TScrollBar, LineSize и PageSize.Установка значений управляющих элементов
Для управления сложными блоками диалога или окнами с мно- жеством дочерних окон управляющих элементов вы обычно можете для хранения и выяснения состояния его управляющих элементов создать производный тип объекта. Состояние управляющего элемента включает в себя текст управляющего элемента редактирования, положение по- лосы прокрутки и установку кнопки с зависимой фиксацией.Выбор типа комбинированного блока
Раскрывающиеся комбинированные блоки списка удобно использо- вать тогда, когда не допускаются никакие другие варианты, кроме перечисленных в области списка. Например, при выборе принтера для печати можно выбрать только принтер, к которому есть доступ в ва- шей системе.С другой стороны, раскрывающиеся комбинированные блоки могут воспринимать выбор, который отличается от приведенных в списке элементов. Раскрывающийся комбинированный блок можно использовать для выбора файлов на диске при их открытии или записи. Пользова- тель может либо просматривать каталоги в поисках нужного файла, либо ввести полный маршрут и имя файла в области редактирования, независимо от того, присутствует ли это имя файла в области спис- ка.
Вызов конструкторов объектов управляющих элементов
В то время как обычный конструктор объекта дочернего окна имеет только два параметра (порождающее окно и строку заголовка), конструктор управляющего объекта имеет их не менее шести. Объект блока списка имеет наиболее простой из всех управляющих элементов конструктор, который требует задания только шести параметров:- объекта порождающего окна; - идентификатора управляющего элемента; - координату x верхнего левого угла; - координату y верхнего левого угла; - ширину; - высоту.
TListBox.Init описывается следующим образом:
constructor TListBox.Init(AParent: PWindowsObject; AnID: Integer; X, Y, W, H: Integer);
Все объекты управляющих элементов ObjectWindows (кроме TMDIClient) требуют не менее 6 параметров. Большинство из них воспринимают также параметр, задающий текст управляющего элемен- та.
Pascal 7 & Objects
Абстрактный объект проверки допустимости
Абстрактный тип TValidator служит базовым типом для всех объектов проверки допустимости, но сам по себе он не делает ниче- го полезного. По существу, TValidator - это объект проверки до- пустимости, для которого всегда допустим любой ввод: IsValid и IsValidInput возвращают True, а Error не выполняет никаких функ- ций. Наследующие типы переопределяют IsValid и/или IsValidInput для фактического определения того, какие значения являются допус- тимыми.Если никакие из других объектных типов проверки допустимости не годятся в качестве исходных, вы можете использовать TValidator в качестве отправной точки собственных объектов проверки допусти- мости.
Добавление к управляющим элементам редактирования средств проверки допустимости
Каждый управляющий элемент редактирования имеет поле с име- нем Validator, установленное по умолчанию в nil, которое может указывать на объект проверки допустимости. Если вы не присваивае- те объекта полю Validator, то управляющий элемент редактирования ведет себя так, как описано в Главе 12. После присваивания с по- мощью вызова SetValidator объекта проверки допустимости управляю- щий элемент редактирования автоматически проверяется им при обра- ботке основных событий и при самом вызове для проверки допусти- мости.Обычно, как показано ниже, объект проверки допустимости строится и присваивается в одном операторе:
. . { создание трехсимвольного управляющего элемента редакти- . рования } Ed := New(PEdit, Init(@Self, id_Me, '', 10, 10, 50, 30, 3, False)); Ed^.SetValidator(New(PRangeValidator, Init(100, 999))); . . .
Фильтрация ввода
Простейший способ обеспечения включения в поле только допус- тимых данных состоит в обеспечении ввода пользователем только до- пустимых данных. Например, числовое поле ввода может быть ограни- чено вводом пользователем только цифровых данных.Объект фильтра проверки допустимости ObjectWindows представ- ляет общий механизм, ограничивающий вид символов, которые пользо- ватель может вводить в данном управляющем элементе редактирова- ния. Объекты проверки допустимости рисунков могут также контроли- ровать форматирование и типы символов, которые может набирать пользователь.
Фильтрация
Фильтрующие объекты проверки допустимости - это простая реа- лизация средств проверки допустимости, при которой проверяется только набираемый пользователем ввод. Конструктор фильтрующего объекта проверки допустимости воспринимает один параметр - набор допустимых символов:constructor TFilterValidator.Init(AValidChars: TCharSet);
TFilterValidator переопределяет IsValidInput для возврата True только в том случае, если все символы в текущей строке ввода содержатся в наборе символов, переданных конструктору. Управляю- щие элементы редактирования включают символы только в том случае, если IsValidInput возвращает True, так что нет необходимости пе- реопределять IsValid. Поскольку символы проходят через фильтр ввода, полная строка допустима по определению.
Потомки TFilterValidator, такие как TRAngeValidator, могут сочетать фильтрацию ввода с другими проверками завершенной стро- ки.
Использование механизма проверки допустимости данных
Использование объекта проверки допустимости данных с управ- ляющим элементом редактирования требует двух шагов:* Построение объекта проверки допустимости. * Присваивание объекта проверки допустимости управляющему элементу редактирования.
После того, как вы построите объект проверки допустимости и свяжите его с управляющим элементом редактирования, вам не потре- буется взаимодействовать с ним непосредственно. Управляющий эле- мент редактирования знает, когда вызывать методы проверки допус- тимости и в какие моменты.
Как работает проверка допустимости
В ObjectWindows предусмотрено несколько видов объектов про- верки допустимости, которые должны охватывать большинство ваших потребностей по проверке данных. Из абстрактных типов проверки допустимости вы можете также построить свои собственные произ- водные типы.В данном разделе освещаются следующие темы:
* Виртуальные методы объекта проверки допустимости. * Стандартные типы объекта проверки допустимости.
Методы объекта проверки допустимости
Каждый объект проверки допустимости наследует от абстрактно- го объектного типа TValidator четыре важных метода. Различным образом переопределяя эти методы, наследующие объекты проверки допустимости выполняют свои конкретные задачи по проверке. Если вы собираетесь модифицировать стандартные объекты проверки допус- тимости или написать собственные объекты проверки допустимости, то нужно понимать, что делает каждый из этих методов и как их ис- пользуют управляющие элементы редактирования.Этими четырьмя методами являются следующие:
* Valid
* IsValid
* IsValidInput
* Error
Единственными методами, вызываемыми вне объекта, являются Valid и IsValidInput. Error и IsValid - единственные методы, вы- зываемые другими методами объекта проверки допустимости.
Построение объектов проверки допустимости
Так как объекты проверки допустимости не являются интерфейс- ными объектами, их конструкторам требуется только информация, достаточная для установки критерия проверки допустимости. Напри- мер, объект проверки допустимости числового диапазона воспринима- ет два параметра - минимальное и максимальное значения в допусти- мом диапазоне:constructor TRangeValidator.Init(AMin, AMax: Integer);
Просмотр строк
Рабочий пример объекта проверки допустимости с преобразова- нием представляет TStringLookupValidator, сравнивающий переданную из управляющего элемента редактирования строку с элементами в списке строк. Если переданная строка содержится в списке, метод объекта проверки допустимости с просмотром строки возвращает True. Конструктор воспринимает только один параметр - список до- пустимых строк:constructor TStringLookupValidator.Init(AString: PStringCollection);
Чтобы после построения объекта проверки допустимости с прос- мотром использовать другой список строк, передайте новый список методу NewStringList объекта проверки допустимости (который унич- тожает старый список и задает новый).
TStringLookupValidator переопределяет методы Lookup и Error, так что Lookup возвращает True, если переданная строка содержится в наборе строк, а Error выводит на экран блок сообщения, указыва- ющий, что строка отсутствует в списке.
Проверка диапазона
Объект проверки допустимости диапазона TRangeVaidator - это потомок TFilterValidator, которые воспринимают только числа и до- бавляют к итоговым результатам проверку диапазона. Конструктор воспринимает два параметра, определяющим минимальное и максималь- ное допустимое значение:constructor TRangeValidator.Init(AMin, AMax: Integer);
Объект проверки допустимости диапазона сам строит числовое средство проверки-фильтрации, воспринимающее только цифры '0''9' и символы плюса и минуса. Таким образом, наследуемый IsValidInput обеспечивает отфильтрацию только цифр. Затем TRangeValidator переопределяет IsValid, чтобы он возвращал True только если введенные числа находятся в допустимом диапазоне, оп- ределяемом в конструкторе. Метод Error выводит блок сообщения, указывающий, что введенное значение находится вне диапазона.
Проверка допустимости данных
Основным внешним интерфейсом с объектами проверки допусти- мости данных является метод Valid. Аналогично методу CanClose ин- терфейсных объектов, Valid представляет собой булевскую функцию, которая возвращает значение True, если переданная ей строка со- держит допустимые данные. Один из компонентов метода CanClose уп- равляющего элемента редактирования является вызов метода Valid с переданным ему текущим текстом управляющего элемента редактирова- ния.При использовании средств проверки допустимости с управляю- щими элементами редактирования вам никогда не требуется вызывать или переопределять метод Valid объекта проверки допустимости. По умолчанию Valid возвращает True, если возвращает True метод IsValid. В противном случае для уведомления пользователя об ошиб- ке и возврата значения False вызывается Error.
Проверка допустимости каждого поля
Иногда удобно гарантировать, чтобы пользователь обеспечивал для конкретного поля допустимый ввод перед переходом к следующему полю. Этот подход часто называют "проверкой допустимости по табу- ляции", поскольку переход в новое поле обычно выполняется по кла- више Tab.В качестве примера можно привести приложение, которое выпол- няет поиск в базе данных, где пользователь вводит в поле некото- рые виды ключевой информации, а приложение отвечает на это считы- ванием соответствующей записи и фильтрацией остальных полей. В таком случае вашему приложению перед действием по клавише требу- ется проверка, что пользователь набрал в этом ключевом поле пра- вильную информацию.
Проверка допустимости нажатий клавиш
Когда объект управляющего элемента редактирования получает имеющее для него значение событие нажатия клавиши, он вызывает метод IsValidInput объекта проверки допустимости. По умолчанию методы IsValid всегда возвращают True. Это означает, что воспри- нимаются все нажатия клавиш. Однако, наследующие объекты проверки допустимости могут переопределять метод IsValidInput, чтобы от- фильтровывать нежелательные нажатия клавиш.Например, средства проверки допустимости диапазона, которые используются для числового ввода, возвращают из IsValidInput True только для цифр и символов '+' и '-'.
IsValidInput воспринимает два параметра. Первый параметр - это параметр-переменная, содержащая текущий текст ввода. Второй параметр - это булевское значение, указывающее, следует ли перед попыткой проверки допустимости применять к строке ввода дополне- ние или заполнение. TPictureValidator - это единственный из стан- дартных объектов проверки допустимости, использующий второй пара- метр.
Проверка допустимости по шаблону
Объекты проверки допустимости с шаблоном сравнивают строки, набранные пользователем, с шаблоном, описывающим формат допусти- мого ввода. Применяемые шаблоны совместимы с теми, которые ис- пользуются для контроля ввода в реляционной базе данных Paradox фирмы Borland. При построении объекта проверки допустимости по шаблону используется два параметра: строка, содержащая образ шаб- лона, и булевское значение, указывающее, нужно ли заполнять по шаблону литеральные строки.Примечание: Синтаксис шаблонов описывается в справоч- нике. См. TPXPictureValidator.Picture.
constuctor TPictureValidator.Init(const APic: String; AAutoFill: Boolean);
TPictureValidator переопределяет Error, IsValidInput и IsValid и добавляет новый метод Picture. Изменения в Error и IsValid просты: Error выводит на экран блок сообщения, указываю- щий, какой формат должна иметь строка, а IsValid возвращает True только если True возвращается функцией Picture, позволяя получать новые производные типы проверки допустимости по шаблону путем пе- реопределения только метода Picture. IsValidInput проверяет сим- волы по мере набора их пользователем, допуская только те символы, которые разрешены в шаблоне формата, и возможно дополняя лите- ральные символы из шаблона.
Метод Picture пытается сформатировать заданную строку ввода в соответствии с шаблоном формата и возвращает значение, указы- вающее степень успеха: полный, неполный или ошибка.
Проверка допустимости полных экранов
Проверить допустимость полных экранов вы можете тремя раз- личными способами:* Проверкой допустимости режимных окон. * Проверкой допустимости при изменении фокуса. * Проверкой допустимости по запросу.
Проверка допустимости режимных окон
Когда пользователь закрывает режимное окно, оно перед закры- тием автоматически проверяет допустимость всех своих подобластей просмотра (если закрывающей командой не была cmCancel). Для про- верки допустимости всех подобластей окно вызывает метод CanClose каждой подобласти, и если каждый из них возвращает True, то окно можно закрыть. Если любая из подобластей возвращает значение False, то окно закрыть нельзя.
Пока пользователь не обеспечит допустимые данные, режимное окно с недопустимыми данными можно только отменить.
Проверка допустимости по запросу
В любой момент вы можете указать окну на необходимость про- верки всех его подокон путем вызова метода CanClose. CanClose по существу спрашивает окно "Если сейчас будет дана команда закры- тия, являются ли все поля допустимыми?" Окно вызывает методы CanClose всех своих дочерних окон в порядке включения и возвраща- ет True, если все они возвращают значение True.
Вызов CanClose не обязывает вас фактически закрывать окно. Например, вы можете вызвать CanClose, когда пользователь "нажима- ет" командную кнопку Save (Сохранение), обеспечивая проверку до- пустимости данных перед их сохранением.
Вы можете проверить любое окно (режимное или безрежимное) и в любое время. Однако автоматическую проверку допустимости при закрытии имеют только режимные окна. Если вы используете безре- жимные окна ввода данных, то нужно обеспечить, чтобы приложение перед выполнением действий с введенными данными вызывало метод CanClose окна.
Проверка допустимости с просмотром
Абстрактный объект проверки допустимости с просмотром TLookupValidator обеспечивает основу для общего типа объекта про- верки допустимости, который для определения допустимости сравни- вает введенное значение со списком воспринимаемый элементов.TLookupValidator - это абстрактный тип, который никогда не используется сам по себе, но служит важным изменением и дополне- нием к стандартному объекту проверки допустимости.
Примечание: Пример работы такого объектного типа вы можете найти в разделе по преобразованию строк.
Новый метод, вводимый объектом TLookupValidator называется Lookup. По умолчанию Lookup возвращает значение False, но при об- разовании производного абстрактного объекта проверки допустимости c просмотром вы можете переопределить Lookup для сравнения пере- данной строки со списком и возвращать True, если строка содержит допустимую запись.
TLookupValidator переопределяет IsValid для возврата True только если Lookup также возвращает True. В наследующих типах проверки допустимости с просмотром вам следует переопределять не IsValid, а Lookup.
Проверка полной строки
Объекты проверки допустимости содержат виртуальный метод IsValid, который воспринимает в качестве единственного аргумента строку и возвращает True, если строка представляет допустимые данные. IsValid - это метод, который выполняет фактическую про- верку допустимости, так что если вы создаете собственные объекты проверки допустимости, то почти всегда переопределяете IsValid.Заметим, что метод IsValid не вызывается вами явно. Исполь- зуйте для вызова IsValid метод Valid, так как для уведомления пользователя в случае возврата методом IsValid значения False Valid вызывает метод Error. Не путайте также проверку допустимос- ти сообщением об ошибке.
Сообщение о недопустимых данных
Виртуальный метод Error уведомляет пользователя, что содер- жимое управляющего элемента редактирования не прошло проверку до- пустимости. Стандартные объекты проверки допустимости в общем случае представляет простой блок сообщения, уведомляющий пользо- вателя, что содержимое ввода недопустимо, и описывающее, каким должен быть правильный ввод.Например, метод Error для проверки допустимости диапазона создает блок сообщения, указывающий, что значение в управляющем элементе редактирования не находится между указанными минимальным и максимальным значениями.
Хотя большинство объектов проверки допустимости переопреде- ляют Error, вам не следует вызывать его непосредственно. Метод Error вызывается методом Valid, если IsValid возвращает False (что является единственным моментом, когда необходимо вызывать Error).
Стандартные средства проверки допустимости
ObjectWindows включает в себя шесть стандартных типов объек- тов проверки допустимости, включая абстрактный объект проверки допустимости и следующие пять специальных типов таких объектов:* Фильтрация. * Проверка диапазона. * Проверка допустимости с просмотром. * Проверка допустимости с просмотром строк. * Проверка допустимости с просмотром шаблонов.
Три вида проверки допустимости данных
Существует три различных типа проверки допустимости данных, и ObjectWindows поддерживает их по-разному. Этими тремя видами являются:* Фильтрация ввода.
* Проверка допустимости каждого элемента.
* Проверка допустимости полных экранов.
Заметим, что эти методы не являются взаимно-исключающими. Ряд стандартных средств проверки допустимости могут комбинировать в одном механизме проверки допустимости различные методы.
Важно запомнить, что проверка допустимости выполняется объ- ектом проверки допустимости, а не объектом управляющего элемента редактирования. Если вы уже создали для особого назначения специ- ализированный управляющий элемент редактирования, то возможно сдублировали возможность, встроенную в управляющие элементы ре- дактирования и их средства проверки допустимости.
В разделе данной главы "Как работают средства проверки до- пустимости" описываются различные способы, с помощью которых объ- екты управляющего элемента редактирования автоматически вызывают объекты проверки допустимости.
Pascal 7 & Objects
Автоматические дочерние окна
Может потребоваться, чтобы ваша окно-рамка воспроизводило только одно дочернее окно MDI при своем первом появлении. Для этого первого дочернего окна вы можете явно задать его размер. В отличие от других дочерних окон, дочерние окна MDI должны быть сконструированы и созданы в методе SetupWindow окна-рамки MDI, а не в Init. Вы также должны явно создать экранный элемент дочерне- го окна с помощью вызова MakeWindow:procedure MyMDIWindow.SetupWindow; var ARect: TRect; NewChild: PMyChild; begin TMDIWindow.SetupWindow; NewChild:=PMyChild(InitChild); GetClientRect(HWindow, ARect); with NewChild^.Attr, ARect do begin W:=(right*4) div 5; H:=(bottom*3) div 5; Title:='Child #1'; end; Application^.MakeWindow(NewChild); end;
В некоторых приложениях вам может потребоваться создать до- чернее окно MDI в ответ на более чем один выбор в меню. Например, пункты меню New и Open в редакторе файла могут приводить к воз- никновению нового дочернего окна с заголовком в виде имени файла. В этом случае определите для построения дочернего окна методы ав- томатической реакции. ObjectWindows определяет команды cm_MDIFileOpen и cm_MDIFileNew, что облегчает дифференциацию от стандартных cm_FileOpen и cm_FileNew.
Дочерние окна MDI
Каждое дочернее окно MDI имеет некоторые характеристики пе- рекрывающего окна. Его можно максимизировать до полного размера окна клиента MDI или минимизировать в пиктограмму, которая поме- щается к нижней границе окна-рамки. Дочернее окно MDI никогда не выходит за границы его окна-рамки (обрамляющего окна). Дочернее окно MDI не может иметь меню, поэтому все его функции реализуются меню окна-рамки. Заголовок каждого дочернего окна MDI часто представляет собой имя открытого файла, связанного с этим окном, хотя его поведение заранее неизвестно и определяется программой. Можно рассматривать приложение MDI как мини-сеанс Windows, когда несколько приложений представлены окнами или пиктограммами.Меню дочернего окна
Строка меню окна-рамки содержит меню, управляющее дочерними окнами MDI. Меню дочернего окна содержит такие элементы как Tile (Вывод без перекрытия), Cascade (Вывод с перекрытием), Arrange (Упорядочить) и Close All (Закрыть все). Имя каждого открытого окна MDI автоматически добавляется к концу этого меню с выбором текущего окна.Настройка активизации дочернего окна
Пользователь приложения MDI может свободно активизировать любое открытое или минимизировать дочернее окно MDI. Однако, вам может потребоваться предпринять некоторые действия, когда пользо- ватель дезактивирует одно дочернее окно активизацией другого. Например, меню окна-рамки может отражать текущее состояние актив- ного дочернего окна, выделяя его цветом. Каждый раз, когда дочер- нее окно становится активным или неактивным, оно получает сообще- ние Windows wm_MDIActivate. Определив метод реакции на это с об- щение для дочернего окна, вы можете отслеживать, какое дочернее окно активно и соответственно реагировать.Обработка сообщений в приложении MDI
Как и для обычных порождающих и дочерних окон, основанные на командах и дочерних идентификаторах сообщения Windows сначала поступают в дочерние окна для их восприятия и обработки. Затем сообщения поступают в порождающее окно. Однако, в случае приложе- ния MDI сообщения поступают к текущему дочернему окну MDI, затем к окну клиента MDI, и, наконец, к окну-рамке MDI (которое являет- ся порождающим окном для всех дочерних окон MDI). Следовательно, меню окна-рамки можно использовать для управления работой в теку- щем активном дочернем окне MDI. Затем шанс отреагировать получают окно клиента и окно-рамка.Окна MDI в ObjectWindows
ObjectWindows определяет типы для представления рамок MDI и клиентов MDI. Это соответственно TMDIWindow и TMDIClient. TMDIWindow является производным от TWindow, но TMDIClient на са- мом деле представляет собой управляющий элемент и является произ- водным от TControl. В приложении MDI ObjectWindows, окно-рамки владеет своим окном клиента MDI и хранит его в поле ClientWnd. Окно-рамка также содержит каждое из дочерних окон MDI в связанном списке ChildList. Дочерние окна MDI являются экземплярами типа объекта, производного от написанного вами TWindow.Методы TMDIWindow занимаются в основном конструированием и управлением дочерними окнами MDI, окном клиента MDI и обработкой выбора в меню. Главная работа TMDIClient происходит скрытно от пользователя и состоит в управлении дочерними окнами MDI. При разработке приложений MDI вы в общем случае будете создавать но- вые производные типы для своих рамок и дочерних окон соответс- твенно от TMDIWindow и TWindow.
Построение приложения MDI
Построение приложения MDI в ObjectWindows представляет собой относительно простую задачу:* Построение основного окна MDI.
* Установка меню дочернего окна.
* Предоставление основному окну возможности создания дочер- них MDI.
Окно MDI обрабатывает для вас все специфические функции, а ваши функции, специфические для приложения, могут перейти в до- черние окна.
Построение рамки MDI
Окно-рамка MDI всегда является основным окном приложения, поэтому оно конструируется в методе InitMainWindow его объекта приложения. Однако, существует два аспекта рамки MDI, которые от- личают его от других основных окон:* Рамка MDI всегда является основным окном, поэтому оно ни- когда не имеет порождающего окна. Таким образом, TMDIWindow.Init нет необходимости воспринимать в качестве параметра указатель порождающего окна.
* Окно-рамка MDI всегда должно иметь меню, так что вторым параметром Init является описатель меню. Для основных окон, отличных от MDI и производных от TWindows, вы опре- деляете Init для установки Attr.Menu в допустимый описа- тель меню. TMDIWindow.Init устанавливает для вас AttrMenu.
Типичный метод InitMainWindow для приложения MDI может выг- лядеть следующим образом:
procedure TMDIApplication.InitMainWindow; begin MainWindow := New(PMyFrame, Init('Заголовок рамки', LoadMenu(HInstance, 'MenuName'));
Если предположить, что TMyFrame - это потомок TMDIWindow, при этом будет создаваться окно-рамка MDI с заголовком "Заголовок рамки" и строкой меню, заданной ресурсом "MenuName".
Пример приложения MDI
Программа MDITest создает приложение MDI, показанное на Рис. 14.1. Полный текст файла MDITEST.PAS содержится на ваших дистрибутивных дискетах.Создание дочерних окон MDI
TMDIWindow определяет автоматический метод реакции CreateChild, который вызывается при выборе из меню варианта, ре- зультатом которого будет команда с идентификатором Create_Child. Обычно этот вариант меню называется New или Create. Как это опре- делено в TMDIWindow, CreateChild конструирует и создает дочернее окно MDI типа TWindow вызовом TMDIWindow.InitChild. Для задания корректного типа дочернего окна (производного от TWindow), пере- определим InitChild для вашего типа окна-рамки MDI:function MyMDIWindow.InitChild: PWindowsObject; begin InitChild:=New(PMyChild, Init(@Self, 'Новое дочернее окно')); end;
Создание меню дочерних окон
Меню окна-рамки должно включать в себя меню дочернего окна в стиле MDI. Открытие дочернего окна MDI добавляет его заголовок к меню дочернего окна, а закрытие дочернего окна удаляет его из списка. Это позволяет пользователю активизировать любое дочернее окно, даже если оно не является видимым.Окно-рамка должно знать, каким элементом меню верхнего уров- ня является меню его дочернего окна. Объект TMDIWindow хранит це- лое значение позиции в поле объекта ChildMenuPos. TMDIWindow.Init первоначально устанавливает ChildMenuPos в ноль, указывая край- ний левый элемент меню верхнего уровня. Однако, для установки по- зиции ChildMenuPos вы можете переопределить Init для своего про- изводного от TMDIWindow типа:
constructor TMyMDIWindow.Init(ATitle: PChar; AMenu: HMenu); begin inherited Init(ATitle, AMenu); ChildMenuPos := 1; end;
TMDIWindow.Init также вызывает InitClientWindow для констру- ирования объекта TMDIClient, который будет служит его окном кли- ента MDI. TMDIWindow.SetupWindow создает окно клиента MDI.
Управление дочерним окном MDI
Тип окна MDI в ObjectWindows содержит методы манипулирования дочерними окнами MDI приложения MDI. Хотя большая часть скрытой работы делается в TMDIClient, доступ к данным и функциям происхо- дит через метод TMDIWindow.TMDIWindow определяет методы реакции на сообщения Windows, которые автоматически реагируют на выбор команды стандартного ме- ню MDI: Title, Cascade, Arrange Icon и Close All. Эти методы ожи- дают основанных на командах сообщений с заранее определенными константами идентификаторов меню. Обязательно используйте эти идентификаторы при построении ресурса меню дочернего окна:
Стандартные методы, команды и действия MDI Таблица 14.1 +----------------+------------------------+---------------------+ | Действие | Константа ID меню | Метод TMDIWindow | +----------------+------------------------+---------------------| | Tile | cm_TileChildren | CM_TileChildren | | Cascade | cm_CascadeChildren | CM_CascadeChildren | | Arrange Icons| cm_ArrangeChildIcons | CM_ArrangeChildIcons| | Close All | cm_CloseChildren | CM_CloseChildren | +----------------+------------------------+---------------------+
Методы реакции TMDIWindows, подобные CMTileChildren, вызыва- ют другие методы TMDIWindows, такие как CMChildren. Эти методы вызывают методы TMDIClient с тем же именем, например, TMDIClient^.TileChildren. Для переопределения такого автоматичес- кого поведения нужно переопределить TMDIWindow.TileChildren или другой метод TMDIWindow. Для дочерних окон MDI не подходит реаги- рование на основанные на командах сообщения, генерируемые меню дочернего окна.
Pascal 7 & Objects
Другие соглашения по печати
Объекты распечатки содержат также несколько других методов, которые вы можете при необходимости переопределить. Методы BeginPrintint и EndPrinting вызываются, соответственно, перед пе- чатью и после печати любого документа. Если вам требуется специ- альная установка, вы можете выполнить ее в BeginPrinting и отме- нить в EndPrinting.Печать страниц выполняется последовательно. То есть, для каждой страницы в последовательности принтер вызывает метод PrintPage. Однако, перед первым вызовом PrintPage объект принтера вызывает BeginDocument, передавая номер первой и последней стра- ницы, которые будут печататься. Если для вашего документа при пе- чати страниц, отличных от первой, требуется специальная подготов- ка, переопределите метод BeginDocument. После распечатки послед- ней страницы вызывается соответствующий метод EndDocument.
Может потребоваться также переопределение метода GetSelection. GetSelection указывает в своем возвращаемом булевс- ком значении, имеет ли документ выделенную часть. Если это так, диалоговое окно печати предоставляет вам возможность распечатать только эту выделенную часть. Позицию выделения указывают два па- раметра-переменных Start и Stop. Например, TEditPrintout интерп- ретирует Start и Stop как позиции символов, но может представлять также строки текста, страницы и т.д.
Назначение конкретного принтера
В некоторых случаях вам может потребоваться назначить для своего объекта принтера специфическое устройство печати. TPrinter имеет метод SetDevice, который именно это и делает.SetDevice воспринимает в качестве параметров три строки: имя устройства, имя драйвера и имя порта.
Печать документа
Windows рассматривает распечатку как последовательность страниц, поэтому задачей вашего объекта распечатки является прев- ращение документа в последовательность станичных образов для пе- чати Windows. Аналогично тому как оконные объекты используются для отображения образов на экране дисплея, объекты распечатки ис- пользуются для печати образов на принтере.ObjectWindows предусматривает абстрактный объект распечатки TPrintout, из которого вы можете создать производные объекты рас- печатки. Вам нужно переопределить в TPrintout только несколько методов.
Ваши объекты распечатки должны делать следующее:
* Устанавливать параметры принтера.
* Подсчитывать страницы.
* Отображать каждую страницу в контексте устройства.
* Указывать, есть ли еще страницы.
Остальная часть этой главы ссылается на пример программы PrnTest, записанной на ваших дистрибутивных дискетах под именем PRNTEST.PAS. PrnTest считывает текстовый файл в набор строк, а затем по команде печатает документ. Объект PrnTest описывается следующим образом:
type PTextPrint = ^TTextPrint; TTextPrint = object(TPrintout); TextHeight, LinesPerPage, FirstOnPage, LastOnPage: Integer; TheLines; PCollection; constructor Init(ATitle: PChar; TheText: PPCharCollection); function GetDialogInfo(var Pages: Intger): Boolean; virtual; function HasNextPage(Page: Word): Boolean; virtual; procedure SetPrintParams(ADC: HDC; ASize: TPoint); virtual; procedure PrintPage(Page: Word; var Rect: TRect; Flags: Word); virtual; end;
Печать каждой страницы
Когда объект принтера предоставляет возможность разбивки до- кумента на страницы, то для печати каждой страницы он вызывает метод PrintPage объекта распечатки. Процесс распечатки только части документа, которому принадлежит данная страница, аналогичен определению того, какую часть прокручиваемого окна нужно отобра- жать на экране. Например, можно отметить подобие методов отобра- жения окна и отображения страницы:procedure TTextWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var Line: Integer; TextMetrics: TTextMetric; TheText: PChar;
function TextVisible(ALine: Integer): Boolean; begin with Scroller^ do TextVisible := IsVisible(0, (ALine div YUnit) + YPos, 1, Attr.W div YUnit); end;
begin GetTextMetrics(PaintDC, TextMetrics); Scroller^.SetUnits(TextMetrics.tmAveCharWidth, TextMetrics.tmHeight); Line := 0; while (Line < FileLines^.Count) and TextVisible(Line) do begin TheText := PChar(FileLines^.At(Line)); if TheText <> nil then TextOut(PaintDC, 0, Line * Scroller^.YUnit, TheText, StrLen(TheText)); Inc(Line); end; end;
procedure TTextPrint.PrintPage(Page: Word; var Rect: TRect; Flags: Word); var Line: Integer; TheText: PChar; begin FirstOnPage := (Page - 1) * LinesPerPage; LastOnPage := (Page * LinesPerPage) - 1; if LastOnPage >= TheLines^.Count then LastOnPage := TheLines^.Count - 1; for Line := FirstOnPage to LastOnPage do begin TheText := Theines^.At(Line); if TheText <> nil then TextOut(DC, 0, (Line - FirstOnPage) * TextHeight, TheText, StrLen(TheText)); end; end;
При написании методов PrintPage следует иметь в виду следую- щее:
* Независимость от устройств. Убедитесь, что в вашем коде не делается предположений относительно масштаба, коэффициента относительного удлинения или цветах. Для различных уст- ройств видеоотображения и печати эти характеристики могут отличаться, так что в программе следует избегать такой за- висимости.
* Возможности устройств. Хотя большинство видеоустройств поддерживают все операции GDI, некоторые принтеры этого не делают. Например, многие устройства печати (такие как гра- фопостроители) совсем не воспринимают графических изобра- жений. При выполнении сложных задач вывода в программе следует вызывать функцию API Windows GetDeviceCaps, кото- рая возвращает важную информацию о данном устройстве выво- да.
Печать содержимое окна
Поскольку окно не разбивается на несколько страниц, и окон- ные объекты уже знают, как отображаться в контексте устройства, простейшим видом генерируемой распечатки является копия содержи- мого окна.Чтобы еще более облегчить эту общую операцию, ObjectWindows обеспечивает дополнительный вид объекта распечатки - TWindowPrint. Любой оконный объект ObjectWindows может без моди- фикации печатать свое содержимое в объект TWindowPrintout. Объек- ты распечатки масштабируют образ для заполнения нужного числа страниц и поддерживают коэффициент относительного удлинения.
Создание объекта распечатки окна требует только одного шага. Все, что требуется сделать - это построить объект печати окна, передавая ему строку заголовка и указатель на окно, которое тре- буется напечатать:
PImage := New(PWindowPrintout, Init('Заголовок', PSomeWindow));
Часто возникает необходимость в том, чтобы окно само созда- вало свою распечатку, возможно в ответ на команды меню:
procedure TSomeWindow.CMPrint(var Msg: TMessage); var P: PPrintout; begin P := New(PWindowPrintout, Init('Дамп экрана', @Self)); { передать образ на экран } Dispose(P, One); end;
TWindowPrintout не предусматривает разбивки на страницы. При печати документа вам нужно печатать каждую страницу отдельно, но так как окна не имеют страниц, вам нужно напечатать только один образ. Окно уже знает, как создать этот образ - оно имеет метод Paint. TWindowsPrintout печатается путем вызова метода Paint окна объекта с контекстом устройства печати вместо контекста дисплея.
Печать в ObjectWindows
Модуль ObjectWindows OPrinter предусматривает для упрощения печати два объекта - TPrinter и TPrintout. TPrinter инкапсулирует доступ к устройствам печати. Он предоставляет возможность конфи- гурирования принтера, выводя диалог, в котором пользователь может выбрать нужный принтер, а также установить параметры печати, та- кие как графическое разрешение или ориентация (горизонтальная и вертикальная) печати.TPtintout инкапсулирует задачу печати документа. К принтеру этот объект имеет такое же отношение, как TWindow - к экрану. Ри- сование на экране выполняется методом Paint объекта TWindow, а печать на принтере - методом PrintPage объекта TPrintout. Чтобы напечатать что-то на принтере, приложение должно передать методу Print объекта TPrinter экземпляр TPrintout.
Почему печать представляет трудности?
С одной стороны печать в Windows достаточно проста. Вы може- те использовать для генерации распечатки те же функции GDI, что используются для вывода образов на экран. Для вывода текста ис- пользуется функция TextOut, а для вычерчивания прямоугольника - Rectangle.С другой стороны, процесс осложняется, так как Windows тре- бует непосредственного "общения" с драйверами принтера через вы- зовы Escape или получения адреса DeviceMode или ExtDeviceMode. Это еще более осложняется требованием Windows, чтобы приложение считывало имя драйвера устройства из файла WIN.INI. Кроме того, устройства печати обладают большими возможностями проверки допус- тимости и возможностями разрешения, чем видеоустройства.
ObjectWindows не может преодолеть все препятствия на этом пути, но делает процесс печати более простым и легким для понима- ния.
Подсчет страниц
После вызова SetPrintParams объект печати вызывает булевскую функцию GetDialogInfo. GetDialogInfo задает информацию, необходи- мую для вывода на экран диалогового блока, позволяющего пользова- телю выбрать перед печатью диапазон страниц. В подсчете страниц в GetDialogInfo и индикации вывода диалогового блока имеются два аспекта.Функция GetDialogInfo воспринимает единственный параметр-пе- ременную Pages, которую она должна устанавливать в число страниц в документе или в 0, если она не может подсчитать страницы. Возв- ращаемое значение равно True, если вы хотите вывести диалоговый блок, и False для подавления его вывода.
По умолчанию GetDialogInfo устанавливает Pages в 0 и возвра- щает True, что означает, что она не знает, сколько может полу- читься страниц, и что перед печатью будет выводиться диалоговый блок. GetDialogInfo обычно переопределяется для установки Pages в число страниц в документе и возвращает True.
Например, PrnTest подсчитывает, сколько строк текста выбран- ного шрифта может поместиться в области печати в SetPrintParams, а затем использует это число для подсчета количества страниц, ко- торые нужно напечатать в GetDialogInfo:
procedure TTextPrint.SetPrintParams(ADC: HDC; ASize: TPoint); var TextMetrics: TTextMetric; begin inherited SetPrintParams(ADC, ASize); { установить DC и размер Size } GetTextMetrics(DC, TextMetrics); { получить информацию о размере текста } TextHeigh := TextMetrics.tmHeight; { вычислить высоту строки } LinesPerPages := Size.Y div TextHeight; { и число строк на странице } end;
function TTextPtint.GetDialogInfo(var Pages: Integer): Boolean); begin Pages:= TheLines^.Count div LinesPerPage + 1; GetDialogInfo := True { вывод перед печатью диалогового блоки } end;
Построение объекта принтера
В большинстве случаев приложению требуется в каждый момент времени доступ только к одному принтеру. Простейшим способом реа- лизации этого является задание в объекте основного окна поля с именем Printer (типа PPrinter), которые другие объекты в програм- ме вызывают для целей печати. Чтобы сделать принтер доступным, поле Printer должно указывать на экземпляр TPrinter.В большинстве приложений это просто. Основное окно приложе- ния инициализирует объект принтера, который использует заданный по умолчанию принтер, указанный в WIN.INI:
constructor TSomeWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin Inherited Init(AParent, ATitle); . . . Printer := New(PPrinter, Init); end;
В некоторых случаях может возникать необходимость использо- вать в приложении различные принтеры одновременно из различных окон. В этом случае постройте объект принтера в конструкторах каждого такого окна, затем смените устройство принтера на один или более таких принтеров. Если программа использует различные принтеры, но не одновременно, то возможно лучше использовать один и тот же объект принтера и при необходимости выбирать различные принтеры.
Хотя вы можете сомневаться насчет переопределения конструк- тора TPrinter для использования принтера, отличного от заданного в системе по умолчанию, рекомендуемой процедурой является исполь- зование конструктора по умолчанию, а затем смена связанного с объектом устройства. См. раздел "Выбор другого принтера".
Создание распечатки
Единственной "хитрой" частью процесса печати в ObjectWindows являются создание распечатки. Этот процесс аналогичен написанию метода Paint для объекта окна: вы используете графические функции Windows для генерации в контексте устройства нужного графического образа. Контекст устройства оконного объекта обрабатывает ваши взаимодействия с устройством экрана; аналогичным образом контекст устройства распечатки изолирует вас от устройства печати.Примечание: Графические функции Windows поясняются в Главе 17.
Чтобы создать объект распечатки, постройте новый тип, произ- водный от TPtintout, который переопределяет PrintPage. В очень простых случаях это все, что требуется сделать. Если документ имеет размер более одной страницы, то вам нужно также переопреде- лить HasNextPage для возврата True. Текущий номер страницы пере- дается в качестве параметра PrintPage.
Объект распечатки имеет поля, содержащие размер страницы и контекст устройства, который уже инициализирован для принтера. Объект принтера устанавливает значения, вызывая метод объекта распечатки SetPrintParams. Используйте контекст устройства объек- та распечатки в любом вызове графических функций Windows.
Модуль OPrinter включает в себя два специализированных объ- екта распечатки, которые показывают диапазон сложности распеча- ток. Объект TWindowPrintout, печатающий содержимое окна, очень прост. TEditPrintout, который печатает содержимое управляющего элемента редактирования, очень сложен, так как имеет множество возможностей.
Указание оставшихся страниц
У объектов распечатки имеется также последняя обязанность - указать объекту принтера, имеются ли после данной страницы еще печатаемые страницы. Метод HasNextPage воспринимает в качестве параметра номер строки и возвращает значение Boolean, указываю- щее, существуют ли еще страницы. По умолчанию HasNextPage всегда возвращает значение False. Чтобы напечатать несколько страниц, ваши объекты распечатки должны переопределять HasNextPage для возврата True, если документ имеет больше страниц для печати, и False, если переданным параметром является последняя страница.Например, PrnTest сравнивает номер последней напечатанной строки с последней строкой в файле и определяет, нужно ли печа- тать еще страницы:
function TTextPrint.HasNextPage(Page: Word): Boolean; begin HasNextPage := LastOnPage < TheLines^.Count - 1; end;
Убедитесь, что HasNextPage возвращает в некоторой точке зна- чение False. Если HasNextPage всегда возвращает True, то процесс печати попадет в бесконечный цикл.
Выбор другого принтера
Когда у вас в приложении есть объект принтера, вы можете связать его с любым установленным в Windows устройстве печати. По умолчанию TPrinter использует заданный по умолчанию принтер Windows (как это определено в разделе устройств файла WIN.INI).Существует два способа задания альтернативного принтера: не- посредственно в программе и через диалоговое окно пользователя.
Выбор принтера пользователем
Наиболее общим способом назначения другого принтера является вывод диалогового окна, предоставляющего пользователю возможность выбора из списка установленных устройств печати. TPtinter делает это автоматически при вызове его метода Setup. Как показано на Рис. 15.1, Setup использует для этого диалогового окна объект TPrinterSetupDlg.+---------------------------------------------------------------+ |#=#XXXXXXXXXXXXXXXXXXXSelectPrinterXXXXXXXXXXXXXXXXXXXXXXXXXXXX| +---------------------------------------------------------------| | | | Printer and port: | | +--------------------------------------------+-+ | | |PostScript Printer on LPT1: |v| | | +--------------------------------------------+-+ | | +-----------+ +-----------+ +-----------+ | | |####OK#####| |##Setup####| |##Cancel###| | | +-----------+ +-----------+ +-----------+ | | | +---------------------------------------------------------------+
Рис. 15.1 Диалоговое окно задания принтера.
Настройка конфигурации принтера
Одна из командных кнопок в диалоге выбора принтера позволяет пользователям изменить конфигурацию конкретного принтера. Кнопка Setup выводит диалоговый блок конфигурации, определенной в драй- вере принтера. Ваше приложение не может управлять внешним видом или функциями диалогового блока конфигурации драйвера.
Вывод распечатки на принтер
При наличии объекта принтера и объекта распечатки фактичес- кая печать выполняется достаточно просто, независимо от того, происходит это из документа или из окна. Все, что вам нужно сде- лать - это вызов метода Paint объекта принтера, передача указате- ля на порождающее окно и указателя на объект распечатки:Printer^.Print(PParentWindow, PPrintoutObject);
В этом случае порождающее окно - это окно, к которому будут присоединены все всплывающие диалоговые блоки (например, состоя- ния принтера или сообщений об ошибках). Обычно это будет окно, генерирует команду печати, такое как основное окно со строкой ме- ню. В процессе печати для предотвращения передачи множественных команд печати это окно запрещается.
Предположим, у вас есть приложение, основное окно которого является экземпляром TWidGetWindow. В меню этого окна вы можете выбрать команду меню для печати содержимого окна, генерирующую команду cm_Print. Метод реакции на сообщения может иметь следую- щий вид:
procedure TWidgetWindow.CMPrint(var Msg: TMessage); var P: PPrintout; begin P := New(PWindowPrint, Init('Widgets', @Self)); Printer^.Print(@Self, P); Dispose(P, Done); end;
Задание параметров печати
Перед запросом распечатки документа объект принтера предос- тавляет вашему документу возможность разбивки на страницы. Для этого вызываются два метода объекта распечатки - SetPrintParams и GetDialogInfo.Функция SetPrintParams предназначена для инициализации структур данных, которые могут потребоваться для получения эффек- тивной распечатки отдельных страниц. SetPrintParams - это первая возможность вашей распечатки обратиться к контексту устройства и задать для принтера размер страницы. Приводимый ниже фрагмент программы показывает пример переопределенного метода SetPrintParams.
Если вы переопределяете SetPrintParams, убедитесь в вызове наследуемого метода, устанавливающего значения полей распечатки объекта.
Pascal 7 & Objects
Что такое сообщение?
Если вы не используете программирование, управляемое событи- ями, Windows может выглядеть достаточно странной операционной средой. Возможно, вам придется писать программы, которые основную часть своего времени просто ждут ввода от пользователя (например, в операторе Readln).Программирование, управляемое событиями, обходит эту ситуа- цию, возлагая обработку ввода от пользователя на центральную подпрограмму, которую вам даже не нужно вызывать. В этом случае Microsoft Windows сама взаимодействует с пользователем и опраши- вает список взаимодействий для каждого работающего приложения. Эти информационные пакеты называются сообщениями и представляют собой просто структуры записей типа TMsg:
type TMsg = record hwnd: HWnd; message: Word; wParam: Word; lParam: Longint; time: Longint; pt: TPoint; end;
Поля записи сообщения дают приложению информацию о том, ка- кой вид событий сгенерировал сообщение, где оно произошло и какое окно должно на него реагировать.
Относительно сообщений следует иметь в виду, что ваше сооб- щение в общем случае получает их после того, что произошло. Нап- ример, если вы изменяете размер окна на экране, то объект окна получает сообщение wm_Size, когда вы закончите изменение размера. Некоторые сообщения запрашивают выполнение каких-то действий. Од- нако в большинстве случаев это уведомления о том, что пользова- тель или система выполнили некоторые действия, на которые следует реагировать вашей программе.
Теперь, когда вы знаете, как написать метод реакции на сооб- щение, можно рассмотреть, какая информация содержится в сообще- нии. Запись TMessage, передаваемая методу реакции на сообщение, выглядит следующим образом:
type TMessage = record Receiver: HWnd; Message: Word; case Integer of 0: ( WParam: Word; LParam: Longint; Result: Longint); 1: ( WParamLo: Byte; WParamHi: Byte; LParamLo: Word; LParamHi: Word; ResultLo: Word; ResultHi: Word); end;
Поля Receiver и Message для объектов ObjectWindows не осо- бенно полезны, поскольку описатель Receiver обычно представляет собой тоже самое, что и поле HWindow оконного объекта, а Message уже отсортировано в цикле сообщения ObjectWindows.
Однако другие три поля очень важны. WParam и LParam - это 16- и 32-битовые параметры, передаваемые в сообщениях от Windows. Result содержит код результата, который может потребоваться пере- дать обратно. Заметим, что TMessage - вариантная запись, так что вы можете обращаться к старшему и младшему байту слов параметров.
Динамические виртуальные методы
Ключем к автоматической диспетчеризации сообщений является расширение описаний в объектах виртуальных методов, называемых динамическими виртуальными методами. По существу вы связываете с методом целочисленный номер (такой как константа сообщения). Ваша программа ObjectWindows (в данном случае цикл сообщения объекта приложения) может затем вызвать этот метод на основе номера сооб- щения.Например, сообщение, генерируемое при нажатии в окне левой кнопки "мыши", содержит в своем поле message wm_LButtonDown ($0201). Когда цикл сообщения ObjectWindows считывает для одного из своих окон такое сообщение, то выполняется поиск в таблице виртуальных методов данного оконного объекта и определяется дина- мический метод, описанный для данного значения. Если такой метод найден, то он вызывается, и ему в качестве параметра передается распакованная запись сообщения типа TMessage. Если оконный объект не описывает метод с данным индексом динамического метода, то цикл сообщения вызывает используемую по умолчанию оконную проце- дуру.
Дополнение поведения по умолчанию
Иногда может возникнуть потребность в комбинировании некото- рых действий с используемой по умолчанию реакцией на сообщение. ObjectWindows предоставляет для этого объектно-ориентированный способ. Обычно, когда вы хотите, чтобы объект выполнял некоторые действия на основе используемого по умолчанию поведения, вы може- те встроить в свой переопределенный метод наследуемый метод. ObjectWindows позволяет вам делать это также для реакции на сооб- щения.Сообщения Windows
Приложения Windows управляются событиями. То есть вместо не- посредственной обработки оператор за оператором управление прог- раммой определяется внешними событиями, такие как взаимодействия с пользователем и системное оповещение. Приложения узнают о собы- тиях, на которые они должны реагировать, получая от Windows сооб- щения.Данная глава охватывает ряд тем, связанных с передачей, по- лучением и обработкой сообщений, включая следующие вопросы:
* что такое сообщение?
* как выполняется диспетчеризация сообщений?
* обработка сообщений Windows;
* определение ваших собственных сообщений;
* передача и адресация сообщений;
* диапазоны сообщений.
Именующие сообщения
Наиболее важным полем сообщений является поле message, кото- рое содержит одну из констант сообщений Windows, начинающихся с wm_. Каждое сообщение Windows уникальным образом идентифицируется 16-битовым числом с соответствующим мнемоническим идентификато- ром. Например, сообщение, являющееся результатом нажатия клавиши, содержит в поле сообщения wm_KeyDown ($0100). На сообщения обычно ссылаются по их мнемоническим именам.Командные сообщения
ObjectWindows обрабатывает команды меню и оперативных клавиш путем отдельной диспетчеризации командных сообщений в основном аналогично другим сообщениям Windows. Реально обработка выполня- ется внутри метода WMCommand ваших оконных объектов, наследуемого из TWindowsObject. Но вместо обработки самих команд WMCommand вы- полняет диспетчеризацию командных сообщений на основе генерируе- мого командой идентификатора меню или оперативной клавиши.Например, если вы определяете элемент меню с идентификатором cm_DoSomething, в ваших объектах следует на основе этого иденти- фикатора определить методы реакции:
type TSomeWindow = object(TWindow) . . . procedure CMDoSomething(var Msg: TMessage); virtual cm_First + cm_DoSomething; end;
procedure TSomeWindow.CMDoSomething(var Msg: TMessage); begin { реакция на команду } end;
Аналогично wm_First, cm_First - это константа ObjectWindows, определяющая начало диапазона сообщений. Ваши командные константы должны лежать в диапазоне 024319.
Обработка команд по умолчанию
Чтобы вызвать используемую по умолчанию реакцию на команду, для нее обычно вызывается наследуемый метод реакции. Если в объ- екте-предке не определяется метод реакции на конкретную команду, по умолчанию обработка выполняется с помощью DefCommandProc. DefCommandProc работает во многом аналогично методу DefWndProc для сообщений Windows, но обрабатывает команды.
Командные, уведомляющие и управляющие идентификаторы
Сообщение - это просто запись данных, идентифицируемая конк- ретным значением в поле message, а ObjectWindows экономит вам массу времени и усилий путем сортировки этих сообщений и диспет- черизации их методам реакции на сообщения в ваших объектах. Одна- ко, сообщение Windows wm_Command имеет много вариантов, которые нужно отсортировывать с помощью большого оператора case. ObjectWindows также выполняет это для вас.Например, сообщение wm_Command посылается при выборе элемен- та меню или нажатии оперативной клавиши. В обычном приложении сообщение Windows передавалось бы вашей функции окна, где в боль- шом операторе case нужно было бы отсортировать различные сообще- ния, вызывая другие подпрограммы при обнаружении wm_Command. Эта подпрограмма должна в свою очередь определить, какая была переда- на команда (с помощью другого большого оператора case).
Написание методов реакции на сообщение
Чтобы описать методы реакции на сообщение, нужно задать в оконном объекте процедуру, названную по имени константы сообще- ния. Например, чтобы ответить на сообщение wm_LButtonDown, вам нужно описать методы следующим образом:type TMyWindow = object(TWindow) . . . procedure WMLButtonDown(var Msg: TMessage); virtual wm_First + wm_LButtonDown; . . . end;
На самом деле метод может называться как угодно, но наимено- вание методов по сообщениям, на которые они реагируют, делают программу понятней. Единственным реальным ограничением является то, что этот метод должен быть процедурой с единственным парамет- ром типа TMessage.
Поскольку для методов реакции ObjectWindows использует нес- колько диапазонов, а все индексы методы отсчитываются с 0, в ка- честве смещения используется константа wm_First. Добавив для каж- дого метода это смещение, вы создадите уникальный индекс динами- ческого метода. Подробнее о диапазонах сообщений рассказывается в разделе "Диапазоны сообщений" данной главы.
Объектно-ориентированная обработка сообщения
Одним из огромных преимуществ обработки сообщения в ObjectWindows (кроме того, что можно избавиться от больших опера- торов case) является обработка сообщений объектно-ориентированным способом. То есть, ваши оконные объекты наследуют возможность оп- ределенной реакции на сообщения, и вам нужно только изменить ре- акцию на конкретные сообщения, которые ваш объект обрабатывает по-другому.Обычная диспетчеризация сообщений
Обычные приложения Windows (то есть не использующие ObjectWindows) имеют цикл сообщения, в котором выполняется выбор- ка и диспетчеризация сообщения. По существу, в цикле сообщения вызывается связанная с окном функция, заданная описателем окна в поле hwnd записи сообщения.Каждое окно имеет функцию окна, заданную при его создании. Когда Windows находит сообщение для конкретного окна, она переда- ет сообщение функции данного окна. Функция окна отсортировывает сообщения на основе типа сообщения, а затем вызывает подпрограмму реакции на конкретное сообщение.
Обычный способ обработки сообщений в приложении и его окнах показывает программа GENERIC.PAS. Вы можете видеть, что оконная функция каждого окна для сортировки сообщение содержит большой оператор case. Все это может выглядеть не так плохо, пока вы не осознаете тот факт, что в окне может потребоваться обрабатывать более 100 различных сообщений Windows. После этого идея написания и обслуживания такого оператора case будет выглядеть менее впе- чатляющей.
Даже если у вас имеется несколько аналогичных окон, каждое из них будет, очевидно, иметь свою оконную функцию с аналогичным большим оператором case.
Определение ваших собственных сообщений
Windows резервирует для собственного использования 1024 со- общения. В этот диапазон попадают все стандартные сообщения. На- чало диапазона сообщений определяется константой wm_User. Чтобы определить сообщение, используемое окнами вашей программы, опре- делите идентификатор сообщения, попадающий в диапазон wm_Userwm_User+31744.Чтобы избежать конфликта со стандартными сообщениями, вам следует определить идентификаторы своих сообщений как константы на основе wn_User. Например, в приложении, где требуется три оп- ределенных пользователем сообщения, вы можете описать их следую- щим образом:
const wm_MyFirstMessage = wm_User; wm_MySecondMessage = wm_User + 1; wm_MyThirdMessage = wm_User + 2;
Реакция на ваши сообщения аналогично реакции на любое другое сообщение:
TCustomWindow = object(TWindow) . . . procedure WMMyFirstMessage(var Msg: TMessage); virtual wm_First + wm_MyFirstMessage
Согласно общему правилу, определенные пользователем сообще- ния следует использовать для внутреннего обмена сообщения. Если вы посылаете определенное пользователем сообщение другому прило- жению, нужно убедиться, что это другое приложение определяет со- общение также, как это делается в коде вашего приложения. Внешние коммуникации лучше обрабатываются с помощью динамического обмена данными (DDE).
Откуда поступают сообщения
Генерировать сообщения позволяют несколько различных событий:* взаимодействия с пользователем, такие как нажатия клавиш, щелчок кнопкой "мыши" или ее буксировка; * вызовы функций Windows, которым нужно информировать об из- менениях другие окна; * ваша программа явно посылает сообщение; * другое приложение посылает сообщение через DDE (динамичес- кий обмен данными); * сообщение генерируется самой Windows (например, сообщение об останове системы).
Вашему приложению в общем случае не важно, как генерируются сообщения. Основным в программировании, управляемом событиями, является генерация сообщений и реакция на них. Поскольку Windows и ObjectWindows берут на себя функции по доставке сообщений из одного места в другое, вы можете сосредоточиться на генерации со- общений с соответствующими параметрами и реакции на сообщения, а не на механизме их доставки из одного места в другое.
Отмена поведения по умолчанию
Иногда, когда вы переопределяете используемую по умолчанию реакцию на сообщение, это делается потому что данное поведение просто нежелательно. Простейшим случаем является ситуация, когда объект должен игнорировать сообщение, а не отвечать на него. Для этого вы можете просто написать пустой метод реакции на сообще- ние. Например, следующий метод сообщает управляющему элементу ре- дактирования, что нужно игнорировать передаваемые в сообщении wm_Char символы:procedure TNonEdit.WMChar(var Msg: TMessage); begin end;
Отправление сообщения
Отправление сообщения полезно использовать, если вас не осо- бенно волнует, когда должна последовать реакция на сообщение, или вам нужно избежать циклов, которые может вызвать SendMessage. Ес- ли все, что вам нужно сделать - это убедиться в передаче сообще- ния, и вы можете продолжать работу, не ожидая реакции, то можете вызывать функцию PostMessage. Ее параметры идентичны параметрам функции SendMessage. PostMessage не следует использовать для пе- редачи сообщений управляющим элементам.Передача и отправление сообщений
Тем не менее, иногда возникает необходимость передачи сооб- щений в окна вашего приложения. Windows предусматривает две функ- ции, позволяющие вам генерировать сообщения - SendMessage и PostMessage. Основное отличие этих двух функций состоит в том, что SendMessage ожидает обработки сообщения перед возвратом. PostMessage просто добавляет сообщение в очередь сообщений и возвращает управление.Имейте в виду, что SendMessage, особенно при вызове в методе реакции на сообщение, может вызвать бесконечный цикл или клинч, приводящие к сбою программ. Вам следует не только избегать оче- видных циклов, таких как методы реакции на сообщения, генерирую- щих то же сообщение, которое его вызывает, но избегать также пе- редачи сообщений с побочными эффектами. Например, метод Paint объекта, который вызывается в ответ на сообщения wm_Paint, оче- видно должен явным образом посылать самому себе другое сообщение wm_Paint. Но он должен также избегать других действий, дающих в результате другое сообщение wm_Paint, посылаемое, пока метод еще активен, таких как изменение размеров окна, запрещение окна или создание/уничтожение перекрывающихся окон.
Передача сообщений
До сих пор мы определяли реакцию на сообщения, но не говори- ли пока о том, как генерировать сообщения. Большинство сообщений Windows генерируются самой Windows в ответ на действия пользова- теля или системные события. Но ваша программа можете генериро- вать сообщения либо моделируя действия пользователя, либо манипу- лирую элементами на экране.ObjectWindows уже обеспечивает для вас способы передачи мно- гих сообщений, которые в противном случае пришлось бы передавать вручную. Например, общим случаем для генерации сообщений является работа с управляющим элементами. Чтобы добавить строку в блок списка, Windows определяет такие сообщения как lb_AddString, а чтобы отменить выбор кнопки с зависимой фиксацией или выбрать ее - bm_SetCheck. ObjectWindows определяет методы для объектов уп- равляющих элементов (TListBox.AddString и TCheckBox.SetCheck), посылающие для вас эти сообщения, так что вам даже не нужно ду- мать об их использовании.
Возможно вы найдете, что для большинства функций (которые в противном случае пришлось бы выполнять передачей сообщений) уже существуют объектные методы.
Передача сообщения управляющему элементу
Возможно, единственным случаем, когда вам потребуется пере- дать сообщение элементу на экране, не имеющему соответствующего объекта, является случай, когда вам нужно взаимодействовать с уп- равляющим элементом в диалоговом окне. Проще всего для этого ис- пользовать объекты (управляющего элемента, диалогового блока или обоих).Если управляющий элемент имеет связанный с ним объект, вы можете просто использовать для получения идентификатора управляю- щего элемента поле HWindow объекта. Если диалоговый блок имеет связанный с ним объект, вы можете вызвать метод SendDlgItemMsg объекта диалогового блока, для которого задаются идентификатор управляющего элемента, идентификатор сообщения и два параметра сообщения. В большинстве приложений ObjectWindows для вас досту- пен любой их этих подходов.
Если по каким-то причинам у вас нет ни объекта диалогового блока, ни доступного объекта управляющего элемента, вы можете послать сообщение управляющему элементу в диалоговом окне с по- мощью функции API Windows SendDlgItemMessage, которая воспринима- ет в качестве параметров описатель диалогового блока, идентифика- тор управляющего элемента, идентификатор сообщения и два парамет- ра сообщения.
Передача сообщения
Для передачи сообщения требуется следующее: описатель ок- на-получателя, номер сообщения и параметры Word и Longint. В при- ложении ObjectWindows описателем получателя является обычно поле HWindow интерфейсного объекта. Идентификатор сообщения - это просто константа, идентифицирующая конкретное сообщение, которое вы хотите передать (такая как wm_More или em_SetTabStops). Пара- метры в зависимости от сообщения могут быть различными.Значение, возвращаемое SendMessage - это значение поля Result в записи сообщения при завершении обработки. Имейте в ви- ду, что если вы вызываете наследуемый или используемый по умолча- нию метод реакции на сообщение, ваше значение может быть переза- писано.
Windows с помощью SendMessage обеспечивает ограниченное средство циркулярной рассылки сообщений. Если вы в качестве опи- сателя окна, в которое нужно передать сообщение, зададите $FFFF, Windows посылает сообщения всем всплывающим и перекрывающимся ок- нам в системе (не только в вашем приложении). Таким образом, вы не должны использовать циркулярную рассылку сообщений, опреде- ляемых пользователем, так как не может быть уверены, что другое приложение не определило данное сообщение каким-то другим спосо- бом.
Поле Result
Поле Result сообщения TMessage управляет возвращаемым значе- нием сообщения. Иногда программа, посылающая сообщение, ожидает возврата конкретного значения, такого как булевское значение, указывающее успешное или неуспешное выполнение или код ошибки. Вы можете задать возвращаемое значение, присвоив значение полю Result.Например, когда пользователь пытается восстановить окно из состояния пиктограммы, ему посылается сообщение wm_QueryOpen. По умолчанию wm_QueryOpen возвращает булевское значение True (не ноль). Если вы хотите иметь окно, которое всегда выводится в виде пиктограммы, то вы можете ответить на сообщение wm_QueryOpen и установить Result в 0. Это означает, что окно не может быть отк- рыто:
procedure TIconWindow.WMQueryOpen(var Msg: TMessage); begin Msg.Result := 0; end;
Поля параметров
Поля параметров записи сообщения имеют для каждого сообщения свой смысл. Однако, можно сделать некоторые обобщения.WParam
Параметр WParam типа Word обычно содержит описатель, иден- тификатор (например, идентификатор управляющего элемента) или бу- левское значение. Например, параметр WParam сообщения wm_SetCursor содержит описатель окна, в котором находится курсор. Уведомляющие сообщения управляющего элемента, такие как bn_Clicked, содержат в WParam идентификатор соответствующего уп- равляющего элемента. wm_Enable использует WParam для булевского значения, указывающего, разрешено или запрещено соответствующее окно.
LParam
Параметр LParam типа Longint обычно содержит значение-указа- тель двух переменных размером в слово, таких как координаты x и y. Например, параметр LParam сообщения wm_SetText указывает на строку с завершающим нулем, содержащую устанавливаемый текст. Со- общения "мыши", такие как wm_LButtonDown, используют LParam для записи координат события "мыши". Благодаря вариантным частям за- писи сообщения, LParamLo содержит x-координату, а LParamHi - y-координату.
Способ, предлагаемый ObjectWindows
ObjectWindows вносит в это обычный способ диспетчеризации сообщений два основных улучшения. Первое состоит в том, что цикл сообщений скрыт в ядре вашего объекта приложения. Все, что вам нужно сделать - это привести свое приложение в действие, вызвав метод Run объекта приложения. После этого оно будет получать со- общения Windows.Второе основное улучшение - это автоматическая диспетчериза- ция сообщений. Вместо необходимости иметь для каждого окна окон- ную функцию, вы просто определяете в оконных объектах методы, ко- торые реагируют на конкретные сообщения. Эти методы называются методами реакции на сообщения.
Уведомление порождающего объекта
Если управляющий элемент не имеет связанного с ним интер- фейсного объекта, или управляющий объект не определяет реакции на конкретную команду, уведомляющие сообщения посылаются вместо объ- екта управляющего элемента порождающему окну управляющего элемен- та. Так как порождающее окно должно знать, какой из его управляю- щих элементов вызвал уведомление, уведомляющее сообщение порожда- ющему окну основывается на идентификаторе управляющего элемента.Например, чтобы ответить на уведомление о взаимодействиях с управляющим элементом с идентификатором id_MyControl, вы можете определить метод следующим образом:
type TMyWindow = object(TWindow) . . . procedure IDMyControl(var Msg: TMessage); virtual id_First + id_MyControl; end;
procedure TMyWindow.IDMyControl(var Msg: TMessage); begin { реакция на сообщение } end;
В качестве смещения в диапазоне сообщений, используемых для реакции на уведомления управляющих элементов, ObjectWindows опре- деляет константу id_First.
Windows редко определяет реакцию на конкретные управляющие элементы, заданную по умолчанию. Однако, если вы хотите позабо- титься о заданном по умолчанию поведении, то можете вызвать DefChildProc. DefChildProc работает аналогично DefWndProc, но об- рабатывает вместо сообщений Windows уведомляющее сообщение по- рождающему объекту.
Уведомления управляющих элементов и порождающих объектов
Возможно, иногда вам потребуется, чтобы одновременно на не- которое взаимодействие пользователя с управляющим элементом реа- гировали и управляющий элемент, и порождающее окно. ObjectWindows также обеспечивает способ реализовать это. Поскольку уведомление порождающего объекта предусматривает реакцию по умолчанию, когда объект управляющего элемента не определяет реакции на уведомле- ние, все, что нужно сделать - это вызов реакции по умолчанию в дополнение реакции управляющего элемента.Например, с учетом приведенного выше объекта TBeepButton вы можете также уведомлять порождающее окно с помощью добавления вы- зова DefNotificationProc:
procedure TBeepButton.BNClicked(var Msg: TMessage); begin MessageBeep(0); DefNotificationProc(Msg); end;
Вызов DefNotificationProc обеспечивает получение уведомляю- щего сообщения на основе идентификатора порождающего окна управ- ляющего элемента, как если бы управляющий элемент вовсе не опре- делял реакции.
Уведомления управляющих элементов
Обычно управляющим элементам не требуется в ответ на дейс- твия пользователя делать ничего особенного; ожидаемым поведением является поведение, используемое по умолчанию. Но если вы хотите, чтобы управляющий элемент делал что-то дополнительно или что-то другое, то уведомляющие сообщения позволяют вам осуществить это.Предположим, например, что вы хотите, чтобы при каждом щелч- ке кнопкой "мыши" раздавался звуковой сигнал. Вы можете просто задать для объекта кнопки метод реакции на уведомление:
type TBeepButton = object(TButton) procedure BNClicked(var Msg: TMessage); virtual nf_First + bn_Clicked; end;
procedure TBeepButton.BNClicked(var Msg: TMessage); begin MessageBeep(0); end;
ObjectWindows определяет в качестве смещения, задающего диа- пазон используемых в уведомлениях управляющих элементов сообще- ний, константу nf_First.
Уведомляющие сообщения
Команды не обязательно должны поступать от меню или команд- ных клавиш. Управляющие элементы в окнах посылают сообщения своим порождающим окнам, когда вы щелкаете на них "мышью" или что-либо набираете. Эти сообщения называются уведомляющими сообщениями, и ObjectWindows обрабатывает их двумя различными путями.В основном уведомления могут передаваться объекту управляю- щего элемента или его порождающему окну. Если управляющий элемент имеет связанный с ним объект ObjectWindows, ObjectWindows дает объекту возможность сначала ответить на команду. Это называется уведомлением управляющего элемента. Если управляющий элемент не имеет соответствующего объекта, или объект управляющего элемента не определяет реакцию на команду, то ответить имеет возможность порождающее окно. Это называется уведомлением порождающего объек- та.
Вызов наследуемых методов
Предположим, например, что вы создали новый оконный объект и хотите, чтобы он в дополнение к другим обычно выполняемым дейс- твиям он давал звуковой сигнал при щелчке в окне левой кнопкой "мыши". Все, что вам нужно сделать - это вызов в вашем новом ме- тоде наследуемого метода TWindow.WMLButtonDown:procedure TBeepWindow.WMLButtonDown(var Msg: TMessage); begin inherited WMLButtonDown(Msg); MessageBeep(0); end;
В данном случае неважно, размещаете ли вы вызов наследуемого метода WMLButtonDown перед или после вызова MessageBeep. Вам нуж- но решить, должен ли ваш метод вызывать наследуемые действия пе- ред специальной обработкой или после нее (на основе того, нужны ли вам параметры сообщения, или требуется их изменить).
Следует иметь в виду, что вы можете изменять параметры Msg перед вызовом наследуемого метода. Однако делать это следует ак- куратно, так как передача параметров вне диапазона может привес- ти к тому, что ваша программа вызовет сбой Windows, особенно если эти параметры содержат описатели или указатели. Если вы будете аккуратны, изменение Msg может оказаться полезным, но нужно учи- тывать, что это может быть также опасным.
Вызов процедур, используемых по умолчанию
Как вы могли заметить, в данном разделе при вызове наследу- емых методов используется не такой пример, как в предыдущих раз- делах. Вы, возможно, ожидали, что новый управляющий элемент ре- дактирования для обработки действия по умолчанию будет вызывать свой наследуемый метод WMChar. Это имеет смысл, но не будет рабо- тать, так как TEdit не имеет метода WMChar, поэтому производный из TEdit объект не может вызывать WMChar в качестве наследуемого метода.К счастью, ваши объекты все равно могут наследовать реакцию на сообщения. Когда ObjectWindows диспетчеризует сообщение объек- ту, и этот объект не определяет конкретного метода реакции, ObjectWindows передает запись TMessage методу с именем DefWndProc - используемой по умолчанию оконной процедуре. DefWndProc знает, как применять действия по умолчанию для всех сообщений. Таким об- разом, если компилятор дает ошибку, когда вы пытаетесь наследо- вать метод реакции на сообщения, поскольку для данного сообщения нет наследуемого метода, вызовите вместо этого DefWndProc.
Например, чтобы в дополнение к вставке набираемых символов добавить к управляющему элементу редактирования звуковой сигнал, вам нужно вызвать MessageBeep и DefWndProc:
procedure TBeepEdit.WMChar(var Msg: TMessage); begin MessageBeep(0); DefWndProc(Msg); end;
Вызов DefWndProc вы можете рассматривать как используемый вместо всех не определенных конкретно наследуемых методов реакции на сообщения. Отметим, однако, что это применяется только к сооб- щениям Windows. Командные сообщения, уведомляющие сообщения и уп- равляющие сообщения имеют свои собственные используемые по умол- чанию обработчики сообщений. Эти сообщения и их используемые по умолчанию процедуры описываются в следующем разделе.
Замена поведения по умолчанию
Более полезным подходом, чем простое игнорирование сообще- ния, является замена поведения по умолчанию чем-то совершенно другим. Например, следующий метод сообщает управляющему элементу редактирования, что вместо вставки символа при нажатии любой кла- виши нужно давать звуковой сигнал:procedure TBeepEdit.WMChar(var Msg: TMessage); begin MessageBeep(0); end;
Звуковой сигнал по нажатию клавиш сам по себе не особенно полезен, но дает хорошую иллюстрацию. В общем случае вы можете заменить используемое по умолчанию поведение некоторым другим. Определяемая вами реакция будет единственной.
Pascal 7 & Objects
Чем отличаются контексты устройства?
Для областей клиента окна Windows обеспечивают специальные контексты устройства, называемые контекстами дисплея. Вместо представления самого устройства, такого как экран или принтер, контекст дисплея позволяет вам интерпретировать область клиента окна как целое устройство.На практике контекст дисплея вам не требуется интерпретиро- вать как-то иначе, чем другой контекст устройства. Он позволяет вам работать так, как если бы ваше окно было целым устройством, так что вам не нужно беспокоиться о смещении позиции на экране и т.д.
Что содержится в контексте устройства?
Контекст устройства содержит набор изобразительных средств, таких как перья, кисти и шрифты. Реально эти инструментальные средства не существуют. Они просто представляют собой удобный способ предположений об определенной группе изобразительных ха- рактеристик.Перо, например, это просто удобный способ интерпретации ха- рактеристик линий на устройстве. Графическая линия между двумя точками имеет три характеристики: ширину, цвет и стиль (например, пунктирная линия и линия из точек), которые совместно можно расс- матривать как "перо". Эти логические группы изобразительных ха- рактеристик значительно упрощают графику в Windows.
Хотя обычно вам не нужно будет изменять большинство атрибу- тов контекста дисплея, важно по крайней мере знать, что в нем со- держится. Данный раздел кратко описывает некоторые элементы кон- текста дисплея, включая побитовые отображения, цвета, области и изобразительные средства. Некоторые из этих тем также рассматри- ваются более подробно в некоторых других разделах данной главы.
Цвет
Цвет, который устройство использует для рисования, хранится в цветовой палитре. Если вы желаете добавить цвет, которого нет в цветовой палитре, то его можно добавить. Более часто вы будете настраивать драйвер устройства на аппроксимацию нужного цвета пу- тем смешивания цветов палитры. Работа с цветовой палитрой более подробно рассматривается в разделе данной главы "Использование цветовой палитры".Функции изображения текста
Функция рисования текста использует для рисования заданный текущий шрифт контекста дисплея. Функция TextOut рисует текст в заданной точке. TextOut выравнивает текст в зависимости от теку- щих значений флагов форматирования текста. По умолчанию происхо- дит выравнивание слева. Текущий метод выравнивания можно посмот- реть с помощью функции GetTextAlign и установить с помощью функ- ции SetTextAlign.Функция TextOut - это самая часто используемая функция рисо- вания текста. Используя установленные по умолчанию флаги формати- рования текста, данный метод Paint рисует выравненный слева мас- сив символов, левый верхний угол которого имеет координаты (10,15).
procedure TMyWindow.Paint(PaintDC: HDC; var PaintINfo: TPaintStruct); var MyTextString: array[020] of Char; begin StrCopy(MyTextString, 'Hello, World'); TextOut(PaintDC, 10, 15, MyTextString, StrLen(MyTextString)); end;
+-------------------------------- | | (10, 15) | * Hello Word | |
Рис. 17.3 Результат выполнения функции TextOut.
Функции рисования линий
Функции рисования линии используют для рисования заданное текущее перо контекста дисплея. Большинство линий рисуется с ис- пользованием функций MoveTo и LineTo. Эти функции воздействуют на атрибут контекста дисплея - текущую позицию. Если использовать аналогию с карандашом и листом бумаги, то текущая позиция это точка, где карандаш касается бумаги.Функции MoveTo и LineTo
Функция MoveTo перемещает текущую позицию в заданные коорди- наты. Функция LineTo рисует линию из текущей позиции к точке с заданными координатами. Заданные координаты затем становятся те- кущей позицией. Следующий метод Paint рисует линию от (100,150) до (10,15).
procedure TMyWindow.Paint(PaintDC: HDC; var PaintINfo: TPaintStruct); begin MoveTo(PaintDC, 100, 150); LineTo(PaintDC, 10, 15); end;
+-------------------------------- | | (10, 15) | * | \ | \ | \ | \ | * (100, 150)
Рис. 17.4. Результат выполнения функции LineTo.
Функция PolyLine
Функция Polyline рисует последовательность линий, соединяю- щих заданные точки. По действию она аналогична выполнению после- довательности функций MoveTo и LineTo, однако, Polyline выполняет эту операцию намного быстрее и никак не воздействует на текущую позицию пера. Следующий метод Paint рисует прямой угол.
procedure TMyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var Points: array[02] of TPoint; begin Points[0].X:=10; Points[0].Y:=15; Points[1].X:=10; Points[1].Y:=150; Points[2].X:=100; Points[2].Y:=150; Polyline(PaintDC, @Points, 3); end;
+-------------------------------- | | (10, 15) | * | | | | | | | | | +-----* (100, 150)
Рис. 17.5. Результат выполнения функции Polyline.
Функция Arc
Функция Arc рисует дуги по периметру эллипса, ограниченного заданным прямоугольником. Дуга начинается в точке пересечения эл- липса и линии из центра эллипса в заданную точку начала. Дуга ри- суется против часовой стрелки до тех пор, пока она не достигнет точки пересечения эллипса с линией из центра эллипса к заданной точке конца.
Следующий метод Paint рисует верхнюю четверть окружности с началом в (40,25) и окончанием в (10,25), используя ограничиваю- щий прямоугольник (10,10), (40,40), начальную точку (0,0) и ко- нечную точку (50,0). Действие производится даже в том случае, ес- ли заданная начальная и конечная точка не лежат на дуге.
procedure TMyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); begin Arc(PaintDC, 10, 10, 40, 40, 50, 0, 0, 0); end;
Графические функции GDI
Данный раздел описывает различные вызовы API, которые вы мо- жете использовать для рисования изображений в окне.Инструментальные средства рисования
Контекст дисплея управляет отображением графики на экране. Для иного способа отображения графики можно изменить инструмен- тальные средства, с помощью которых создается изображение. Атри- буты инструментальных средств задают проявление изображений с по- мощью функций GDI, например, LineTo, Rectange и TextOut. Перья задают внешний вид линий, кисти задают внешний вид закрашенных областей и шрифт задает внешний вид изображаемого текста.Для задания атрибутов инструмента программа Windows выбирает логический инструмент в контекст дисплея. Логический инструмент создается вашей программой путем заполнения полей определенной записи, TLogPen, TLogBrush или TLogFont. Текущий инструмент - это инструмент, определенный в Windows, представляющий самый общие варианты атрибута, например, непрерывное черное перо, серая кисть или системный шрифт.
Использование изобразительных инструментальных средств
Контекст дисплея позволяет вам рисовать в окне и, кроме это- го, он содержит инструментальные средства рисования: перья, кис- ти, шрифты и палитры, которые вы используете для рисования текста и изображений. При рисовании линии в контексте дисплея линия вы- водится со следующими атрибутами текущего пера: цвет, стиль (неп- рерывная, пунктирная и т.п.) и толщина. При закрашивании области она выводится со следующими атрибутами текущей кисти: образец и цвет. При рисовании текста в контексте дисплея он выведется с ат- рибутами текущего шрифта: шрифт (Modern, Roman, Swiss и т.п.), размер, стиль (наклонный, жирный и т.п.). Палитра содержит набор текущих доступных цветов.Контекст дисплея содержит по одному типу каждого инструмен- тального средства рисования. Вновь полученный контекст дисплея содержит набор инструментальных средств, используемых по умолча- нию: тонкое черное перо, непрерывная черная кисть, системный шрифт и палитра по умолчанию. Если этот набор вас устраивает, то нет необходимости его изменять.
Для изменения набора инструментальных средств по умолчанию, нужно создать новый инструментальный элемент и выбрать его в кон- текст дисплея. Например, при выборе новой ручки старая автомати- чески удаляется. Мы рекомендуем вам сохранять старые инструмен- тальные средства и повторно устанавливать их после окончания ис- пользования новых:
var NewPen, OldPen: HPen; TheDC: HDC; begin { задать ширину пера 10 } NewPen := CreatePen(ps_Solid, 10, RGB(0, 0, 0)); TheDC := GetDC(AWindow^.HWindow); OldPen := SelectObject(TheDC, NewPen); { выполнить черчение } SelectObject(TheDC, OldPen); ReleaseDC(AWindow^.HWindow, TheDC); DeleteObject(NewPen); end;
Как показано в данном примере, новый инструмент рисования должен быть создан, а затем удален. Подобно контексту дисплея, элементы хранятся в памяти Windows. Если их не удалить, это при- водит к потерям памяти и возможности возникновения сбоя. Как и для контекста дисплея, вы должны хранить описатели инструменталь- ных средств рисования в переменных типа HPen, HBrush, HFont и HPalette.
Функция Windows DeleteObject удаляет инструментальные средс- тва рисования из памяти Windows. Ни в коем случае не удаляйте инструментальные средства рисования, которые выбраны в данный мо- мент в контекст дисплея!
Контекст дисплея может хранить только по одному инструменту рисования каждого типа в данный момент времени, поэтому нужно отслеживать доступные инструментальные средства отображения. Очень важно удалить их все до завершения работы вашего приложе- ния. Один из методов, (использован в примере Главы 2) состоит в определении поля объекта окна с именем ThePen для хранения описа- теля текущего пера. Когда пользователь выбирает новый стиль пера, создается новое перо, а старое удаляется. Следовательно, оконча- тельное перо будет удалено методом основного окна CanClose. Вам не нужно удалять набор инструментальных средств по умолчанию, поставляемый во вновь полученном контексте дисплея.
Есть два способа создания новых инструментальных средств ри- сования. Самый простой способ состоит в использовании существую- щего альтернативного инструмента, называемого основным или опор- ным. Список основных инструментальных средств приведен в Таблице 17.1.
Для установки основного инструментального средства в объекте контекста дисплея используются методы SetStockPen, SetStockBrush, SetStockFont и SetStockPalette. Например:
ThePen:=GetStockObject(Black_Pen);
Не удаляйте основные инструменты из памяти Windows, посколь- ку вы будете настраивать их. Иногда возникает ситуация, когда нет основного инструмента, который имел бы нужный вам атрибут. Напри- мер, все основные перья воспроизводят тонкие линии, а вам требу- ется толстая. В этом случае имеется два способа создания настро- енных инструментальных средств рисования. Один способ состоит в вызове функций Windows CreatePen, CreateFont, CreateSolidBrush или CreateDIBPatternBrush. Эти функции используют параметры, ко- торые описывают нужный инструмент, и возвращают описатель инстру- ментального средства, который используется в вызовах SelectObject.
Другой способ создания настроенных инструментальных средств состоит в построении описания атрибутов логического инструмента. Логический инструмент реализуется структурами данных Windows TLogPen, TLogBrush, TLogFont и TLogPalette. Например, TLogPen имеет поля для хранения толщины цвета и стиля. После создания записи данных логического инструмента, она передается в качестве параметра в CreatePenInderect, CreateBrushInderect, CreateFontInderect или CreatePalette. Эти функции возвращают опи- сатели инструментального средства которые могут быть использованы в вызовах SelectObject. В данном примере устанавливается синее перо для изображения в контексте дисплея окна:
procedure SampleWindow.ChangePenToBlue; var ALogPen: TLogPen; ThePen: HPen; begin ALogPen.lopnColor:=RGB(0, 0, 255); ALogPen.lopnStyle:=ps_Solid; ALogPen.lopnWidth.X:=0; ALogPen.lopnWidth.Y:=0; ThePen:=CreatePenInderect(@ALogPen); SelectObject(TheDC, ThePen); end;
Использование палитр
Некоторые типы дисплейных устройств компьютера могут выво- дить множество цветов, но только ограниченное их число в каждый момент времени. Системная или физическая палитра - это группа или набор цветов, которые в данный момент доступны дисплею для однов- ременного отображения. Windows дает вашему приложению частичное управление цветами, входящими в системную палитру устройства. Ес- ли ваше приложение использует только простые цвета, то вам нет необходимости непосредственно использовать палитру.Однако, изменение палитры системы воздействует на все изоб- ражение, имеющееся на экране, включая другие приложения. Одно приложение может вызвать вывод всех других приложений в некор- ректных цветах. Администратор палитры Windows разрешает эту проб- лему, согласовывая изменения системной палитры с приложениями. Windows предоставляет каждому приложению свою логическую палитру, которая представляет собой группу цветов, используемых приложени- ем. Администратор палитры связывает запрошенные логической палит- рой цвета с имеющимися цветами системной палитры. Если запрошен- ный цвет отсутствует в системной палитре, администратор палитры может добавить его. Если в логической палитре задано больше цве- тов, чем может содержаться в системной палитре, то для дополни- тельных цветов подбирается максимально похожий цвет системной па- литры.
Когда приложение становится активным, имеется возможность заполнить системную палитру цветами из логической палитры. Это действие может повлиять на распределение цветов, заданных логи- ческими палитрами других приложений. В любом случае Windows ре- зервирует 20 цветов в системной палитре для общего представления цветовой гаммы всех приложений и самого Windows.
Изображение фигур
Функции изображения фигур используют текущее перо заданного контекста дисплея для изображения периметра и текущую кисть для закраски внутренней области. На текущую позицию они не влияют.Функция Rectangle
Функция Rectangle рисует прямоугольник от его левого верхне- го угла к правому нижнему. Например, следующий оператор метода Paint рисует прямоугольник от (10,15) до (100,150).
Rectangle(PaintDC, 10, 15, 100, 150);
+-------------------------------- | | (10, 15) | *-----* | |#####| | |#####| | |#####| | |#####| | *-----* (100, 150)
Рис. 17.6. Результат выполнения функции Rectangle.
Функция RoundRect
Функция RoundRect рисует прямоугольник со скругленными угла- ми. Скругления углов определены как четверти эллипса. Например, следующий оператор метода Paint рисует прямоугольник от (10,15) до (100,150), углы которого будут скруглены четвертями эллипса шириной 9 и высотой 11.
RoundRect(PaintDC, 10, 15, 100, 150, 9, 11);
Функция Ellipse
Функция Ellipse рисует эллипс, задаваемый ограничивающим его прямоугольником. Следующий пример рисует эллипс в прямоугольнике от (10,15) до (110,70).
Ellipse(PaintDC, 10, 50, 100, 150);
Функции Pie и Chord
Функции Pie и Chord рисуют секторы эллипса. Они рисуют дугу, подобно функции Arc. Однако, результатом Pie и Chord будут облас- ти. Функция Pie соединяет центр эллипса с его граничными точками. Следующая функция Pie рисует верхнюю четверть круга, заключенного в прямоугольник от (10,10) до (40,40).
Pie(PaintDC, 10, 10, 40, 40, 50, 0, 0, 0);
Функция Chord соединяет две граничные точки дуги.
Chord(PaintDC, 10, 10, 40, 40, 50, 0, 0, 0);
Функция Polygon
Функция Polygon рисует непрерывную последовательность сег- ментов линий, аналогично функции Polyline, но в конце работы за- мыкает область, рисуя линию от последней заданной точки к первой заданной точке. И, наконец, он заполняет полученный многоугольник текущей кистью, используя установленный режим закрашивания много- угольника. Следующий метод Paint рисует и закрашивает прямоуголь- ный треугольник.
procedure TMyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var Points: array[02] of TPoint; begin Points[0].X:=10; Points[0].Y:=15; Points[1].X:=10; Points[1].Y:=150; Points[2].X:=100; Points[2].Y:=150; Polygon(PaintDC, @Points, 3); end;
+-------------------------------- | | (10, 15) | * | |\ | |#\ | |##\ | |###\ | *-----* (100, 150) (10, 150)
Рис. 17.7. Результат выполнения функции Polygon.
Изображение окон
Когда возникает необходимость нарисовать окно, оно становит- ся запрещенным. Это значит что изображение дисплея не соответс- твует действительности и должно быть изменено. Это происходит в момент первоначального отображения окна, восстановления из пик- тограммы или удаления другого окна, которое перекрывало часть данного окна. Во всех этих случаях Windows посылает сообщение wm_Paint соответствующему приложению. Это сообщение автоматически вызывает метод Paint вашего окна. Один из параметров Paint, PaintDC, представляет собой контекст дисплея, который использует- ся для рисования.Метод TWindow Paint ничего не рисует, поскольку объекты TWindow не имеют графики для рисования. В типе вашего окна опре- делим метод Paint, который будет вызывать методы и функции, изоб- ражающие в окне текст и графику.
Единственное, что вы можете сделать с контекстом дисплея, это выбрать в него новый инструмент рисования, например, перья других цветов или кисти других образцов. Вам придется указать эти инструменты в контексте дисплея рисования вашего метода Paint.
После завершения работы метода Paint контекст дисплея рисо- вания автоматически освобождается.
Изобразительные средства
Чтобы выполнить большую часть фактического изображения, кон- текст дисплея использует три инструментальных средства: перо, кисть и шрифт.Перья
Перо используется для изображения линий, дуг и ломаных ли- ний, представляющих собой множественные линейные сегменты. Атри- буты пера включают в себя его цвет, ширину и стиль (например, пунктирная линия или линия из точек).
Кисти
Кисть используется при закраске замкнутых фигур, таких как прямоугольники, прямоугольники с округлыми краями и многоугольни- ки. Функции, рисующие непрерывные формы используют перо для вы- черчивания границ, а кисть - для закраски внутренней области. Су- ществуют четыре типа кистей - непрерывные, с засечками, с образ- цом побитового отображения и с независимым от устройств образцом побитового отображения.
Шрифты
Инструментальное средство шрифта используется при изображе- ния в контексте дисплея текста. Оно задаете высоту шрифта, его ширину, семейство и имя гарнитуры.
Все три инструментальных средства используют для заполнения пробелов атрибут фонового цвета контекста дисплея. Изобразитель- ные инструментальные средства подробнее освещаются в разделе "Изобразительные средства" данной главы.
Логические инструментальные средства
Записи логических инструментов, TLogPen, TLogBrush и TLogFont, содержат поля для хранения каждого атрибута инструмен- та. Например, TLogPen.lopnColor содержит значение цвета ручки. Каждый тип записи определяет свой собственный набор атрибутов, соответствующий типу инструмента.Логические кисти
Вы можете создавать логические кисти с помощью функций Windows CreateHatchBrush, CreatePatternBrush, CreateDIBPatternBrush или CreateBrushInderect. Например:TheBrush := CreateHatchBrush(hs_Vertical, RGB(0, 255, 0)); TheBrush := CreateBrushInderect(@ALogBrush);
Определение записи TLogBrush имеет следующий вид:
TLogBrush = record lbStyle: Word; lbColor: Longint; lbHatch: Integer; end;
Поле стиля, lbStyle, содержит константы, задающие стиль кис- ти:
* bs_DIBPattern указывает, что образец кисти задан аппарат- но-независимым побитовым отображением.
* bs_Hatched задает один из заранее определенных образцов штриховки (см. lbHatch).
* bs_Hollow - это пустая кисть.
* bs_Pattern использует левый верхний угол 8 на 8 элементов побитового отображения, которое находится в этот момент в памяти.
* bs_Solid - это непрерывная кисть.
Поле lbColor содержит значение цвета, аналогично записи TLogPen. Это поле игнорируется кистями со стилями bs_Hollow и bs_Pattern.
Поле lbHatch содержит целую константу, задающую образец штриховки для кисти со стилем bs_Hatched. Если стиль bs_DIBPattern, то lbHatch содержит описатель побитового отображе- ния.
----------------------------------------------------------------- Константа Результат -----------------------------------------------------------------
////////////////////////////////////// HS_BDIAGONAL ////////////////////////////////////// //////////////////////////////////////
++++++++++++++++++++++++++++++++++++++ HS_CROSS ++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx HS_DIAGCROSS xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ HS_FDIAGONAL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------- HS_HORIZONTAL -------------------------------------- --------------------------------------
HS_VERTICAL
-----------------------------------------------------------------
Рис. 17.2 Стили штриховки для кисти.
Логические перья
Вы можете создавать логические перья с помощью функций Windows CreatePen или CreatePenInderect. Например:ThePen := CreatePen(ps_Dot, 3, RGB(0, 0, 210)); ThePen := CreatePenInderect(@ALogPen);
Определение записи TLogPen имеет следующий вид:
TLogPen = record lopnStyle: Word; lopnWidth: TPoint; lopnColor: Longint; end;
Поле стиля, lopnStyle, содержит константу, задающую стиль линии.
----------------------------------------------------------------- Константа Результат ----------------------------------------------------------------- PS_SOLID ------------------------------------- PS_DASH ------------------------------------- PS_DOT . PS_DASHDOT .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. PS_DASHDOTDOT ------------- PS_NULL -----------------------------------------------------------------
Рис.17.1 Стили линий для пера.
Поле толщины, lopnWidth, содержит точку, координата x кото- рой задает толщину линии в координатах устройства. На экране VGA, если задано значение 0, то будет рисоваться линия толщиной в один элемент изображения. Значение координаты y игнорируется. Поле цвета, lopnColor, содержит значение Longint, байты которого зада- ют величины интенсивности основных цветов (красного, зеленого и синего), смешение которых и дает нужный цвет. Значение lopnColor должно иметь вид $00bbggrr, где bb - значение синего цвета, gg - значение зеленого цвета, а rr - значение красного цвета. Доступ- ный диапазон интенсивности для каждого первичного цвета от 0 до 255, или от 0 до FF в шестнадцатиричном исчислении. Следующая таблица показывает некоторые примеры значений цвета:
Примеры значений цвета Таблица 17.2 +--------------+--------------+ | Значение | Цвет | +--------------+--------------| | $00000000 | черный | | $00FFFFFF | белый | | $000000FF | красный | | $0000FF00 | зеленый | | $00FF0000 | синий | | $00808080 | серый | +--------------+--------------+
В качестве альтернативы для воспроизведения цвета можно ис- пользовать функцию RGB. RGB(0,0,0) возвратит черный цвет, RGB(255,0,0) возвратит красный и т.д.
Логические шрифты
Вы можете создавать логические шрифты с помощью функций Windows CreateFont или CreateFontInderect.Определение записи TLogBrush следующее:
TLogFont = record lfHight: Integer; lfWidht: Integer; lfEscapement: Integer; lfOrientation: Integer; lfWeight: Integer; lfItalic: Byte; lfUnderline: Byte; lfStrikeOut: Byte; lfCharSet: Byte; lfOutPrecision: Byte; lfClipPrecision: Byte; lfQuality: Byte; lfPitchAndFamily: Byte; lfFaceName: array[0lf_FaceSize - 1] of Byte; end;
При использовании TLogFont для создания шрифта вы задаете атрибуты нужного вам шрифта. Однако, ваша программа не использует эту информацию для генерации шрифта на экране. Вместо этого она отображает запрос экранного шрифта в текущий экранный шрифт се- анса Windows.
Поле lfHight задает необходимую высоту шрифта. Нулевое зна- чение устанавливает размер по умолчанию. Положительное значение есть высота элемента в логических единицах. Отрицательное значе- ние воспринимается как положительное.
Поле lfWidht задает нужную ширину букв в единицах устройс- тва. Если задан ноль, то коэффициент относительного удлинения сохраняется.
Для поворачиваемого текста lfEscapement задает значение в десятых долях градуса, на которое поворачивается текст против ча- совой стрелки. lfOrientation делает аналогичный поворот каждого символа.
Параметр lfWeight задает нужный вес символов. В качестве значений можно использовать константы fw_Light, fw_Normal, fw_Bold и fw_DontCare.
Для трех атрибутов шрифта - lfItalic, lfUnderline и lfStrikeOut - нужно задать ненулевые значения.
В поле lfCharSet требуется задать конкретный набор символов, ANSI_CharSet, OEM_CharSet или Symbol_CharSet. Набор символов ANSI содержится в "Руководстве пользователя по Microsoft Windows", в Приложении B. OEM_CharSet является системно-зависимым.
Поле lfOutPrecision задает, как точно создаваемый Windows шрифт должен соответствовать запросам на размеры и позиционирова- ние. Значение поля по умолчанию - Out_Default_Precis. Поле lfClipPrecision задает способ рассмотрения частично видимых сим- волов. Значение поля по умолчанию Clip_Default_Precis.
Поле lfQuality показывает как точно предоставляемый Windows шрифт соответствует запрошенным атрибутам шрифта. Может быть ус- тановлено значение Default_Quality, Draft_Quality или Proof_Quality. Для значения Proof_Quality жирные, подчеркнутые, наклонные шрифты и шрифты с надпечаткой синтезируются, даже если их нет. Поле lfPitchAndFamily задает шаг и семейство шрифта. Оно может быть результатом логической операции or между константой шага и константой семейства.
Константы шага и семейства шрифта Таблица 17.3 +---------------------+----------------------------+ | Константы шага | Константы семейства | +---------------------+----------------------------| | Default_Pitch | ff_Modern | | Fixed_Pitch | ff_Roman | | Variable_Pitch | ff_Script | | | ff_Swiss | | | ff_Decorative | | | ff_DontCare | +---------------------+----------------------------+
И, наконец, lfFaceName - это строка, которая задает запро- шенный вид букв. Если задано значение 0, то вид букв будет стро- иться на основании значений других полей TLogFont. Приведем нес- колько примеров исходного кода, определяющего записи TLogFont:
procedure MyWindow.MakeFont; var MyLogFont: TLogFont; begin with MyLogFont do begin lfHight := 30; lfWidht := 0; lfEscapement := 0; lfOrientation := 0; lfWeight := fw_Bold; lfItalic := 0; lfUnderline := 0; lfStrikeOut := 0; lfCharSet := ANSI_CharSet; lfOutPrecision := Out_Default_Precis; lfClipPrecision := Clip_Default_Precis; lfQuality := Default_Quality; lfPitchAndFamily := Variable_Pitch or ff_Swiss; StrCopy(@FaceName, 'Helv'); end; TheFont := CreateFontInderect(@MyLogFont); end;
procedure MyWindow.MakeFont; var MyLogFont: TLogFont; begin with MyLogFont do begin lfHight := 10; lfWidht := 0; lfEscapement := 0; lfOrientation := 0; lfWeight := fw_Normal; lfItalic := Ord(True); lfUnderline := Ord(True); lfStrikeOut := 0; lfCharSet := ANSI_CharSet; lfOutPrecision := Out_Default_Precis; lfClipPrecision := Clip_Default_Precis; lfQuality := Default_Quality; lfPitchAndFamily := Fixed_Pitch or ff_DontCare; StrCopy(@FaceName, 'Courier'); end; TheFont := CreateFontInderect(@MyLogFont); end;
procedure MyWindow.MakeFont; var MyLogFont: TLogFont; begin with MyLogFont do begin lfHight:=30; lfWidht:=0; lfEscapement:=0; lfOrientation:=0; lfWeight:=fw_Normal; lfItalic:=0; lfUnderline:=0; lfStrikeOut:=0; lfCharSet:=Symbol_CharSet; lfOutPrecision:=Out_Default_Precis; lfClipPrecision:=Clip_Default_Precis; lfQuality:=Proof_Quality; lfPitchAndFamily:=Fixed_Pitch or ff_Roman; StrCopy(@FaceName, 'Rmn'); end; TheFont:=CreateFontInderect(@MyLogFont); end;
Модификация палитры
Есть два способа изменения элементов логической палитры. Функция SetPaletteEntries берет те же самые аргументы, что и GetPaletteEntries и меняет заданные элементы на те, на которые указывает третий аргумент. Обратите внимание на то, что произве- денные изменения не отражаются в системной палитре до вызова RealizePalette, и их не видно до перерисовки области клиента. Функция AnimatePalette воспринимает те же аргументы, что и SetPaletteEntries, но используется для быстрых изменений палитры приложения, и они немедленно становятся видимыми. При вызове AnimatePalette элементы палитры с полем peFlags установленным в константу pc_Reserved будут заменены на соответствующие новые элементы, и это найдет немедленное отражение в системной палитре. На другие элементы это никак не повлияет.Например, вам нужно взять первые десять элементов палитры, сменить их значение, добавив на единицу содержание красного цвета и уменьшив содержимое синего и зеленого. Все эти изменения должны сразу же стать видимыми (Предполагается, что некоторые из элемен- тов имеют установленное значение pc_Reserved):
GetObject(ThePalette, SizeOf(NumEntries), @NumEntries); if NumEntries >= 10 then begin GetPaletteEntries(ThePalette, 0, 10, @PaletteEntries); for i:=0 to 9 do begin PaletteEntries[i].peRed:=PaletteEntries[i].peRed+40; PaletteEntries[i].peGreen:=PaletteEntries[i].peGreen-40; PaletteEntries[i].peBlue:=PaletteEntries[i].peBlue-40; end; AnimatePalette(ThePalette, 0, 10, @PaletteEntries); end;
Вместо AnimatePalette мы могли бы использовать:
SetPaletteEntries(ThePalette, 0, 10, @PaletteEntries); RealizePalette(ThePalette);
и затем перерисовать окно, чтобы увидеть изменения цветов.
Обрезание областей
Для предотвращения рисования вне заданной области каждый контекст дисплея имеет атрибут области вырезанного изображения. Область вырезанного изображения может быть сложным многоугольни- ком или эллипсом, внутри которого и может происходить действи- тельное рисование на виртуальной поверхности контекста дисплея. Для большинства приложений выделяемая по умолчанию область выре- занного изображения будет вполне достаточной. Изменять эту об- ласть придется только для приложений, которые воспроизводят неко- торые специальные визуальные эффекты.Основные инструментальные средства
Основные инструментальные средства создаются функцией GDI GetStockObject. Например:var TheBrush: HBrush begin TheBrush:=GetStockObject(LtGray_Brush); . . . end;
где LtGray_Brush - это целая константа, определенная в модуле WinTypes в ObjectWindows. Приведем список всех имеющихся констант основного инструментального средства:
Основные инструменты рисования Таблица 17.1 +-------------------+------------------+------------------------+ | Кисти | Перья | Шрифты | +-------------------+------------------+------------------------| | White_Brush | White_Pen | OEM_Fixed_Font | | LtGray_Brush | Black_Pen | ANSI_Fixed_Font | | Gray_Brush | Null_Pen | ANSI_Var_Font | | DkGray_Brush | | System_Font | | Black_Brush | | Device_Default_Font | | Null_Brush | | System_Fixed_Font | | Hoolow_Brush | | | +-------------------+------------------+------------------------+
В отличие от логических инструментальных средств основные инструментальные средства не удаляются после использования.
Отображение графики в окнах
Рисование - это процесс отображения контекста окна. Приложе- ние Windows отвечает за рисование его окон при их первом появле- нии и их изменении, например, после восстановления из пиктограммы или перекрытия другими окнами. Windows не обеспечивает автомати- ческого рисования контекста окон, она только информирует окно, когда ему нужно нарисовать себя. Данный раздел показывает, как рисовать в окне, объясняет механизм рисования и объясняет исполь- зование контекста дисплея.В данном разделе термин "рисование" относится к отображению графики в окне. Рисование - это автоматическое отображение графи- ки при первом появлении или изменении окна. С другой стороны ри- сование - это процесс создания и отображения специфических изоб- ражений в другие моменты времени под управлением программы. Под графикой понимается как текст, так и элементы изображения, напри- мер, побитовые отображения и прямоугольники.
Побитовая графика
Действительная поверхность контекста дисплея называется по- битовым отображением (битовым массивом). Побитовые отображения представляют конфигурацию памяти конкретного устройства. Следова- тельно, они зависят от вида адресуемого устройства. Это создает проблему, поскольку охраненные для одного устройства побитовые отображения будут несовместимы с другим устройством. GDI имеет ряд средств для разрешения этой проблемы, включая аппаратно-неза- висимые побитовые отображения. Имеются следующие функции GDI, ко- торые создают побитовые отображения: CreateCompatibleDC, CreateCompatibleBitmap и CreateDIBitmap. Имеются следующие функ- ции GDI по манипулированию побитовыми отображениями: BitBlt, StretchBlt, StretchDIBits и SetDIBitsToDevice.Работа с контекстом дисплея
Обычно нужно определять поле оконного объекта для записи описателя текущего контекста дисплея, аналогично тому, как в HWindow сохраняется описатель окна:type TMyWindows = object(TWindow) TheDC: HDC; . . . end;
Чтобы получить для окна контекст дисплея, вызовите функцию Windows GetDC:
TheDC := GetDC(HWindow);
Затем вы можете выполнить операцию изображения в контексте дисплея. Вы можете использовать описатель контекста дисплея в графических функциях Windows:
LineTo(TheDC, Msg.LParamLo, Msg.LParamHi);
Как только вы закончите работу с контекстом дисплея, освобо- дите его с помощью вызова функции ReleaseDC:
ReleaseDC(HWindow, TheDC);
Не вызывайте GetDC в строке дважды, не включив между вызова- ми вызов ReleaseDC. В итоге это приведет к сбою системы при рабо- те программы, так как она исчерпает все доступные контексты дисп- лея.
Реакция на изменения палитры
Когда окно принимает сообщение wm_PaletteChanged, это свиде- тельствует о том, что активное окно сменило системную палитру пу- тем реализации ее логической палитры. Окно, принявшее сообще- ние, может отреагировать на него тремя способами: оно может ниче- го не делать (очень быстрый способ, но может привести к некор- ректным цветам), оно может реализовать свою логическую палитру и перерисовать себя (медленнее, но цвета будут максимально коррект- ными), либо оно может реализовать свою логическую палитру и затем использовать функцию UpdateColors для быстрого изменения области клиента в соответствии с системной палитрой. UpdateColors в общем случае работает быстрее, чем перерисовка области клиента, но при ее использовании могут быть некоторая потеря точности в цветопе- редаче. Поле WParam записи TMessage, переданной в сообщении wm_PaletteChanged, содержит описатель окна, которое реализовало свою палитру. Если в ответ вы решили реализовать свою собственную палитру, сначала убедитесь в том, что этот описатель не является описателем вашего окна, чтобы не создать бесконечного цикла.Программа PaTest создает и реализует логическую палитру из восьми цветов. При нажатии левой кнопки "мыши" она будет рисо- вать раскрашенные квадраты с образцами каждого из цветов логичес- кой палитры. При нажатии правой кнопки происходит сдвиг цветов логической палитры. Используется индекс палитры TColorRef, поэто- му, когда логическая палитра меняется раскрашенные квадраты также сменят свой цвет. При использовании индекса палитры TColorRef мо- жет оказаться удобным использование функции PaletteIndex.
Полный текст программы содержится в файле PALTEST.PAS на ва- ших дистрибутивных дискетах.
Режимы отображения
Очень трудно выбрать устройство рисования, когда заранее не- известно, какое устройство будет использоваться для отображения. Большинство приложений игнорируют эту проблему и предполагают, что вполне удовлетворительно будет работать единица рисования по умолчанию (один элемент изображения). Однако, некоторые приложе- ния требуют, чтобы отображение точно воспроизводило размеры нуж- ного образа. Для таких приложений GDI допускает различные режимы отображения, некоторые из которых не зависят от аппаратуры. Каж- дый из методов распределения имеет свою единицу размерности и систему координатной ориентации. Режим распределения по умолчанию устанавливает начало координат в левом верхнем углу контекста дисплея с положительным направлением оси X вправо и положительным направлением оси Y вниз. Каждый контекст дисплея имеет атрибуты распределения для интерпретации задаваемых вами координат.Иногда нужно транслировать логические координаты, используе- мые вами для рисования, в физические координаты побитового отоб- ражения. Для большинства приложений начало координат для экрана - это его левый верхний угол, но для окна началом координат будет левый верхний угол области клиента. Некоторые окна прокручивают свою поверхность клиента так, что начало координат не будет даже находиться в области клиента. Некоторые функции GDI работают только в конкретной системе координат, поэтому преобразование ко- ординат просто необходимо. В GDI имеется ряд функций для подобно- го пересчета координат: ScreenToClient, ClientToScreen, DPToLP и LPToDP.
Рисование с палитрами
После реализации палитры вашего приложения, оно может осу- ществлять рисование с использованием его цветов. Цвета палитры можно задавать прямо или косвенно. Для прямого задания цвета ис- пользуется индекс палитры, TColorRef. Индекс палитры TColorRef есть значение типа Longint, где старший байт установлен в 1, а индекс элемента логической палитры содержится в двух младших бай- тах. Например, $01000009 задает девятый элемент логической палит- ры. Это значение можно использовать везде, где ожидается аргумент TColorRef. Например:ALogPen.lopnColor := $01000009;
Если ваше дисплейное устройство допускает использование пол- ного 24-битового цвета без системной палитры, то использование индекса палитры неоправданно ограничивает вас цветами вашей логи- ческой палитры. Чтобы избежать этого ограничения, вы можете за- дать цвет палитры косвенно, используя относительное значение па- литры TColorRef. Относительное значение TColorRef почти совпадает с абсолютным значением RGB TColorRef, но байт старшего разряда установлен в 2. Три младших байта содержат значение цвета RGB. Например, $020000FF задают значение чистого красного цвета. Если устройство поддерживает системную палитру, то Windows подберет максимально соответствующий цвет RGB логической палитры. Если устройство не поддерживает системную палитру, то TColorRed ис- пользуется так, как если бы он задавал явное значение RGB.
Рисование в окнах
Для рисования любого текста или изображения в объекте окна сначала нужно получить контекст дисплея. После рисования контекст дисплея нужно освободить. (В одном сеансе Windows доступны только пять элементов контекста дисплея.) Вы можете использовать описа- тель контекста дисплея в качестве аргумента любой графической функции Windows.Вызов графических функций окна
Одно из правил GDI состоит в том, что для работы функций не- обходимо в качестве аргумента задавать контекст дисплея. Обычно вы будете вызывать эти функции из методов типа окна. Например, TextOut - это функция рисования текста на контексте дисплея в за- данном месте:
TheDC := GetDC(HWindow); TextOut(TheDC, 50, 50, 'Sample Text', 11); ReleaseDC(HWindow, TheDC);
Стратегия графики
Метод Paint отвечает за рисование текущего содержимого окна в любой момент времени, включая первое появление этого окна. Сле- довательно, метод Paint должен уметь рисовать все "постоянные" изображения окна. Кроме того, он должен уметь восстанавливать лю- бые изображения, добавленные в окно после его первого появления. Для воспроизведения этой "динамической" графики метод Paint дол- жен иметь доступ к инструкциям или данным, с помощью которых было создано изображение.Вы можете выбрать один из двух возможных вариантов. Первый подход состоит в выделении нескольких графических методов и при динамическом рисовании вызывать их из метода Paint. Другой под- ход, показанный в примере Главы 3, состоит в хранении данных, от- носящихся к графическому контексту окна, в полях объекта этого окна. Эти данные могут включать, например, координаты, формулы и побитовые распределения. Затем метод Paint повторно вызывает гра- фические функции, которые нужны для преобразования этих данных в изображения.
Используя эти стратегии и способность объекта хранить свои собственные данные и функции вы можете разрабатывать очень разви- тые и впечатляющие графические приложения.
Управление контекстом дисплея
Для организации рисования в контексте дисплея сначала нужно получить контекст дисплея для нужного окна. Требования к памяти со стороны контекста дисплея очень велики, поэтому можно одновре- менно организовать доступ только к пяти контекстам дисплея в каж- дом сеансе Windows. Это значит, что каждое окно не может поддер- живать свой собственный контекст дисплея. Оно получает его только в случае необходимости и освобождает при первой возможности. Это может несколько обескуражить вас, но вы не можете управлять атри- бутами контекста дисплея. Другое окно и даже другое приложение может сменить атрибуты контекста дисплея. Кроме того, средства рисования не являются частью памяти контекста дисплея. Они могут быть выбраны в контекст дисплея каждый раз при его получении.Установка палитры
Логические палитры являются инструментами рисования, такими же как перья и кисти, описанные в разделе "Инструментальные средства изображения". Для создания логической палитры использу- ется функция CreatePalette, которая берет указатель на запись данных LogPalette, создает новую палитру и возвращает ее описа- тель, который передается в SelectPalette для выбора палитры в контекст дисплея. Запись TLogPalette содержит поля номера версии Windows (в настоящее время $0300), число элементов палитры и мас- сив элементов палитры. Каждый элемент палитры - это запись типа TPaletteEntry. Тип TPaletteEntry имеет три байтовых поля для спе- цификации цвета (peRed, peGreen и peBlue) и одно поле для флагов (peFlags).GetStockObject(Default_Palette) создает палитру по умолча- нию, состоящую из 20 цветов, которые всегда присутствуют в палит- ре системы.
После выбора палитры в контекст дисплея с помощью SelectPalette, он должен до использования "реализовать" ее. Это делается с помощью функции Windows RealizePalette:
ThePalette := CreatePalette(@ALogPalette); SelectPalette(TheDC, ThePalette, 0); RealizePalette(TheDC);
RealizePalette помещает цвета из вашей логической палитры в системную палитру устройства. Сначала Windows проверяет соответс- твие цветов с уже имеющимися в системной палитре, а затем добав- ляет ваши новые цвета в палитру системы, если для этого есть мес- то. Цветам, которым не нашлись идентичные цвета в системной па- литре, подбирается наиболее соответствующий цвет из палитры сис- темы. Ваше приложение должно реализовать свою палитру до рисова- ния, как это делается для других инструментальных средств рисова- ния.
Запись на устройство вывода
В отличие от традиционных графических программ DOS программы Windows никогда не выводят элементы изображения непосредственно на экран или на принтер, а записывают их в логическую сущность, называемую контекстом дисплея. Контекст дисплея - это виртуальная поверхность с присущими ей атрибутами, такими как перо, кисть, шрифт, цвет фона, цвет текста и текущая позиция. Для вашего при- ложения, независимо от того, какое это на самом деле устройство, все контексты устройства выглядят аналогично.Когда вы вызываете функции GDI для рисования в контексте устройства, связанный с этим контекстом драйвер устройства пере- водит действия по рисованию в соответствующие команды. Эти коман- ды воспроизводят насколько возможно точно действия рисования на дисплее, независимо от возможностей самого дисплея. Дисплей может быть монохромным экраном низкого разрешения или экраном с четырь- мя миллионами цветовых оттенков.
Контекст дисплея можно представить себе как холст для рисо- вания. Окно это - картинка, включающая рамку. Вместо рисования на картине в рамке вы рисуете на холсте, а уже затем устанавливаете его в рамку. Аналогично этому, вы рисуете в контексте дисплея ок- на. Контекст дисплея обладает рядом инструментов рисования, нап- ример, ручки, кисти и шрифты. Контекст дисплея - это управляемый Windows элемент, похожий на элемент окна с тем исключением, что контекст дисплея не имеет соответствующего элемента ObjectWindows.
Нужно помнить о том, что контекст устройства представляет собой только часть устройства, на котором вы реально рисуете. Хо- тя вы можете рассматривать вывод в терминах всего окна (рамки, меню, области клиента) или печатаемой страницы, контекст устройс- тва охватывает только ту часть, где вы рисуете - область клиента окна или печатаемую часть страницы.
Контекст устройства - это элемент, управляемый Windows (ана- логично оконному элементу, только контекст устройства не имеет соответствующего объекта ObjectWindows).
Запрос палитры
Windows определяет функцию, которая позволяет вам получать информацию относительно палитры. GetPaletteEntries воспринимает индекс, диапазон и указатель на TPaletteEntry и заполняет буфер заданными элементами палитры.Pascal 7 & Objects
Добавление ресурсов к выполняемой программе
Ресурсы хранятся в двоичном формате в файле .RES, поэтому они должны быть добавлены к выполняемому файлу приложения (.EXE). Результатом будет файл, который наряду со скомпилированным кодом приложения будет содержать и его ресурсы.Есть три способа добавления ресурсов к выполняемому файлу:
- Можно использовать редактор ресурсов для копирования ре- сурсов из файла .RES в уже скомпилированный файл программы .EXE. Инструкции по этой операции содержатся в руководстве пользователя по пакету разработчика ресурсов.
- Можно задать директиву в исходном коде файла. Например, эта программа на языке Паскаль:
program SampleProgram; {$r SAMPLE.RES} . . .
добавит файл ресурсов SAMPLE.RES к выполняемому файлу. Каждая программа на языке Паскаль может иметь только один файл ресурсов (хотя этот файл ресурсов может включать дру- гие файлы ресурсов). Все эти файлы должны быть файлами .RES и хранить ресурсы в двоичном формате. Директива ком- пилятора $R позволяет вам задать отдельный файл .RES.
- Использовать компилятор ресурсов.
Использование побитовых отображений для создания кистей
Вы можете использовать графические образы для создания кис- тей, которые могут закрашивать области экрана. Область может быть закрашена сплошным цветом или в виде заданного образца. Минималь- ный размер используемого в кисти графического образа составляет 8 на 8 элементов изображения. Если вы применяете большее графичес- кое изображение, то в кисти используется только его левый верхний угол 8 на 8. Предположим, что вы хотите заполнить область полос- ками, как это показано на Рис. 18.1.+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXBitmap drawing testXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | File Help | +---------------------------------------------------------------| | | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | | +---------------------------------------------------------------+
Рис. 18.1. Заполнение области экрана полосками.
При заполнении области на Рис. 18.1 Windows циклически копи- рует кисть. Действительный размер побитового распределения - лишь 8 на 8 элементов изображения, но кистью можно закрасить весь эк- ран.
XXXXXX###### XXXXXX###### XXXXXX######
Рис. 18.2. Ресурс графического изображения для создания кис- ти по образцу Рис. 18.1.
Следующий код помещает образец графического образа в кисть:
procedure SampleWindow.MakeBrush; var MyLogBrush: TLogBrush; begin HMyBit := LoadBitmap(HInstance, PChar(502)); MyLogBrush.lbStyle := bs_Pattern; MyLogBrush.lbHatch := HMyBit; TheBrush := CreateBrushInderect(@MyLogBrush); end;
Для проверки образца, отобразим его в прямоугольнике:
procedure MyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); begin SelectObject(PaintDC, TheBrush); Rectangle(PaintDC, 20, 20, 200, 200); end;
После использования кисти вы должны удалить и кисть, и гра- фическое изображение:
DeleteObject(HMyBit); DeleteObject(TheBrush);
Отображение графических изображений в меню
Для отображение графических образов в меню используется функция ModifyMenu. Она меняет существующий элемент меню таким образом, что выводит побитовое отображение вместо текста варианта меню, определенного в редакторе меню. Например, этот конструктор Init добавляет и модифицирует меню окна:type MyLong = record case Integer of 0: (TheLong: Longint); 1: (Lo: Word; Hi: Word); end;
constructor SampleWindow.Init(AParent: PWindowsObject; ATitle: PChar); var ALong: MyLong; begin TWindow.Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, PChar(100)); ALong.Lo := LoadBitmap(HInstance, PChar(503)); ModifyMenu(Attr.Menu, 111, mf_ByCommand or mf_Bitmap, 211, PChar(ALong.TheLong)); . . . end;
В приведенном выше коде 111 - это идентификатор команды ва- рианта меню, который изменяется, а 211 это его новый идентифика- тор. Однако, вы можете использовать один и тот же идентификатор в обоих случаях.
+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| |XXFileXXX Help | +--------------+------------------------------------------------| |XXXXNewXXXXXXX| | | pick | | | me | | | Save | | | Save As | | +--------------+ | | | | | +---------------------------------------------------------------+
Рис. 18.3. Меню, где в качестве одного из пунктов выбора ис- пользовано графическое изображение.
Создание ресурсов
Вы можете создавать ресурсы, используя редактор ресурсов или компилятор ресурсов. Паскаль поддерживает оба эти метода, поэтому вы сами можете выбрать, какой их них является более удобным. В большинстве случаев проще использовать редактор ресурсов и созда- вать ваши ресурсы визуально. Однако, иногда более удобно исполь- зовать компилятор ресурсов для компиляции файла описания ресурса, который может вам встретиться в книге или в журнале. Независимо от выбранного вами подхода вы обычно создаете файл ресурса (.RES) для каждого приложения. Этот файл ресурса будет содержать двоич- ную информацию о всех меню, диалогах, графических образах и дру- гих ресурсах, используемых вашим приложением.Двоичный файл ресурса (.RES) добавляется к вашему исполняе- мому файлу (.EXE) в процессе компиляции с использованием директи- вы компилятора $R, как это описано в данной главе. Вы также долж- ны написать код, который будет загружать ресурсы в память. Это придаст вам дополнительную гибкость, поскольку ваша программа бу- дет использовать память лишь для ресурсов, которые используются в данный момент. Загрузка ресурсов в память также рассматривается в данной главе.
Загрузка блоков диалога
Блоки диалога являются единственным типом ресурса, который непосредственно соответствует типу объекта ObjectWindows. TDialog и его производные типы, включая TDlgWindow, определяют объекты интерфейса, которые используют ресурсы блока диалога. Каждый объ- ект блока диалога обычно связан с одним ресурсом блока диалога, который задает его размер, местоположение и ассортимент управляю- щих элементов (таких, как командные кнопки и блоки списка).При конструировании блока диалога задайте ресурс объекта блока диалога. Как ресурсы меню и командных клавиш, ресурс диало- га может иметь символьное имя или целочисленный идентификатор. Например:
Adlg := New(PSampleDialog, Init(@Self, 'AboutBox')); или Adlg := New(PSampleDialog, Init(@Self, PChar(120)));
Дополнительная информация по созданию объектов диалога со- держится в Главе 11, "Объекты диалоговых блоков".
Загрузка графических изображений
Функция Windows LoadBitmap загружает ресурсы графических изображений (битовых отображений). LoadBitmap загружает побитовое распределение в память и возвращает его описатель. Например:HMyBit:=LoadBitmap(HInstance, PChar(501));
загружает ресурс побитового отображения с идентификатором 501 и записывает его описатель в переменную HMyBit. После загрузки по- битового отображения оно останется в памяти до его явного удале- ния вами. В отличие от других ресурсов, оно остается в памяти да- же после закрытия пользователем вашего приложения.
В Windows имеется ряд заранее определенных графических изоб- ражений, которые используются как часть графического интерфейса Windows. Ваше приложение может загружать эти изображения (напри- мер, obm_DnArrow, obm_Close и obm_Zoom). Как и предопределенные пиктограммы и курсоры, предопределенные графические изображения могут быть загружены, если в вызове LoadBitmap вместо HInstance задать ноль:
HMyBit:=LoadBitmap(0, PChar(obm_Close));
После загрузки графического образа ваше приложение может ис- пользовать его разными способами:
* Для рисования картинки на экране. Например, вы можете заг- рузить побитовое распределение в блок информации о прило- жении в качестве заставки.
* Для создания кисти, которую вы можете использовать для за- полнения областей экрана или для создания фона окна. Соз- дав кисть по графическому образу, вы можете закрасить ей область фона.
* Для отображения картинок вместо текста в элементах меню или элементах блока списка. Например, вы можете вместо слова 'Arrow' (стрелка) в пункте меню поместить изображе- ние стрелки.
Дополнительная информация относительно использования графики с побитовым отображением содержится в Главе 17.
Если побитовое отображение не используется, то его нужно удалить из памяти. В противном случае занимаемая им память будет недоступна другим приложениям. Даже если вы не удаляете его после использования приложением, вы обязательно должны удалить его до прекращения работы приложения. Графический образ удаляется из па- мяти с помощью функции Windows DeleteObject:
if DeleteObject(HMyBit) then { успешно };
После удаления графического изображения его описатель стано- вится некорректным, и его нельзя использовать.
Загрузка курсоров и пиктограмм
----------------------------------------------------------------- Каждый тип объекта окна имеет специальные атрибуты, называе- мые атрибутами регистрации. Среди этих атрибутов есть курсор окна и пиктограмма. Для установки этих атрибутов для типа окна вы должны определить метод GetWindowClass (как и GetClassName).Например, вы создаете курсор для выбора элементов в блоке списка. Курсор имеет вид указательного пальца и хранится в ресур- се курсора с именем 'Finger'. Кроме того, вы создаете ресурс пик- тограммы с именем 'SampleIcon', который выглядит как улыбающееся лицо. Вы должны написать метод GetWindowClass следующим образом:
procedure SampleWindow.GetWindowClass(var AWndClass: TWndClass); begin TWindow.GetWindowClass(AWndClass); AWndClass.hCursor:=LoadCursor(HInstance, 'Finger'); AWndClass.hIcon:=LoadIcon(HInstance, 'SampleIcon'); end;
Однако, между курсором и пиктограммой имеется одно отличие. Оно состоит в том, что курсор задается для одного окна, а пиктог- рамма представляет все приложение. Следовательно, пиктограмма ус- танавливается в типе объекта только для основного окна. У этого правила имеется одно исключение: для приложений, которые следуют правилам многодокументального интерфейса (MDI), каждое дочернее окно MDI имеет свою собственную пиктограмму. Для использования одного из уже имеющихся курсоров или пик- тограмм Windows, передайте 0 в HInstance и используйте значение idc_ (например, idc_IBeam) для курсора и значение idi_ (например, idi_Hand) для пиктограммы. Например:
procedure SampleWindow.GetWindowClass(var AWndClass: TWndClass); begin TWindow.GetWindowClass(AWndClass); AWndClass.hCursor := LoadCursor(HInstance, idc_IBeam); AWndClass.hIcon := LoadIcon(HInstance, idi_Hand); end;
Дополнительную информацию по регистрационным атрибутам окна можно найти в Главе 10, "Объекты окна".
Загрузка меню
Меню окна является одним из атрибутов его создания. Другими словами это характеристика окна, которая должна быть задана до создания соответствующего элемента меню (с помощью метода Create). Следовательно, меню может быть задано в типе конструкто- ра Init или вскоре после конструирования. Ресурсы меню загружают- ся вызовом функции Windows LoadMenu со строкой идентификатора ме- ню при конструировании нового объекта окна. Например:constructor SampleMainWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin TWindow.Init(AParent, ATitle); Attr.Menu:=LoadMenu(HInstance, PChar(100)); . . end;
Код PChar(100) переводит целое значение 100 в тип PChar, совместимый с Windows тип строки. LoadMenu загружает ресурс меню с идентификатором 100 в новый объект окна. Ресурс может иметь символьное имя (строку), например, 'SampleMenu', а не числовой идентификатор. В этом случае предыдущий код будет выглядеть сле- дующим образом:
constructor SampleMainWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin TWindow.Init(AParent, ATitle); Attr.Menu:=LoadMenu(HInstance, 'SampleMenu'); . . end;
Дополнительная информация по созданию объектов окна содер- жится в Главе 10, "Объекты окна".
Для обработки выбора варианта меню просто определяется метод для окна, которое владеет этим меню, используя специальное расши- рение заголовка определения метода идентификатором cm_First:
procedure HandleMenu101(var Msg: TMessage); virtual cm_First+101;
Обратите внимание на то, что 101 представляет собой выбран- ного варианта меню (а не самого ресурса меню). Каждый пункт меню имеет уникальный целочисленный идентификатор. В этом методе зада- ча состоит в организации соответствующей реакции на выбор пункта меню. Обычно вы будете определять символьные константы для команд вашего меню.
Загрузка оперативных клавиш
Оперативные клавиши - это активные клавиши или комбинации клавиш, которые используются для задания команд приложения. Обыч- но оперативные клавиши определяются как эквиваленты выбора пунк- тов меню. Например, клавиша Del - это стандартная оперативная клавиша, которую можно использовать как альтернативу выбора пунк- та Delete в меню Edit. Однако, оперативные клавиши могут реализо- вывать команды, которые не соответствуют элементам меню.Ресурсы оперативных клавиш хранятся в таблице оперативных клавиш. Для загрузки таблицы оперативных клавиш используется функция Windows LoadAccelerators, которая просто возвращает опи- сатель таблицы. В отличие от ресурса меню, который связан с конк- ретным окном, ресурс оперативной клавиши принадлежит всему прило- жению. Каждое приложение может иметь только один такой ресурс. Объекты приложения резервируют одно поле объекта, HAccTable, для хранения описателя ресурса оперативных клавиш. Обычно вы будете загружать ресурс оперативных клавиш в методе объекта приложения InitInstance:
procedure SampleApplication.InitInstance; begin TApplication.InitInstance; HAccTable := LoadAccelerators(HInstance, 'SampleAccelerators'); end;
Часто вы будете определять оперативные клавиши для быстрого выбора вариантов меню. Например, Shift+Ins обычно используется для быстрого выбора команды Paste. Оперативные клавиши генерируют основанные на команде сообщения, которые идентичны сообщениям, генерируемым выбором пункта меню. Для привязки метода реакции на выбор в меню с соответствующей оперативной клавишей нужно убе- диться в том, что определенное в ресурсе значение оперативной клавиши идентично идентификатору элемента меню.
Загрузка ресурсов в приложение
После добавления ресурса к выполняемому файлу, он должен быть явно загружен приложением до своего использования. Конкрет- ный способ загрузки ресурса зависит от типа ресурса.Загрузка строковых ресурсов
----------------------------------------------------------------- Принципиальная причина выделения строк приложения в качестве ресурсов состоит в облегчении процесса настройки приложения на конкретное применение или возможности перевода приложения на иностранный язык. Если строки определены в исходном коде, то для их изменения или перевода нужно иметь доступ к исходному коду. Если же они определены как ресурсы, то хранятся в таблице строк выполняемого файла приложения. Вы можете использовать редактор строк для перевода строк в таблице, без изменения и даже доступа к исходному коду. Каждый выполняемый файл может иметь только одну таблицу строк.Для загрузки строки из таблицы в буфер сегмента данных ваше- го приложения используется функция LoadString. Синтаксис LoadString следующий:
LoadString(HInstance, StringID, @TextItem,SizeOf(TextItem));
* Параметр StringID - это номер идентификатора строки (нап- ример, 601) в таблице строк. Это число можно заменить константой.
* Параметр @TextItem - это указатель на массив символов (PChar), который принимает строку.
* Параметр SizeOf(TextItem) - это максимальное число симво- лов, передаваемых в @TextItem. Максимальный размер ресурса строки 255 символов, поэтому передача буфера из 256 симво- лов гарантирует полную передачу строки.
LoadString возвращает число скопированных в буфер символов, или ноль, если ресурс не существует.
Вы можете использовать ресурс строки для вывода текста в блоке сообщения. Например, вы можете вывести сообщение об ошибке. В данном примере вы определяете строку 'Program unavailable' в таблице строк и определяете константу ids_NoProgrm в качестве идентификатора строки. Для использования этого ресурса строки в блоке сообщения об ошибке, вы можете написать следующую процеду- ру:
procedure TestDialog.RunErrorBox(ErrorNumber: Integer); virtual; var TextItem: array[0255] of Char; begin LoadString(HInstance, ids_NoPrgrm, @TextItem, 20); MessageBox(HWindow, @TextItem, 'Error', mb_OK or mb_IconExclamation); end;
Данный пример загружает отдельную строку в блок сообщения об ошибке. Для загрузки списка строк в блок списка вызывается LoadString для загрузки каждой строки, затем вызывается AddString для добавления ее в блок списка.
Другое использование ресурса строк применяется для элементов меню, которые добавляются в меню вашего исходного кода. В этом случае сначала получается ресурс строки с помощью LoadString. За- тем эта строка передается как параметр в вызовы функций Window CreateMenu и AppendMenu. Например:
procedure SampleWindow.Init(AParent: PWindpwsObject; ATitle: PChar); var TextItem: array[0255] of Char; begin TWindow.Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, PChar(100)); LoadString(HInstance, 301, @TextItem, 20); AppendMenu(Attr.Menu, mf_String ormf_Enabled, 501, @TextItem); end;
Pascal 7 & Objects
Динамическая установка размеров наборов
Размер стандартного массива в стандартном Паскале фиксирует- ся во время компиляции. Хорошо, если вы точно знаете, какой раз- мер должен иметь ваш массив, но это может быть не столь хорошо к тому моменту, когда кто-нибудь будет запускать на вашу программу. Изменение размера массива требует изменения исходного кода и пе- рекомпиляции.Однако, для наборов вы устанавливаете только их начальный размер, который динамически увеличивается в процессе работы прог- раммы, для размещения в нем всех нужных данных. Это делает ваше приложение в его скомпилированном виде значительно более гибким. Тем не менее, следует иметь в виду, что набор не может сжиматься, поэтому следует быть аккуратным и не делать его неоправданно большим.
Итератор ForEach
ForEach воспринимает указатель на процедуру. Процедура имеет один параметр, который является указателем на хранимый в наборе элемент. Для каждого элемента набора ForEach вызывает процедуру один раз, в той последовательности, в которой элементы появляются в наборе. Процедура PrintAll в Collect1 показывает пример итера- тора FoeEach.procedure PrintAll(C: PCollection); procedure CallPrint(P: PClient); far; begin P^.Print; {Вызов метода Print} end; begin {Print} Writeln; Writeln; Writeln('Client list:'); C^.ForEach(@CallPrint); { распечатка для каждого клиента } end;
Для каждого элемента набора, переданного в качестве парамет- ра в PrintAll, вызывается вложенная процедура CallPrint. CallPrint просто распечатывает информацию об объекте клиента в отформатированных колонках.
Примечание: Итераторы должны вызывать локальные проце- дуры far.
Вам нужно быть аккуратным с сортировкой процедур, которые вы вызываете итераторами. Для того, чтобы быть вызванной итератором, процедура (в данном примере, CallPrint) должна:
* Быть процедурой - она не может быть функцией или методом объекта, хотя данный пример показывает, что процедура мо- жет вызвать метод.
* Быть локальной (вложенной) относительно вызывающей ее прог- раммы.
* Описываться как дальняя процедура директивой far или ди- рективой компилятора {$F+}.
* Воспринимать указатель на элемент набора в качестве своего единственного параметра.
Итераторы FirstThat и LastThat
Кроме возможности приложения процедуры к каждому элементу набора, часто бывает очень нужно найти конкретный элемент набора на основании некоторого критерия. Это является предназначением итераторов FirstThat и LastThat. Как это следует из их имен, они просматривают набор в противоположных направлениях до момента на- хождения первого элемента набора, который удовлетворяет критерию булевской функции, переданной в качестве элемента.FirstThat и LastThat возвращают указатель на первый (или последний) элемент, который удовлетворяет условию поиска. Предпо- ложим, что в приведенном ранее примере списка клиентов, вы не мо- жете вспомнить номер счета клиента или не помните точно написание имени клиента. К счастью, вы точно помните, что это был ваш пер- вый клиент из штата Монтана. Следовательно, вы можете организо- вать поиск первого клиента с кодом штата 406 (поскольку ваш спи- сок клиентов ведется хронологически). Данная процедура использует метод FirstThat, который и сделает всю работу:
procedure SearchPhone(C: PCollection; PhoneToFind: PChar); function PhoneMatch(Client: PClient: PClient): Boolean; far; begin PhoneMatch := StrPos(Client^.Phone, PhoneToFind) <> nil; end; var FoundClient: PClient; begin { SearchPhone } Writeln; FoundClient := C^.FirstThat(@PhoneMatch); if FoundClient = nil then Writeln('Такому требованию не отвечает ни один клиент') else begin Writeln('Найден клиент:'); FoundClient^.Print; end; end;
Снова обратите внимание на то, что PhoneMatch вложена и ис- пользует удаленную модель вызова. В этом случае эта функция возв- ращает True только при совпадении номера телефона клиента и за- данного образца поиска. Если в наборе нет объекта, который соот- ветствовал бы критерию поиска, FirstThat возвращает указатель nil.
Запомните: ForEach вызывает определенную пользователем про- цедуру, а FirstThat и LastThat каждая вызывает определенную поль- зователем булевскую функцию. В любом случае определенная пользо- вателем процедура или функция передают указатель на объект набо- ра.
Методы итератора
Вставка и удаление элемента не являются единственными общими операторами набора. Очень часто вы будете писать циклы for для просмотра всех объектов набора с целью отображения данных или вы- полнения некоторых вычислений. В других случаях вы будете искать первый или последний элемент набора, который удовлетворяет неко- торому критерию поиска. Для этих целей у наборов имеется три ме- тода итератора: ForEach, FirstThat и LastThat. Каждый из них воспринимает указатель на процедуру или функцию в качестве своего единственного параметра.Наборы и управление памятью
TCollection может динамически расти от начального размера, установленного Init, до максимального размера в 16380 элементов. ObjectWindows хранит максимальный размер набора в переменной MaxCollectionSize. Каждый добавляемый в набор элемент занимает четыре байта памяти, т.к. он хранится в виде указателя.Ни одна библиотека динамических структур данных не будет полной, если она не снабжена средствами обнаружения ошибок. Если для инициализации набора не хватает памяти, то возвращается ука- затель nil.
Если не хватает памяти при добавлении элемента в набор, то вызывается метод TCollection.Error, и возникает ошибка этапа вы- полнения в динамически распределяемой области памяти. Вы можете переписать TCollection.Error для организации собственного метода информирования или исправления ошибки.
Вам следует уделить особое внимание доступности динамической области памяти, поскольку у пользователя имеет значительно боль- ший контроль над программой ObjectWinodws, чем над обычной прог- раммой языка Паскаль. Если добавлением объектов в набор управляет пользователь (например, открывая новое окно), то ошибку динами- ческой области памяти не так то легко предсказать. Вы можете предпринять некоторые шаги по защите пользователя от фатальной ошибки при выполнении программы либо проверяя память при исполь- зовании набора, либо обрабатывая сбой выполняемой программы таким образом, чтобы избежать прекращения ее работы.
Наборы строк
Многим программам требуется работать с отсортированными строками. Для этих целей ObjectWindows предоставляет набор специ- ального назначения TStrCollection (он совпадает с типом TStringCollection, определенным для хранения строк Паскаля). Об- ратите внимание, что элементы TStrCollection - это не объекты. Они представляют собой указатели на строки, заканчивающиеся ну- лем. Поскольку наборы строк происходят от TSortedCollection, мож- но хранить и дублированные строки.Использовать наборы строк несложно. Просто определяется пе- ременная указателя для хранения набора строк. Разместим набор, задав его начальный размер и приращение для роста при добавлении новых строк (см. COLLECT3.PAS):
var WordList: PCollection; WordRead: PChar; . . . begin WordList:=New(PStrCollection, Init(10,5)); . . .
WordList первоначально рассчитан для хранения 10 строк с последующим приращением по 5 строк. Все что вам нужно сделать - это вставить несколько строк в набор. В данном примере слова счи- тываются из текстового файла и вставляются в набор:
repeat . . . if GetWord(WordRead, WordFile)^ <> #0 then WordList^.Insert(StrNew(WordRead)); . . . until WordRead[0]=#0; . . . Dispose(WordList, Done);
Обратите внимание, что функция StrNew используется для копи- рования считанных слов, и адрес скопированной строки передается в набор. При использовании набора вы всегда передаете ему контроль над данными набора. Он позаботится об освобождении данных после работы. Он при этом делает то, что происходит при вызове Dispose: удаляется каждый элемент набора, и затем удаляется сам набор WordList.
Объединение в набор элементов, не являющихся объектами
Вы даже можете добавить в набор нечто, что вообще не являет- ся объектом, но это также может явиться серьезным предметом оза- боченности. Наборы ожидают получения нетипизированных указателей незаданного типа на нечто. Но некоторые методы TCollection пред- назначены специально для работы с наборами элементов, производных от TObject. Это касается методов доступа к потоку PutItem и GetItem, и стандартной процедуры FreeItem.Например, это означает, что вы можете хранить PChar в набо- ре, но при попытке послать этот набор в поток, результаты будут не столь успешными, если вы не перепишете стандартные методы на- бора GetItem и PutItem. Аналогично, при попытке освобождения на- бора будет сделана попытка удаления каждого элемента с помощью FreeItem. Например, это делает TStrCollection.
Если вам удастся преодолеть все эти трудности, вы обнаружи- те, что наборы (и построенные вами производные наборов) являются быстрыми, гибкими и надежными структурами данных.
Объекты наборов
Будучи объектами и тем самым имея встроенные методы, наборы обладают двумя дополнительными чертами, которые имеют отношение к обычным массивам языка Паскаль - это динамическое установка раз- меров и полиморфизм.Отсортированные наборы
Иногда вам бывает нужно, чтобы ваши данные были определенным образом отсортированы. ObjectWindows имеет специальный тип набо- ра, который позволяет вам упорядочить ваши данные произвольным образом. Это тип TSortedCollection.TSortedCollection является производным от TCollection и ав- томатически сортирует задаваемые ему объекты. При добавлении но- вого элемента он автоматически проверяет набор на дублирование ключей. Булевское поле Duplicates контролирует разрешение дубли- рования ключей. Если для поля Duplicates установлено значение False (по умолчанию), то новый элемент добавляется к набору, за- меняя существующий член с тем же самым ключом. Если Duplicates имеет значение True, то новый член просто вставляется в набор.
TSortedCollection - это набор абстрактного типа. Для его ис- пользования вы должны сначала решить, какой тип данных вы собира- етесь собирать и определить два метода, отвечающих вашим конкрет- ным требованиям сортировки. Для этого вам нужно создать новый тип, производный от TSortedCollection. В данном случае назовем его TClientCollection. Ваш TClientCollection уже знает, как де- лать всю реальную работу с набором. Он может вставить (Insert) запись о новом клиенте и удалять (Delete) существующие записи - он унаследовал эти основные черты поведения от TCollection. Все что нужно сделать - это научить TClientCollection, какое поле ис- пользовать в качестве ключа сортировки и как сравнивать двух кли- ентов при решении вопроса о том, какой из них должен стоять в на- боре выше другого. Это делается переписыванием методов KeyOf и Compare и реализации их следующим образом:
PClientCollection = ^TClientCollection; TClientCollection = object(TSortedCollection) function KeyOf(Item: Pointer): Pointer; virtual; function Compare(Key1, Key2: Pointer): Integer; virtual; end;
function TClientCollection.KeyOf(Item: Pointer): Pointer; begin KeyOf := PClient(Item)^.Account; end;
function TClientCollection.Compare(Key1, Key2: Pointer): Integer; begin Compare := StrIComp(PChar(Key1), PChar(Key2)); end;
Примечание: Так как ключи являются нетипизированными указателями, для них нужно выполнять приведение типа.
KeyOf определяет, какое поле или поля используются в качест- ве ключей сортировки. В данном случае это поле клиента Account. Compare воспринимает два ключа сортировки и определяет, какой из них должен идти первым в соответствии с правилами сортировки. Compare возвращает -1, 0 или 1 в зависимости от того, Key1 мень- ше, равен или больше Key2, соответственно. В данном примере ис- пользуется сортировка по алфавиту (для букв верхнего и нижнего регистра) ключевой строки (Account) путем вызова модуля Strings функции StrIComp. Вы можете легко сортировать набор по именам, вместо номера счета, если замените возвращаемое KeyOf поле на Name.
Обратите внимание на то, что ключи, возвращаемые KeyOf и пе- редаваемые в Compare являются нетипизированными указателями, поэ- тому до их разыменования и передачи в StrIComp в данном примере вы должны привести их тип к PChar.
Это практически все, что вам нужно определить! Теперь, если вы переопределите ClientList как PClientCollection вместо PCollection (сменив объявление var и вызов New), то легко сможете распечатать ваших клиентов в алфавитном порядке (COLLECT2.PAS):
var ClientList: PClientCollection; . . begin ClientList:=New(PClientCollection, Init(10,5)); . . end.
Обратите внимание и на то, как легко будет сменить сортиров- ку списка клиентов по номеру счета на сортировку по имени. Все что вам нужно сделать, это сменить метод KeyOf на возврат поля Account на поле Name.
Пересмотренные итераторы
Метод ForEach просматривает весь набор, элемент за элемен- том, и выполняет над каждым из них заданную процедуру. В предыду- щем примере процедуре PrintWord передавался указатель строки для ее отображения. Обратите внимание, что процедура PrintWord вло- женная (или локальная). Она работает в другой процедуре, Print, которой передается указатель на TstrCollection. Print использует метод итератора ForEach для передачи каждого элемента своего на- бора в процедуру PrintWord.procedure Print(C: PCollection); procedure PrintWord(P: PChar); far; begin Writeln(P); { вывести строку } end; begin {Print} Writeln; Writeln; C^.ForEach(@PrintWord); { вызов PrintWord } end;
PrintWord должен выглядеть как уже знакомая процедура. Она просто берет указатель строки и передает его значение Writeln. Обратите внимание на директиву far после описания PrintWord. PrintWord не может быть методом, это просто процедура. Кроме того это должна быть вложенная процедура. Print надо рассматривать как некую оболочку вокруг процедуры, которая выполняет некоторую ра- боту над каждым элементом набора (может быть отображает или моди- фицирует данные). Вы можете иметь несколько аналогичных PrintWord процедур, но каждая из них должна быть вложена в Print и должна быть дальней процедурой (использовать директиву far или {$F+}).
Нахождение элемента
Отсортированные наборы (и следовательно наборы строк) имеют метод Search, который возвращает индекс элемента с конкретным значением ключа. Но как найти элемент в неотсортированном наборе? Или когда критерий поиска не использует сам ключ? Конечно же, следует использовать FirstThat и LastThat. Вы просто определяете булевскую функцию для проверки нужного вам критерия и вызываете FirstThat.
Полиморфические наборы
Как вы уже видели, что наборы могут динамически хранить лю- бой тип данных, и они обладают множеством методов, которые помо- гают вам организовывать эффективный доступ к данным. В действи- тельности сам TCollection определяет 23 метода. Когда вы исполь- зуете наборы в ваших программах, вы будете удивлены скоростью их работы: они разработаны с максимальной гибкостью и реализованы для использования с максимальной скоростью.Теперь пришло время рассмотреть реальные возможности набо- ров, элементы могут обрабатываться полиморфически. Это значит, что вы не просто можете хранить определенный тип объекта в набо- ре; вы можете хранить несколько разных типов объектов, взятых произвольно из вашей иерархии объектов.
Если вы рассмотрите приведенные примеры наборов, вы можете заметить, что все элементы каждого набора были одно и того же ти- па. Мы имели дело со списком строк в котором каждый элемент был строкой. Мы также занимались списком клиентов. Но наборы могут хранить любые производные от TObject объекты, и вы можете произ- вольно смешивать эти объекты. Естественно, что вы желаете, чтобы эти объекты имели нечто общее. На самом деле вам нужно, чтобы у них был общий абстрактный объект-предок.
В качестве примера рассмотрим программу, которая помещает в набор три различных графических объекта. Затем итератор ForEach используется для просмотра набора и отображения каждого объекта. В отличие от других примеров данной главы данный пример (Collect4) использует функции Windows для рисования в окне. Обя- зательно включите WinProcs и WinTypes в uses данного примера. Сначала определяется абстрактный объект-предок (см. COLLECT4.PAS).
type PGraphObject = ^TGraphObject; TGraphObject = object(TObject) Rect: TRect; constructor Init(Bounds: TRect); procedure Draw(DC: HDC); virtual; end;
Из этого объявления вы можете видеть, что каждый графический объект может инициализировать себя (Init) и отобразить себя на графическом экране (Draw). Теперь определим эллипс, прямоугольник и сектор как производные от этого общего предка:
PGraphEllipse = ^TGraphEllipse; TGraphEllipse = object(TGraphObject) procedure Draw(DC: HDC); virtual; end;
PGraphRect=^TGraphRect; TGraphRect=object(TGraphObject) procedure Draw(DC: HDC); virtual; end; PGraphPie = ^TGraphPie; TGraphPie = object(TGraphObject) ArcStart, ArcEnd: TPoint; constructor Init(Bounds: TRect); procedure Draw(DC: HDC); virtual; end;
Все эти три типа объекта наследуют поле Rect из TGraphObject, но все они разного размера. TGraphEllipse и TGraphRect нужно только добавить их новые методы рисования, т.к. их методам рисования нужны только размеры и расположение, а TGraphPie нужны дополнительные поля и другой конструктор для их корректного представления. Приведем исходный код для помещения этих фигур в набор:
. . . GraphicsList := New(PCollection, Init(10,5)); { создать набор } for I := 1 to NumToDraw do begin case I mod 3 of { создать объект } 0: P := New(GraphRect, Init(Bounds)); 1: P := New(GraphEllipse, Init(Bounds)); 2: P := New(GraphPie, Init(Bounds)); end; GraphicsList^.Insert(P); { добавить в набор } end; . .
Как вы можете видеть цикл, for вставляет графические объекты в набор GraphicsList. Вы знаете только то, что каждый объект в GraphicsList представляет собой некоторый вид TGraphObject. После помещения в набор у вас уже нет информации о том, является ли элемент набора прямоугольником, эллипсом или сектором. Благодаря полиморфизму, вам этого и не нужно знать, поскольку каждый объект содержит все данные и код (Draw), который ему нужен. Просмотрим набор с использованием итеративного метода и каждый набор будет сам отображать себя:
procedure DrawAll(C: PCollection);
procedure CallDraw(P: PGraphObject); far; begin P^.Draw(PaintDC); { вызов метода Draw } end;
begin {DrawAll} C^.ForEach(@CallDraw); { прорисовать каждый объект } end;
var GraphicsList: PCollection; begin . . . if GraphicsList <> nil then DrawAll(GraphicsList); . . . end.
Способность наборов хранить разные, но связанные объекты ос- новывается на мощном краеугольном камне объектно-ориентированного программирования. В следующей главе вы увидите тот же принцип по- лиморфизма, примененный к потокам с равными приоритетами.
Полиморфизм наборов
Второй аспект, по которому массивы могут ограничивать ваше приложение, состоит в том, что каждый элемент массива должен иметь один и тот же тип, и этот тип должен быть определен при компиляции кода.Наборы обходят это ограничение использованием нетипизирован- ных указателей. Это сказывается не только на быстроте и эффектив- ности, но наборы могут состоять из объектов (и даже не из объек- тов) разного типа и размера. Набору не нужно знать что-либо об объектах, которые он обрабатывает. Он просто организует связь с ними в случае необходимости.
Проверка типа и наборы
Наборы ограничивают традиционную мощную проверку типа языка Паскаль. Это означает, что можете поместить нечто в набор и когда запрашиваете это назад, компилятор уже не может проверить ваших предположений относительно объекта. Вы можете поместить нечто как PHedgehog (еж), а прочитать назад как PSheep (овца), и набор ни- как не сможет насторожить вас.Как программист, работающий на языке Паскаль, вы вполне оп- равданно будете нервничать по поводу результатов. Проверка типов языка Паскаль в конце концов сберегает несколько часов при поиске некоторых достаточно иллюзорных ошибок. Поэтому вы должны принять во внимание следующее предупреждение. Вы даже представить себе не можете насколько трудно бывает искать ошибки несоответствия типа, поскольку обычно эту работу за вас выполнял компилятор! Однако, если вы обнаружите, что ваша программа сбивается или зацикливает- ся, тщательно проверьте хранимые типы объектов и считываемые из наборов.
Создание набора
Создание набора столь же просто, как и создание типа данных, которые вы хотите в нем хранить. Предположим, что вы - консуль- тант, и вам нужно хранить и искать номер счета, фамилию и номер телефона каждого из ваших клиентов. Сначала определим тип объекта клиента (TClient), который будет хранится в наборе (не забудьте определить тип указателя для каждого нового типа объекта):type PClient=^TClient; TClient=object(TObject) Account, Name, Phone: PChar; constructor Init(NewAccount, NewName, NewPhone: PChar); destructor Done; virtual; procedure Print; virtual; end;
Затем реализуем методы Init и Done для размещения и удаления данных о клиенте и метод Print для отображения данных о клиенте в виде таблицы. Обратите внимание, что поля объекта имеют тип PChar, поэтому память выделяется только для той части строки, ко- торая действительно используется. Функции StrNew и StrDispose очень эффективно обрабатывают динамические строки.
constructor TClient.Init(NewAccount, NewName, NewPhone: PChar); begin Account := StrNew(NewAccount); Name := StrNew(NewName); Phone := StrNew(NewPhone); end;
destructor TClientDone; begin StrDispose(Account); StrDispose(Name); StrDispose(Phone); end;
procedure TClient.Print; begin Writeln( ' ', Account, '':10 - StrLen(Account), Name, '':20 - StrLen(Name), Phone, '':16 - StrLen(Phone)); end;
TClient.Done будет автоматически вызываться для каждого кли- ента при удалении всего набора. Сейчас вы просто инициируете на- бор для хранения ваших клиентов и вставляете в него записи о кли- ентах. Головное тело программы (COLLECT1.PAS) будет выглядеть следующим образом:
var ClientList: PCollection; begin ClientList:=New(PCollection, Init(10,5)); with ClientList^ do begin Insert(New(PClient, Init('91-100', 'Anders, Smitty', '(406) 111-2222'))); Insert(New(PClient, Init('90-167', 'Smith, Zelda', '(800) 555-1212'))); Insert(New(PClient, Init('90-177', 'Smitty, John', '(406) 987-4321'))); Insert(New(PClient, Init('90-160', 'Johnson, Agatha', '(302) 139-8913'))); end; PrintAll(ClientList); SearchPhone(ClientList, '(406)'); Dispose(ClientList, Done); end.
Примечание: Процедуры PrintAll и SearchPhone будут рассмотрены позднее.
Обратите внимание, насколько просто было построить набор. Первый оператор размещает новый экземпляр TCollection с именем ClientList с начальным размером на 10 клиентов. В случае необхо- димости размещения более 10 клиентов в ClientList, его размер бу- дет увеличиваться каждый раз на 5 клиентов. Следующие два опера- тора создают новый объект клиента и вставляют его в набор. Вызов Dispose в конце операции освобождает весь набор клиентов.
Нигде не нужно было сообщать набору, какой вид данных пред- полагается хранить - для этого просто используется указатель.
Pascal 7 & Objects
Автоматические поля
Поле VmtLink это связь с таблицей виртуальных методов объек- тов (VMT). Вы просто задаете его как отклонение типа вашего объ- екта:RSomeObject.VmtLink := Ofs(TypeOf(TSomeObject)^);
Поля Load и Store содержат соответственно адреса методов Load и Store.
RSomeObject.Load := @TSomeObject.Load; RSomeObject.Store := @TSomeObject.Store;
Значение последнего поля, Next, задается RegisterType и не требует никакого вмешательства с вашей стороны. Оно просто обес- печивает внутреннее использование скомпонованного списка регист- рационных записей потока.
Чтение из потока и запись в поток
Основной объект потока TStream реализует три главных метода, которые вам нужно четко понимать: Get, Put и Error. Get и Put грубо соответствуют процедурам Read и Write, которые вы использу- ете в обычных операциях ввода-вывода. Error - это процедура, ко- торая вызывается при появлении ошибок потока.Метод Put
Давайте сначала рассмотрим процедуру Put. Общий синтаксис метода Put следующий:
SomeStream.Put(PSomeObject);
где SomeStream - это некоторый производный от TStream объект, ко- торый был инициализирован, а PSomeObject представляет собой ука- затель на некоторый производный от TObject объект, который заре- гистрирован с потоком. Это все, что вам нужно сделать. Поток мо- жет из таблицы виртуальных методов PSomeObject узнать, какой это тип объекта (предполагается, что тип зарегистрирован), поэтому он знает какой номер идентификатора писать, и сколько после него бу- дет данных.
Специальный интерес для вас, как для программиста, работаю- щего с ObjectWindows, состоит в том факте, что при помещении в поток группы с дочерними окнами, дочерние окна также автоматичес- ки помещаются в поток. Таким образом, запись сложных объектов не так уж и сложна, более того, это делается автоматически! Вы може- те сохранить полное состояние диалога простым помещением объекта диалога в поток. При повторном запуске вашей программы и загрузке диалога будет выведено его состояние в момент записи.
Метод Get
Считывание объектов из потока столь же просто. Все что вам нужно сделать, это вызвать функцию Get:
PSomeObject := SomeStream.Get;
где SomeStream - это инициализированный поток ObjectWindows, а PSomeObject - указатель на некоторый тип объекта ObjectWindows. Get просто возвращает указатель на нечто, что он взял из потока. Сколько данных было взято и какой тип таблицы виртуальных методов (VMT) присвоен данным, определяется не типом PSomeObject, а типом объекта, обнаруженным в потоке. Следовательно, если объект в те- кущей позиции SomeStream имеет не совпадающий с PSomeObject тип, у вас будет некорректная информация.
Как и Put, Get ищет сложные объекты. Следовательно, если вы ищите в потоке окно, которое владеет дочерними окнами, то они также будут загружены.
Метод Error
И, наконец, процедура Error определяет что происходит при возникновении ошибки потока. По умолчанию TStream.Error просто устанавливает значение двух полей в потоке (Status и ErrorInfo). Если вы хотите сделать что-либо более содержательное, например, сгенерировать соответствующее сообщение о сбое в работе программы или вывести блок диалога c сообщением об ошибке, то вам нужно пе- реопределить процедуру Error.
Добавление методов Store
Приведем методы Store. Обратите внимание, что для PGraphEllipse и PGraphRect не требуются свои собственные методы, т.к. они не добавляют новых полей к унаследованным от PGraphObject:type PGraphObject = ^TGraphObject; TGraphObject = object(TObject) Rect: TRect; constructor Init(Bounds: TRect); procedure Draw(DC: HDC); virtual; procedure Store(var S: TStream); virtual; end;
PGraphEllipse = ^TGraphEllipse; TGraphEllipse = object(TGraphObject) procedure Draw(DC: HDC); virtual; end;
PGraphRect = ^TGraphRect; TGraphRect = object(TGraphObject) procedure Draw(DC: HDC); virtual; end;
PGraphPie = ^TGraphPie; TGraphPie = object(TGraphObject) ArcStart, ArcEnd: TPoint; constructor Init(Bounds: TRect); procedure Draw(DC: HDC); virtual; procedure Store(var S: TStream); virtual; end;
Реализация метода Store вполне очевидна. Каждый объект вызы- вает свой унаследованный метод Store, который хранит все унасле- дованные данные. Затем вызывается метод Write для записи дополни- тельных данных:
procedure TGraphObject.Store(var S: TStream); begin S.Write(Rect, SizeOf(Rect)); end; procedure TGraphPie.Store(var S: TStream); begin TGraphObject.Store(S); S.Write(ArcStart, SizeOf(ArcStart)); S.Write(ArcEnd, SizeOf(ArcEnd)); end;
Обратите внимание, что метод TStream Write делает двоичную запись. Его первый параметр может быть переменной любого типа, но TStream.Write не может узнать размеры этой переменной. Второй па- раметр содержит эту информацию, и вы должны придерживаться согла- шения относительно использования стандартной функции SizeOf. Та- ким образом, компилятор всегда может гарантировать, что вы всегда считываете и записываете нужное количество данных.
Как сделать объекты потоковыми
Все стандартные объекты ObjectWindows готовы к использованию в потоках, и все потоки ObjectWindows узнают стандартные объекты. При изготовлении нового типа объекта, производного от стандартно- го объекта, его очень просто подготовить к использованию в потоке и известить потоки о его существовании.Как все хранится?
Относительно потоков нужно сделать важное предостережение: только владелец объекта должен записывать его в поток. Это пре- достережение аналогично традиционному предостережению языка Паскаль, которое вам должно быть известно: только владелец указа- теля может уничтожить его.В реальных сложных приложениях множество объектов часто име- ют указатель на конкретную структуру. Когда возникает необходи- мость в выполнении операций ввода-вывода, вы должны решить, кто "владеет" структурой. Только этот владелец должен посылать струк- туру в поток. Иначе у вас может получиться несколько копий одной структуры в потоке. При считывании такого потока будет создано несколько экземпляров структуры, и каждый из первоначальных объ- ектов будет указывать на собственную персональную копию структуры вместо единственной первоначальной структуры.
Копирование потока
TStream имеет метод CopyFrom(S,Count), который копирует за- данное число байт (Count) из заданного потока S. Метод CopyFrom может быть использован для копирования содержимого одного потока в другой. Если, например, вы циклически обращаетесь к дисковому потоку, то можете скопировать его в поток EMS для организации бо- лее быстрого доступа:NewStream := New(TEmsStream, Init(OldStream^.GetSize)); OldStream^.Seek(0); NewStream^.CopyFrom(OldStream, OldStream^.GetSize);
Механизм потока
После того, как мы посмотрели на процесс использования пото- ков, следует заглянуть во внутреннюю работу, которую производит ObjectWindows c вашими объектами с помощью методов Put и Get. Это прекрасный пример взаимодействия объектов и использования встро- енных в них методов.Методы загрузки и хранения
Действительное чтение и запись объектов в поток производится методами Load и Store. Каждый объект должен иметь эти методы для использования потока, поэтому вы никогда не будете вызывать их непосредственно (они вызываются из методов Get и Put.) Все что вам нужно сделать, это убедиться в том, что объект знает, как послать себя в поток, когда это потребуется.Благодаря объектно-ориентированному программированию это де- лается очень просто, т.к. большинство механизмов наследуются от объекта-предка. Все что должен делать ваш объект, это загружать и хранить те свои компоненты, которые вы в него добавляете, об ос- тальном позаботится метод предка. Например, вы производите от TWindow новый вид окна с именем художника-сюрреалиста Рене Маг- ритте, который нарисовал много известных картин с окнами:
type TMagritte = object(TWindow) Surreal: Boolean; constructor Load(var S: TStream); procedure Store(var S: TStream); . . . end;
Все что было добавлено к данным окна - это одно булевское поле. Для загрузки объекта вы просто считываете стандартный TWindow, а затем считываете дополнительный байт булевского поля. Типичные методы Load и Store для производных объектов будут выг- лядеть следующим образом:
constructor TMagritte.Load(var S: Stream); begin TWindow.Load(S); { загрузка типа } S.Read(Surreal, SizeOf(Surreal)); { чтение дополнительных полей } end;
procedure TMagritte.Store(var S: Stream); begin TWindow.Store(S); { сохранение типа } S.Write(Surreal, SizeOf(Surreal)); { запись дополнительных полей } end;
Вы должны контролировать, что записывается и загружается один и тот же объем данных, и загрузка данных производится в той же последовательности, что и их запись. Компилятор не покажет ошибки. Если вы будете недостаточно аккуратны, то могут возник- нуть серьезные проблемы. Если вы изменяете поля объекта, то нужно изменить и метод Load, и метод Store.
Наборы в потоке: пример
В Главе 19, "Наборы", вы уже видели как наборы могут содер- жать разные, но связанные объекты. Это свойство полиморфизма так- же применимо и к потокам, и их можно использовать для записи на- боров на диск для последующего обращения, даже в другой програм- ме. Вернемся к примеру COLLECT4.PAS. Что еще нужно добавить в эту программу для помещения набора в поток?Ответ будет очень простым. Сначала возьмем базовый объект TGraphObject и "научим" его хранить его данные (X и Y) в потоке. Для этого нужен метод Store. Затем определим новый метод Store для любого производного от TGraphObject объекта, в котором до- бавляются дополнительные поля (например, TGraphPie добавляет ArcStart и ArcEnd).
Затем построим регистрационную запись для каждого типа объ- екта, который предполагается хранить, и зарегистрируем все эти типы при первом запуске вашей программы. Вот и все. Остальное бу- дет подобно обычным операциям ввода-вывода в файл: определяется переменная потока; создается новый поток; одним простым операто- ром весь набор помещается в поток, и поток закрывается.
Необъектные элементы потоков
В поток можно записывать и элементы, которые не являются объектами, но для этого следует использовать несколько иной под- ход. Стандартные методы потока Get и Put требуют загрузки или за- писи объекта, производного от TObject. Если вам нужно создать по- ток, который состоит не из объектов, переходите на нижний уровень процедур Read и Write, где в поток записывается или из него счи- тывается заданное число байт. Этот же механизм используют методы Get и Put для чтения и записи данных об объектах. Вы просто обхо- дите механизм VMT, который заложен в Put и Get.Номера идентификаторов объектов
Вам действительно нужно думать только о поле ObjType записи, все остальное делается механически. Каждому новому определяемому вами типу требуется его собственный уникальный идентификатор типа в виде числа. ObjectWindows резервирует регистрационные номера от 0 до 99 для стандартных объектов, поэтому ваши регистрационные номера будут лежать в диапазоне от 100 до 65535.Ответственность за создание и ведение библиотеки номеров идентификаторов для всех ваших новых объектов, которые будут ис- пользоваться в потоках ввода-вывода, ложиться целиком на вас. Нужно сделать эти идентификаторы доступными для пользователей ва- ших модулей. Как и для идентификатора меню и определенных пользо- вателем сообщений, присваиваемые вами числа могут быть произволь- ными, но они должны быть уникальными и попадать в указанный диа- пазон.
Обработка ошибок потока
TStream имеет метод Error(Code, Info), который вызывается при обнаружении ошибки потока. Error просто присваивает полю Status потока значение одной из констант, приведенных в Главе 21 "Справочник по ObjectWindows" в разделе "Константы stXXXX".Поле ErrorInfo не определено, если значение Status не есть stGetError или stPutError. Если значение поля Status равно stGetError, то поле ErrorInfo содержит номер идентификатора пото- ка незарегистрированного типа. Если значение поля Status равно stPutError, то поле ErrorInfo содержит смещение VMT типа, который вы пытались поместить в поток. Вы можете переписать TStream.Error для генерации любого уровня обработки ошибок, включая ошибки эта- па выполнения.
Обработка указателей объектов со значением nil
Вы можете записать в поток объект nil. Однако, если это сде- лать, то в поток запишется слово 0. При считывании идентификатора слова 0 поток возвратит указатель nil. Поэтому 0 считается заре- зервированным и не может использоваться в качестве номера иденти- фикатора объекта потока. ObjectWindows резервирует идентификатор потока от 0 до 99 для внутреннего использования.Ответ: потоки
ObjectWindows позволяет обойти все эти трудности и даже су- лит вам получение некоторых дополнительных выгод. Потоки дают вам простое, но изящное средство хранение данных объекта вне вашей программы.Полиморфизм потоков
Потоки ObjectWindows позволяют вам работать с файлами опре- деленного и неопределенного типа: проверка типа имеется, но тип посылаемого объекта не должен обязательно определяться во время компиляции. Смысл в том, что потоки знают, что они имеют дело с объектами, и поскольку все объекты являются производными от TObject, поток может их обработать. В действительности различные объекты ObjectWindows могут также легко записываться в один по- ток, как и группы идентичных объектов.Поля в потоке
Много раз вы видели, что удобно хранить указатель на дочер- ние окна группы в локальной переменной (поле данных объекта). Например, блок диалога может хранить указатель на его объекты уп- равления в полях с мнемоническими именами для более удобного дос- тупа (OKButton или FileINputLine). При создании такого дочернего окна порождающее окно будет иметь на него два указателя, один - в поле, и еще один - в списке дочерних окон. Если на это не обра- тить внимания, то считывание такого объекта из потока приведет к дублированию.Решение состоит в использовании методов TWindowsObject GetChildPtr и PutChildPtr. При хранении поля, которое является дочерним окном, вместо записи указателя, как если бы это была простая переменная, вы вызываете метод PutChildPtr, который запи- сывает ссылку на позицию дочернего окна в списке дочерних окон группы. Аналогично, при загрузке (Load) группы из потока, вы вы- зываете GetChildPtr, который гарантирует, что поле и список до- черних окон указывают на один и тот же объект. Приведем короткий пример использования GetChildPtr и PutChildPtr в простом окне:
type TDemoWinodw = object(TWindow) Msg: PStatic; constructor Load(var S: TStream); procedure Store(var S: TStream); end;
constructor TDemoWindow.Load(var S: TStream); begin TWindow.Load(S); GetChildPtr(S, Msg); end;
procedure TDemoWindow.Store(var S: TStream); begin TWindow.Store(S); PutChildPtr(S, Msg); end;
Давайте рассмотрим, чем этот метод Store отличается от обыч- ного Store. После обычного сохранения окна все что нам нужно сде- лать, это записать ссылку на поле Msg, вместо записи самого поля, как мы это обычно делали. Действительный объект кнопки хранится в виде дочернего окна для окна, которое вызывается TWindow.Store.
Кроме этого нужно поместить эту информацию в поток с указа- нием того, что Msg указывает на это дочернее окно. Метод Load производит обратные действия, сначала загружая окно и его дочер- нее окно командной кнопки, а уже затем восстанавливая указатель на это дочернее окно через Msg.
Потоки обрабатывают объекты
Все что вам нужно сделать - это определить для потока, какие объекты ему нужно будет обрабатывать, чтобы он знал, как согласо- вывать данные с таблицами виртуальных методов. Затем без ка- ких-либо усилий вы можете помещать объекты в поток и извлекать их из потока.Но каким образом один и тот же поток может считывать и запи- сывать такие разные объекты как TCollection и TDialog, даже не зная в момент компиляции, какие типы объектов он будет обрабаты- вать? Это существенно отличается от традиционных операций вво- да-вывода языка Паскаль. В действительности потоки могут обраба- тывать даже новые типы объектов, которые вообще еще не были соз- даны к моменту компиляции потока.
Ответом на это является так называемая регистрация. Каждому типу объекта ObjectWindows (или любому новому производному типу объекта) присваивается уникальный регистрационный номер. Этот но- мер записывается в поток перед данными объекта. Затем, при считы- вании объекта из потока, ObjectWindows сначала берет регистраци- онный номер и на его основании узнает, сколько данных нужно счи- тывать и какие таблицы виртуальных методов подключать к данным.
Потоки произвольного доступа
До этого момента мы работали с потоками как с устройствами последовательного доступа: вы помещали (Put) объекты в конец ва- шего потока и считывали их назад (Get) в той же последовательнос- ти. Но ObjectWindows имеет и более мощные средства. Имеется воз- можность рассматривать поток как виртуальное устройство произ- вольного доступа. Кроме методов Get и Put, которые соответствуют Read и Write при работе с файлом, потоки обладают средствами про- ведения операций Seek, FilePos, FileSize и Truncate.- Процедура потока Seek перемещает текущий указатель потока к заданной позиции (число байт от начала потока), как стандартная процедура Seek языка Паскаль.
- Процедура GetPos по своему действию обратна процедуре Seek. Она возвращает значение Longint с текущей позицией потока.
- Функция GetSize возвращает размер потока в байтах.
- Процедура Truncate удаляет все данные, которые расположены после текущей позиции потока, при этом текущая позиция по- тока становится концом потока.
Поскольку работа с этими программами очень удобна, потоки произвольного доступа требуют от вас отслеживать индекс, отмечаю- щий начальную позицию каждого объекта в потоке. В этом случае для хранения индекса вы можете использовать набор.
Процесс Get
Когда вы считываете объект из потока с помощью метода Get, сначала ищется номер его идентификатора, и просматривается на совпадение список зарегистрированных типов. После обнаружения совпадения регистрационная запись дает потоку местоположение ме- тода Load объект и VMT. Затем для чтения нужного объема данных из потока вызывается метод Load.Вы опять просто говорите потоку, что нужно взять (Get) сле- дующий объект и поместить его в место, определяемое заданным вами указателем. Ваш объект даже не беспокоится о том, с каким потоком он имеет дело. Поток сам беспокоится о считывании нужного объема данных из потока с помощью метода объекта Load, который в свою очередь опирается на метод потока Read.
Для программиста все это достаточно прозрачно, но в то же время вы ясно должны понять, насколько важно зарегистрировать тип до проведения каких-либо попыток ввода-вывода с потоком.
Процесс Put
Когда вы посылаете объект в поток с помощью метода Put, по- ток сначала берет указатель VMT со смещением 0 от объекта и прос- матривает список зарегистрированных типов потоков системы с целью найти совпадение. Когда это совпадение найдено, поток ищет ре- гистрационный номер идентификатора объекта и записывает его в по- ток. Затем поток вызывает метод Store объекта для завершения за- писи объекта. Метод Store использует процедуру потока Write, ко- торая действительно пишет корректное число байт в поток.Ваш объект не должен ничего знать о потоке - это может быть файл на диске, память EMS или любой другой вид потока - ваш объ- ект просто говорит "запишите меня в поток", и поток делает все остальное.
Разработка пользователем собственных потоков
Данный раздел суммирует возможности методов и обработки оши- бок потоков ObjectWindows, чтобы вы знали, что можно использовать для создания новых типов потоков.Сам TStream является абстрактным объектом и его можно расши- рить для создания удобного типа потока. Большинство методов TStream являются абстрактными и должны быть реализованы как их производные методы, основывающиеся на абстрактных методах TStream. Полностью реализованы только методы Error, Get и Put. GetPos, GetSize, Read, Seek, SetPos, Truncate и Write должны быть переписаны. Если производный тип объекта имеет буфер, то должен быть переписан и метод Flush.
Регистрация на месте
После конструирования регистрационной записи потока вы вызы- ваете RegisterType с вашей записью в качестве параметра. Поэтому для регистрации вашего нового объекта TMagritte для его использо- вания в потоке вы включаете следующий код:const RMagritte: TStreamRec = ( ObjType: 100; VmtLink: Ofs(TypeOf(TMagritte)^); Load: @TMagritte.Load; Store: @TMagritte.Store ); RegisterType(RMagritte);
Вот и все. Теперь вы можете помещать (Put) экземпляры вашего нового типа объекта в любой поток ObjectWindows и считывать их из потоков.
Регистрация потока
Кроме определения методов Load и Store для новых объектов, вы также должны зарегистрировать этот новый тип объекта в пото- ках. Регистрация - это простой процесс, который состоит из двух этапов: сначала определяется запись регистрации потока, а затем она передается глобальной процедуре регистрации RegisterType.Примечание: ObjectWindows уже имеет зарегистрированны- ми все стандартные объекты, поэтому вам нужно регистриро- вать только новые, определяемые вами объекты.
Для определения записи регистрации потока нужно следовать приводимому ниже формату. Запись регистрации потока это запись языка Pascal типа TStreamRec, которая определяется следующим об- разом:
PStreamRec = ^TStreamRec; TStreamRec = record ObjType: Word; VmtLink: Word; Load: Pointer; Store: Pointer; Next: Word; end;
По соглашению всем регистрационным записям потока ObjectWindows присваивается то же имя, что и соответствующим ти- пам объектов, но начальное "T" заменяется на "R". Следовательно, регистрационная запись для TCollection будет иметь имя RCollection. Такие абстрактные типы как TObject и TWindowsObject не имеют регистрационных записей, поскольку их экземпляры вы ни- когда не будете хранить в потоках.
Регистрация стандартных объектов
ObjectWindows определяет регистрационные записи потоков для всех его стандартных объектов. Кроме того, модуль WObjects опре- деляет процедуру RegisterWObjects, которая автоматически регист- рирует все объекты этого модуля. Например, модуль OWindows содер- жит процедуру RegisterOWindows, а ODialogs - RegisterODialogs.Регистрация записей
Наш последний шаг состоит в определении константы регистра- ционной записи для каждого производного типа. Хороший прием прог- раммирования состоит в следовании соглашению ObjectWindows отно- сительно использования для имени типа идентификатора, где вместо первой буквы T ставится R.Помните о том, что каждой регистрационной записи присваива- ется уникальный номер идентификатора объекта (ObjType). Номера от 0 до 99 резервируются ObjectWindows для стандартных объектов. Хо- рошо бы отслеживать все номера идентификаторов ваших объектов по- тока в некотором центральном месте, чтобы избежать дублирования.
const RGraphEllipse: TStreamRec = ( ObjType: 150; VmtLink: Ofs(TypeOf(TGraphEllipse)^); Load: nil; { метод загрузки отсутствует } Store: @TGraphEllipse.Store); RGraphRect: TStreamRec = ( ObjType: 151; VmtLink: Ofs(TypeOf(TGraphRect)^); Load: nil; { метод загрузки отсутствует } Store: @TGraphRect.Store); RGraphPie: TStreamRec = ( ObjType: 152; VmtLink: Ofs(TypeOf(TGraphPie)^); Load: nil; { метод загрузки отсутствует } Store: @TGraphPie.Store);
Вам не нужно регистрационная запись для TGraphObject, так как это абстрактный тип, и он никогда не будет помещаться в набор или в поток. Указатель Load каждой регистрационной записи уста- навливается в nil, поскольку в данном примере рассматривается только помещение данных в поток. В следующем примере методы Load будут определены, и изменены регистрационные записи (см. STREAM2.PAS).
Регистрация
Всегда нужно зарегистрировать каждую из этих записей до про- ведения каких-либо операций ввода-вывода с потоком. Самый простой способ сделать это состоит в том, чтобы объединить их все в одну процедуру и вызвать ее в самом начале вашей программы (или в ме- тоде Init вашего приложения):procedure StreamRegistration; begin RegisterType(RCollection); RegisterType(RGraphEllipse); RegisterType(RGraphRect); RegisterType(RGraphPie); end;
Обратите внимание, что вам нужно зарегистрировать TCollection (используя его запись RCollection - теперь вы видите, что соглашения о присваивании имен упрощают программирование), хотя вы и не определили TCollection. Правило очень простое и не- забываемое именно вы отвечаете за регистрацию каждого типа объек- та, который ваша программа будет помещать в поток.
Родство экземпляров окон
Аналогичная ситуация может возникнуть, если окно имеет поле, указывающее на одного из его родственников. Окно называется родс- твенным другому окну, если они оба принадлежат одному и тому же порождающему окну. Например, у вас есть управляющий элемент ре- дактированием и две кнопки с независимой фиксацией, которые уп- равляют активизацией управляющего элемента редактирования. При изменении состояния кнопки с независимой фиксацией, она соответс- твенно активизирует или дезактивизирует управляющий элемент ре- дактирования. TActivateRadioButton должна знать управляющий эле- мент редактирования, который также является компонентом этого же окна, поэтому управляющий элемент редактирования добавляется в качестве переменной.Как и для дочерних окон, при чтении и записи родственных ссылок в поток могут возникнуть проблемы. Решение также будет аналогичным. Методы TWindowsObject PutSiblingPtr и GetSiblingPtr предоставляют средства доступа к родственникам:
type TActivateRadioButton=object(TRadioButton) EditControl: PEdit; . . . constructor Load(var S: TStream); procedure Store(var S: TStream); virtual; . . . end;
constructor TActivateRadioButton.Load(var S: TStream); begin TRadioButton.Load(S); GetPeerPtr(S, EditControl); end;
procedure TActivateRadioButton.Store(var S: TStream); begin TRadioButton.Load(S); PutPeerPtr(S, EditControl); end;
Единственно о чем нужно побеспокоиться, так это о загрузке ссылок на родственные окна, которые еще не загружены (т.е. в списке дочерних окон они идут ниже и, следовательно, позднее в потоке). ObjectWindows автоматически обрабатывает эту ситуацию, отслеживая все подобные будущие ссылки и разрешая их после заг- рузки всех дочерних окон. Вам нужно иметь в виду, что родственные ссылки не будут действовать, пока Load не выполнится целиком. Принимая это во внимание, вы не должны помещать в Load никакой код, который использует дочерние окна, которые зависят от их родственных окон, поскольку в этом случае результат может быть непредсказуемым.
Смысл использования потоков
На фундаментальном уровне вы можете рассматривать потоки как файлы языка Паскаль. В своей основе файл языка Паскаль представля- ет собой последовательное устройство ввода-вывода: вы записываете в него и считываете из него. Поток - это полиморфическое устройс- тво последовательного ввода-вывода, т.е. оно ведет себя, как пос- ледовательный файл, но вы можете считывать и записывать различные типы объектов в каждый момент времени.Потоки (как и файлы Паскаля) можно также просматривать, как устройства ввода-вывода произвольного доступа, искать определен- ное место в файле, считывать данные в этой точке или записывать данные в эту точку, возвращать позицию указателя файла и т.д. Все эти операции можно выполнять с потоками, и они описаны в разделе "Потоки с произвольным доступом".
Есть два разных аспекта использования потоков, которыми вам нужно овладеть, и к счастью оба они очень простые. Первый - это установка потока, а второй - считывание и запись файлов в поток.
Установка потока
Все что нужно сделать для использования потока - это инициа- лизировать его. Точный синтаксис конструктора Init может быть разным, в зависимости от типа потока, с которым вы имеете дело. Например, если вы открываете поток DOS, вам нужно передать имя файла DOS и режим доступа (только чтение, только запись, чте- ние/запись) для содержащего поток файла.
Например, для инициализации буферизированного потока DOS при загрузке набора объектов в программу, все что вам нужно это:
var SaveFile: TBufStream; begin SaveFile.Init('COLLECT.DTA', stOpen, 1024); . .
После инициализации потока все готово к работе.
TStream это абстрактный механизм потока, поэтому вы будет работать не с ним, а с производными от TStream удобными объектами потока. Это будет, например, TDosStream, для выполнения дисковых операций ввода-вывода, TBufStream для буферизованных операций ввода-вывода (очень удобен для частых операций считывания или за- писи небольших объемов информации на диск) и TEmsStream для пере- дачи объектов в память EMS. Кроме того, ObjectWindows реализует индексированные потоки с указателем, указывающим место в потоке. Перемещая этот указатель вы можете организовать произвольный дос- туп в потоке.
Вопрос: объектный ввод-вывод
Поскольку вы пишете программы на языке Паскаль, то знаете, что до выполнения операций ввода-вывода с файлом, вы сначала должны сообщить компилятору, какой тип данных вы будете писать и считывать из файла. Файл должен иметь тип, и этот тип должен быть установлен во время компиляции.Паскаль реализует в этой связи очень удобное правило: можно организовать доступ к файлу неопределенного типа с помощью процедур BlockWrite и BlockRead. Отсутствие проверки типа возла- гает некоторую дополнительную ответственность на программиста, хотя позволяет очень быстро выполнять двоичные операции ввода-вы- вода.
Вторая проблема состоит в том, что вы не можете непосредс- твенно использовать файлы с объектами. Паскаль не позволяет вам создавать файл с объектным типом. Объекты могут содержать вирту- альные методы, адреса которых определяются в процессе выполнения программы, поэтому хранение информации о виртуальных методах вне программы лишено смысла, еще более бессмысленно считывать эту ин- формацию в программу.
Но эту проблему снова можно обойти. Вы можете выделить дан- ные из ваших объектов и записать эту информацию в какой-то файл некоторого вида, а уже позднее восстановить объекты из этих ис- ходных данных. Подобное решение, однако, будет недостаточно эле- гантным и существенно усложняет конструирование объектов.
Закрытие потока
Когда вы закончили использование потока, вы вызываете его метод Done, как вы обычно вызывали Close для дискового файла. Как и для других объектов ObjectWindows, это делается следующим обра- зом:Dispose(SomeStream, Done);
как для уничтожения объекта потока, так и для его закрытия.
Запись в поток
Нужно следовать обычной последовательности операций вво- да-вывода в файл: создать поток; поместить в него данные (набор); закрыть поток. Вам не нужно писать итератор ForEach для помещения в поток каждого элемента набора. Вы просто говорите потоку, что нужно поместить (Put) набор в поток:var . . . GraphicsStream: TBufStream; begin . . . StreamRegistration; { регистрация всех объектов потока } GraphicsStream.Init('GRAPH.SMT', stCreate, 1024); GraphicsStream.Put(GraphicsList); { выходной набор } if GraphicsStream.Status <> 0 then Status:=em_Stream; GraphicsStream.Done; { сброс потока } end;
В результате создастся файл на диске, который содержит всю информацию, необходимую для "считывания" набора назад в память. Когда поток открыт, и ищется набор, то (см. STREAM2.PAS) "маги- чески" восстанавливаются все скрытые связи между набором и его элементами, объекты и их таблицы виртуальных методов. Следующий раздел поясняет, как помещать в поток объекты, которые содержат связи с другими объектами.
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования