|
Печать каждой страницы
Когда объект принтера предоставляет возможность разбивки документа на
страницы, то для печати каждой страницы он вызывает метод PrintPage объекта
распечатки. Процесс распечатки только части документа, которому принадлежит
данная страница, аналогичен определению того, какую часть прокручиваемого
окна нужно отобра-
B.Pascal 7 & Objects/OW - 262 -
жать на экране. Например, можно отметить подобие методов отображения окна
и отображения страницы:
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 следует иметь в виду следующее:
B.Pascal 7 & Objects/OW - 263 -
* Независимость от устройств. Убедитесь, что в вашем коде не
делается предположений относительно масштаба, коэффициента
относительного удлинения или цветах. Для различных устройств видеоотображения
и печати эти характеристики могут отличаться, так что в программе следует
избегать такой зависимости.
* Возможности устройств. Хотя большинство видеоустройств поддерживают
все операции GDI, некоторые принтеры этого не делают. Например, многие
устройства печати (такие как графопостроители) совсем не воспринимают
графических изображений. При выполнении сложных задач вывода в программе
следует вызывать функцию API Windows GetDeviceCaps, которая возвращает
важную информацию о данном устройстве вывода.
Указание оставшихся страниц
У объектов распечатки имеется также последняя обязанность - указать объекту
принтера, имеются ли после данной страницы еще печатаемые страницы. Метод
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, то процесс печати попадет в бесконечный
цикл.
Другие соглашения по печати
Объекты распечатки содержат также несколько других методов, которые вы
можете при необходимости переопределить. Методы BeginPrintint и EndPrinting
вызываются, соответственно, перед печатью и после печати любого документа.
Если вам требуется специальная установка, вы можете выполнить ее в BeginPrinting
и отменить в EndPrinting.
B.Pascal 7 & Objects/OW - 264 -
Печать страниц выполняется последовательно. То есть, для каждой страницы
в последовательности принтер вызывает метод PrintPage. Однако, перед первым
вызовом PrintPage объект принтера вызывает BeginDocument, передавая номер
первой и последней страницы, которые будут печататься. Если для вашего
документа при печати страниц, отличных от первой, требуется специальная
подготовка, переопределите метод BeginDocument. После распечатки последней
страницы вызывается соответствующий метод EndDocument.
Может потребоваться также переопределение метода
GetSelection. GetSelection указывает в своем возвращаемом булевском значении,
имеет ли документ выделенную часть. Если это так, диалоговое окно печати
предоставляет вам возможность распечатать только эту выделенную часть.
Позицию выделения указывают два параметра-переменных Start и Stop. Например,
TEditPrintout интерпретирует Start и Stop как позиции символов, но может
представлять также строки текста, страницы и т.д.
Печать содержимое окна
Поскольку окно не разбивается на несколько страниц, и оконные объекты
уже знают, как отображаться в контексте устройства, простейшим видом генерируемой
распечатки является копия содержимого окна.
Чтобы еще более облегчить эту общую операцию, 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 не предусматривает разбивки на страницы. При
B.Pascal 7 & Objects/OW - 265 -
печати документа вам нужно печатать каждую страницу отдельно, но
так как окна не имеют страниц, вам нужно напечатать только один
образ. Окно уже знает, как создать этот образ - оно имеет метод
Paint. TWindowsPrintout печатается путем вызова метода Paint окна объекта
с контекстом устройства печати вместо контекста дисплея.
Вывод распечатки на принтер
При наличии объекта принтера и объекта распечатки фактическая печать выполняется
достаточно просто, независимо от того, происходит это из документа или
из окна. Все, что вам нужно сделать - это вызов метода 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;
Выбор другого принтера
Когда у вас в приложении есть объект принтера, вы можете связать его с
любым установленным в Windows устройстве печати. По умолчанию TPrinter
использует заданный по умолчанию принтер Windows (как это определено в
разделе устройств файла WIN.INI).
Существует два способа задания альтернативного принтера: непосредственно
в программе и через диалоговое окно пользователя.
B.Pascal 7 & Objects/OW - 266 -
Выбор принтера пользователем
Наиболее общим способом назначения другого принтера является вывод диалогового
окна, предоставляющего пользователю возможность выбора из списка установленных
устройств печати. TPtinter делает это автоматически при вызове его метода
Setup. Как показано на Рис. 15.1, Setup использует для этого диалогового
окна объект TPrinterSetupDlg.
SelectPrinter
Printer and port:
|PostScript Printer on LPT1: |v|
------------- ------------- -------------| OK | | Setup | | Cancel |-------------
------------- -------------
Рис. 15.1 Диалоговое окно задания принтера.
Настройка конфигурации принтера
Одна из командных кнопок в диалоге выбора принтера позволяет пользователям
изменить конфигурацию конкретного принтера. Кнопка Setup выводит диалоговый
блок конфигурации, определенной в драйвере принтера. Ваше приложение не
может управлять внешним видом или функциями диалогового блока конфигурации
драйвера.
Назначение конкретного принтера
В некоторых случаях вам может потребоваться назначить для своего объекта
принтера специфическое устройство печати. TPrinter имеет метод SetDevice,
который именно это и делает.
SetDevice воспринимает в качестве параметров три строки: имя устройства,
имя драйвера и имя порта.
B.Pascal 7 & Objects/OW - 267 -
------------------------------------------------------------------------
Часть 3. Продвинутое программирование с использование ObjectWindows
-----------------------------------------------------------------
Глава 16. Сообщения Windows
Приложения Windows управляются событиями. То есть вместо непосредственной
обработки оператор за оператором управление программой определяется внешними
событиями, такие как взаимодействия с пользователем и системное оповещение.
Приложения узнают о событиях, на которые они должны реагировать, получая
от Windows сообщения.
Данная глава охватывает ряд тем, связанных с передачей, получением и обработкой
сообщений, включая следующие вопросы:
* что такое сообщение?
* как выполняется диспетчеризация сообщений?
* обработка сообщений Windows;
* определение ваших собственных сообщений;
* передача и адресация сообщений;
* диапазоны сообщений.
Что такое сообщение?
Если вы не используете программирование, управляемое событиями, Windows
может выглядеть достаточно странной операционной средой. Возможно, вам
придется писать программы, которые основную часть своего времени просто
ждут ввода от пользователя (например, в операторе Readln).
Программирование, управляемое событиями, обходит эту ситуацию, возлагая
обработку ввода от пользователя на центральную подпрограмму, которую вам
даже не нужно вызывать. В этом случае Microsoft Windows сама взаимодействует
с пользователем и опрашивает список взаимодействий для каждого работающего
приложения. Эти информационные пакеты называются сообщениями и представляют
собой просто структуры записей типа TMsg:
type
TMsg = record hwnd: HWnd; message: Word; wParam: Word; lParam: Longint;
B.Pascal 7 & Objects/OW - 268 -
time: Longint;
pt: TPoint;
end;
Поля записи сообщения дают приложению информацию о том, какой вид событий
сгенерировал сообщение, где оно произошло и какое окно должно на него
реагировать.
Относительно сообщений следует иметь в виду, что ваше сообщение в общем
случае получает их после того, что произошло. Например, если вы изменяете
размер окна на экране, то объект окна получает сообщение wm_Size, когда
вы закончите изменение размера. Некоторые сообщения запрашивают выполнение
каких-то действий. Однако в большинстве случаев это уведомления о том,
что пользователь или система выполнили некоторые действия, на которые
следует реагировать вашей программе.
Именующие сообщения
Наиболее важным полем сообщений является поле message, которое содержит
одну из констант сообщений Windows, начинающихся с wm_. Каждое сообщение
Windows уникальным образом идентифицируется 16-битовым числом с соответствующим
мнемоническим идентификатором. Например, сообщение, являющееся результатом
нажатия клавиши, содержит в поле сообщения wm_KeyDown ($0100). На сообщения
обычно ссылаются по их мнемоническим именам.
Откуда поступают сообщения
Генерировать сообщения позволяют несколько различных событий:
* взаимодействия с пользователем, такие как нажатия клавиш,
щелчок кнопкой "мыши" или ее буксировка;
* вызовы функций Windows, которым нужно информировать об изменениях другие
окна;
* ваша программа явно посылает сообщение;
* другое приложение посылает сообщение через DDE (динамический обмен данными);
* сообщение генерируется самой Windows (например, сообщение
об останове системы).
Вашему приложению в общем случае не важно, как генерируются сообщения.
Основным в программировании, управляемом событиями, является генерация
сообщений и реакция на них. Поскольку Windows и ObjectWindows берут на
себя функции по доставке сообщений из одного места в другое, вы можете
сосредоточиться на генерации сообщений с соответствующими параметрами
и реакции на сообщения, а не на механизме их доставки из одного места
в другое.
B.Pascal 7 & Objects/OW - 269 -
Обычная диспетчеризация сообщений
Обычные приложения Windows (то есть не использующие ObjectWindows) имеют
цикл сообщения, в котором выполняется выборка и диспетчеризация сообщения.
По существу, в цикле сообщения вызывается связанная с окном функция, заданная
описателем окна в поле hwnd записи сообщения.
Каждое окно имеет функцию окна, заданную при его создании. Когда Windows
находит сообщение для конкретного окна, она передает сообщение функции
данного окна. Функция окна отсортировывает сообщения на основе типа сообщения,
а затем вызывает подпрограмму реакции на конкретное сообщение.
Обычный способ обработки сообщений в приложении и его окнах показывает
программа GENERIC.PAS. Вы можете видеть, что оконная функция каждого окна
для сортировки сообщение содержит большой оператор case. Все это может
выглядеть не так плохо, пока вы не осознаете тот факт, что в окне может
потребоваться обрабатывать более 100 различных сообщений Windows. После
этого идея написания и обслуживания такого оператора case будет выглядеть
менее впечатляющей.
Даже если у вас имеется несколько аналогичных окон, каждое из них будет,
очевидно, иметь свою оконную функцию с аналогичным большим оператором
case.
Способ, предлагаемый ObjectWindows
ObjectWindows вносит в это обычный способ диспетчеризации сообщений два
основных улучшения. Первое состоит в том, что цикл сообщений скрыт в ядре
вашего объекта приложения. Все, что вам нужно сделать - это привести свое
приложение в действие, вызвав метод Run объекта приложения. После этого
оно будет получать сообщения Windows.
Второе основное улучшение - это автоматическая диспетчеризация сообщений.
Вместо необходимости иметь для каждого окна оконную функцию, вы просто
определяете в оконных объектах методы, которые реагируют на конкретные
сообщения. Эти методы называются методами реакции на сообщения.
B.Pascal 7 & Objects/OW - 270 -
Динамические виртуальные методы
Ключем к автоматической диспетчеризации сообщений является расширение
описаний в объектах виртуальных методов, называемых динамическими виртуальными
методами. По существу вы связываете с методом целочисленный номер (такой
как константа сообщения). Ваша программа ObjectWindows (в данном случае
цикл сообщения объекта приложения) может затем вызвать этот метод на основе
номера сообщения.
Например, сообщение, генерируемое при нажатии в окне левой кнопки "мыши",
содержит в своем поле message wm_LButtonDown ($0201). Когда цикл сообщения
ObjectWindows считывает для одного из своих окон такое сообщение, то выполняется
поиск в таблице виртуальных методов данного оконного объекта и определяется
динамический метод, описанный для данного значения. Если такой метод найден,
то он вызывается, и ему в качестве параметра передается распакованная
запись сообщения типа TMessage. Если оконный объект не описывает метод
с данным индексом динамического метода, то цикл сообщения вызывает используемую
по умолчанию оконную процедуру.
Написание методов реакции на сообщение
Чтобы описать методы реакции на сообщение, нужно задать в оконном объекте
процедуру, названную по имени константы сообщения. Например, чтобы ответить
на сообщение wm_LButtonDown, вам нужно описать методы следующим образом:
type
TMyWindow = object(TWindow)
.
.
.
procedure WMLButtonDown(var Msg: TMessage);
virtual wm_First + wm_LButtonDown;
.
.
.
end;
На самом деле метод может называться как угодно, но наименование методов
по сообщениям, на которые они реагируют, делают программу понятней. Единственным
реальным ограничением является то, что этот метод должен быть процедурой
с единственным параметром типа TMessage.
Поскольку для методов реакции ObjectWindows использует несколько диапазонов,
а все индексы методы отсчитываются с 0, в качестве смещения используется
константа wm_First. Добавив для каждого метода это смещение, вы создадите
уникальный индекс динами-
B.Pascal 7 & Objects/OW - 271 -
ческого метода. Подробнее о диапазонах сообщений рассказывается в
разделе "Диапазоны сообщений" данной главы.
Что такое сообщение?
Теперь, когда вы знаете, как написать метод реакции на сообщение, можно
рассмотреть, какая информация содержится в сообщении. Запись 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 -
вариантная запись, так что вы можете обращаться к старшему и младшему
байту слов параметров.
Поля параметров
Поля параметров записи сообщения имеют для каждого сообщения свой смысл.
Однако, можно сделать некоторые обобщения.
WParam
Параметр WParam типа Word обычно содержит описатель, идентификатор (например,
идентификатор управляющего элемента) или булевское значение. Например,
параметр WParam сообщения wm_SetCursor содержит описатель окна, в котором
находится курсор. Уведомляющие сообщения управляющего элемента, такие
как
B.Pascal 7 & Objects/OW - 272 -
bn_Clicked, содержат в WParam идентификатор соответствующего управляющего
элемента. wm_Enable использует WParam для булевского значения, указывающего,
разрешено или запрещено соответствующее окно.
LParam
Параметр LParam типа Longint обычно содержит значение-указатель двух переменных
размером в слово, таких как координаты x и y. Например, параметр LParam
сообщения wm_SetText указывает на строку с завершающим нулем, содержащую
устанавливаемый текст. Сообщения "мыши", такие как wm_LButtonDown,
используют LParam для записи координат события "мыши". Благодаря
вариантным частям записи сообщения, LParamLo содержит x-координату, а
LParamHi - y-координату.
Поле Result
Поле Result сообщения TMessage управляет возвращаемым значением сообщения.
Иногда программа, посылающая сообщение, ожидает возврата конкретного значения,
такого как булевское значение, указывающее успешное или неуспешное выполнение
или код ошибки. Вы можете задать возвращаемое значение, присвоив значение
полю Result.
Например, когда пользователь пытается восстановить окно из состояния пиктограммы,
ему посылается сообщение wm_QueryOpen. По умолчанию wm_QueryOpen возвращает
булевское значение True (не ноль). Если вы хотите иметь окно, которое
всегда выводится в виде пиктограммы, то вы можете ответить на сообщение
wm_QueryOpen и установить Result в 0. Это означает, что окно не может
быть открыто:
procedure TIconWindow.WMQueryOpen(var Msg: TMessage);
begin
Msg.Result := 0; end;
Объектно-ориентированная обработка сообщения
Одним из огромных преимуществ обработки сообщения в ObjectWindows (кроме
того, что можно избавиться от больших операторов case) является обработка
сообщений объектно-ориентированным способом. То есть, ваши оконные объекты
наследуют возможность определенной реакции на сообщения, и вам нужно только
изменить реакцию на конкретные сообщения, которые ваш объект обрабатывает
по-другому.
Отмена поведения по умолчанию
B.Pascal 7 & Objects/OW - 273 -
Иногда, когда вы переопределяете используемую по умолчанию реакцию на
сообщение, это делается потому что данное поведение просто нежелательно.
Простейшим случаем является ситуация, когда объект должен игнорировать
сообщение, а не отвечать на него. Для этого вы можете просто написать
пустой метод реакции на сообщение. Например, следующий метод сообщает
управляющему элементу редактирования, что нужно игнорировать передаваемые
в сообщении wm_Char символы:
procedure TNonEdit.WMChar(var Msg: TMessage);
begin
end;
Замена поведения по умолчанию
Более полезным подходом, чем простое игнорирование сообщения, является
замена поведения по умолчанию чем-то совершенно другим. Например, следующий
метод сообщает управляющему элементу редактирования, что вместо вставки
символа при нажатии любой клавиши нужно давать звуковой сигнал:
procedure TBeepEdit.WMChar(var Msg: TMessage);
begin
MessageBeep(0); end;
Звуковой сигнал по нажатию клавиш сам по себе не особенно полезен, но
дает хорошую иллюстрацию. В общем случае вы можете заменить используемое
по умолчанию поведение некоторым другим. Определяемая вами реакция будет
единственной.
Дополнение поведения по умолчанию
Иногда может возникнуть потребность в комбинировании некоторых действий
с используемой по умолчанию реакцией на сообщение. ObjectWindows предоставляет
для этого объектно-ориентированный способ. Обычно, когда вы хотите, чтобы
объект выполнял некоторые действия на основе используемого по умолчанию
поведения, вы можете встроить в свой переопределенный метод наследуемый
метод. ObjectWindows позволяет вам делать это также для реакции на сообщения.
Вызов наследуемых методов
Предположим, например, что вы создали новый оконный объект и хотите, чтобы
он в дополнение к другим обычно выполняемым действиям он давал звуковой
сигнал при щелчке в окне левой кнопкой "мыши". Все, что вам
нужно сделать - это вызов в вашем новом методе наследуемого метода TWindow.WMLButtonDown:
B.Pascal 7 & Objects/OW - 274 -
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 вы можете рассматривать как используемый
B.Pascal 7 & Objects/OW - 275 -
вместо всех не определенных конкретно наследуемых методов реакции
на сообщения. Отметим, однако, что это применяется только к сообщениям
Windows. Командные сообщения, уведомляющие сообщения и управляющие сообщения
имеют свои собственные используемые по умолчанию обработчики сообщений.
Эти сообщения и их используемые по умолчанию процедуры описываются в следующем
разделе.
Командные, уведомляющие и управляющие идентификаторы
Сообщение - это просто запись данных, идентифицируемая конкретным значением
в поле message, а ObjectWindows экономит вам массу времени и усилий путем
сортировки этих сообщений и диспетчеризации их методам реакции на сообщения
в ваших объектах. Однако, сообщение Windows wm_Command имеет много вариантов,
которые нужно отсортировывать с помощью большого оператора case. ObjectWindows
также выполняет это для вас.
Например, сообщение wm_Command посылается при выборе элемента меню или
нажатии оперативной клавиши. В обычном приложении сообщение Windows передавалось
бы вашей функции окна, где в большом операторе case нужно было бы отсортировать
различные сообщения, вызывая другие подпрограммы при обнаружении wm_Command.
Эта подпрограмма должна в свою очередь определить, какая была передана
команда (с помощью другого большого оператора case).
Командные сообщения
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
реакция на команду
B.Pascal 7 & Objects/OW - 276 -
end;
Аналогично wm_First, cm_First - это константа ObjectWindows, определяющая
начало диапазона сообщений. Ваши командные константы должны лежать в диапазоне
0..24319.
Обработка команд по умолчанию
Чтобы вызвать используемую по умолчанию реакцию на команду, для нее обычно
вызывается наследуемый метод реакции. Если в объекте-предке не определяется
метод реакции на конкретную команду, по умолчанию обработка выполняется
с помощью DefCommandProc. DefCommandProc работает во многом аналогично
методу DefWndProc для сообщений Windows, но обрабатывает команды.
Уведомляющие сообщения
Команды не обязательно должны поступать от меню или командных клавиш.
Управляющие элементы в окнах посылают сообщения своим порождающим окнам,
когда вы щелкаете на них "мышью" или что-либо набираете. Эти
сообщения называются уведомляющими сообщениями, и ObjectWindows обрабатывает
их двумя различными путями.
В основном уведомления могут передаваться объекту управляющего элемента
или его порождающему окну. Если управляющий элемент имеет связанный с
ним объект ObjectWindows, ObjectWindows дает объекту возможность сначала
ответить на команду. Это называется уведомлением управляющего элемента.
Если управляющий элемент не имеет соответствующего объекта, или объект
управляющего элемента не определяет реакцию на команду, то ответить имеет
возможность порождающее окно. Это называется уведомлением порождающего
объекта.
Уведомления управляющих элементов
Обычно управляющим элементам не требуется в ответ на действия пользователя
делать ничего особенного; ожидаемым поведением является поведение, используемое
по умолчанию. Но если вы хотите, чтобы управляющий элемент делал что-то
дополнительно или что-то другое, то уведомляющие сообщения позволяют вам
осуществить это.
Предположим, например, что вы хотите, чтобы при каждом щелчке кнопкой
"мыши" раздавался звуковой сигнал. Вы можете просто задать для
объекта кнопки метод реакции на уведомление:
type
TBeepButton = object(TButton)
procedure BNClicked(var Msg: TMessage);
virtual nf_First + bn_Clicked;
end;
B.Pascal 7 & Objects/OW - 277 -
procedure TBeepButton.BNClicked(var Msg: TMessage);
begin
MessageBeep(0); end;
ObjectWindows определяет в качестве смещения, задающего диапазон используемых
в уведомлениях управляющих элементов сообщений, константу nf_First.
Уведомление порождающего объекта
Если управляющий элемент не имеет связанного с ним интерфейсного объекта,
или управляющий объект не определяет реакции на конкретную команду, уведомляющие
сообщения посылаются вместо объекта управляющего элемента порождающему
окну управляющего элемента. Так как порождающее окно должно знать, какой
из его управляющих элементов вызвал уведомление, уведомляющее сообщение
порождающему окну основывается на идентификаторе управляющего элемента.
Например, чтобы ответить на уведомление о взаимодействиях с управляющим
элементом с идентификатором 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 уведомляющее сообщение
порождающему объекту.
B.Pascal 7 & Objects/OW - 278 -
Уведомления управляющих элементов и порождающих объектов
-----------------------------------------------------------------
Возможно, иногда вам потребуется, чтобы одновременно на некоторое взаимодействие
пользователя с управляющим элементом реагировали и управляющий элемент,
и порождающее окно. ObjectWindows также обеспечивает способ реализовать
это. Поскольку уведомление порождающего объекта предусматривает реакцию
по умолчанию, когда объект управляющего элемента не определяет реакции
на уведомление, все, что нужно сделать - это вызов реакции по умолчанию
в дополнение реакции управляющего элемента.
Например, с учетом приведенного выше объекта TBeepButton вы можете также
уведомлять порождающее окно с помощью добавления вызова DefNotificationProc:
procedure TBeepButton.BNClicked(var Msg: TMessage);
begin
MessageBeep(0);
DefNotificationProc(Msg); end;
Вызов DefNotificationProc обеспечивает получение уведомляющего сообщения
на основе идентификатора порождающего окна управляющего элемента, как
если бы управляющий элемент вовсе не определял реакции.
Определение ваших собственных сообщений
Windows резервирует для собственного использования 1024 сообщения. В этот
диапазон попадают все стандартные сообщения. Начало диапазона сообщений
определяется константой wm_User. Чтобы определить сообщение, используемое
окнами вашей программы, определите идентификатор сообщения, попадающий
в диапазон wm_User..wm_User+31744.
Чтобы избежать конфликта со стандартными сообщениями, вам следует определить
идентификаторы своих сообщений как константы на основе wn_User. Например,
в приложении, где требуется три определенных пользователем сообщения,
вы можете описать их следующим образом:
const
wm_MyFirstMessage = wm_User;
wm_MySecondMessage = wm_User + 1;
wm_MyThirdMessage = wm_User + 2;
Реакция на ваши сообщения аналогично реакции на любое другое сообщение:
TCustomWindow = object(TWindow)
B.Pascal 7 & Objects/OW - 279 -
.
.
.
procedure WMMyFirstMessage(var Msg: TMessage);
virtual wm_First + wm_MyFirstMessage
Согласно общему правилу, определенные пользователем сообщения следует
использовать для внутреннего обмена сообщения. Если вы посылаете определенное
пользователем сообщение другому приложению, нужно убедиться, что это другое
приложение определяет сообщение также, как это делается в коде вашего
приложения. Внешние коммуникации лучше обрабатываются с помощью динамического
обмена данными (DDE).
Передача сообщений
До сих пор мы определяли реакцию на сообщения, но не говорили пока о том,
как генерировать сообщения. Большинство сообщений Windows генерируются
самой Windows в ответ на действия пользователя или системные события.
Но ваша программа можете генерировать сообщения либо моделируя действия
пользователя, либо манипулирую элементами на экране.
ObjectWindows уже обеспечивает для вас способы передачи многих сообщений,
которые в противном случае пришлось бы передавать вручную. Например, общим
случаем для генерации сообщений является работа с управляющим элементами.
Чтобы добавить строку в блок списка, Windows определяет такие сообщения
как lb_AddString, а чтобы отменить выбор кнопки с зависимой фиксацией
или выбрать ее
- bm_SetCheck. ObjectWindows определяет методы для объектов управляющих
элементов (TListBox.AddString и TCheckBox.SetCheck), посылающие для вас
эти сообщения, так что вам даже не нужно думать об их использовании.
Возможно вы найдете, что для большинства функций (которые в противном
случае пришлось бы выполнять передачей сообщений) уже существуют объектные
методы.
Передача и отправление сообщений
Тем не менее, иногда возникает необходимость передачи сообщений в окна
вашего приложения. Windows предусматривает две функции, позволяющие вам
генерировать сообщения - SendMessage и PostMessage. Основное отличие этих
двух функций состоит в том, что SendMessage ожидает обработки сообщения
перед возвратом. PostMessage просто добавляет сообщение в очередь сообщений
и возвращает управление.
Имейте в виду, что SendMessage, особенно при вызове в методе реакции на
сообщение, может вызвать бесконечный цикл или клинч, приводящие к сбою
программ. Вам следует не только избегать оче-
B.Pascal 7 & Objects/OW - 280 -
видных циклов, таких как методы реакции на сообщения, генерирующих то
же сообщение, которое его вызывает, но избегать также передачи сообщений
с побочными эффектами. Например, метод Paint объекта, который вызывается
в ответ на сообщения wm_Paint, очевидно должен явным образом посылать
самому себе другое сообщение wm_Paint. Но он должен также избегать других
действий, дающих в результате другое сообщение wm_Paint, посылаемое, пока
метод еще активен, таких как изменение размеров окна, запрещение окна
или создание/уничтожение перекрывающихся окон.
Передача сообщения
Для передачи сообщения требуется следующее: описатель окна-получателя,
номер сообщения и параметры Word и Longint. В приложении ObjectWindows
описателем получателя является обычно поле HWindow интерфейсного объекта.
Идентификатор сообщения - это просто константа, идентифицирующая конкретное
сообщение, которое вы хотите передать (такая как wm_More или em_SetTabStops).
Параметры в зависимости от сообщения могут быть различными.
Значение, возвращаемое SendMessage - это значение поля Result в записи
сообщения при завершении обработки. Имейте в виду, что если вы вызываете
наследуемый или используемый по умолчанию метод реакции на сообщение,
ваше значение может быть перезаписано.
Windows с помощью SendMessage обеспечивает ограниченное средство циркулярной
рассылки сообщений. Если вы в качестве описателя окна, в которое нужно
передать сообщение, зададите $FFFF, Windows посылает сообщения всем всплывающим
и перекрывающимся окнам в системе (не только в вашем приложении). Таким
образом, вы не должны использовать циркулярную рассылку сообщений, определяемых
пользователем, так как не может быть уверены, что другое приложение не
определило данное сообщение каким-то другим способом.
Отправление сообщения
Отправление сообщения полезно использовать, если вас не особенно волнует,
когда должна последовать реакция на сообщение, или вам нужно избежать
циклов, которые может вызвать SendMessage. Если все, что вам нужно сделать
- это убедиться в передаче сообщения, и вы можете продолжать работу, не
ожидая реакции, то можете вызывать функцию PostMessage. Ее параметры идентичны
параметрам функции SendMessage. PostMessage не следует использовать для
передачи сообщений управляющим элементам.
Передача сообщения управляющему элементу
Возможно, единственным случаем, когда вам потребуется пере-
B.Pascal 7 & Objects/OW - 281 -
дать сообщение элементу на экране, не имеющему соответствующего
объекта, является случай, когда вам нужно взаимодействовать с управляющим
элементом в диалоговом окне. Проще всего для этого использовать объекты
(управляющего элемента, диалогового блока или обоих).
Если управляющий элемент имеет связанный с ним объект, вы можете просто
использовать для получения идентификатора управляющего элемента поле HWindow
объекта. Если диалоговый блок имеет связанный с ним объект, вы можете
вызвать метод SendDlgItemMsg объекта диалогового блока, для которого задаются
идентификатор управляющего элемента, идентификатор сообщения и два параметра
сообщения. В большинстве приложений ObjectWindows для вас доступен любой
их этих подходов.
Если по каким-то причинам у вас нет ни объекта диалогового блока, ни доступного
объекта управляющего элемента, вы можете послать сообщение управляющему
элементу в диалоговом окне с помощью функции API Windows SendDlgItemMessage,
которая воспринимает в качестве параметров описатель диалогового блока,
идентификатор управляющего элемента, идентификатор сообщения и два параметра
сообщения.
B.Pascal 7 & Objects/OW - 282 -
Диапазоны сообщений
Сообщение определяется полем message записи сообщения (16-битовое значение).
Windows резервирует для своих собственных стандартных сообщений 0..$03FF,
а остальные сообщения до $7FFF резервируются для сообщений, определенных
пользователем. Диапазоны остальных сообщений в ObjectWindows подразделяются
на диапазоны команд и уведомлений (как показано в следующей таблице):
Диапазоны сообщений Таблица 16.1
Диапазон Значения
Сообщения, зарезервированные для Windows. $0000-$7FFF
Сообщения, определяемые пользователем. $0400-$8FFF
Уведомляющие сообщения управляющих элементов. $8000-$8FFF
Зарезервированные в Windows уведомляющие сообщения управляющих элементов.
$8F00-$8FFF
Уведомляющие сообщения порождающего объекта. $9000-$9FFF
Зарезервированные в Windows уведомляющие сообщения порождающего объекта.
$9F00-$9FFF
Командные сообщения. $A000 $FFFF
Команды, зарезервированные в ObjectWindows. $FF00-$FFFF
B.Pascal 7 & Objects/OW - 283 -
--------------------------
cm_Internal ($FF00) --------------------------
| |
| |
| Команды |
| |
| |
cm_First ($A000) --------------------------
nf_Internal ($9F00) --------------------------
| |
| Уведомления |
| управляющих элементов |
| |
nf_First ($9000) --------------------------
id_Internal ($8F00) --------------------------
| |
| Уведомления |
| порождающих объектов |
| |
id_First ($8000) -------------------------- -
| | |
| | |
| Сообщения, | |
| определенные | - wm_Count
| пользователем | | ($8000)
| | |
| | |
wm_User ($A000) -------------------------- -
| Сообщения Windows |
wm_First($0000) --------------------------
Рис. 16.1 Диапазоны сообщений и команд.
B.Pascal 7 & Objects/OW - 284 -
Глава 17. Интерфейс с графическими устройствами
Многим типам приложений Windows для организации полного интерфейса пользователя
нужны только окна, блоки диалога и управляющие элементы. Но некоторым
приложениям (например, программами рисования и манипулирования изображениями)
требуется наличие графических средств для заполнения окон. Эта графика
может быть в форме линий, форм, текста и побитовых образов.
Для предоставления приложениям графических функциональных возможностей
Windows имеет набор функций, называемый интерфейсом с графическим устройством
- GDI. GDI можно представить себе как графическую машину, которую используют
приложения Windows для отображения и манипулирования графикой. Функции
GDI предоставляют вашему приложению возможности рисования, которые не
зависят от используемого дисплея. Например, вы можете использовать одни
и те же функции для организации вывода на дисплей EGA, на дисплей VGA
и даже на принтер PostScript.
Аппаратная независимость реализуется через использование драйверов устройств,
которые переводят функции GDI в команды, воспринимаемые используемым устройством
вывода. Это означает, что вам не нужно беспокоиться о том, как конкретное
устройство работает с графическим образом. Вы сообщаете драйверу, что
нужно что-то сделать, и он работает с устройством как нужно.
Запись на устройство вывода
В отличие от традиционных графических программ DOS программы Windows никогда
не выводят элементы изображения непосредственно на экран или на принтер,
а записывают их в логическую сущность, называемую контекстом дисплея.
Контекст дисплея - это виртуальная поверхность с присущими ей атрибутами,
такими как перо, кисть, шрифт, цвет фона, цвет текста и текущая позиция.
Для вашего приложения, независимо от того, какое это на самом деле устройство,
все контексты устройства выглядят аналогично.
Когда вы вызываете функции GDI для рисования в контексте устройства, связанный
с этим контекстом драйвер устройства переводит действия по рисованию в
соответствующие команды. Эти команды воспроизводят насколько возможно
точно действия рисования на дисплее, независимо от возможностей самого
дисплея. Дисплей может быть монохромным экраном низкого разрешения или
экраном с четырьмя миллионами цветовых оттенков.
Контекст дисплея можно представить себе как холст для рисования. Окно
это - картинка, включающая рамку. Вместо рисования на картине в рамке
вы рисуете на холсте, а уже затем устанавливаете его в рамку. Аналогично
этому, вы рисуете в контексте дисплея окна. Контекст дисплея обладает
рядом инструментов рисования, например, ручки, кисти и шрифты. Контекст
дисплея - это управляемый
B.Pascal 7 & Objects/OW - 285 -
Windows элемент, похожий на элемент окна с тем исключением, что контекст
дисплея не имеет соответствующего элемента ObjectWindows.
Нужно помнить о том, что контекст устройства представляет собой только
часть устройства, на котором вы реально рисуете. Хотя вы можете рассматривать
вывод в терминах всего окна (рамки, меню, области клиента) или печатаемой
страницы, контекст устройства охватывает только ту часть, где вы рисуете
- область клиента окна или печатаемую часть страницы.
Контекст устройства - это элемент, управляемый Windows (аналогично оконному
элементу, только контекст устройства не имеет соответствующего объекта
ObjectWindows).
Чем отличаются контексты устройства?
Для областей клиента окна Windows обеспечивают специальные контексты устройства,
называемые контекстами дисплея. Вместо представления самого устройства,
такого как экран или принтер, контекст дисплея позволяет вам интерпретировать
область клиента окна как целое устройство.
На практике контекст дисплея вам не требуется интерпретировать как-то
иначе, чем другой контекст устройства. Он позволяет вам работать так,
как если бы ваше окно было целым устройством, так что вам не нужно беспокоиться
о смещении позиции на экране и т.д.
Управление контекстом дисплея
Для организации рисования в контексте дисплея сначала нужно получить контекст
дисплея для нужного окна. Требования к памяти со стороны контекста дисплея
очень велики, поэтому можно одновременно организовать доступ только к
пяти контекстам дисплея в каждом сеансе Windows. Это значит, что каждое
окно не может поддерживать свой собственный контекст дисплея. Оно получает
его только в случае необходимости и освобождает при первой возможности.
Это может несколько обескуражить вас, но вы не можете управлять атрибутами
контекста дисплея. Другое окно и даже другое приложение может сменить
атрибуты контекста дисплея. Кроме того, средства рисования не являются
частью памяти контекста дисплея. Они могут быть выбраны в контекст дисплея
каждый раз при его получении.
B.Pascal 7 & Objects/OW - 286 -
Работа с контекстом дисплея
Обычно нужно определять поле оконного объекта для записи описателя текущего
контекста дисплея, аналогично тому, как в 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.
В итоге это приведет к сбою системы при работе программы, так как она
исчерпает все доступные контексты дисплея.
Что содержится в контексте устройства?
Контекст устройства содержит набор изобразительных средств, таких как
перья, кисти и шрифты. Реально эти инструментальные средства не существуют.
Они просто представляют собой удобный способ предположений об определенной
группе изобразительных характеристик.
Перо, например, это просто удобный способ интерпретации характеристик
линий на устройстве. Графическая линия между двумя точками имеет три характеристики:
ширину, цвет и стиль (например, пунктирная линия и линия из точек), которые
совместно можно рассматривать как "перо". Эти логические группы
изобразительных характеристик значительно упрощают графику в Windows.
Хотя обычно вам не нужно будет изменять большинство атрибу-
B.Pascal 7 & Objects/OW - 287 -
тов контекста дисплея, важно по крайней мере знать, что в нем содержится.
Данный раздел кратко описывает некоторые элементы контекста дисплея, включая
побитовые отображения, цвета, области и изобразительные средства. Некоторые
из этих тем также рассматриваются более подробно в некоторых других разделах
данной главы.
Побитовая графика
Действительная поверхность контекста дисплея называется побитовым отображением
(битовым массивом). Побитовые отображения представляют конфигурацию памяти
конкретного устройства. Следовательно, они зависят от вида адресуемого
устройства. Это создает проблему, поскольку охраненные для одного устройства
побитовые отображения будут несовместимы с другим устройством. GDI имеет
ряд средств для разрешения этой проблемы, включая аппаратно-независимые
побитовые отображения. Имеются следующие функции GDI, которые создают
побитовые отображения: CreateCompatibleDC, CreateCompatibleBitmap и CreateDIBitmap.
Имеются следующие функции GDI по манипулированию побитовыми отображениями:
BitBlt, StretchBlt, StretchDIBits и SetDIBitsToDevice.
Изобразительные средства
Чтобы выполнить большую часть фактического изображения, контекст дисплея
использует три инструментальных средства: перо, кисть и шрифт.
Перья
Перо используется для изображения линий, дуг и ломаных линий, представляющих
собой множественные линейные сегменты. Атрибуты пера включают в себя его
цвет, ширину и стиль (например, пунктирная линия или линия из точек).
Кисти
Кисть используется при закраске замкнутых фигур, таких как прямоугольники,
прямоугольники с округлыми краями и многоугольники. Функции, рисующие
непрерывные формы используют перо для вычерчивания границ, а кисть - для
закраски внутренней области. Существуют четыре типа кистей - непрерывные,
с засечками, с образцом побитового отображения и с независимым от устройств
образцом побитового отображения.
Шрифты
Инструментальное средство шрифта используется при изображения в контексте
дисплея текста. Оно задаете высоту шрифта, его ширину, семейство и имя
гарнитуры.
Все три инструментальных средства используют для заполнения
B.Pascal 7 & Objects/OW - 288 -
пробелов атрибут фонового цвета контекста дисплея. Изобразительные инструментальные
средства подробнее освещаются в разделе "Изобразительные средства"
данной главы.
B.Pascal 7 & Objects/OW - 289 -
Цвет
Цвет, который устройство использует для рисования, хранится в цветовой
палитре. Если вы желаете добавить цвет, которого нет в цветовой палитре,
то его можно добавить. Более часто вы будете настраивать драйвер устройства
на аппроксимацию нужного цвета путем смешивания цветов палитры. Работа
с цветовой палитрой более подробно рассматривается в разделе данной главы
"Использование цветовой палитры".
Режимы отображения
Очень трудно выбрать устройство рисования, когда заранее неизвестно, какое
устройство будет использоваться для отображения. Большинство приложений
игнорируют эту проблему и предполагают, что вполне удовлетворительно будет
работать единица рисования по умолчанию (один элемент изображения). Однако,
некоторые приложения требуют, чтобы отображение точно воспроизводило размеры
нужного образа. Для таких приложений GDI допускает различные режимы отображения,
некоторые из которых не зависят от аппаратуры. Каждый из методов распределения
имеет свою единицу размерности и систему координатной ориентации. Режим
распределения по умолчанию устанавливает начало координат в левом верхнем
углу контекста дисплея с положительным направлением оси X вправо и положительным
направлением оси Y вниз. Каждый контекст дисплея имеет атрибуты распределения
для интерпретации задаваемых вами координат.
Иногда нужно транслировать логические координаты, используемые вами для
рисования, в физические координаты побитового отображения. Для большинства
приложений начало координат для экрана - это его левый верхний угол, но
для окна началом координат будет левый верхний угол области клиента. Некоторые
окна прокручивают свою поверхность клиента так, что начало координат не
будет даже находиться в области клиента. Некоторые функции GDI работают
только в конкретной системе координат, поэтому преобразование координат
просто необходимо. В GDI имеется ряд функций для подобного пересчета координат:
ScreenToClient, ClientToScreen, DPToLP и LPToDP.
Обрезание областей
Для предотвращения рисования вне заданной области каждый контекст дисплея
имеет атрибут области вырезанного изображения. Область вырезанного изображения
может быть сложным многоугольником или эллипсом, внутри которого и может
происходить действительное рисование на виртуальной поверхности контекста
дисплея. Для большинства приложений выделяемая по умолчанию область вырезанного
изображения будет вполне достаточной. Изменять эту область придется только
для приложений, которые воспроизводят некоторые специальные визуальные
эффекты.
B.Pascal 7 & Objects/OW - 290 -
Инструментальные средства рисования
Контекст дисплея управляет отображением графики на экране. Для иного способа
отображения графики можно изменить инструментальные средства, с помощью
которых создается изображение. Атрибуты инструментальных средств задают
проявление изображений с помощью функций GDI, например, LineTo, Rectange
и TextOut. Перья задают внешний вид линий, кисти задают внешний вид закрашенных
областей и шрифт задает внешний вид изображаемого текста.
Для задания атрибутов инструмента программа Windows выбирает логический
инструмент в контекст дисплея. Логический инструмент создается вашей программой
путем заполнения полей определенной записи, TLogPen, TLogBrush или TLogFont.
Текущий инструмент - это инструмент, определенный в Windows, представляющий
самый общие варианты атрибута, например, непрерывное черное перо, серая
кисть или системный шрифт.
Основные инструментальные средства
Основные инструментальные средства создаются функцией GDI GetStockObject.
Например:
var
TheBrush: HBrush begin
TheBrush:=GetStockObject(LtGray_Brush);
.
.
.
end;
где LtGray_Brush - это целая константа, определенная в модуле
WinTypes в ObjectWindows. Приведем список всех имеющихся констант основного
инструментального средства:
Основные инструменты рисования Таблица 17.1
Кисти Перья Шрифты
White_BrushLtGray_BrushGray_BrushDkGray_BrushBlack_BrushNull_BrushHoolow_Brush
White_PenBlack_PenNull_Pen OEM_Fixed_FontANSI_Fixed_FontANSI_Var_FontSystem_FontDevice_Default_FontSystem_Fixed_Font
В отличие от логических инструментальных средств основные
B.Pascal 7 & Objects/OW - 291 -
инструментальные средства не удаляются после использования.
Логические инструментальные средства
Записи логических инструментов, TLogPen, TLogBrush и TLogFont, содержат
поля для хранения каждого атрибута инструмента. Например, TLogPen.lopnColor
содержит значение цвета ручки. Каждый тип записи определяет свой собственный
набор атрибутов, соответствующий типу инструмента.
Логические перья
Вы можете создавать логические перья с помощью функций 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, содержит константу, задающую стиль линии.
B.Pascal 7 & Objects/OW - 292 -
----------------------------------------------------------------- Константа
Результат
PSPSPSPSPSPS SOLIDDASHDOTDASHDOTDASHDOTDOTNULL --------------------------------------------------------------------------......................................-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-..-..-..-..-..-..-..-..-..-..-..-..-
Рис.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) возвратит красный
и т.д.
B.Pascal 7 & Objects/OW - 293 -
Логические кисти
Вы можете создавать логические кисти с помощью функций 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 содержит
описатель побитового отображения.
B.Pascal 7 & Objects/OW - 294 -
----------------------------------------------------------------- Константа
Результат
HS BDIAGONAL //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
HS CROSS ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
HSHS DIAGCROSSFDIAGONAL xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
HS HORIZONTAL ----------------------------------------------------------------------------
HS VERTICAL ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Рис. 17.2 Стили штриховки для кисти.
Логические шрифты
Вы можете создавать логические шрифты с помощью функций 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;
B.Pascal 7 & Objects/OW - 295 -
lfQuality: Byte;
lfPitchAndFamily: Byte;
lfFaceName: array[0..lf_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
B.Pascal 7 & Objects/OW - 296 -
Константы шага Константы семейства
Default_PitchFixed_PitchVariable_Pitch ffffffffffff ModernRomanScriptSwissDecorativeDontCare
И, наконец, 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);
B.Pascal 7 & Objects/OW - 297 -
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;
B.Pascal 7 & Objects/OW - 298 -
Использование изобразительных инструментальных средств
Контекст дисплея позволяет вам рисовать в окне и, кроме этого, он содержит
инструментальные средства рисования: перья, кисти, шрифты и палитры, которые
вы используете для рисования текста и изображений. При рисовании линии
в контексте дисплея линия выводится со следующими атрибутами текущего
пера: цвет, стиль (непрерывная, пунктирная и т.п.) и толщина. При закрашивании
области она выводится со следующими атрибутами текущей кисти: образец
и цвет. При рисовании текста в контексте дисплея он выведется с атрибутами
текущего шрифта: шрифт (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. Ни в коем случае не удаляйте
B.Pascal 7 & Objects/OW - 299 -
инструментальные средства рисования, которые выбраны в данный момент в
контекст дисплея!
Контекст дисплея может хранить только по одному инструменту рисования
каждого типа в данный момент времени, поэтому нужно отслеживать доступные
инструментальные средства отображения. Очень важно удалить их все до завершения
работы вашего приложения. Один из методов, (использован в примере Главы
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
B.Pascal 7 & Objects/OW - 300 -
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 посылает
сообщение wm_Paint соответствующему приложению. Это сообщение автоматически
вызывает метод Paint вашего окна. Один из параметров Paint, PaintDC, представляет
собой контекст дисплея, который используется для рисования.
Метод TWindow Paint ничего не рисует, поскольку объекты TWindow не имеют
графики для рисования. В типе вашего окна определим метод Paint, который
будет вызывать методы и функции, изображающие в окне текст и графику.
Единственное, что вы можете сделать с контекстом дисплея, это выбрать
в него новый инструмент рисования, например, перья других цветов или кисти
других образцов. Вам придется указать эти
B.Pascal 7 & Objects/OW - 301 -
инструменты в контексте дисплея рисования вашего метода Paint.
После завершения работы метода Paint контекст дисплея рисования автоматически
освобождается.
Стратегия графики
Метод Paint отвечает за рисование текущего содержимого окна в любой момент
времени, включая первое появление этого окна. Следовательно, метод Paint
должен уметь рисовать все "постоянные" изображения окна. Кроме
того, он должен уметь восстанавливать любые изображения, добавленные в
окно после его первого появления. Для воспроизведения этой "динамической"
графики метод Paint должен иметь доступ к инструкциям или данным, с помощью
которых было создано изображение.
Вы можете выбрать один из двух возможных вариантов. Первый подход состоит
в выделении нескольких графических методов и при динамическом рисовании
вызывать их из метода Paint. Другой подход, показанный в примере Главы
3, состоит в хранении данных, относящихся к графическому контексту окна,
в полях объекта этого окна. Эти данные могут включать, например, координаты,
формулы и побитовые распределения. Затем метод Paint повторно вызывает
графические функции, которые нужны для преобразования этих данных в изображения.
Используя эти стратегии и способность объекта хранить свои собственные
данные и функции вы можете разрабатывать очень развитые и впечатляющие
графические приложения.
Рисование в окнах
Для рисования любого текста или изображения в объекте окна сначала нужно
получить контекст дисплея. После рисования контекст дисплея нужно освободить.
(В одном сеансе Windows доступны только пять элементов контекста дисплея.)
Вы можете использовать описатель контекста дисплея в качестве аргумента
любой графической функции Windows.
Вызов графических функций окна
Одно из правил GDI состоит в том, что для работы функций необходимо в
качестве аргумента задавать контекст дисплея. Обычно вы будете вызывать
эти функции из методов типа окна. Например, TextOut - это функция рисования
текста на контексте дисплея в заданном месте:
TheDC := GetDC(HWindow);
TextOut(TheDC, 50, 50, 'Sample Text', 11);
ReleaseDC(HWindow, TheDC);
B.Pascal 7 & Objects/OW - 302 -
Графические функции GDI
Данный раздел описывает различные вызовы API, которые вы можете использовать
для рисования изображений в окне.
Функции изображения текста
Функция рисования текста использует для рисования заданный текущий шрифт
контекста дисплея. Функция TextOut рисует текст в заданной точке. TextOut
выравнивает текст в зависимости от текущих значений флагов форматирования
текста. По умолчанию происходит выравнивание слева. Текущий метод выравнивания
можно посмотреть с помощью функции GetTextAlign и установить с помощью
функции SetTextAlign.
Функция TextOut - это самая часто используемая функция рисования текста.
Используя установленные по умолчанию флаги форматирования текста, данный
метод Paint рисует выравненный слева массив символов, левый верхний угол
которого имеет координаты (10,15).
procedure TMyWindow.Paint(PaintDC: HDC;
var PaintINfo: TPaintStruct);
var
MyTextString: array[0..20] of Char; begin
StrCopy(MyTextString, 'Hello, World');
TextOut(PaintDC, 10, 15, MyTextString,
StrLen(MyTextString)); end;
---------------------------------
|
| (10, 15)
| * Hello Word
|
|
Рис. 17.3 Результат выполнения функции TextOut.
B.Pascal 7 & Objects/OW - 303 -
Функции рисования линий
Функции рисования линии используют для рисования заданное текущее перо
контекста дисплея. Большинство линий рисуется с использованием функций
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[0..2] of TPoint; begin
Points[0].X:=10;
Points[0].Y:=15;
Points[1].X:=10;
B.Pascal 7 & Objects/OW - 304 -
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;
B.Pascal 7 & Objects/OW - 305 -
Изображение фигур
Функции изображения фигур используют текущее перо заданного контекста
дисплея для изображения периметра и текущую кисть для закраски внутренней
области. На текущую позицию они не влияют.
Функция 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).
B.Pascal 7 & Objects/OW - 306 -
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[0..2] 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.
B.Pascal 7 & Objects/OW - 307 -
Использование палитр
Некоторые типы дисплейных устройств компьютера могут выводить множество
цветов, но только ограниченное их число в каждый момент времени. Системная
или физическая палитра - это группа или набор цветов, которые в данный
момент доступны дисплею для одновременного отображения. Windows дает вашему
приложению частичное управление цветами, входящими в системную палитру
устройства. Если ваше приложение использует только простые цвета, то вам
нет необходимости непосредственно использовать палитру.
Однако, изменение палитры системы воздействует на все изображение, имеющееся
на экране, включая другие приложения. Одно приложение может вызвать вывод
всех других приложений в некорректных цветах. Администратор палитры Windows
разрешает эту проблему, согласовывая изменения системной палитры с приложениями.
Windows предоставляет каждому приложению свою логическую палитру, которая
представляет собой группу цветов, используемых приложением. Администратор
палитры связывает запрошенные логической палитрой цвета с имеющимися цветами
системной палитры. Если запрошенный цвет отсутствует в системной палитре,
администратор палитры может добавить его. Если в логической палитре задано
больше цветов, чем может содержаться в системной палитре, то для дополнительных
цветов подбирается максимально похожий цвет системной палитры.
Когда приложение становится активным, имеется возможность заполнить системную
палитру цветами из логической палитры. Это действие может повлиять на
распределение цветов, заданных логическими палитрами других приложений.
В любом случае Windows резервирует 20 цветов в системной палитре для общего
представления цветовой гаммы всех приложений и самого Windows.
Установка палитры
Логические палитры являются инструментами рисования, такими же как перья
и кисти, описанные в разделе "Инструментальные средства изображения".
Для создания логической палитры используется функция CreatePalette, которая
берет указатель на запись данных LogPalette, создает новую палитру и возвращает
ее описатель, который передается в SelectPalette для выбора палитры в
контекст дисплея. Запись TLogPalette содержит поля номера версии Windows
(в настоящее время $0300), число элементов палитры и массив элементов
палитры. Каждый элемент палитры - это запись типа TPaletteEntry. Тип TPaletteEntry
имеет три байтовых поля для спецификации цвета (peRed, peGreen и peBlue)
и одно поле для флагов (peFlags).
GetStockObject(Default_Palette) создает палитру по умолчанию, состоящую
из 20 цветов, которые всегда присутствуют в палитре системы.
B.Pascal 7 & Objects/OW - 308 -
После выбора палитры в контекст дисплея с помощью SelectPalette, он должен
до использования "реализовать" ее. Это делается с помощью функции
Windows RealizePalette:
ThePalette := CreatePalette(@ALogPalette);
SelectPalette(TheDC, ThePalette, 0);
RealizePalette(TheDC);
RealizePalette помещает цвета из вашей логической палитры в системную
палитру устройства. Сначала Windows проверяет соответствие цветов с уже
имеющимися в системной палитре, а затем добавляет ваши новые цвета в палитру
системы, если для этого есть место. Цветам, которым не нашлись идентичные
цвета в системной палитре, подбирается наиболее соответствующий цвет из
палитры системы. Ваше приложение должно реализовать свою палитру до рисования,
как это делается для других инструментальных средств рисования.
Рисование с палитрами
После реализации палитры вашего приложения, оно может осуществлять рисование
с использованием его цветов. Цвета палитры можно задавать прямо или косвенно.
Для прямого задания цвета используется индекс палитры, TColorRef. Индекс
палитры TColorRef есть значение типа Longint, где старший байт установлен
в 1, а индекс элемента логической палитры содержится в двух младших байтах.
Например, $01000009 задает девятый элемент логической палитры. Это значение
можно использовать везде, где ожидается аргумент TColorRef. Например:
ALogPen.lopnColor := $01000009;
Если ваше дисплейное устройство допускает использование полного 24-битового
цвета без системной палитры, то использование индекса палитры неоправданно
ограничивает вас цветами вашей логической палитры. Чтобы избежать этого
ограничения, вы можете задать цвет палитры косвенно, используя относительное
значение палитры TColorRef. Относительное значение TColorRef почти совпадает
с абсолютным значением RGB TColorRef, но байт старшего разряда установлен
в 2. Три младших байта содержат значение цвета RGB. Например, $020000FF
задают значение чистого красного цвета. Если устройство поддерживает системную
палитру, то Windows подберет максимально соответствующий цвет RGB логической
палитры. Если устройство не поддерживает системную палитру, то TColorRed
используется так, как если бы он задавал явное значение RGB.
Запрос палитры
Windows определяет функцию, которая позволяет вам получать информацию
относительно палитры. GetPaletteEntries воспринимает
B.Pascal 7 & Objects/OW - 309 -
индекс, диапазон и указатель на TPaletteEntry и заполняет буфер
заданными элементами палитры.
Модификация палитры
Есть два способа изменения элементов логической палитры. Функция 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);
и затем перерисовать окно, чтобы увидеть изменения цветов.
Реакция на изменения палитры
Когда окно принимает сообщение wm_PaletteChanged, это свидетельствует
о том, что активное окно сменило системную палитру путем реализации ее
логической палитры. Окно, принявшее сообщение, может отреагировать на
него тремя способами: оно может ниче-
B.Pascal 7 & Objects/OW - 310 -
го не делать (очень быстрый способ, но может привести к некорректным цветам),
оно может реализовать свою логическую палитру и перерисовать себя (медленнее,
но цвета будут максимально корректными), либо оно может реализовать свою
логическую палитру и затем использовать функцию UpdateColors для быстрого
изменения области клиента в соответствии с системной палитрой. UpdateColors
в общем случае работает быстрее, чем перерисовка области клиента, но при
ее использовании могут быть некоторая потеря точности в цветопередаче.
Поле WParam записи TMessage, переданной в сообщении wm_PaletteChanged,
содержит описатель окна, которое реализовало свою палитру. Если в ответ
вы решили реализовать свою собственную палитру, сначала убедитесь в том,
что этот описатель не является описателем вашего окна, чтобы не создать
бесконечного цикла.
Программа PaTest создает и реализует логическую палитру из восьми цветов.
При нажатии левой кнопки "мыши" она будет рисовать раскрашенные
квадраты с образцами каждого из цветов логической палитры. При нажатии
правой кнопки происходит сдвиг цветов логической палитры. Используется
индекс палитры TColorRef, поэтому, когда логическая палитра меняется раскрашенные
квадраты также сменят свой цвет. При использовании индекса палитры TColorRef
может оказаться удобным использование функции PaletteIndex.
Полный текст программы содержится в файле PALTEST.PAS на ваших дистрибутивных
дискетах.
B.Pascal 7 & Objects/OW - 311 -
Глава 18. Более подробно о ресурсах
Программы Windows очень легко использовать, т.к. они предоставляют пользователю
стандартный интерфейс. Например, большинство программ Windows используют
меню для реализации команд программы, и курсор, который позволяет применять
"мышь" в качестве управления такими инструментальными средствами,
как указатель "мыши" стрелка или кисть для рисования.
Меню и курсоры - это два примера ресурсов программы Windows. Ресурсы это
данные, хранимые в выполняемом (.EXE) файле программы, но они располагаются
отдельно от обычного сегмента данных программы. Ресурсы разрабатываются
и специфицируются вне кода программы, затем добавляются к скомпилированному
коду программы для создания выполняемого файла программы.
Следующие ресурсы вы будете создавать и использовать наиболее часто:
- Меню.
- Блоки диалога.
- Пиктограммы.
- Курсоры.
- Оперативные клавиши.
- Графические изображения.
- Строки символов.
Обычно при загрузке приложения в память Windows оставляет ресурсы на диске
и загружает в случае необходимости отдельные ресурсы в процессе выполнения
программы. За исключением побитовых отображений (графических образов),
Windows удаляет ресурс из памяти после окончания его использования. Если
вы хотите загрузить ресурс при загрузке программы или не хотите, чтобы
Windows имела возможность удалить ресурс из памяти, вы можете сменить
его атрибуты. Детальное описание создания и модификации ресурсов содержится
в руководстве пользователя по пакету разработчика ресурсов.
Создание ресурсов
Вы можете создавать ресурсы, используя редактор ресурсов или компилятор
ресурсов. Паскаль поддерживает оба эти метода, поэтому вы сами можете
выбрать, какой их них является более удобным. В большинстве случаев проще
использовать редактор ресурсов и создавать ваши ресурсы визуально. Однако,
иногда более удобно использовать компилятор ресурсов для компиляции файла
описания ресурса, который может вам встретиться в книге или в журнале.
Независимо от выбранного вами подхода вы обычно создаете файл ресурса
(.RES) для каждого приложения. Этот файл ресурса будет содержать двоичную
информацию о всех меню, диалогах, графических образах и других ресурсах,
используемых вашим приложением.
B.Pascal 7 & Objects/OW - 312 -
Двоичный файл ресурса (.RES) добавляется к вашему исполняемому файлу (.EXE)
в процессе компиляции с использованием директивы компилятора $R, как это
описано в данной главе. Вы также должны написать код, который будет загружать
ресурсы в память. Это придаст вам дополнительную гибкость, поскольку ваша
программа будет использовать память лишь для ресурсов, которые используются
в данный момент. Загрузка ресурсов в память также рассматривается в данной
главе.
Добавление ресурсов к выполняемой программе
Ресурсы хранятся в двоичном формате в файле .RES, поэтому они должны быть
добавлены к выполняемому файлу приложения (.EXE). Результатом будет файл,
который наряду со скомпилированным кодом приложения будет содержать и
его ресурсы.
Есть три способа добавления ресурсов к выполняемому файлу:
- Можно использовать редактор ресурсов для копирования ресурсов из файла
.RES в уже скомпилированный файл программы .EXE. Инструкции по этой операции
содержатся в руководстве пользователя по пакету разработчика ресурсов.
- Можно задать директиву в исходном коде файла. Например, эта программа
на языке Паскаль:
program SampleProgram;
$r SAMPLE.RES
.
.
.
добавит файл ресурсов SAMPLE.RES к выполняемому файлу.
Каждая программа на языке Паскаль может иметь только один файл ресурсов
(хотя этот файл ресурсов может включать другие файлы ресурсов). Все эти
файлы должны быть файлами .RES и хранить ресурсы в двоичном формате. Директива
компилятора $R позволяет вам задать отдельный файл .RES.
- Использовать компилятор ресурсов.
Загрузка ресурсов в приложение
После добавления ресурса к выполняемому файлу, он должен быть явно загружен
приложением до своего использования. Конкретный способ загрузки ресурса
зависит от типа ресурса.
Загрузка меню
Меню окна является одним из атрибутов его создания. Другими словами это
характеристика окна, которая должна быть задана до создания соответствующего
элемента меню (с помощью метода 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 представляет собой выбранного варианта
меню (а не самого ресурса меню). Каждый пункт меню имеет уникальный целочисленный
идентификатор. В этом методе задача состоит в организации соответствующей
реакции на выбор пункта меню. Обычно вы будете определять символьные константы
для команд вашего меню.
Загрузка оперативных клавиш
Оперативные клавиши - это активные клавиши или комбинации
B.Pascal 7 & Objects/OW - 314 -
клавиш, которые используются для задания команд приложения. Обычно оперативные
клавиши определяются как эквиваленты выбора пунктов меню. Например, клавиша
Del - это стандартная оперативная клавиша, которую можно использовать
как альтернативу выбора пункта Delete в меню Edit. Однако, оперативные
клавиши могут реализовывать команды, которые не соответствуют элементам
меню.
Ресурсы оперативных клавиш хранятся в таблице оперативных клавиш. Для
загрузки таблицы оперативных клавиш используется функция Windows LoadAccelerators,
которая просто возвращает описатель таблицы. В отличие от ресурса меню,
который связан с конкретным окном, ресурс оперативной клавиши принадлежит
всему приложению. Каждое приложение может иметь только один такой ресурс.
Объекты приложения резервируют одно поле объекта, HAccTable, для хранения
описателя ресурса оперативных клавиш. Обычно вы будете загружать ресурс
оперативных клавиш в методе объекта приложения InitInstance:
procedure SampleApplication.InitInstance;
begin
TApplication.InitInstance;
HAccTable := LoadAccelerators(HInstance,
'SampleAccelerators');
end;
Часто вы будете определять оперативные клавиши для быстрого выбора вариантов
меню. Например, Shift+Ins обычно используется для быстрого выбора команды
Paste. Оперативные клавиши генерируют основанные на команде сообщения,
которые идентичны сообщениям, генерируемым выбором пункта меню. Для привязки
метода реакции на выбор в меню с соответствующей оперативной клавишей
нужно убедиться в том, что определенное в ресурсе значение оперативной
клавиши идентично идентификатору элемента меню.
Загрузка блоков диалога
Блоки диалога являются единственным типом ресурса, который непосредственно
соответствует типу объекта ObjectWindows. TDialog и его производные типы,
включая TDlgWindow, определяют объекты интерфейса, которые используют
ресурсы блока диалога. Каждый объект блока диалога обычно связан с одним
ресурсом блока диалога, который задает его размер, местоположение и ассортимент
управляющих элементов (таких, как командные кнопки и блоки списка).
При конструировании блока диалога задайте ресурс объекта блока диалога.
Как ресурсы меню и командных клавиш, ресурс диалога может иметь символьное
имя или целочисленный идентификатор. Например:
Adlg := New(PSampleDialog, Init(@Self, 'AboutBox')); или
Adlg := New(PSampleDialog, Init(@Self, PChar(120)));
Дополнительная информация по созданию объектов диалога содержится в Главе
11, "Объекты диалоговых блоков".
Загрузка курсоров и пиктограмм
Каждый тип объекта окна имеет специальные атрибуты, называемые атрибутами
регистрации. Среди этих атрибутов есть курсор окна и пиктограмма. Для
установки этих атрибутов для типа окна вы должны определить метод 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, "Объекты окна".
Загрузка строковых ресурсов
Принципиальная причина выделения строк приложения в качестве
B.Pascal 7 & Objects/OW - 316 -
ресурсов состоит в облегчении процесса настройки приложения на
конкретное применение или возможности перевода приложения на
иностранный язык. Если строки определены в исходном коде, то для
их изменения или перевода нужно иметь доступ к исходному коду.
Если же они определены как ресурсы, то хранятся в таблице строк выполняемого
файла приложения. Вы можете использовать редактор строк для перевода строк
в таблице, без изменения и даже доступа к исходному коду. Каждый выполняемый
файл может иметь только одну таблицу строк.
Для загрузки строки из таблицы в буфер сегмента данных вашего приложения
используется функция 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[0..255] of Char; begin
LoadString(HInstance, ids_NoPrgrm, @TextItem, 20);
MessageBox(HWindow, @TextItem, 'Error', mb_OK or mb_IconExclamation);
end;
Данный пример загружает отдельную строку в блок сообщения об
B.Pascal 7 & Objects/OW - 317 -
ошибке. Для загрузки списка строк в блок списка вызывается
LoadString для загрузки каждой строки, затем вызывается AddString для
добавления ее в блок списка.
Другое использование ресурса строк применяется для элементов меню, которые
добавляются в меню вашего исходного кода. В этом случае сначала получается
ресурс строки с помощью LoadString. Затем эта строка передается как параметр
в вызовы функций Window CreateMenu и AppendMenu. Например:
procedure SampleWindow.Init(AParent: PWindpwsObject; ATitle:
PChar); var
TextItem: array[0..255] 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;
Загрузка графических изображений
Функция 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));
После загрузки графического образа ваше приложение может использовать
его разными способами:
* Для рисования картинки на экране. Например, вы можете загрузить побитовое
распределение в блок информации о приложении в качестве заставки.
B.Pascal 7 & Objects/OW - 318 -
* Для создания кисти, которую вы можете использовать для заполнения областей
экрана или для создания фона окна. Создав кисть по графическому образу,
вы можете закрасить ей область фона.
* Для отображения картинок вместо текста в элементах меню
или элементах блока списка. Например, вы можете вместо
слова 'Arrow' (стрелка) в пункте меню поместить изображение стрелки.
Дополнительная информация относительно использования графики с побитовым
отображением содержится в Главе 17.
Если побитовое отображение не используется, то его нужно удалить из памяти.
В противном случае занимаемая им память будет недоступна другим приложениям.
Даже если вы не удаляете его после использования приложением, вы обязательно
должны удалить его до прекращения работы приложения. Графический образ
удаляется из памяти с помощью функции Windows DeleteObject:
if DeleteObject(HMyBit) then успешно ;
После удаления графического изображения его описатель становится некорректным,
и его нельзя использовать.
B.Pascal 7 & Objects/OW - 319 -
Использование побитовых отображений для создания кистей
Вы можете использовать графические образы для создания кистей, которые
могут закрашивать области экрана. Область может быть закрашена сплошным
цветом или в виде заданного образца. Минимальный размер используемого
в кисти графического образа составляет 8 на 8 элементов изображения. Если
вы применяете большее графическое изображение, то в кисти используется
только его левый верхний угол 8 на 8. Предположим, что вы хотите заполнить
область полосками, как это показано на Рис. 18.1.
Bitmap drawing test
File Help
Рис. 18.1. Заполнение области экрана полосками.
При заполнении области на Рис. 18.1 Windows циклически копирует кисть.
Действительный размер побитового распределения - лишь 8 на 8 элементов
изображения, но кистью можно закрасить весь экран.
Рис. 18.2. Ресурс графического изображения для создания кисти по образцу
Рис. 18.1.
Следующий код помещает образец графического образа в кисть:
procedure SampleWindow.MakeBrush;
var
MyLogBrush: TLogBrush; begin
HMyBit := LoadBitmap(HInstance, PChar(502));
MyLogBrush.lbStyle := bs_Pattern;
MyLogBrush.lbHatch := HMyBit;
B.Pascal 7 & Objects/OW - 320 -
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 это его новый идентификатор. Однако, вы можете
использовать один и тот же идентификатор в
B.Pascal 7 & Objects/OW - 321 -
обоих случаях.
Steps
File Help
New |pick |me |Save |Save As |
Рис. 18.3. Меню, где в качестве одного из пунктов выбора использовано
графическое изображение.
B.Pascal 7 & Objects/OW - 322 -
Глава 19. Наборы
Программисты, работающие на языке Паскаль, обычно тратят очень много времени
на создание кода по манипулированию и обеспечению структур данных, таких
как связанные списки и массивы с динамической установкой размеров. И очень
часто один и тот же код имеет тенденцию к повторному переписыванию и отладке.
Что касается традиционного языка Паскаль, он лишь предоставляет вам встроенные
типы записи и массива. Все другие структуры остаются на ваше усмотрение.
Например, если вы собираетесь хранить данные в массиве, то обычно вам
нужно написать код для создания массива, импорта данных в массив, получение
данных массива для обработки, и, возможно, вывода данных на устройство
ввода-вывода. Позднее, когда потребуется новый тип элемента массива, вы
начинаете все сначала.
Было бы замечательно, если бы тип массива поставлялся вместе с кодом,
обрабатывающего бы многие из тех операций, которые обычно выполняются
с массивом. Это был бы тип массива, который можно было бы расширять без
нарушения первоначального кода. Все это является целью создания типа ObjectWindows
TCollection. Это объект, который хранит наборы указателей и обладает набором
методов по манипулированию ими.
Объекты наборов
Будучи объектами и тем самым имея встроенные методы, наборы обладают двумя
дополнительными чертами, которые имеют отношение к обычным массивам языка
Паскаль - это динамическое установка размеров и полиморфизм.
Динамическая установка размеров наборов
Размер стандартного массива в стандартном Паскале фиксируется во время
компиляции. Хорошо, если вы точно знаете, какой размер должен иметь ваш
массив, но это может быть не столь хорошо к тому моменту, когда кто-нибудь
будет запускать на вашу программу. Изменение размера массива требует изменения
исходного кода и перекомпиляции.
Однако, для наборов вы устанавливаете только их начальный размер, который
динамически увеличивается в процессе работы программы, для размещения
в нем всех нужных данных. Это делает ваше приложение в его скомпилированном
виде значительно более гибким. Тем не менее, следует иметь в виду, что
набор не может сжиматься, поэтому следует быть аккуратным и не делать
его неоправданно большим.
B.Pascal 7 & Objects/OW - 323 -
Полиморфизм наборов
Второй аспект, по которому массивы могут ограничивать ваше приложение,
состоит в том, что каждый элемент массива должен иметь один и тот же тип,
и этот тип должен быть определен при компиляции кода.
Наборы обходят это ограничение использованием нетипизированных указателей.
Это сказывается не только на быстроте и эффективности, но наборы могут
состоять из объектов (и даже не из объектов) разного типа и размера. Набору
не нужно знать что-либо об объектах, которые он обрабатывает. Он просто
организует связь с ними в случае необходимости.
Проверка типа и наборы
Наборы ограничивают традиционную мощную проверку типа языка Паскаль. Это
означает, что можете поместить нечто в набор и когда запрашиваете это
назад, компилятор уже не может проверить ваших предположений относительно
объекта. Вы можете поместить нечто как PHedgehog (еж), а прочитать назад
как PSheep (овца), и набор никак не сможет насторожить вас.
Как программист, работающий на языке Паскаль, вы вполне оправданно будете
нервничать по поводу результатов. Проверка типов языка Паскаль в конце
концов сберегает несколько часов при поиске некоторых достаточно иллюзорных
ошибок. Поэтому вы должны принять во внимание следующее предупреждение.
Вы даже представить себе не можете насколько трудно бывает искать ошибки
несоответствия типа, поскольку обычно эту работу за вас выполнял компилятор!
Однако, если вы обнаружите, что ваша программа сбивается или зацикливается,
тщательно проверьте хранимые типы объектов и считываемые из наборов.
B.Pascal 7 & Objects/OW - 324 -
Объединение в набор элементов, не являющихся объектами
Вы даже можете добавить в набор нечто, что вообще не является объектом,
но это также может явиться серьезным предметом озабоченности. Наборы ожидают
получения нетипизированных указателей незаданного типа на нечто. Но некоторые
методы TCollection предназначены специально для работы с наборами элементов,
производных от TObject. Это касается методов доступа к потоку PutItem
и GetItem, и стандартной процедуры FreeItem.
Например, это означает, что вы можете хранить PChar в наборе, но при попытке
послать этот набор в поток, результаты будут не столь успешными, если
вы не перепишете стандартные методы набора GetItem и PutItem. Аналогично,
при попытке освобождения набора будет сделана попытка удаления каждого
элемента с помощью FreeItem. Например, это делает TStrCollection.
Если вам удастся преодолеть все эти трудности, вы обнаружите, что наборы
(и построенные вами производные наборов) являются быстрыми, гибкими и
надежными структурами данных.
Создание набора
Создание набора столь же просто, как и создание типа данных, которые вы
хотите в нем хранить. Предположим, что вы - консультант, и вам нужно хранить
и искать номер счета, фамилию и номер телефона каждого из ваших клиентов.
Сначала определим тип объекта клиента (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);
B.Pascal 7 & Objects/OW - 325 -
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 клиентов.
Следующие два оператора создают новый объект клиента и вставляют его в
набор. Вызов
B.Pascal 7 & Objects/OW - 326 -
Dispose в конце операции освобождает весь набор клиентов.
Нигде не нужно было сообщать набору, какой вид данных предполагается хранить
- для этого просто используется указатель.
Методы итератора
Вставка и удаление элемента не являются единственными общими операторами
набора. Очень часто вы будете писать циклы for для просмотра всех объектов
набора с целью отображения данных или выполнения некоторых вычислений.
В других случаях вы будете искать первый или последний элемент набора,
который удовлетворяет некоторому критерию поиска. Для этих целей у наборов
имеется три метода итератора: ForEach, FirstThat и LastThat. Каждый из
них воспринимает указатель на процедуру или функцию в качестве своего
единственного параметра.
Итератор 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) должна:
* Быть процедурой - она не может быть функцией или методом
B.Pascal 7 & Objects/OW - 327 -
объекта, хотя данный пример показывает, что процедура может вызвать метод.
* Быть локальной (вложенной) относительно вызывающей ее программы.
* Описываться как дальняя процедура директивой 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;
B.Pascal 7 & Objects/OW - 328 -
Снова обратите внимание на то, что PhoneMatch вложена и использует удаленную
модель вызова. В этом случае эта функция возвращает True только при совпадении
номера телефона клиента и заданного образца поиска. Если в наборе нет
объекта, который соответствовал бы критерию поиска, FirstThat возвращает
указатель nil.
Запомните: ForEach вызывает определенную пользователем процедуру, а FirstThat
и LastThat каждая вызывает определенную пользователем булевскую функцию.
В любом случае определенная пользователем процедура или функция передают
указатель на объект набора.
Отсортированные наборы
Иногда вам бывает нужно, чтобы ваши данные были определенным образом отсортированы.
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
B.Pascal 7 & Objects/OW - 329 -
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.
Наборы строк
Многим программам требуется работать с отсортированными
B.Pascal 7 & Objects/OW - 330 -
строками. Для этих целей 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: удаляется каждый элемент набора, и затем удаляется
сам набор
B.Pascal 7 & Objects/OW - 331 -
WordList.
Пересмотренные итераторы
Метод 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 метода. Когда вы исполь-
B.Pascal 7 & Objects/OW - 332 -
зуете наборы в ваших программах, вы будете удивлены скоростью их
работы: они разработаны с максимальной гибкостью и реализованы
для использования с максимальной скоростью.
Теперь пришло время рассмотреть реальные возможности наборов, элементы
могут обрабатываться полиморфически. Это значит, что вы не просто можете
хранить определенный тип объекта в наборе; вы можете хранить несколько
разных типов объектов, взятых произвольно из вашей иерархии объектов.
Если вы рассмотрите приведенные примеры наборов, вы можете заметить, что
все элементы каждого набора были одно и того же типа. Мы имели дело со
списком строк в котором каждый элемент был строкой. Мы также занимались
списком клиентов. Но наборы могут хранить любые производные от 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;
B.Pascal 7 & Objects/OW - 333 -
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
B.Pascal 7 & Objects/OW - 334 -
.
.
.
if GraphicsList <> nil then DrawAll(GraphicsList);
.
.
.
end.
Способность наборов хранить разные, но связанные объекты основывается
на мощном краеугольном камне объектно-ориентированного программирования.
В следующей главе вы увидите тот же принцип полиморфизма, примененный
к потокам с равными приоритетами.
B.Pascal 7 & Objects/OW - 335 -
Наборы и управление памятью
TCollection может динамически расти от начального размера, установленного
Init, до максимального размера в 16380 элементов. ObjectWindows хранит
максимальный размер набора в переменной MaxCollectionSize. Каждый добавляемый
в набор элемент занимает четыре байта памяти, т.к. он хранится в виде
указателя.
Ни одна библиотека динамических структур данных не будет полной, если
она не снабжена средствами обнаружения ошибок. Если для инициализации
набора не хватает памяти, то возвращается указатель nil.
Если не хватает памяти при добавлении элемента в набор, то вызывается
метод TCollection.Error, и возникает ошибка этапа выполнения в динамически
распределяемой области памяти. Вы можете переписать TCollection.Error
для организации собственного метода информирования или исправления ошибки.
Вам следует уделить особое внимание доступности динамической области памяти,
поскольку у пользователя имеет значительно больший контроль над программой
ObjectWinodws, чем над обычной программой языка Паскаль. Если добавлением
объектов в набор управляет пользователь (например, открывая новое окно),
то ошибку динамической области памяти не так то легко предсказать. Вы
можете предпринять некоторые шаги по защите пользователя от фатальной
ошибки при выполнении программы либо проверяя память при использовании
набора, либо обрабатывая сбой выполняемой программы таким образом, чтобы
избежать прекращения ее работы.
B.Pascal 7 & Objects/OW - 336 -
Глава 20. Потоки
Техника объектно-ориентированного программирования и
ObjectWindows дают вам мощные средства инкапсуляции кода и данных и большие
возможности построения взаимосвязанных структур объектов. Но что делать,
если стоит простая задача, например, по хранению некоторых объектов на
диске?
Когда-то данные хранились исключительно в записях, и помещение данных
на диск было тривиальной задачей. Но данные в программах ObjectWindows
неразрывно связаны с объектами. Конечно, вы можете отделить данные от
объекта и записать их в дисковый файл. Объединение дает вам значительный
шаг в направлении прогресса, а разъединение отбрасывает вас назад.
Есть ли в самом объектно-ориентированном программировании и ObjectWindows
некоторые средства, которые могли бы разрешить эту проблему? Есть, и это
потоки.
Поток в ObjectWindows - это набор объектов на их пути куда-либо: обычно
в файл, EMS, в последовательный порт или некоторое другое устройство.
Потоки обслуживают операции ввода-вывода на уровне объектов, а не на уровне
данных. При расширении объекта ObjectWindows вам нужно обеспечить обработку
определенных вами дополнительных полей. Все сложные аспекты обработки
на уровне объектов будут проделаны за вас.
Вопрос: объектный ввод-вывод
Поскольку вы пишете программы на языке Паскаль, то знаете, что до выполнения
операций ввода-вывода с файлом, вы сначала должны сообщить компилятору,
какой тип данных вы будете писать и считывать из файла. Файл должен иметь
тип, и этот тип должен быть установлен во время компиляции.
Паскаль реализует в этой связи очень удобное правило: можно организовать
доступ к файлу неопределенного типа с помощью процедур BlockWrite и BlockRead.
Отсутствие проверки типа возлагает некоторую дополнительную ответственность
на программиста, хотя позволяет очень быстро выполнять двоичные операции
ввода-вывода.
Вторая проблема состоит в том, что вы не можете непосредственно использовать
файлы с объектами. Паскаль не позволяет вам создавать файл с объектным
типом. Объекты могут содержать виртуальные методы, адреса которых определяются
в процессе выполнения программы, поэтому хранение информации о виртуальных
методах вне программы лишено смысла, еще более бессмысленно считывать
эту информацию в программу.
Но эту проблему снова можно обойти. Вы можете выделить дан-
B.Pascal 7 & Objects/OW - 337 -
ные из ваших объектов и записать эту информацию в какой-то файл
некоторого вида, а уже позднее восстановить объекты из этих исходных данных.
Подобное решение, однако, будет недостаточно элегантным и существенно
усложняет конструирование объектов.
Ответ: потоки
ObjectWindows позволяет обойти все эти трудности и даже сулит вам получение
некоторых дополнительных выгод. Потоки дают вам простое, но изящное средство
хранение данных объекта вне вашей программы.
Полиморфизм потоков
Потоки ObjectWindows позволяют вам работать с файлами определенного и
неопределенного типа: проверка типа имеется, но тип посылаемого объекта
не должен обязательно определяться во время компиляции. Смысл в том, что
потоки знают, что они имеют дело с объектами, и поскольку все объекты
являются производными от TObject, поток может их обработать. В действительности
различные объекты ObjectWindows могут также легко записываться в один
поток, как и группы идентичных объектов.
Потоки обрабатывают объекты
Все что вам нужно сделать - это определить для потока, какие объекты ему
нужно будет обрабатывать, чтобы он знал, как согласовывать данные с таблицами
виртуальных методов. Затем без каких-либо усилий вы можете помещать объекты
в поток и извлекать их из потока.
Но каким образом один и тот же поток может считывать и записывать такие
разные объекты как TCollection и TDialog, даже не зная в момент компиляции,
какие типы объектов он будет обрабатывать? Это существенно отличается
от традиционных операций ввода-вывода языка Паскаль. В действительности
потоки могут обрабатывать даже новые типы объектов, которые вообще еще
не были созданы к моменту компиляции потока.
Ответом на это является так называемая регистрация. Каждому типу объекта
ObjectWindows (или любому новому производному типу объекта) присваивается
уникальный регистрационный номер. Этот номер записывается в поток перед
данными объекта. Затем, при считывании объекта из потока, ObjectWindows
сначала берет регистрационный номер и на его основании узнает, сколько
данных нужно считывать и какие таблицы виртуальных методов подключать
к данным.
B.Pascal 7 & Objects/OW - 338 -
Смысл использования потоков
На фундаментальном уровне вы можете рассматривать потоки как файлы языка
Паскаль. В своей основе файл языка Паскаль представляет собой последовательное
устройство ввода-вывода: вы записываете в него и считываете из него. Поток
- это полиморфическое устройство последовательного ввода-вывода, т.е.
оно ведет себя, как последовательный файл, но вы можете считывать и записывать
различные типы объектов в каждый момент времени.
Потоки (как и файлы Паскаля) можно также просматривать, как устройства
ввода-вывода произвольного доступа, искать определенное место в файле,
считывать данные в этой точке или записывать данные в эту точку, возвращать
позицию указателя файла и т.д. Все эти операции можно выполнять с потоками,
и они описаны в разделе "Потоки с произвольным доступом".
Есть два разных аспекта использования потоков, которыми вам нужно овладеть,
и к счастью оба они очень простые. Первый - это установка потока, а второй
- считывание и запись файлов в поток.
Установка потока
Все что нужно сделать для использования потока - это инициализировать
его. Точный синтаксис конструктора Init может быть разным, в зависимости
от типа потока, с которым вы имеете дело. Например, если вы открываете
поток DOS, вам нужно передать имя файла DOS и режим доступа (только чтение,
только запись, чтение/запись) для содержащего поток файла.
Например, для инициализации буферизированного потока DOS при загрузке
набора объектов в программу, все что вам нужно это:
var
SaveFile: TBufStream; begin
SaveFile.Init('COLLECT.DTA', stOpen, 1024);
.
.
После инициализации потока все готово к работе.
TStream это абстрактный механизм потока, поэтому вы будет работать не
с ним, а с производными от TStream удобными объектами потока. Это будет,
например, TDosStream, для выполнения дисковых операций ввода-вывода, TBufStream
для буферизованных операций ввода-вывода (очень удобен для частых операций
считывания или записи небольших объемов информации на диск) и TEmsStream
для передачи объектов в память EMS. Кроме того, ObjectWindows реализует
индексированные потоки с указателем, указывающим место в потоке. Перемещая
этот указатель вы можете организовать произвольный доступ в потоке.
B.Pascal 7 & Objects/OW - 339 -
Чтение из потока и запись в поток
Основной объект потока 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 ищет сложные объекты. Следовательно, если вы ищите в потоке
окно, которое владеет дочерними окнами, то они
B.Pascal 7 & Objects/OW - 340 -
также будут загружены.
Метод Error
И, наконец, процедура Error определяет что происходит при возникновении
ошибки потока. По умолчанию TStream.Error просто устанавливает значение
двух полей в потоке (Status и ErrorInfo). Если вы хотите сделать что-либо
более содержательное, например, сгенерировать соответствующее сообщение
о сбое в работе программы или вывести блок диалога c сообщением об ошибке,
то вам нужно переопределить процедуру Error.
Закрытие потока
Когда вы закончили использование потока, вы вызываете его метод Done,
как вы обычно вызывали Close для дискового файла. Как и для других объектов
ObjectWindows, это делается следующим образом:
Dispose(SomeStream, Done);
как для уничтожения объекта потока, так и для его закрытия.
Как сделать объекты потоковыми
Все стандартные объекты ObjectWindows готовы к использованию в потоках,
и все потоки ObjectWindows узнают стандартные объекты. При изготовлении
нового типа объекта, производного от стандартного объекта, его очень просто
подготовить к использованию в потоке и известить потоки о его существовании.
Методы загрузки и хранения
Действительное чтение и запись объектов в поток производится методами
Load и Store. Каждый объект должен иметь эти методы для использования
потока, поэтому вы никогда не будете вызывать их непосредственно (они
вызываются из методов Get и Put.) Все что вам нужно сделать, это убедиться
в том, что объект знает, как послать себя в поток, когда это потребуется.
Благодаря объектно-ориентированному программированию это делается очень
просто, т.к. большинство механизмов наследуются от объекта-предка. Все
что должен делать ваш объект, это загружать и хранить те свои компоненты,
которые вы в него добавляете, об остальном позаботится метод предка. Например,
вы производите от TWindow новый вид окна с именем художника-сюрреалиста
Рене Магритте, который нарисовал много известных картин с окнами:
type
TMagritte = object(TWindow)
B.Pascal 7 & Objects/OW - 341 -
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.
Регистрация потока
Кроме определения методов Load и Store для новых объектов, вы также должны
зарегистрировать этот новый тип объекта в потоках. Регистрация - это простой
процесс, который состоит из двух этапов: сначала определяется запись регистрации
потока, а затем она передается глобальной процедуре регистрации RegisterType.
Примечание: ObjectWindows уже имеет зарегистрированными все стандартные
объекты, поэтому вам нужно регистрировать только новые, определяемые вами
объекты.
Для определения записи регистрации потока нужно следовать приводимому
ниже формату. Запись регистрации потока это запись языка Pascal типа TStreamRec,
которая определяется следующим образом:
B.Pascal 7 & Objects/OW - 342 -
PStreamRec = ^TStreamRec;
TStreamRec = record
ObjType: Word;
VmtLink: Word;
Load: Pointer;
Store: Pointer;
Next: Word; end;
По соглашению всем регистрационным записям потока
ObjectWindows присваивается то же имя, что и соответствующим типам объектов,
но начальное "T" заменяется на "R". Следовательно,
регистрационная запись для TCollection будет иметь имя RCollection. Такие
абстрактные типы как TObject и TWindowsObject не имеют регистрационных
записей, поскольку их экземпляры вы никогда не будете хранить в потоках.
Номера идентификаторов объектов
Вам действительно нужно думать только о поле ObjType записи, все остальное
делается механически. Каждому новому определяемому вами типу требуется
его собственный уникальный идентификатор типа в виде числа. ObjectWindows
резервирует регистрационные номера от 0 до 99 для стандартных объектов,
поэтому ваши регистрационные номера будут лежать в диапазоне от 100 до
65535.
Ответственность за создание и ведение библиотеки номеров идентификаторов
для всех ваших новых объектов, которые будут использоваться в потоках
ввода-вывода, ложиться целиком на вас. Нужно сделать эти идентификаторы
доступными для пользователей ваших модулей. Как и для идентификатора меню
и определенных пользователем сообщений, присваиваемые вами числа могут
быть произвольными, но они должны быть уникальными и попадать в указанный
диапазон.
Автоматические поля
Поле VmtLink это связь с таблицей виртуальных методов объектов (VMT).
Вы просто задаете его как отклонение типа вашего объекта:
RSomeObject.VmtLink := Ofs(TypeOf(TSomeObject)^);
Поля Load и Store содержат соответственно адреса методов Load и Store.
RSomeObject.Load := @TSomeObject.Load;
RSomeObject.Store := @TSomeObject.Store;
Значение последнего поля, Next, задается RegisterType и не требует никакого
вмешательства с вашей стороны. Оно просто обес-
B.Pascal 7 & Objects/OW - 343 -
печивает внутреннее использование скомпонованного списка регистрационных
записей потока.
Регистрация на месте
После конструирования регистрационной записи потока вы вызываете RegisterType
с вашей записью в качестве параметра. Поэтому для регистрации вашего нового
объекта TMagritte для его использования в потоке вы включаете следующий
код:
const
RMagritte: TStreamRec = (
ObjType: 100;
VmtLink: Ofs(TypeOf(TMagritte)^);
Load: @TMagritte.Load;
Store: @TMagritte.Store
);
RegisterType(RMagritte);
Вот и все. Теперь вы можете помещать (Put) экземпляры вашего нового типа
объекта в любой поток ObjectWindows и считывать их из потоков.
Регистрация стандартных объектов
ObjectWindows определяет регистрационные записи потоков для всех его стандартных
объектов. Кроме того, модуль WObjects определяет процедуру RegisterWObjects,
которая автоматически регистрирует все объекты этого модуля. Например,
модуль OWindows содержит процедуру RegisterOWindows, а ODialogs - RegisterODialogs.
Механизм потока
После того, как мы посмотрели на процесс использования потоков, следует
заглянуть во внутреннюю работу, которую производит ObjectWindows c вашими
объектами с помощью методов Put и Get. Это прекрасный пример взаимодействия
объектов и использования встроенных в них методов.
Процесс Put
Когда вы посылаете объект в поток с помощью метода Put, поток сначала
берет указатель VMT со смещением 0 от объекта и просматривает список зарегистрированных
типов потоков системы с целью найти совпадение. Когда это совпадение найдено,
поток ищет регистрационный номер идентификатора объекта и записывает его
в поток. Затем поток вызывает метод Store объекта для завершения записи
объекта. Метод Store использует процедуру потока Write, которая действительно
пишет корректное число байт в поток.
B.Pascal 7 & Objects/OW - 344 -
Ваш объект не должен ничего знать о потоке - это может быть файл на диске,
память EMS или любой другой вид потока - ваш объект просто говорит "запишите
меня в поток", и поток делает все остальное.
Процесс Get
Когда вы считываете объект из потока с помощью метода Get, сначала ищется
номер его идентификатора, и просматривается на совпадение список зарегистрированных
типов. После обнаружения совпадения регистрационная запись дает потоку
местоположение метода Load объект и VMT. Затем для чтения нужного объема
данных из потока вызывается метод Load.
Вы опять просто говорите потоку, что нужно взять (Get) следующий объект
и поместить его в место, определяемое заданным вами указателем. Ваш объект
даже не беспокоится о том, с каким потоком он имеет дело. Поток сам беспокоится
о считывании нужного объема данных из потока с помощью метода объекта
Load, который в свою очередь опирается на метод потока Read.
Для программиста все это достаточно прозрачно, но в то же время вы ясно
должны понять, насколько важно зарегистрировать тип до проведения каких-либо
попыток ввода-вывода с потоком.
Обработка указателей объектов со значением nil
Вы можете записать в поток объект nil. Однако, если это сделать, то в
поток запишется слово 0. При считывании идентификатора слова 0 поток возвратит
указатель nil. Поэтому 0 считается зарезервированным и не может использоваться
в качестве номера идентификатора объекта потока. ObjectWindows резервирует
идентификатор потока от 0 до 99 для внутреннего использования.
Наборы в потоке: пример
В Главе 19, "Наборы", вы уже видели как наборы могут содержать
разные, но связанные объекты. Это свойство полиморфизма также применимо
и к потокам, и их можно использовать для записи наборов на диск для последующего
обращения, даже в другой программе. Вернемся к примеру COLLECT4.PAS. Что
еще нужно добавить в эту программу для помещения набора в поток?
Ответ будет очень простым. Сначала возьмем базовый объект TGraphObject
и "научим" его хранить его данные (X и Y) в потоке. Для этого
нужен метод Store. Затем определим новый метод Store для любого производного
от TGraphObject объекта, в котором добавляются дополнительные поля (например,
TGraphPie добавляет ArcStart и ArcEnd).
B.Pascal 7 & Objects/OW - 345 -
Затем построим регистрационную запись для каждого типа объекта, который
предполагается хранить, и зарегистрируем все эти типы при первом запуске
вашей программы. Вот и все. Остальное будет подобно обычным операциям
ввода-вывода в файл: определяется переменная потока; создается новый поток;
одним простым оператором весь набор помещается в поток, и поток закрывается.
Добавление методов 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);
B.Pascal 7 & Objects/OW - 346 -
begin
TGraphObject.Store(S);
S.Write(ArcStart, SizeOf(ArcStart));
S.Write(ArcEnd, SizeOf(ArcEnd)); end;
Обратите внимание, что метод TStream Write делает двоичную запись. Его
первый параметр может быть переменной любого типа, но TStream.Write не
может узнать размеры этой переменной. Второй параметр содержит эту информацию,
и вы должны придерживаться соглашения относительно использования стандартной
функции SizeOf. Таким образом, компилятор всегда может гарантировать,
что вы всегда считываете и записываете нужное количество данных.
Регистрация записей
Наш последний шаг состоит в определении константы регистрационной записи
для каждого производного типа. Хороший прием программирования состоит
в следовании соглашению 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, поскольку в данном
примере рассматривается
B.Pascal 7 & Objects/OW - 347 -
только помещение данных в поток. В следующем примере методы Load
будут определены, и изменены регистрационные записи (см.
STREAM2.PAS).
Регистрация
Всегда нужно зарегистрировать каждую из этих записей до проведения каких-либо
операций ввода-вывода с потоком. Самый простой способ сделать это состоит
в том, чтобы объединить их все в одну процедуру и вызвать ее в самом начале
вашей программы (или в методе Init вашего приложения):
procedure StreamRegistration;
begin
RegisterType(RCollection);
RegisterType(RGraphEllipse);
RegisterType(RGraphRect);
RegisterType(RGraphPie); end;
Обратите внимание, что вам нужно зарегистрировать
TCollection (используя его запись RCollection - теперь вы видите, что
соглашения о присваивании имен упрощают программирование), хотя вы и не
определили TCollection. Правило очень простое и незабываемое именно вы
отвечаете за регистрацию каждого типа объекта, который ваша программа
будет помещать в поток.
Запись в поток
Нужно следовать обычной последовательности операций ввода-вывода в файл:
создать поток; поместить в него данные (набор); закрыть поток. Вам не
нужно писать итератор 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;
B.Pascal 7 & Objects/OW - 348 -
В результате создастся файл на диске, который содержит всю информацию,
необходимую для "считывания" набора назад в память. Когда поток
открыт, и ищется набор, то (см. STREAM2.PAS) "магически" восстанавливаются
все скрытые связи между набором и его элементами, объекты и их таблицы
виртуальных методов. Следующий раздел поясняет, как помещать в поток объекты,
которые содержат связи с другими объектами.
Как все хранится?
Относительно потоков нужно сделать важное предостережение: только владелец
объекта должен записывать его в поток. Это предостережение аналогично
традиционному предостережению языка Паскаль, которое вам должно быть известно:
только владелец указателя может уничтожить его.
В реальных сложных приложениях множество объектов часто имеют указатель
на конкретную структуру. Когда возникает необходимость в выполнении операций
ввода-вывода, вы должны решить, кто "владеет" структурой. Только
этот владелец должен посылать структуру в поток. Иначе у вас может получиться
несколько копий одной структуры в потоке. При считывании такого потока
будет создано несколько экземпляров структуры, и каждый из первоначальных
объектов будет указывать на собственную персональную копию структуры вместо
единственной первоначальной структуры.
Поля в потоке
Много раз вы видели, что удобно хранить указатель на дочерние окна группы
в локальной переменной (поле данных объекта). Например, блок диалога может
хранить указатель на его объекты управления в полях с мнемоническими именами
для более удобного доступа (OKButton или FileINputLine). При создании
такого дочернего окна порождающее окно будет иметь на него два указателя,
один - в поле, и еще один - в списке дочерних окон. Если на это не обратить
внимания, то считывание такого объекта из потока приведет к дублированию.
Решение состоит в использовании методов TWindowsObject GetChildPtr и PutChildPtr.
При хранении поля, которое является дочерним окном, вместо записи указателя,
как если бы это была простая переменная, вы вызываете метод PutChildPtr,
который записывает ссылку на позицию дочернего окна в списке дочерних
окон группы. Аналогично, при загрузке (Load) группы из потока, вы вызываете
GetChildPtr, который гарантирует, что поле и список дочерних окон указывают
на один и тот же объект. Приведем короткий пример использования GetChildPtr
и PutChildPtr в простом окне:
type
TDemoWinodw = object(TWindow)
B.Pascal 7 & Objects/OW - 349 -
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.
Родство экземпляров окон
Аналогичная ситуация может возникнуть, если окно имеет поле, указывающее
на одного из его родственников. Окно называется родственным другому окну,
если они оба принадлежат одному и тому же порождающему окну. Например,
у вас есть управляющий элемент редактированием и две кнопки с независимой
фиксацией, которые управляют активизацией управляющего элемента редактирования.
При изменении состояния кнопки с независимой фиксацией, она соответственно
активизирует или дезактивизирует управляющий элемент редактирования. TActivateRadioButton
должна знать управляющий элемент редактирования, который также является
компонентом этого же окна, поэтому управляющий элемент редактирования
добавляется в качестве переменной.
Как и для дочерних окон, при чтении и записи родственных ссылок в поток
могут возникнуть проблемы. Решение также будет аналогичным. Методы TWindowsObject
PutSiblingPtr и GetSiblingPtr предоставляют средства доступа к родственникам:
type
TActivateRadioButton=object(TRadioButton)
EditControl: PEdit;
B.Pascal 7 & Objects/OW - 350 -
.
.
.
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 никакой код, который использует
дочерние окна, которые зависят от их родственных окон, поскольку в этом
случае результат может быть непредсказуемым.
Копирование потока
TStream имеет метод CopyFrom(S,Count), который копирует заданное число
байт (Count) из заданного потока S. Метод CopyFrom может быть использован
для копирования содержимого одного потока в другой. Если, например, вы
циклически обращаетесь к дисковому потоку, то можете скопировать его в
поток EMS для организации более быстрого доступа:
NewStream := New(TEmsStream, Init(OldStream^.GetSize));
OldStream^.Seek(0);
NewStream^.CopyFrom(OldStream, OldStream^.GetSize);
Потоки произвольного доступа
До этого момента мы работали с потоками как с устройствами последовательного
доступа: вы помещали (Put) объекты в конец ва-
B.Pascal 7 & Objects/OW - 351 -
шего потока и считывали их назад (Get) в той же последовательности. Но
ObjectWindows имеет и более мощные средства. Имеется возможность рассматривать
поток как виртуальное устройство произвольного доступа. Кроме методов
Get и Put, которые соответствуют Read и Write при работе с файлом, потоки
обладают средствами проведения операций Seek, FilePos, FileSize и Truncate.
- Процедура потока Seek перемещает текущий указатель потока к заданной
позиции (число байт от начала потока), как стандартная процедура Seek
языка Паскаль.
- Процедура GetPos по своему действию обратна процедуре Seek. Она возвращает
значение Longint с текущей позицией потока.
- Функция GetSize возвращает размер потока в байтах.
- Процедура Truncate удаляет все данные, которые расположены после текущей
позиции потока, при этом текущая позиция потока становится концом потока.
Поскольку работа с этими программами очень удобна, потоки произвольного
доступа требуют от вас отслеживать индекс, отмечающий начальную позицию
каждого объекта в потоке. В этом случае для хранения индекса вы можете
использовать набор.
Необъектные элементы потоков
В поток можно записывать и элементы, которые не являются объектами, но
для этого следует использовать несколько иной подход. Стандартные методы
потока Get и Put требуют загрузки или записи объекта, производного от
TObject. Если вам нужно создать поток, который состоит не из объектов,
переходите на нижний уровень процедур Read и Write, где в поток записывается
или из него считывается заданное число байт. Этот же механизм используют
методы Get и Put для чтения и записи данных об объектах. Вы просто обходите
механизм VMT, который заложен в Put и Get.
Разработка пользователем собственных потоков
Данный раздел суммирует возможности методов и обработки ошибок потоков
ObjectWindows, чтобы вы знали, что можно использовать для создания новых
типов потоков.
Сам TStream является абстрактным объектом и его можно расширить для создания
удобного типа потока. Большинство методов TStream являются абстрактными
и должны быть реализованы как их производные методы, основывающиеся на
абстрактных методах TStream. Полностью реализованы только методы Error,
Get и Put. GetPos, GetSize, Read, Seek, SetPos, Truncate и Write должны
быть переписаны. Если производный тип объекта имеет буфер, то должен
B.Pascal 7 & Objects/OW - 352 -
быть переписан и метод Flush.
Обработка ошибок потока
TStream имеет метод Error(Code, Info), который вызывается при обнаружении
ошибки потока. Error просто присваивает полю Status потока значение одной
из констант, приведенных в Главе 21 "Справочник по ObjectWindows"
в разделе "Константы stXXXX".
Поле ErrorInfo не определено, если значение Status не есть stGetError
или stPutError. Если значение поля Status равно stGetError, то поле ErrorInfo
содержит номер идентификатора потока незарегистрированного типа. Если
значение поля Status равно stPutError, то поле ErrorInfo содержит смещение
VMT типа, который вы пытались поместить в поток. Вы можете переписать
TStream.Error для генерации любого уровня обработки ошибок, включая ошибки
этапа выполнения.
B.Pascal 7 & Objects/OW - 353 -
Часть 4. Справочник по ObjectWindows
Глава 21. Объектные типы ObjectWindows
Эта глава содержит алфавитный список всех стандартных объектных типов
ObjectWindows с объяснением их назначения и использования, их операциями,
элементами, полями и методами. Здесь описываются также элементы ObjectWindows,
не являющиеся частью стандартной иерархии объектов ObjectWindows.
Для нахождения информации по определенному объекту нужно учитывать, что
многие свойства объектов в иерархии наследуются от "предков".
Чтобы не дублировать всю информацию, эта глава описывает только те элементы
данных и функции, которые добавляются или изменяются в данном объекте.
Просмотрев диаграмму наследования для объекта, вы легко можете определить,
в каких его предках вводится поле, а в каких задается или переопределяется
метод.
Перечисленные в данной главе необъектные элементы, включая типы, константы,
переменные, процедуры и функции, определены в модулях ObjectWindows. Ниже
приведен пример справочной записи для объекта или процедуры:
B.Pascal 7 & Objects/OW - 354 -
TSample модуль TSample
TObject TSample
-------- ----------------
-------- | AField |
|-Init-| | AnotherField |
| Done | ----------------
| Free | | Init |
-------- | Zilch |
----------------
Сначала дается общее описание объекта, его связи с дpугими объектами и
использование. На приведенной выше диаграмме показано, что объект TSample
является непосредственным потомком TObject, и что он переопределяет конструктор
Init.
Поля
В данном разделе в алфавитном порядке перечисляются поля каждого объекта.
Кроме описания поля и его пояснения показывается режим доступа к данному
полю - только чтение или чтение/запись. Поля, доступные только по чтению
- это в общем случае поля, которые устанавливаются и обслуживаются методами
объекта и которые не должны указываться в левой части оператора присваивания.
AField
AField: SomeType: (только чтение)
AField - это поле, которое содержит некоторую информацию о данном примере
объекта. Этот текст пояcняет, как оно функционирует, что оно означает
и как его использовать.
См. также: родственные поля, методы, объекты, глобальные функции и т.д.
AnotherField
AhotherField: Word: (чтение/запись)
Далее следует поясняющая информация, аналогичная AField.
Методы
В данном разделе перечисляются все методы, вновь определяемые для данного
объекта или переопределяющие наследуемые методы. Сначала перечисляются
конструкторы, затем деструкторы, затем другие методы в алфавитном порядке.
Для виртуальных методов указывается также, насколько часто
B.Pascal 7 & Objects/OW - 355 -
требуется переопределять метод: никогда, редко, иногда, часто или
всегда.
Init
constructor Init(AParameter: SomeType);
Init создает новый объект, вызывая сначала конструктор Init, наследуемый
из TObject, затем устанавливая поле AField в AParameter.
См. также: TObject.Init
Zilch (иногда переопределяется)
procedure Zilch; virtual;
Процедура Zilch вызывает выполнение объектом некоторых действий.
См. также TSomethingElse.Zilch.
Процедура Sample (модуль Sample)
Описание: procedure Sample(AParameter);
Назначение: Sample выполняет некоторые полезные действия со своим параметром
AParemeter.
См. также: функцию Example.
Процедура Abstract модуль Objects
Описание: procedure Absract;
Назначение: Вызов данной процедуры завершает программу с ошибкой этапа
выполнения 211. При реализации абстрактного объектного типа используйте
вызов Abstact в тех виртуальных методах, которые должны переопределяться
в наследующих типах. Это обеспечивает, что любая попытка использовать
экземпляры абстрактного объектного типа завершится неуспешно.
Функция AllocMultiSel модуль ODialogs
Описание: function AllocMultiSel(Count: Integer):
PMultiSelRec;
Назначение: Распределяет TMultiSelRec со счетчиком, равным
B.Pascal 7 & Objects/OW - 356 -
Count, и пространством в поле Selections, достаточным для размещения Count
выборов (0..Count-1). Если для распределения всей записи места нет, возвращает
значение nil.
Переменная Application модуль OWindows
Описание: Application: PApplocation = nil;
Назначение: В начале TApplicationInit переменная Application устанавливается
в @Self, а в ApplicationDone сбрасывается в nil. Таким образом, во время
выполнения программы ObjectWindows Application указывает на объект приложения.
См. также: TApplication.Init.
Константы bf_XXXX модуль ODialogs
Назначение: Командные кнопки, кнопки с независимой фиксацией и кнопки
с зависимой фиксацией используют константы bf_XXXX для определения их
трех возможных состояний.
Значения: Определены следующие значения констант флага кнопки:
Константы флагов кнопок Таблица 21.1
Константа Значение Смысл
bfbfbf UNCHECKEDCHECKEDGRAYED 012 Элемент не выбран.Элемент выбран.Элемент
выделен (серый).
B.Pascal 7 & Objects/OW - 357 -
Стили кнопок bs_XXXX модуль WinTypes
Назначение: Эти параметры вы можете передавать в качестве параметра стиля
конструкторам объектов кнопок, либо задать стили кнопок при создании командных
кнопок с помощью CreateWindow и CreateWindowEx.
Значения: В Windows определены следующие константы:
Стили кнопок, bs_ Таблица 21.1
Константа Смысл
bs_3State Этот стиль кнопки является блоком, котоpый может быть выбpан,
не выбpан и затенен. Затенение (отобpажение сеpым) обычно используется
для указания того, что блок заблокиpован.
bs_Auto3State Это то же самое, что и bs_3State, но в этом случае состояние
блока пеpеключается автоматически пpи щелчке "мышью".
bs_AutoCheckBox Это то же самое, что и bs_CheckBox, но в этом случае состояние
блока пеpеключается автоматически пpи щелчке "мышью".
bs_AutoRadioButton Это то же самое, что и bs_RadioButton, но в этом случае
состояние кнопки пеpеключается автоматически пpи щелчке "мышью",
пpи выбоpе кнопки и пpи удалении отметок выбоpа со всех дpугих кнопок
в этой гpуппе.
bs_CheckBox Этот стиль кнопки является блоком, котоpый может быть выбpан
или не выбpан. Связанный с ним текст помещается спpава от блока.
bs_DefPushButton Это тот же стиль кнопки, что и стиль bs_PushButton, за
исключением того, что данная кнопка пpинимается по умолчанию, если только
с помощью "мыши" или с клавиатуpы не выбpана дpугая кнопка или
блок.
| bs_GroupBox | Этот стиль кнопки является блоком для|
| | гpуппиpования дpугих кнопок. Связанный|
| | с ней текст помещается в левый веpхний|
| | угол. |
B.Pascal 7 & Objects/OW - 358 -
bs_LeftText Пpи использовании со стилями bs_3State, bs_CheckBox или bs_RadioButton,
этот стиль вызывает pазмещение текста слева, а не спpава от кнопки или
блока.
bs_OwnerDraw Этот стиль кнопки является кнопкой, отображаемой владельцем.
Кpоме обычных кодов уведомления, посылаемых чеpез сообщение wm_Command,
порождающий объект также получает запpос на pаскpаску, инвеpтиpование
и блокиpование кнопки.
bs_PushButton Этот стиль кнопки пpедставляет кнопку с любым помещенным
внутpи ее связанным с ней текстом.
bs_RadioButton Этот стиль кнопки пpедставляет маленькую кpуглую кнопку,
котоpая может быть или выбpана или не выбpана. Связанный с ней текст помещается
спpава от кнопки. Селективные кнопки обычно используются в гpуппах, в
котоpых каждый pаз выбиpается одна и только одна кнопка.
См. также: TButton.Init. TCheckBox.Init, TRadioButton.Init.
Переменная BWCCClassNames модуль OWindows
Описание: BWCCClassNames: Boolean = False;
Назначение: Указывает, использует ли приложение имена класса окна специализированные
управляющие элементы Borland (BWCC). Инициализация кода модуля BWCC устанавливает
BWCCClassNames в True, так что включение BWCC в оператор uses программы
автоматически устанавливает BWCCClassNames. "Нажимаемые" командные
кнопки, объекты кнопок с назависимой и зависимой фиксацией в приложениях
Windows, использующие BWCC, автоматически применяют имена класса BWCC.
Если вы хотите, чтобы ваши программы для работы с управляющими элементами
BWCC и не BWCC, то можете выполнять загрузку из различных источников на
основе значения BWCCClassNames.
См. также: методы GetClassName.
B.Pascal 7 & Objects/OW - 359 -
Стили комбинированного блока cbs_XXXX модуль WinTypes
Функция: Вы можете передавать эти константы в параметрах стиля конструкторов
объекта комбинированного блока или для задания стилей комбинированных
блоков при создании комбинированных блоков с помощью функций CreateWindows
и CreateWindowsEx.
Значения: Windows определяет следующие значения
Стили комбинированных блоков Таблица 21.3
Константа Смысл
cbs_AutoHScroll Этот стиль комбиниpованного блока пpокpучивает текст в
управляющем элементе pедактиpования впpаво, когда пользователь вводит
символ в конце стpоки. Без этого стиля ввод текста за пpеделами гpаниц
управляющего элемента редактирования был бы невозможен.
cbs_DropDown Этот стиль то же самое, что cbs_Simple, но здесь блок списка
отобpажается тогда, когда выбиpается пиктогpамма, следующая за полем выбоpа.
cbs_DropDownList Этот стиль то же самое, что cbs_DropDown, но здесь для
отобpажения текущего выбоpа используется статический текст, а не элемент
упpавления pедактиpованием.
cbs_HasStrings Этот стиль может быть использован в сочетании со стилемcbs_OwnerDrawFixed
или стилемcbs_OwnerDrawVariable. Этот стиль вкачестве элементов использует
стpоки.Стpоки обслуживаются системой и могут быть считаны с помощью сообщения
cb_GetLBText.
cbs_NoIntegralHeihgt Этот стиль комбиниpованного блока является в точности
pазмеpом, заданным пpи создании комбиниpованного блока. Обычно pазмеp,
используемый для создания комбиниpованного блока, может изменяться, поэтому,
комбиниpованный блок не отобpажает частичные элементы.
B.Pascal 7 & Objects/OW - 360 -
| cbs_OEMConvert | Этот стиль может быть использован в|
| | сочетании со стилями cbs_Simple или|
| | cbs_DropDown. Этот стиль комби-|
| | ниpованного блока пеpеводит каждый|
| | символ, введенный в элемент упpавле-|
| | ния комбиниpованного блока из набоpа|
| | символов ANSI в набоp символов OEM, и|
| | обpатно. Тогда, в пpименении к эле-|
| | ментам в блоке списка комбиниpованно-|
| | го блока или к тексту в элементе|
| | упpавления pедактиpованием комби-|
| | ниpованного блока функция AnsiToOem|
| | будет pаботать коppектно. Стилем|
| | cbs_OEMConvert удобно пользоваться|
| | для комбиниpованных блоков, содеpжа-|
| | щих имена файлов. |
cbs_OwnerDrawFixed Этот стиль комбиниpованного блока должен pисоваться
его владельцем. Все элементы в блоке списка комбиниpованного блока имеют
одну и ту же высоту.
cbs_OwnerDrawVariable Этот стиль комбиниpованного блока должен рисоваться
его владельцем. Элементы в блоке списка комбиниpованного блока имеют пеpеменную
высоту.
cbs_Simple Этот стиль комбиниpованного блока постоянно отобpажает свой
блок списка. Текущий выбоp из блока списка отобpажается в оpгане упpавления
pедактиpованием.
cbs_Sort Этот стиль комбиниpованного блока имеет отсоpтиpованный блок
списка. Поpядок соpтиpовки для комбиниpованных блоков со стилями cbs_OwnerDrawFixed
и cbs_OwnerDrawVariable может быть разным.
См. также: TComboBox.Init.
B.Pascal 7 & Objects/OW - 361 -
Константы cm_XXXX модуль OWindows
Назначение: ObjectWindows определяет несколько констант, определяющих
диапазоны констант командных сообщений.
Значения: Определены следующие командные константы:
Константы командных сообщений Таблица 21.4
Константа Значение Смысл
cm_First $A000 Начало командного сообщения.
cm_Count $6000 Число командных сообщений.
cm_Internal $FF00 Начало командных сообщений, зарезервированных для внутреннего
использования.
cm Reserved cm Internal cm First
Константы cm_ определены для трех меню: File, Edit и Window.
B.Pascal 7 & Objects/OW - 362 -
Смещения команд на основе
используемых по умолчанию значений Таблица 21.5
Константа Значение Эквивалент меню
cm_EditCutcm_EditCopycm_EditPastecm_EditDeletecm_EditClearcm_EditUndocm_EditFindcm_EditReplacecm_EditFindNext
cm_Reserved + 0cm_Reserved + 1cm_Reserved + 2cm_Reserved + 3cm_Reserved
+ 4cm_Reserved + 5cm_Reserved + 6cm_Reserved + 7cm_Reserved + 8 Edit|CutEdit|CopyEdit|PasteEdit|DeleteEdit|ClearEdit|UndoEdit|FindEdit|ReplaceEdit|Search
Again
cmcmcmcmcmcm FileNewFileOpenMDIFileNewMDIFileOpenFileSaveFileSaveAs cmcmcmcmcmcm
ReservedReservedReservedReservedReservedReserved 91011121314 EditEditEditEditEditEdit
NewOpenNewOpenSaveSave As
cm_ArrangeIconscm_TileChildrencm_CascadeChildrencm_CreateChildcm_Exit
cm_Reserved + 15cm_Reserved + 16cm_Reserved + 17cm_Reserved + 18cm_Reserved
+ 20 Windows| Arrange IconsWindows|TileWindows| CascadeWindows| Close
AllFile|Exit
B.Pascal 7 & Objects/OW - 363 -
Константы coXXXX модуль Objects
Назначение: Когда TCollection обнаруживает при операции ошибку, константы
coXXXX передаются методу TCollection.Error в качестве параметра Code.
Значения: Для всех стандартных наборов ObjectWindows определены следующие
стандартные коды ошибок:
Коды ошибок наборов Таблица 21.6
Код ошибки Значение Смысл
coIndexError -1 Индекс вне диапазона. Параметр Info, переданный методу
Error, содержит недопустимое значение индекса.
coOverflow -2 Переполнение набора.TCollection.SetLimit не удалось расширить
набор до требуемого размера. Переданный методу Error параметр содержит
требуемый размер.
См. также: объект TCollection.
B.Pascal 7 & Objects/OW - 364 -
Стили класса cs_XXXX модуль WinTypes
Назначение: Эти константы стилей классов окон используются в поле стиля
структуры данных WNDCLASS. Они могут объединяться с помощью операций OR.
Стили классов cs_ Таблица 21.7
Константа Смысл
cs_ByteAlignClient Область пользователя окна выровнена на границу байта
в направлении x.
cs_ByteAlignWindow Окно выровнено на границу байта в направлении x.
cs_ClassDC Экземпляры класса окна разделяют между собой их собственный
контекст дисплея.
cs_DblClks Окно будет получать сообщения от двойного щелчка "мышью".
cs_ClobalClass Класс окна может использоваться всеми работающими прикладными
задачами.
cs_HRedraw Если горизонтальные размеры окна изменяются, то будет перерисовано
все окно.
cs_NoClose Команда выбора Close меню Control окна заблокирована.
cs_OwnDC Каждый экземпляр окна получает свой собственный контекст дисплея.
Использует 800 байт памяти на каждое окно.
cs_ParentDC Окно использует контекст дисплея порождающего окна.
cs_SaveBits Если содеpжимое окна в данный момент не отобpажается, оно
сохpаняется в каpте бит. Эта каpта бит используется для повтоpного отобpажения
содеpжимого. Используется минимальным обpазом.
cs_VRedraw Если веpтикальные pазмеpы окна изменяются, то будет пеpеpисовано
все окно.
См. также: TWindowsObject.GetWindowClass.
Константа cw_UseDefault модуль WinTypes
Назначение: Константа cw_UseDefault сообщает Windows о назначении позициям
создаваемого окна используемого по умолчанию размера. Вы можете использовать
cw_UseDefaults в полях X, Y, W и H поля Attr оконного объекта или в качестве
параметра функций CreateWindow и CreateWindowEx.
См. также: тип TWindowAttr.
Процедура DoneMemory модуль OMemory
Описание: procedure DoneMemory;
Функция: Освобождает память, выделенную для буфера безопасности.
См. также: процедура InitMemory.
Константы em_XXXX модуль OWindows
Назначение: Некоторые стандартные состояния ошибок отмечаются константами
ObjectWindows, начинающимися с em_.
Значения: Определены следующие флаги ошибки:
Константы условия ошибки Таблица 21.8
Константа Значение Смысл
em_InvalidWindow -1 Окно недопустимо из-за неуспешного выполнения Create.
em_OutOfMemory -2 Распределение памяти превысило буфер безопасности.
em_InvalidClient -3 Не может быть создано окно клиента MDI.
em_InvalidChild -4 Одно или более дочерних окон не является допустимым.
em_InvalidMainWindow -5 Основное окно не может быть создано.
Переменная EmsCurHandle модуль Objects
Описание: EmsCurHandle: Word = $FFFF;
B.Pascal 7 & Objects/OW - 366 -
Назначение: Содержит текущий описатель EMS, отображенный TEmsStream в
физическую страницу 0 EMS. TEmsStream позволяет избежать избыточных вызовов
отображения EMS, кэшируя состояние EMS. Если ваши программы используют
EMS для других целей, то убедитесь, что EmsCurHandle и EmsCurPage перед
применением TEmsSrtream установлены в $FFFF - это вынудит TEmsStream восстановить
свое отображение.
См. также TEmsStream.Handle.
Переменная EmsCurPage модуль Objects
Описание: EmsCurPage: Word = $FFFF;
Назначение: Содержит описатель логической страницы EMS, отображенный TEmsStream
в физическую страницу EMS. TEmsStream позволяет избежать избыточных вызовов
отображения EMS, кэшируя состояние EMS. Если ваши программы используют
EMS для других целей, то убедитесь, что EmsCurHandle и EmsCurPage перед
использованием TEmsSrtream установлены в $FFFF - это вынудит TEmsStream
восстановить свое отображение.
См. также TEmsStream.Handle.
Стили управляющих элементов es_XXXX модуль WinTypes
Назначение: Эти константы используются для указания стилей упpавляющих
элементов pедактиpования пpи создании последних с помощью функций CreateWindow
и CreateWindowEx.
Стили упpавляющих элементов редактирования es_ Таблица 21.9
Константа Смысл
es_AutoHScroll Этот стиль управляющего элемента редактирования автоматически
пpокpучивает текст впpаво на 10 символов пpи вводе символа в конце стpоки.
Пpи нажатии Enter текст пpокpучивается назад до нулевой позиции.
es_AutoVScroll Этот стиль упpавляющего элемента редактирования автоматически
пpокpучивает текст на одну стpаницу ввеpх, когда пpи вставке в конце стpоки
нажимается Enter.
| es_Center | Этот стиль упpавляющего элемента pедак-|
B.Pascal 7 & Objects/OW - 367 -
| | тиpования центpиpует текст. Может ис-|
| | пользоваться только в случае, если также|
| | используется стиль es_MultiLine. |
es_Left Этот стиль упpавляющего элемента pедактиpования выpавнивает текст
слева. Может использоваться только в случае, если также используется стиль
es_MultiLine.
es_LowerCase Этот стиль упpавляющего элемента редактирования пpеобpазует
пpи вводе все символы в символы нижнего pегистpа.
es_MultiLine Этот стиль упpавляющего элемента редактирования является
многостpочным оpганом pедактиpования. Стиль es_AutoVScroll может использоваться
только со стилем es_MultiLine. Если стиль es_AutoVScroll не используется,
то при нажатии клавиши Enter пpи вставке в последней стpоке, выдается
звуковой сигнал. Если стиль es_AutoVScroll не используется, то вновь вводимые
слова пpи необходимости автоматически пеpеносятся на следующую стpоку.
Пpи изменении pазмеpов окна позиции этих пеpенесенных слов будут изменяться.
Многостpочный оpган упpавления pедактиpованием с полосами пpокpутки сам
обpабатывает свои сообщения полосы пpокpутки; в пpотивном случае, пpокpутка
выполняется автоматически описанным выше обpазом.
es_NoHideSel Этот стиль упpавляющего элемента редактирования не делает
невидимым выбоp пpи потеpе этим управляющим элементом фокуса ввода. По
умолчанию, пpи потеpе упpавляющим элементо pедактиpования фокуса ввода
выбоp делается невидимым.
es_OEMConvert Этот стиль упpавляющего элемента редакконвеpтиpует введенный
текст из набоpа символов ANSI в набоp символов OEM и обpатно. В этом случае
функция AnsiToOem будет вести себя коppектно пpи пpименении к тексту управляющего
элемента редактирования. Стиль es_OEMConvert удобно использовать для упpавляющих
элементов pедактиpования, содеpжащих имена файлов.
| es_Password | Все символы, введенные в управляющий|
| | элемент pедактиpования, отобpажаются|
| | как '*'. Для изменения отобpажаемого|
| | символа может использоваться сообщение|
B.Pascal 7 & Objects/OW - 368 -
| | em_SetPasswordChar. |
es_Right Этот стиль упpавляющего элемента редактирования выpавнивает текст
спpава. Может использоваться только в случае, если также используется
стиль es_MultiLine.
es_UpperCase Этот стиль упpавляющего элемента редактирования пpеобpазует
пpи вводе все символы в символы веpхнего pегистpа.
См. также: TEdit.Init.
Процедура FreeMultiSel модуль ODialogs
Описание: procedure FreeMultiSel(P: PMultiSelRec);
Освобождает запись TMultiRec, выделенную AllocMultiSel.
См. также: AllocMultiSel, TMultiSelRec.
Константа tsFileSpec модуль OStdDlgs
Описание: fsFileSpec = fsFileName + fsExtension
Назначение: Задает длину имени файла. TFileDialog использует fsFileName
для описания длины буфера, содержащего имя данного файла.
B.Pascal 7 & Objects/OW - 369 -
Константы id_XXXX модуль OWindows
Назначение: ObjectWindows определяет несколько констант, задающих диапазоны
дочерних идентификаторов.
Значения: Определены следующие константы сообщений порожденного идентификатора:
Константы сообщений дочернего идентификатора Таблица 21.10
Константа Значение Смысл
id_First $8000 Начало сообщений дочерний идентификаторов.
id_Count $1000 Число сообщений дочерних идентификаторов.
id_Internal $8F00 Зарезервировано для внутреннего использования.
id Reserved id Internal id First
id_FirstMDIChild id_Reserved + 1 Начало номеров порожденных идентификаторов.
id_MDIClient id_Reserved + 2 Номер дочернего идентификатораклиента MDI.
Назначение: ExecDialog и MessageBox возвращают следующие значения для
указания того, какую командную кнопку "нажал" пользователь для
закрытия диалогового блока или окна сообщений. Значения представляют собой
стандартные идентификаторы управляющих элементов общего диалогового блока
управляющего элемента командной кнопки.
B.Pascal 7 & Objects/OW - 370 -
Значения: Windows определяет следующие идентификаторы:
Константы командных
идентификаторов диалогового блока Таблица 21.11
Константа Смысл
ididididididid AbortCancelIgnoreNoOkRetryYes Была нажата кнопка Abort.Была
нажата кнопка Cancel.Была нажата кнопка Ignore.Была нажата кнопка No.Была
нажата кнопка OK.Была нажата кнопка Retry.Была нажата кнопка Yes.
См. также: TApplication.ExecDialog.
Процедура InitMemory модуль OMemory
Описание: procedure InitMemory;
Назначение: Путем вызова ResrtoreMemory инициализирует буфер безопасности,
затем устанавливает функцию ошибки динамически распределяемой области
памяти. TApplication.Init вызывает InitMemory.
См, также: процедуры DoneMemory, RestoreMemory.
Стили блока списка lbs_XXXX модуль WinTypes
Назначение: Эти константы используются для опpеделения стилей блока списка,
пpи создании блоков списка с помощью функции CreateWindow и CreateWindowEx.
B.Pascal 7 & Objects/OW - 371 -
Значения: Windows определяет следующие стили:
Стили блока списка lbs_ Таблица 21.12
Константа Смысл
lbs_ExtendedSel Этот стиль блока списка позволяет выбиpать несколько элементов
с помощью клавиши Shift и "мыши" или некотоpой дpугой комбинации
клавиш.
lbs_HasStrings Этот стиль может быть использован в сочетании со cbs_OwnerDrawFixed
или cbs_OwnerDrawVariable. Этот стиль в качестве элементов использует
стpоки. Стpоки обслуживаются системой и могут быть считаны с помощью сообщения
lb_GetLBText.
lbs_MultiColumn Этот стиль блока списка имеет несколько столбцов, котоpые
согут быть пpокpучены по гоpизонтали. Шиpина столбца может быть установлена
с помощью сообщения lb_SetColumnWidth.
lbs_MultipleSel Этот стиль блока списка позволяет выбиpать несколько элементов
с помощью "мыши". Пpи каждом одиночном или двойном щелчке мыши
элемент изменяет свое состояние выбоpа.
Часть №4
|
|