Иллюстрированный самоучитель по Tirbo Pascal

         

Фаза событий


Обычно активные события (evKeyDown и evCommand) получают и обрабатывают видимые элементы, принадлежащие цепочке активности. Однако часто возникают ситуации, когда необходимо, чтобы активное событие обработал неактивный элемент. Например, если на экране активно окно скроллера с полосами скроллинга, то события от клавиатуры будут передаваться окну. Как заставить в этом случае полосы реагировать на нажатие клавиш PgUp или PgDn? Для этого в Turbo Vision предусмотрен специальный механизм, основанный на так называемой фазе события. Когда модальный элемент получает событие, его передача выполняется в следующей последовательности:

событие посылается в Z-порядке всем видимым элементам, которые принадлежат модальному элементу и у которых поле Options имеет установленный флаг ofPreProcess;

если событие не очищено ни одним из них, оно посылается активным элементам (по цепочке активности);

если событие все еще не очищено, оно посылается в Z-порядке всем видимым элементам, у которых установлен флаг ofPostProcess.

Таким образом, Вы должны установить флаги ofPreProcess или ofPostProcess (или оба вместе) при инициации видимого элемента, если хотите, чтобы он мог получить активное событие до или после (или и до и после) того, как его получат активные элементы.

Для предыдущего примера необходимо инициировать полосы скроллинга с установленными флагами ofPostProcess, если требуется, чтобы полосы «увидели» и обработали нажатие на клавиши смещения курсора, PgUp, PgDn и т.д. Разумеется, в этом случае полосы получат событие evKeyDown только при условии, что скроллер сам не обработает это событие.

В некоторых ситуациях элемент, перехватывающий событие и до, и после активных элементов, должен модифицировать свое поведение в зависимости от фазы события. Рассмотрим такой типичный пример. Пусть в программе создано диалоговое окно, имеющее строку ввода и три кнопки, для которых определены командные клавиши Q, W, Е. Как добиться того, чтобы эти клавиши использовались в качестве командных клавиш, т.е. приводили к «нажатию» соответствующих кнопок при условии, что активна любая кнопка, а в сочетании с клавишей Аlt - если активна строка ввода (именно так используются командные клавиши в диалоговом окне среды Турбо Паскаля)? Если инициировать кнопки с флагом ofPreProcess, они смогут без труда определить факт нажатия на командную клавишу, однако в строке ввода пользователь не сможет ввести буквы Q, W и E, так как они будут перехвачены кнопками до того, как событие от клавиши получит строка ввода. Если инициировать кнопки с флагом ofPostProcess, пользователь не сможет использовать сочетания Аlt-<клавиша> для нажатия кнопки, если активна строка ввода: все события evKeyDown будут в этом случае направляться в строку. Решение очевидно: нужно определить оба флага, но на препроцессорной фазе следует проверять ввод Аlt-<клавиша>, а на постпроцессорной - <клавиша>.


Для реализации этих проверок обработчик событий объекта TButton должен каким-то образом определить текущую фазу события. С этой целью в любой группе предусмотрено поле Phase. Это поле доступно только для чтения и содержит одно из значений phPreProcess, phFocused или phPostProcess в зависимости от фазы события.

Следующий фрагмент иллюстрирует ту часть обработчика событий кнопок, которая проверяет командную клавишу:

evKeyDown: {Это часть оператора саsе} 

begin

С := HotKey(Title*); {Получаем в С букву клавиши}

{Проверяем Alt-<клавиша>:} 

if (Event.KeyCode = GetAltCode(С)) or 

{Проверяем <клавиша>:}

(Owner*.Phase = phPostProcess) and (C <> #0) 

and (UpCase(Event.CharCode) = C) or 

{Проверяем активность и нажатие пробела:} 

(State and sfFocused <> 0) and (Event.CharCode = ' ') then 



Press {Да, кнопка выбрана: выдаем нужную команду} 

end;

В этом фрагменте не показанная здесь функция HotKey выделяет из надписи на кнопке символ командной клавиши (он обрамляется символом «~»), а стандартная для Turbo Vision функция GetAltCode преобразует этот символ в расширенный код клавиш Аlt-<клавиша>. Метод TButton.Press реализует «нажатие» на кнопку и выдает сообщение evBroadcast с командой TButton. Command.

Отметим, что рассмотренный пример приведен только в качестве иллюстрации: стандартный объект TButton реализует свой обработчик событий именно таким образом и Вам нет нужды переопределять его (по умолчанию экземпляр TButton инициируется с установленными флагами ofPreProcess и ofPostProcess).


Команды


Поскольку события обычно связаны с какими-то действиями пользователя программа должна, как правило, реагировать на эти действия изменением видимого изображения. С точки зрения Turbo Vision это означает, что обработчики событий должны преобразовывать события в действия, связанные с реакцией на произошедшее событие. Для реализации этих действий в Turbo Vision предусмотрены команды.

Команда - это просто целое число без знака, кодирующее определенную последовательность действий. В Turbo Vision предусмотрен ряд стандартных команд для реализации многих стандартных действий. Например, команда cmQuit реализует завершение работы программы и выход в ДОС, cmClose закрывает активное окно, cmZoom распахивает окно на весь экран или возвращает ему прежние размеры и т.д. Идентификаторы стХХХХ являются идентификаторами предопределенных констант, которые кодируют стандартные действия (например, cmQuit = 1, cmZoom = 5 и т.д.).

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



Контекстная помощь


В Turbo Vision предусмотрены средства, облегчающие создание контекстно-зависимой справочной службы. С этой целью каждый видимый элемент имеет специальное шестнадцатиразрядное поле TView.HelpCtx, содержимым которого Вы можете распоряжаться по своему усмотрению. Обычно каждому видимому элементу присваивается свой код (целое число в диапазоне от 0 до 65535), устанавливаемый в поле HelpCtx. В этом случае при нажатии на заранее обусловленную командную клавишу, открывающую доступ к справочной службе (обычно это клавиша F1), программа может получить текущий контекст (прямым чтением поля HelpCtx или с помощью метода GetHelpCtx) и передать его в качестве параметра вызова справочной службе.

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

В следующем примере на экране создается диалоговое окно с двумя кнопками. Клавиша F1 используется для доступа к справочной службе. Если активна (выбрана) левая кнопка, нажатие на F1 даст сообщение «Левая кнопка», если правая - «Правая кнопка». Если на экране нет диалогового окна (оно вызывается клавишей F2), появится сообщение «Нет окна».

Uses CRT,App,Dialogs,Obj ects,Drivers,Views,Menus; 

type

PProg = TProg;

TProg = object (TApplication)

Procedure HandleEvent(var Event: Tevent); Virtual; 

Procedure GetEvent(var Event: Tevent); Virtual; 

Procedure InitStatusLine; Virtual;

end;

Procedure TProg.HandleEvent(var Event: TEvent); 

Procedure Dialoglnit; 

var

R: TRect;

Dia: PDialog; 

B1,B2: PButton;

с: Word; 

begin

ClearEvent(Event);

R.Assign(20,9,60,17);

Dia := New(PDialog, Init(R,''));

R.Assign(3,4,19,6);

Bl := New(PButton,Init(R,'Левая',0,0));

El*.HelpCtx := 1;

DiaA.insert (B1);

R.Assign(20,4,35,6);

B2 := New(PButton,Init(R,'Правая',0,0));


В2. HelpCtx := 2;

Dia.Insert (B2) ; 

с := ExecView(Dia) 

end ; {Dialоg Init} 

begin {TProg.HandleEvent }

TApplication.HandleEvent (Event) ; 

if (Event. What = evCommand)

(Event. Command = cmMenu) then 

Dialoglnit

end; {TProg. Handl eEvent} 

Procedure TProg. InitStatusLine; 

var

R: TRect; 

begin

GetExtent (R) ; 

R.A.Y := pred(R.B.Y) ;

StatusLine := New(PStatusLine, Init(R, 

NewStatusDef (О, О, {Начальная строка статуса} 

NewStatusKey ( ' ~Alt-X~ Выход' , kbAltX, cmQuit, 

NewStatusKey ( '~F1~ Справка' , kbFl,cmHelp, 

NewStatusKey ( '~F2~ Окно диалога' , kbF2, cmMenu, NIL))), 

NewStatusDef (1, 2 , {Строка статуса с диалоговым окном} 

NewStatusKey ( ' ~Esc~ Выход' , kbEsc, cmCancel, 

NewStatusKey ('~F1~ Справка' , kbFl, cmHelp, NIL)), .NIL))));

end; {TProg. InitStatusLine}

Procedure TProg. GetEvent (var Event: TEvent) ; 

const

txt: array [0..2] of String = ('Нет окна', 'Левая кнопка' , 'Правая кнопка'); 

begin

TApplication. GetEvent (Event) ; 

if Event . Command = cmHelp then 

begin

GotoXY(60,l) ; 

TextColor (Black+Blink); 

TextBackGround (White) ; 

Write (Txt [GetHelpCtx] ) ; 

Delay (2000) ; 

GotoXY(60,l) ; 

Write ('                  ') 

end

end; {TProg. GetEvent} 

var

Prog: TProg; 

begin

Prog.Init; 

Prog . Run ; 

Prog . Done 

end.

Для упрощение программы справочное сообщение выводится стандартными средствами Турбо Паскаля. Сообщение появляется в верхнем правом углу экрана и через 2 сек стирается, в течение этого промежутка времени доступ к клавиатуре и мыши блокируется.

Контекст видимого элемента может управлять содержимым строки статуса. С этой целью в методе InitStatusLine используется два или более вызова NewStatusDef со своим диапазоном контекста (см. предыдущий пример).

В состав демонстрационных модулей Turbo Vision включен файл HelpFile.pas и компилятор TVHC.pas, существенно упрощающие процесс создания и использования контекстно-чувствительной помощи.



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

Для использования этих средств необходимо предварительно создать файл справочной службы, содержащий текстовые данные и средства управления ими. Файл справочной службы создается программой TVHC.PAS из обычного текстового файла, в котором используются следующие соглашения:

если очередная строка начинается символами . topic, эта строка определяет заголовок темы;

если в тексте встречается фрагмент, обрамленный фигурными скобками, этот

фрагмент определяет перекрестную ссылку.

Заголовок темы содержит идентифицирующее справку уникальное кодовое слово, и связанный с ним контекст. Например:

.topic Viewer=2

Здесь Viewer - кодовое слово; 2 - контекст справочной службы.

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

.topic FileOpen=3 

File Open

---------

Эта опция меню используется для загрузки файла

Здесь строки

File Open

---------

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



Эта опция меню используется для загрузки файла

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

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

С помощью опции {FileOpen} можно загрузить файл.

или

Окно обеспечивает {просмотр файла: Viewer} в режиме

скроллера.

В первом случае будет выведена строка

С помощью опции FileOpen можно загрузить файл.

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

.topic FileOpen = ... 

Во втором случае в окне выводится строка

Окно обеспечивает просмотр файла в режиме скроллера.

Слова просмотр файла выделяются цветом и используются как перекрестная ссылка на справку с заголовком

.topic Viewer = ... 

В заголовке темы можно указывать несколько кодовых слов, например:

.topic FileOpen=3, OpenFile=103, FFileOpen 

Доступ к такой справке возможен для контекстов

const

chFileOpen = 3; 

chOpenFile = 103; 

chFFileOpen= 104;

Обратите внимание: за кодовым словом FFileOpen не указывается контекст, в этом случае считается, что связанный с ним контекст есть предыдущий контекст, увеличенный на 1, т.е.

chFFileOpen = 103 + 1 = 104

Файл DEMOHELP. TXT содержит пример исходного текстового файла, подготовленного с учетом описанных требований для преобразования программой TVHC.PAS в файл справочной службы.

Подготовка справочного файла и его использование осуществляется в следующей последовательности.

Подготавливается текстовый файл NAMETEXT, содержащий заголовки тем, справки и перекрестные ссылки.

Вызывается программа TVHC.EXE (исходный текст этой программы содержится в файле \BP\EXAMPLES\DOS\TVDEMOS\TVHC.PAS) для создания файла справочной службы NAMEHELP и вспомогательного модуля NAMEPAS. Обращение к программе описано ниже.



В программе, использующей средства модуля HelpFile:

 вызывается процедура RegisterHelpFile для регистрации объектов модуля HelpFile в потоке;

открывается поток, связанный с созданным файлом NAMEHELP; в) создается экземпляр объекта THelpFile и ему передается поток и нужный контекст;

инициируется работа созданного экземпляра;

ликвидируется экземпляр объекта THelpFile.

Действия 3,6,..., 3,д осуществляются каждый раз, когда необходимо выдать ту или иную справку.

Для преобразования текстового файла во внутренний формат, используемый средствами модуля HelpFile, предназначена программа, исходный текст которой поставляется в файле TVHC.PAS. Перед использованием этой программы ее необходимо оттранслировать в ЕХЕ-файл. Формат вызова программы TVHC.EXE:

TVHC NAMETEXT [NAMEHELP [NAMEPAS]]

(в квадратных скобках указаны необязательные параметры). Здесь NAMETEXT - имя исходного текстового файла, NAMEHELP - имя выходного файла справочной службы, NAMEPAS - имя выходного файла, содержащего текст модуля с контекстами в виде констант chXXXX. Если имена выходных файлов опущены, будут созданы файлы с именем исходного файла и расширением HLP для файла справочной службы, PAS для текста модуля.

Текст файл NAMEPAS имеет следующий вид:

unit namepas;

interface

const

chTopicl = nl; 

chTopic2 = n2;

.....

chTopicN = nN; 

implementation 

end.

Поскольку этот файл создается программой TVHC.EXE автоматически, будьте внимательны при обращении к ней. Не рекомендуется опускать имена NAMEHELP и NAMEPAS: если имя исходного текстового файла совпадает с именем программы или

любого другого PAS-файла, старый файл будет стерт и на его месте будет создан файл с текстом модуля!

Для иллюстрации техники использования средств модуля HelpFile рассмотрим следующий пример. Пусть текст справочной службы помещен в файл HelpTest.txt и имеет следующий вид:

.topic NoContext=0

Добро пожаловать

в справочную службу системы Turbo Vision!

В текстовом файле для справочной службы Вы должны подготовить {заголовки тем: topic} и {перекрестные ссылки: CrossRef}. Весь текст от одного заголовка до другого представляет собой текст справки и выводится в окне справочной службы. При подготовке текста учитываются следующие соглашения:



если очередная строка начинается символом пробел, эта строка не будет форматироваться с учетом границ окна;

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

TVHC NAMETXT [NAMEHELP [NAMEPAS]]

Здесь NAMETXT - имя исходного текстового файла; NAMEHLP -имя выходного файла справочной службы; NAMEPAS - имя выходного файла, содержащего текст PAS-модуля с определением всех контекстов в виде констант chXXXX. Имя файла NAMEHELP можно не указывать - в этом случае выходной файл будет иметь имя входного файла и расширение HLP. Если не указан файл HELPPAS, будет создан файл с именем исходного и расширением PAS.

По умолчанию программа использует расширение ТХТ для входного файла, HLP для выходного файла справочной службы и PAS - для файла констант. .topic Topic=l Заголовок темы имеет следующий формат:

.topic Name[=N] [, Namel[=N2] [...]]

Здесь Name - имя темы (может состоять только-из латинских букв и цифр; разница в высоте букв игнорируется); N -контекст справочной службы, при котором выдается данная справка.

В квадратных скобках показаны необязательные параметры. Если опущен контекст N, программа присваивает соответствующей перекрестной ссылке контекст NPrev + 1, где NPrev - предыдущий определенный в программе контекст. .topic CrossRef=2

В произвольное место текста справки можно вставить так называемую перекрестную ссылку:

(text[:title])

Здесь () - фигурные скобки; text - произвольный текст или заголовок темы; title - заголовок темы; этот параметр вместе с двоеточием опускается, если text - заголовок темы.

Если Вы подготовите такой файл, то после вызова

tvhc helptest

будут созданы два файла: файл справочной службы helptest.hlp и файл модуля с определениями констант helptest.pas. Содержимое этого последнего файла будет таким:

unit helptest;

interface

const

hcCrossRef = 2;



hcNoContext= 0;

hctopic = 1; 

implementation 

end.

Следующая программа будет выдавать контекстно-зависимые справочные сообщения

из файла helptest. hlp при каждом нажатии на F1.

Uses App, Menus, Drivers, Views, Objects, HelpFile, Dialogs; 

const

cmChangeCtx =1000; 

type

PCtxView = ^TCtxView; 

TCtxView = object (TView)

Constructor Init; 

Procedure Draw; Virtual;

end; 

MyApp = object (TApplication) 

Ctx: PCtxView; 

Constructor Init;

Procedure InitStatusLine; Virtual; 

Procedure HandleEvent(var Event: TEvent); Virtual; 

end ;

PMyHelpWindow = AMyHelpWindow; 

MyHelpWindow = object (THelpWindow)

Function GetPalette: PPalette; Virtual; 

end;

Procedure MyApp.InitStatusLine; 

var

R: TRect;

begin

GetExtent(R); 

R.A.Y := pred(R.B.Y); 

StatusLine := New(PStatusLine,Init(R,

NewStatusDef(0, $FFFF,

NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit,

NewStatusKey('~F1~ Помощь',kbF1, cmHelp, 

NewStatusKey('~F2~ Изменить контекст',kbF2,

cmChangeCtx, 

NewStatusKey('~F5~ Распахнуть окно1, kbF5,

cmZoom, NIL)))}, 

NIL))) 

end;

Constructor MyApp.Init; 

begin

TApplication.Init; 

Ctx := NewtPCtxView, Init);

Insert(Ctx); 

RegisterHelpFile 

end;

Procedure MyApp.HandleEvent; 

var

HF: PHelpFile; 

HS: PDosStream; 

HW: PMyHelpWindow; 

const

HelpOpen: Boolean = False; 

Procedure DoHelp;

{Обеспечивает доступ к контекстно-зависимой справочной службе} 

var

С: Word;. 

begin

ClearEvent(Event);

{ Открываем DOS-поток: }

HS := New(PDosStream, Init('HELPTEST.HLP', stOpenRead));

{ Создаем и инициируем экземпляр объекта THelpFile: }

HF := New(PHelpFile, Init(HS));

HelpOpen := HSA.status = stOk;

if HelpOpen then

begin

{Создаем окно справочной службы и связываем его с потоком HS и текущим контекстом:}

HW := New(PMyHelpWindow, Init(HF, GetHelpCtx)); 

if ValidView(HW) <> NIL then 

begin



С := ExecView(HW); {Выдаем справку} 

Dispose(HW) {Ликвидируем окно} 

end;

HelpOpen := False 

end 

else

Dispose (HF, Done) 

end; 

begin

TApplication.HandleEvent (Event) ; 

case Event . Command of

cmHelp: if not HelpOpen then

DoHelp;  {Выдаем справку} 

cmChangeCtx: {Изменяем контекст по клавише F2} 

begin

if HelpCtx = 3 then

HelpCtx := 0 

else

inc (HelpCtx) ;

Ctx^.Draw;

ClearEvent (Event) 

end

end 

end;

Function MyHelpWindow.GetPalette; 

const

P = #16#17#18#19#20#47#21#13;

C: String [8] = P; 

begin

GetPalette := @C 

end ;

Constructor TCtxView. Init; 

var

R: TRect; 

begin

R. Assign (67 , 0, 80, 1) ;

TView.Init(R) ; 

end;

Procedure TCtxView. Draw; 

var

S: String;

B: TDrawBuffer;

C: Byte; 

begin

Str (Application^. HelpCtx, S) ;

S := 'Контекст = '+S;

С := GetColor(2) ;

MoveChar(B, ' ', C, Size.X) ;

MoveStr(B, S, C) ;

WriteLine(0, 0,Size.X,1,B) 

end; 

var

Р: МуАрр; 

begin

P.Init;

P.Run;

P.Done 

end.

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

HelpCtx := NNN;

где NNN - нужный контекст.

Для визуализации текущего контекста в программе используется простой объект TCtxView, с помощью которого в верхнем правом углу экрана выводится строка

Контекст = N 

N - текущий контекст.


Маршрутизация событий


Как уже говорилось, любая программа, работающая в среде Turbo Vision, является прямым или косвенным (через TApplicatiori) потомком TProgram и основана на обращении к трем главным методам этого объекта: Init, Run и Done. Например:

Uses App,...; 

type

TMyProgram = object (TApplication)

.....

end;

..... 

var

MyProgram = TMyProgram;

.....

begin {Исполняемая часть программы:}

MyProgram.Init; {Инициировать программу} 

MyProgram.Run; {Выполнить программу} 

MyProgram.Done {Завершить работу} 

end.

Процесс получения и обработки событий инициируется методом TProgram.Run, который для этого обращается к методу TGroup.Execute (любая .программа является потомком TGroup). Метод TGroup.Execute реализует следующий цикл: 

var

Event: TEvent;

begin

Event.What := evNothing; {Инициировать пустое событие} 

repeat {Основной цикл программы} 

if Event.What <> evNothing then

EventError(Event); {Событие не очищено - ошибка} 

GetEvent(Event); {Получить событие} 

HandleEvent(Event); {Передать событие обработчику} 

until EndState <> Continue;

.....

end;

Метод GetEvent наследуется всеми видимыми элементами от TView и является основным источником событий. Этот метод вначале проверяет, не подготовил ли событие метод PutEvent и, если это так, возвращает его. Затем GetEvent проверяет клавиатуру и мышь. Если какое-то из этих устройств изменило свое состояние, метод формирует и возвращает соответствующее событие. Если изменения не произошло, GetEvent обращается к методу TProgram.Idle, чтобы запустить «фоновую» задачу (подробнее об этом см. п. 18.6).

С помощью вызова метода TGroup.Execute события всегда начинают свой путь с модального видимого элемента. Напомню, что модальный элемент определяет точку диалога; в программе в каждый момент времени есть один и только один модальный элемент (см. п. 17.4). В самом начале программы таким элементом является обычно экземпляр объекта TProgram или его потомка. Если в программе открыто модальное диалоговое окно, оно обратится к TGroup.Execute и, следовательно, путь событий начнется с этого окна. В любом случае начинает обработку события метод HandleEvent модального видимого элемента. Дальнейший маршрут события зависит от того, является ли событие позиционированным, активным или общим.



Маскирование и очистка событий


Каждый видимый элемент имеет поле EventMask. По умолчанию значение этого поля устанавливается таким образом, чтобы видимый элемент обрабатывал все необходимые ему события и не откликался на другие. Например, TProgam и TDialog имеют EventMask = $FFFF, что позволяет им откликаться на любые возможные события, в том числе и определенные программистом. Кнопка TButton имеет EventMask =

$0311, т.е. откликается на события evBroadcast, evCommand, evKeyDown, evMouseAuto, evMouseUp и evMouseDown. Как видим, ей доступны все стандартные события, кроме evMouseMove - кнопка не может реагировать на перемещение мыши.

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

var

MouseButton: PButton;

.....

MouseButton := New(PButton, Init(...)); 

MouseButton.EventMask := evMouse; 

nsert(MouseButton);

Для очистки события следует обратиться к методу ClearEvent, который устанавливает в поле Event. What значение evNothing, а в поле Event.InfoPtr - адрес таблицы виртуальных методов объекта. Таким образом, в поле Event.InfoPtr остается «подпись» видимого объекта, который очистил событие. Эта информация используется для организации межэлементного взаимодействия (см.п.18.7).



Неиспользованное время


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

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

В следующем примере каждые 5 сек в правый верхний угол экрана выводится системное время. Для упрощения программы вывод осуществляется стандартными средствами Турбо Паскаля.

Uses DOS,CRT,App; 

type

TProg = object (TApplication)

Procedure Idle; Virtual; 

end;

Procedure TProg.Idle; 

const

old: Byte = 0; {Старое значение секунд} 

dt = 5; {Шаг вывода} 

var

ho,mi,se,s100: Word; Function TimeStr(k: Word): String; 

var

s: String [2]; 

begin

str(k,s);

if k < 10 then

s := '0'+s; 

TimeStr := s 

end; {TimeStr} 

begin {TProg.Idle}

GetTime(ho,mi,se,s100); 

if (se mod dt = 0) and (old <> se) then 

begin

Old := Se; T

extColor(Black) ; 

TextBackGround(White); 

GotoXY(72,1);

Write(TimeStr(ho)+ ':'+TimeStr(mi) + ':'+TimeStr(se)) 

end

end; {TProg.Idle} 

var

Prog:TProg; 

begin

Prog.Init; 

Prog.Run; 

Prog.Done 

end.

Разумеется, не следует поручать методу TView.Idle слишком сложную работу, иначе пользователь Вашей программы будет безуспешно нажимать на клавиши, пытаясь вернуть к жизни «зависшую» программу. Предполагается, что рабочий цикл метода не будет превышать нескольких сотен миллисекунд. Если все-таки Вы хотите выполнить достаточно длинную фоновую задачу, попытайтесь разбить ее на серию мелких шагов.



Ненужные события


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

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



Общие события


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

Модальный элемент получает событие и начинает передавать его своим подэлементам в Z-порядке. Если какой-то из подэлементов - группа, он также передает событие своим подэлементам и также в Z-порядке. Процесс продолжается до тех пор, пока не будет найден элемент, который обработает событие, или пока событие не получат все видимые элементы, принадлежащие (прямо или косвенно) модальному элементу.

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



Перекрытие GetEvent


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

Проще всего перекрыть метод можно при объявлении нового типа Вашей программы, например:

Арр,...;

Uses type

MyProgram = object (TApplication)

Procedure GetEvent(var Event: TEvent);Virtual;

.....

end;

Procedure MyProgram.GetEvent(var Event: TEvent); 

begin

'TApplication.GetEvent(Event); 

if Event.What = evNothing then 

begin

{Обращение к нестандартным источникам информации} 

end 

end;

Поскольку MyProgram в конечном счете наследует GetEvent от TView, все видимые элементы Вашей программы будут пользоваться новым источником информации.

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



Перекрытие HandleEvent


После того как Вы определили команду и установили элемент управления, который генерирует ее (например, элемент меню или кнопка диалогового окна), Вам нужно научить Ваш видимый элемент реагировать на возникновение этой команды.

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

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

Общий вид HandleEvent наследника:

procedure NewDescendant.HandleEvent(var Event: TEvent); 

begin

{Код, изменяющий или ограничивающей унаследованное поведение}

Inherited HandleEvent(Event); 

{Код, выполняющий дополнительные функции} 

end;

Другими словами, если Вы хотите, чтобы новый объект обрабатывал события не так, как это делал его предок, Вы должны перехватить определенные события до передачи события в метод HandleEvent предка. Если Вы хотите, чтобы Ваш новый объект вел себя также, как его предок, но с дополнительными функциями, Вы должны добавить код после вызова процедуры HandleEvent предка.



Позиционированные события


Позиционированные события - это всегда события от мыши (evMouse). Модальный видимый элемент получает позиционированное событие первым и начинает просматривать свои подэлементы в порядке, обратном их Z-упорядочению (см. п. 17.3.2), до тех пор, пока не найдет подэлемент, координаты которого включают в себя координаты указателя мыши. Затем модальный элемент передает событие найденному подэле-менту. Поскольку видимые элементы часто перекрываются на экране, может оказаться, что координата указателя мыши принадлежит более чем одному видимому элементу. Следование обратному Z-упорядочению гарантирует, что событие получит самый верхний видимый элемент. Заметим, что Вам не нужно прослеживать Z-упорядочение элементов в Вашем обработчике событий: достаточно вызвать унаследованный метод HandleEvent, который автоматически направит событие нужным образом.

Процесс передачи события продолжается до тех пор, пока не обнаружится терминальный видимый элемент (например, полоса скроллинга) или не будет найден ни один подэлемент с нужными координатами элемент не знает, как обработать событие, он передает его вверх по активной цепочке своему владельцу. Технически это реализуется с помощью выхода из обработчика HandleEvent вызовом Exit; событие не следует очищать обращением к ClearEvent. Процесс повторяется до тех пор, пока событие не будет обработано или не достигнет модального элемента. Если модальный элемент не знает, как обработать вернувшееся к нему событие, он вызывает метод EventError.

В Turbo Vision существуют средства, позволяющие видимым элементам, не принадлежащим цепочке активности, получать и обрабатывать активные события (см. п. 18.4).



Преобразование активных событий в команды


Как указать на необходимость выполнения команды? Для этого в Turbo Vision Вы просто создаете событие-команду (evCommand), в поле Command которой помещаете код нужной команды. Например:

const

cmMyCommand =100;

.....

{Ниже показан фрагмент обработчика событий:}

Event.What := evCommand; {Определяем событие-команду} 

Event.Command := cmMyCommand; {Указываем код команды} 

Event.InfoPtr := NIL; {Признак активного события} 

PutEvent(Event); {Создаем событие}

В этом фрагменте событие-команда создается обращением к методу PutEvent. Заметим, что поле Event.InfoPtr должно содержать NIL, если событие активно; если событие уже обработано, для его очистки используется стандартный метод ClearEvent, который помещает в поле What признак evNothing, а в поле InfoPtr - указатель @Self (указатель на таблицу виртуальных методов объекта). Подробнее об использовании поля Event.InfoPtr см.п.18.7.

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

Procedure MyProgram.HandleEvent(var Event);

.....

begin

Inherited HandleEvent(Event); 

case Event.What of 

evCommand:

begin {Обработать команды:} 

case Event.Command of

cmMyCommand: MyProcedure; {Выполнить действия, связанные с командой cmMyCommand}

.....

else

exit {He обрабатывать непредусмотренные команды} 

end; {case}

ClearEvent(Event) {Очистить событие} 

end;

.....

end;

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

В следующем примере создается диалоговое окно с двумя кнопками. При нажатии кнопки «Команда cmPrint» окно закроется и на экран будет выведена строка


Действие команды cmPrint

Если нажать кнопку «Выход» или закрыть окно клавишей Esc, эта строка не появится.

Uses CRT,App,Dialogs,Objects,Drivers,Views; 

type

PProg = TProg;

TProg = object (TApplication)

Constructor Init; 

end;

PDial = TDial;

TDial = object (TDialog)

Procedure HandleEvent(var Event: TEvent); Virtual;

end; 

const

cmPrint = 100; Constructor TProg.Init; 

var

R: TRect;

Dia: PDial; 

begin

Inherited Init;

R.Assign(20,9,60,17);

Dia := New(PDial, Init(R,''));

R.Assign(3,4,22,6) ;

DiaA.Insert(New(PButton,

Init(R,'Команда cm~P~rint',cmPrint,bfDefault)));

R.Assign(23,4,35,6);

DiaA.Insert(New(PButton,Init(R,'Выход',cmCancel,bfNormal)));

if ExecView(Dia) = cmPrint then

begin

{Вывод сообщения "в лоб", с помощью стандартных средств Турбо Паскаля. В.- TurboVision есть более удобные способы вывода сообщений}

GotoXY(30,12);

TextColor(Black);

TextBackground(White);

Write (' Действие команды cmPrint ')

end

end; {TProg.Init}

Procedure TDial.HandleEvent(var Event: TEvent); 

begin

Inherited HandleEvent(Event);

if (Event.What = evCommand) and

(Event.Command = cmPrint) then EndModal(cmPrint) 

end; {TDial.HandleEvent) 

var

Prog: TProg; 

begin

Prog.Init;

Prog.Run;

Prog.Done 

end.

В обработчике событий диалогового окна TDial.HandleEvent вначале вызывается стандартный обработчик TDialog.HandleEvent. Это дает возможность кнопке «Команда cmPrint» преобразовать событие, связанное с ее выбором, в команду cmPrint. Вновь созданное событие возвращается обработчику TDialHandleEvent, т.к. именно он является обработчиком событий модального элемента. Возвращаемая модальным элементом команда служит значением стандартной функции ExecView. Для упрощения программы вывод сообщения реализуется стандартными средствами Турбо Паскаля. В Turbo Vision имеется процедура MessageBox, обеспечивающая более удобный вывод сообщений.


Природа событий


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

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

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

type

TEvent = record 

What: Word;{Тип события} 

case Word of evNothing: (); {Пустое событие} 

evMouse: ( {Событие от мыши:}

Buttons: Byte; {Состояние кнопок} 

Double : Boolean; {Признак двойного нажатия} 

Where : TPoint); {Координаты мыши} 

evKeyDown: ( {Событие от клавиатуры:}

case Integer of

0: (KeyCode: Word); {Код клавиши} 

1: (CharCode: Char; 

ScanCode: Byte)); 

evMessage: ( {Событие-сообщение} 

Command: Word; {Код команды} 

case Word of

0: (InfoPtr : Pointer) 

1: (InfoLong: Longlnt)

2: (InfoWord: Word);

3: (Infolnt : Integer);

4: (InfoByte: Byte);

5: (InfoChar: Char));

end;

Ядром события является поле What, которое описывает тип события. Оставшаяся часть записи содержит дополнительную информацию, например, код нажатой клавиши или координаты точки, в которой была нажата кнопка мыши.



Пустые события


Пустым событие становится после его обработки каким-либо видимым элементом. Технически пустое событие соответствует значению TEvent. What = evNothing = 0. Когда событие обработано, видимый элемент вызывает метод ClearEvent, с помощью

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



Как уже неоднократно подчеркивалось, программы,


Природа событий
Виды событий
События от мыши
События от клавиатуры
Сообщения
Пустые сообщения
Маршрутизация событий
Позиционированные события
Общие события
Фаза событий
Команды
Преобразование активных событий в команды
Запрещение и разрешение команд
Модификация и обработка событий
События, определенные пользователем
Маскирование и очистка событий
Перекрытие HandleEvent
Перекрытие GetEvent
Неиспользованное время
Ненужные события
Взаимодействие видимых элементов
Контекстная помощь
Как уже неоднократно подчеркивалось, программы, работающие в среде Turbo Vision,- это программы, управляемые событиями. В этой главе подробно рассматривается механизм событий и способы их использования.

События, определенные пользователем


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

Необходимость в новых классах событий может возникнуть в том случае, когда Ваша программа работает с нестандартными источниками информации. Если, например, Вы собираетесь работать с последовательным портом, Вы, возможно, захотите определить класс событий evSerial, используя для его идентификации один или несколько старших разрядов поля Event.What. Технически получить информацию из нового источника и сделать ее событием можно путем перекрытия метода TProgram.GetEvent (см.п. 18.6.4) или «фонового» метода TProgram.Idle (см.п. 18.6.5).

По умолчанию все новые классы событий маскируются маской evMessage, т.е. считаются сообщениями. Такие события модальный элемент рассылает всем своим подэ-лементам в Z-порядке (см. 17.3.2). Если Вы хотите, чтобы новый класс событий передавался как позиционированные или активные события, Вы можете изменить маски этих событий. В Turbo Vision определены маски PositionalEvents и FocusedEvents. Первая позволяет всем видимым элементам рассматривать событие как позиционированное, вторая - как активное. По умолчанию маска PositionalEvents выделяет все биты evMouse, a FocusedEvents содержит evKeyboard. Если Вам понадобится, чтобы новый класс событий обрабатывался так же как активные или позиционированные события, Вам необходимо добавить к нужной маске биты, соответствующие этому классу в поле What.

Например, создан новый класс событий evSerial с маской $8000, т.е. этот класс связан со старшим разрядом поля What. Если потребуется обработать этот класс так же как активные события, нужно задать новую маску:

const

evSerial = $8000;

.....

FocusedEvents := FocusedEvents or evSerial;

Обратите внимание: при добавлении к любой маске новых разрядов следует применять поразрядные операции над целыми числами (операции or, and, not). He следует использовать операцию арифметического сложения (+), так как в этом случае разрешен поразрядный перенос и вновь полученная маска может оказаться не той, какую Вы хотели. Если, например, к маске evMouse прибавить единицу, получится маска evKeyboard, т.е.

evKeyboard = evMouse + 1

В то же время операция

evMouse or 1

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



События от клавиатуры


В этом классе событий имеется единственное событие evKeyDown, связанное с нажатием на клавишу. В поле TEvent.KeyCode в этом случае содержится так называемый расширенный код клавиши, который Вы можете интерпретировать как совокупность двух байт: CharCode и ScanCode. При нажатии на алфавитно-цифровую клавишу поле CharCode содержит соответствующий ASCII-символ, при нажатии на функциональную клавишу поле CharCode содержит символ #0, а поле ScanCode - код сканирования нажатой клавиши. Для облегчения идентификации нажатой клавиши можно использовать константы kbXXXX, определенные в интерфейсной части модуля Drivers.



События от мыши


Существуют 4 вида событий от мыши: событие evMouseDown возникает как отклик на нажатие кнопки мыши; когда кнопка отпускается, возникает событие evMouseUp; перемещение указателя мыши порождает событие evMouseMove; наконец, если кнопка мыши удерживается в нажатом состоянии, Turbo Vision периодически генерирует событие evMouseAuto. С каждым событием от мыши в записи TEvent передаются также координаты, которые имеет указатель мыши в данный момент. Отметим, что в отличие о.т других координат Turbo Vision координаты мыши задаются относительно границ экрана, а не относительно границ какого-либо видимого элемента. Горизонтальная координата мыши меняется в диапазоне от 0 до 79, вертикальная - от 0 до 24 (режим 25 строк на экране) или от 0 до 42/49 (режим 43/50 строк).

Рис. 18.1. Разряды поля What



Сообщения


Сообщения бывают трех видов: команды, общие сообщения и пользовательские сообщения. Команды помечаются в поле What флагом evCommand, общие сообщения -флагом evBroadcast и пользовательские сообщения - константой, определенной пользователем. Большинство событий преобразуется в команды. Например, если пользователь отметит мышью какое-то поле строки статуса, сообщение от мыши поступит в конечном счете в обработчик событий строки статуса (любой видимый элемент имеет метод HandleEvent, называемый обработчиком событий, см. п.16.6.2), который определит, какое именно поле было отмечено. С каждым полем строки статуса обычно связана какая-то команда, поэтому обработчик очистит пришедшее к нему сообщение от мыши и создаст новое сообщение, содержащее выбранную команду. Общие и пользовательские сообщения не являются исключением и обычно также преобразуются в команды.



Виды событий


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

if Event.What and ((svMouseDown or 

evMouseUp or evMouseMove or evMouseAuto} <> 0) then ... 

if Event.What and (evMouse <> 0) then ...

Для анализа класса событий используются следующие маски:

evNothing {'Пустое' событие};

evMouse {Событие от мыши};

evKeyboard {Событие от клавиатуры};

evMessage {Сообщение}

На рис. 18.1 показаны разряды поля What и соответствующие маски.



Взаимодействие видимых элементов


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

Если программа спроектирована правильно и видимые элементы требуют взаимодействия между собой, можно создать объект-посредник. Типичным примером такого объекта является внутренний буфер Clipboard диалоговой среды Турбо Паскаля (опция Edit). Для передачи фрагмента текста из одного окна редактора в другое фрагмент помещается в буфер командными клавишами Shift-Del или Ctrl-Ins, затем вызывается другое окно и фрагмент вставляется в нужное место командой Shift-Ins. Вы можете организовать такого рода буфер и в Вашей программе, так как с помощью Turbo Vision Вам доступны все средства диалоговой среды Турбо Паскаля. Преимуществом создания объекта-посредника является то, что с его помощью легко решается проблема взаимодействия сразу нескольких объектов друг с другом. Например, если Вы создаете сложную интегрированную систему, включающую текстовый редактор, систему управления базами данных, электронную таблицу и сервисные средства, промежуточный буфер позволит передать данные из текстового редактора в базу данных, или из базы данных в электронную таблицу и т.д.

Другим способом организации взаимодействия элементов является использование событий-сообщений. Эти события создаются с помощью глобальной функции Message. Функция Message описана в интерфейсной части модуля Views следующим образом:

Function Message (Receiver: Pview; What, Command: Word;

InfoPtr: Pointer): Pointer;

Первым параметром указывается ссылка на экземпляр объекта, которому адресуется сообщение. Остальные параметры используются для создания записи TEvent. Функция создает запись события и, если это возможно, вызывает метод Receiver^ .HandleEvent для обработки этого события. Если адресат не существует или при обращении к функции указан параметр Receiver = NIL, функция возвращает NIL - это означает, что событие не было обработано. Если событие успешно обработано (Receiver^. HandleEvent возвращает событие с полем What = evNothing), функция вернет указатель Event.InfoPtr.


Как уже говорилось в п. 18.6.2, стандартный метод ClearEvent очищает событие, устанавливая Event. What = evNothing и Event.InfoPtr = @Self. Таким образом, объект, обработавший и очистивший событие, оставляет в Event.InfoPtr указатель на свою таблицу виртуальных методов. Этот указатель позволяет полностью идентифицировать объект-получатель сообщения и организовать связь с ним.

Рассмотрим пример. В диалоговой среде Турбо Паскаля используется окно отладки, с которым Вы, очевидно, хорошо знакомы.. Если программист потребовал открыть это окно, среда должна проверить, открывалось ли оно ранее: если да, то окно просто переносится наверх (в смысле Z-упорядочения), если нет, создается вновь. Для реализации проверки среда дает сообщение

AreYouThere := Message(DeskTop,evBroadcast,

cmFindWatchWindow, NIL);

В методе HandleEvent окна отладки есть проверка на команду cmFindWatchWindow:

if (Event.What = evBroadcast) and

(Event.Command = cmFindWatchWindow) then 

ClearEvent(Event);

Если окно существует, оно очистит событие и оставит в нем свою «подпись», поэтому сразу за передачей сообщения реализуется такая проверка:

if AreYouThere = NIL then

CreateWatchWindow {Создать новое окно} 

else

AreYouThereA.Select; {Поместить существующее окно наверх}

Поскольку окно отладки - это единственный объект, который знает, как реагировать на команду cmFindWatchWindow, в диалоговой среде Турбо Паскаля может использоваться только одно окно этого типа.

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

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

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


Запрещение и разрешение команд


В качестве значения константы-команды можно использовать любое число в диапазоне от О до 65535, однако следует учесть, что диапазоны 0...99 и 2S6...999 Turbo Vision резервирует для стандартных команд и их не следует использовать для определения команд пользователя. Два диапазона зарезервированных команд выбраны потому, что команды с кодами 0...255 можно временно запретить, в то время как остальные команды запретить невозможно. Для запрещения или разрешения команд используется глобальный тип TCommandSet, представляющий собой множество чисел в диапазоне 0...256 (мощность множеств в Турбо Паскале не может превышать 256, вот почему запретить можно только первые 256 команд). Команды запрещаются обращением к методу DisableCommands, а разрешаются обращением к EnableCommands. Диалоговые элементы, связанные с запрещенными командами, выделяются оттенком и их нельзя выбрать мышью или командными клавишами. Например, если в конструкторе TProgJnit (см. предыдущий пример) перед оператором

if ExecView(Dia) = cmPrint then

вставить оператор

DisableCommand([cmPrint]);

кнопка «Команда cmPrint» будет выведена цветом фона окна и станет недоступна для диалога.



Доступ к элементам коллекций


Итак, оператором

BookList := New(PCollection, Init(50,10)); 

мы объявили о создании коллекции, а операторами

Insert(New(PBook, Init(...)))

наполнили эту коллекцию нужными элементами. Как осуществить доступ к элементам коллекции? Для этого можно использовать несколько способов.

Во-первых, к любому элементу коллекции можно обратиться по его порядковому номеру (индексу). В отличие от массивов Турбо Паскаля, индексы которых могут иметь произвольные границы, коллекции индексируются целыми числами в диапазоне от 0 до Count-l (Count - общее количество элементов в коллекции). Любая коллекция имеет поле Count, которое указывает текущую длину коллекции. Чтобы по индексу получить доступ к нужному элементу, используется метод At, который возвращает указатель на элемент.

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

PrintYear(BookList,1991);

Кроме того, в раздел описаний программы добавим две новых процедуры:

Procedure PrintItem(A,T,PB: String; Y,P: Word); 

{Выводит на экран элемент коллекции} 

begin

WriteLn(A); WriteLn(' ',Т): 

WriteLnC ',РВ,', ',Y,', ',Р) 

end; {PrintItem}

Procedure PrintYear(BookList: PCollection; Y: Word); 

{Выводит на экран издания нужного года выпуска} 

var

Book: PBook; 

k: Integer; 

begin

WriteLn;

for k := 0 to pred(BookList.Count) do 

begin

Book := BookList.At(k); 

with Book do if Year = Y then 

PrintItem(Autor,Title,PubHouse,Year,Pages) 

end 

end; {PrintYear}

В процедуре PrintYear организуется счетный цикл от 0 до pred (TCollection.Count). С помощью оператора

Book := BookList.At(k);

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


Отметим, что обращение к методу At с индексом, выходящим за границы коллекции, активизирует вызов метода TCollection.Error, который по умолчанию аварийно завершает исполнение программы (подробнее см. п. 17.6).

Помимо использования метода At коллекции обеспечивают доступ к трем итерационным методам, которые могут оказаться весьма удобными. Метод ForEach осуществляет некоторую заранее заданную операцию сразу над всеми элементами коллекции, а методы FirstThat и LastThat отыскивают в коллекции первый элемент, удовлетворяющий некоторому опять же заранее заданному критерию поиска: FirstThat ищет от начала коллекции к ее концу, a LastThat - в обратном направлении.

Чтобы воспользоваться методом ForEach мы должны сначала создать процедуру без параметров, которая осуществляет нужные действия над всеми элементами коллекции, и передать адрес этой процедуры как параметр обращения к ForEach. Например, для того чтобы вьшести на экран содержимое всего нашего каталога, мы должны внести в программу следующие изменения. Поместите в раздел описаний следующий текст процедуры PrintAll:

Procedure PrintAll (Book: PCollection) ; 

{Вывод всех элементов коллекции}

Procedure PrintBook(P: PBook) ; far; 

begin

with Р do

PrintItem (Autor, Title, PubHouse, Year, Pages)

end; {PrintBook}

begin {PtintAll}

WriteLn;

Book .ForEach (PrintBook) ; 

end; {PrintAll}

Как видите, эта процедура содержит внутреннюю процедуру PrintBook, в которой осуществляется нужное нам действие - вывод очередного элемента каталога на экран. Этот вывод достигается с помощью вызова уже использованной нами ранее процедуры PrintItem. Таким образом, описание процедуры PrintAll должно следовать после описания PrintItem, чтобы этот вызов был синтаксически правильным. Далее, вывод всех элементов коллекции в процедуре PrintAll осуществляется оператором

Book. ForEach (PrintBook) ;

который обращается к методу TCollection.ForEach, передавая ему в качестве параметра адрес процедуры PrintBook. Чтобы программа успешно выполнила нужные действия, процедура, адрес которой передается методу ForEach, должна удовлетворять двум условиям:



она должна быть рассчитана на дальнюю модель вызова (для этих целей мы указали директиву far сразу за заголовком PrintBook);

она должна быть локальной для процедуры, в которой реализуется вызов

ForEach, именно поэтому мы разместили ее в теле процедуры PrintAll, Осталось в тело главной программы поместить оператор

PrintAll (BookList) ;

перед оператором уничтожения коллекции. Если Вы запустите таким образом подготовленную программу на счет, на экран будет выведено:

Джордейн Р .

Справочник программиста персональных компьютеров типа IBM PC,

XT и AT

Финансы и статистика, 1991, 544

Шелдон

Язык Си для профессионалов

И. В. К. -СОФТ, 1991, 383

Скэнлон Л.

Персональные ЭВМ IBM PC и XT. Программирование на языке ассемблера

Радио и связь, 1991, 336

Йенсен К., Вирт Н.

Паскаль . Руководство для пользователя и описание языка

Финансы и статистика, 1982, 151

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

Точно таким же образом реализуется обращение к методам FirstThat и LastThat. Например, если нам потребуется отыскать в каталоге запись, содержащую слово «Вирт» в noлe Autor, можно добавить в программу следующие строки 

Procedure SearchAutor(BookList: Pcollection; A: String); 

Function FindAutor(P: PBook): Boolean; far; 

begin

FindAutor := pos(А, Р.Autor) <>0 

end; {FindAutor} 

var

Book: PBook; 

begin {SearchAutor}

Book := BookList.FirstThat(FindAutor); 

if Book = NIL then

WriteLn('Heт автора ',А)

else with Book do

begin

WriteLn;

PrintItern(Autor,Title,PubHouse,Year,Pages) 

end 

end; {SearchAutor}

В тело главной программы следует добавить оператор

SearchAutor(BookList,'Вирт'); 

Собственно поиск элемента коллекции реализуется оператором

Book := BookList.FirstThat(FindAutor);

который для этих целей обращается к методу TCollection.FirstThat. В этом методе реализуется последовательный анализ всех элементов коллекции, начиная с самого первого (с индексом 0), причем для анализа используется вызов функции FindAutor. Как видим, эта функция нужным образом анализирует очередную запись и возвращает True, если условие поиска удовлетворено. Метод FirstThat возвращает указатель на элемент коллекции, для которого удовлетворено условие поиска, или NIL, если этому условию не отвечает ни один элемент. Таким образом, оператор



if Book = NIL then

..... 

else

.....

проверяет результат поиска и выводит на печать найденный элемент коллекции или сообщение «Нет автора ...», если условие поиска не удовлетворено.

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

Любой элемент коллекции можно удалить или заменить новым. Для удаления используется метод AtFree, которому в качестве параметра передается индекс удаляемого элемента. При удалении элемента предполагается, что коллекция содержит указатели на объекты, порожденные от TObect и размещенные в куче, поэтому автоматически вызывается метод TObject.Done. Индексы всех элементов, размещенных в коллекции после удаляемого элемента, уменьшаются на 1.

С помощью метода DeleteAll удаляются все элементы из коллекции, но сама коллекция при этом сохраняется, т.е. очищенная коллекция будет иметь Count = 0. Для очистки коллекции вызывается AtFree для каждого элемента.

Чтобы заменить существующий элемент новым, используется метод AtPut (Index, Item), где Index - индекс заменяемого элемента, a Item - указатель на новый элемент.

Метод Atlnsert (Index, Item) вставляет новый элемент в коллекцию в позицию Index и увеличивает индексы всех ранее существовавших в коллекции элементов от элемента Index до конца коллекции на единицу, т.е. «раздвигает» коллекцию.


Элементы коллекций


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

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

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



Коллекции


Элементы коллекций

Создание коллекций

Доступ к элементам коллекций

Отсортированные коллекции

Коллекции строк

Полиморфные коллекции

Коллекции и управление памятью

Одним из недостатков языка Паскаль (и Турбо Паскаль) является невозможность создания и использования в программе массивов с переменной размерностью - так называемых динамических массивов. Этот недостаток особенно ощутимо сказывается в диалоговых программах, работа которых в существенной своей части определяется действиями пользователя. Если программист заранее не знает, какие именно требования к используемым в программе массивам предъявит пользователь, он обычно резервирует для них максимально возможные объемы памяти или размещает массивы в куче. И тот и другой способы нельзя считать вполне удовлетворительными: в первом случае возникают неестественные ограничения на предельно возможные размеры массивов или выделенная под их размещение память расходуется нерационально, во втором случае приходится прибегать к дополнительным ухищрениям, чтобы организовать индексированный доступ к динамической памяти.

Разработчики Turbo Vision решили проблему кардинально, создав механизм коллекций. Хотя экземпляры объектов можно объединять в массивы, как и «обычные» переменные Паскаля, Вы вряд ли захотите использовать массивы для их хранения: коллекции не только снимают проблемы фиксированных границ, но имеют еще и целый ряд новых возможностей, которыми массивы не обладают.

Изучению коллекций посвящается эта глава.





Коллекции и управление памятью


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

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

65520 div SizeOf (Pointer) = 16380

Иными словами, все указатели на элементы коллекции должны располагаться в пределах одного сегмента. Величина 16380 задается значением глобальной константы MaxCollectionSize, объявленной в интерфейсной части модуля Objects.

Таким образом, при наполнении коллекции необходимо следить за общим количеством элементов (переменная TCollection.Couni), которое не может превысить значение, задаваемое константой MaxCollectionSize. Кстати, если при обращении к методу TCollection.Init начальное значение N0 коллекции указано слишком большим, оно заменяется на MaxCollectionSize.

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

В ходе расширения коллекции может оказаться, что динамической памяти не хватает для размещения нового массива указателей (напомню, что расширение коллекции заключается в создании нового массива из NO + k*ND указателей, где N0 - начальная длина, ND - шаг наращивания коллекции, k= 1,2, ...; после этого в новый массив копируется старый массив указателей, а место, выделенное под размещение старого массива, возвращается в кучу). Если обнаружена нехватка памяти для расширения коллекции или если при обращении к методу TCollection.At указан индекс, превышающий размер коллекции, вызывается метод TCollection.Error. По умолчанию этот метод завершает выполнение программы с кодом 212 ошибки периода исполнения. Вы можете перекрыть TCollection.Error, чтобы нужным образом отреагировать на возникшую ситуацию. В этом случае следует учесть, что заголовок TCollection.Error в Turbo Vision имеет следующий вид:

Procedure TCollection.Error (Code, Info': Integer);

Параметр Code в стандартных ситуациях соответствует следующим константам:

coIndexError = -1 - индекс метода At превысил значение Count-1; параметр Info содержит неверный индекс;

coOverflow = -2 - нет памяти для расширения коллекции; параметр Info содержит требуемый размер коллекции. Константы соХХХХ определены в интерфейсной части модуля Objects.



Коллекции строк


Для создания и использования коллекции отсортированных строк в Turbo Vision используется объект TSrtingCollection. Этот объект является прямым потомком от TSortedCollection и отличается от него тем, что его метод Compare не является абстрактным - по умолчанию он осуществляет обычное для Турбо Паскаля лексикографическое сравнение двух строк. Таким образом, если Вам необходимо отсортировать коллекцию строк по алфавиту (точнее, в соответствии с внутренней кодировкой символов), Вы можете использовать экземпляр объекта TSortedCollection без какого-либо перекрытия его методов.

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

Uses Objects; 

var

f: file of Char; Function OpenFile(var Name: String): Boolean;

{Возвращает FALSE, если нельзя открыть файл} 

begin

if ParamCount = 1 then

Name :=" ParamStr(1) {Первый параметр в строке вызова программы должен содержать имя файла,} 

else , {если это не так, анализируется файл,

содержащий текст программы} 

Name := copy(ParamStr(0),1,

posC . ' ,ParamStr(0))) + 'PAS' ; 

Assign(f, Name);

{$I-} 

Reset(f);

{$I+}

OpenFile := IOResult=0 

end; {OpenFile} 

Function GetWord: String; 

{Получает из файла очередное слово} 

var

с: Char;

w: String;

Function Letter(var c: Char): Boolean; 

{Возвращает TRUE, если символ - буква} 

begin

с := UpCase (с);

{проверяем на строчную русскую букву:}

if с in ['а'..'п'] then . {а - русская буква} 

с := chr(ord(c)-ord('а')+ord('А') ) {А - русская буква}

else if с in ['р'..'я'] then , {р - русская буква} 

с := chr(ord(с)-ord('р')+ord('Р')) ; {Р - русская буква}

{Проверяем на заглавную букву:} 

Letter := с in ['А'..'Z','А1..'Я'] 

end; {Letter} 

begin {GetWord}

w : = ' ' ;

С := #0;

while not EOF(f) and not Letter(c) do 


Read(f,c);

if not EOF(f) then while not EOF(f) and Letter(c) do 

begin

w .:= w+c; 

Read(f,c) 

end ;

GetWord := w 

end; {GetWord}

Procedure PrintList(List: PStringCollection); 

{Выводит на экран список слов} 

Procedure PrintWord(p: PString); far; 

begin

Write(р^, ' ':20-Length(р^)) 

end; {PrintWord} 

begin {PrintList}

WriteLn;

WriteLn;

List^.ForEach(@PrintWord);

WriteLn

end; {PrintList} 

var

WordList: PStringCollection;

w: String;

begin {Основная программа}

if not OpenFile(w) then

WriteLn('Нельзя открыть файл '+w)

else 

begin

WordList := New(PStringCollection, Init(200,10)); 

repeat

w := GetWord;

if (w <> ' ') and (MaxAvail > 255) then 

WordList.Insert(NewStr(w)) 

until w=''; 

PrintList(WordList) 

end 

end.

Отметим, что в операторе

if (w <> '') and (MaxAvail > 255) then

осуществляется контроль за доступной динамической памятью. В Turbo Vision есть и встроенные способы контроля кучи - см. п. 19.6.

Как и в любой другой отсортированной коллекции, в коллекции строк по умолчанию хранятся элементы с уникальными ключевыми полями. Чтобы подавить контроль за уникальностью строк, добавьте оператор

WordList.Duplicates := True; 

сразу за оператором создания коллекции

WordList := New(PStringCollection, Init(200,10));

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

Метод TStringCollection.Compare следует перекрыть, если Вы хотите осуществить свой способ сортировки строк. Например, используя объект

type

PStrSor =TStrSor;

TStrSor = object (TStringCollection)

Function Compare(k1, k2: Pointer): Integer; Virtual; 

end;

Function TStrSor.Compare(k1, k2: Pointer): Integer; 

var

s1: PString absolute k1; 

s2: PString.absolute k2; 

begin

if s1< s2 then

Compare := 1 

else if s1 = s2 then

Compare := 0 

else

Compare := -1 

end;

вместо PStringCollection, Вы сможете вывести на экран список слов, отсортированных в обратном порядке.


Отсортированные коллекции


Часто бывает необходимо каким-либо образом упорядочить коллекцию, т.е. расставить ее элементы в определенном порядке. Для этих целей в Turbo Vision предусмотрен специальный объект TSortedCollection. Этот объект порожден от TCollection и, следовательно, уже умеет создавать коллекцию, вставлять в нее элементы и удалять их. Единственное, чего он не умеет - это сортировать коллекцию. В TSortedCollection есть абстрактный метод Compare, который используется для упорядочения элементов и который Вы должны перекрыть, чтобы обеспечить нужную Вам сортировку. Таким образом, чтобы создать отсортированную коллекцию, Вы должны создать объект-потомок от TSortedCollection и перекрыть его метод Compare.

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

Например, нам требуется создать отсортированную коллекцию, содержащую каталог библиотеки (см. пример п. 19.3), причем в качестве ключевого используется поле Autor^. Тогда создадим новый объект

type

PSort = Tsort;

TSort = object (TSortedCollection)

Function Compare(Key1, Key2: Pointer): Integer; Virtual; 

end;

чтобы перекрыть метод Compare. Если теперь объявить новый метод TSort.Compare следующим образом:

Function TSort.Compare(Key1, Key2: Pointer): Integer;

var

A: PSort absolute Key1;

B: PSort absolute Key2; 

begin

if A.Autor < BA.Autor then

Compare := -1 else if A.Autor = B.Autor then

Compare := 0 

else

Compare := 1 

end; {TSort.Compare}

то после объявления

var

BookList: PSort;

вместо

var

BookList: PCollection;

программа выведет каталог, отсортированный по фамилиям авторов:

Джордейн Р.

Справочник программиста персональных компьютеров типа IBM PC, XT и AT

Финансы и статистика, 1991, 544 Йенсен К., Вирт Н.

Паскаль. Руководство для пользователя и описание языка финансы и статистика, 1982, 151 Скэнлон Л.


Персональные ЭВМ IBM PC и XT. Программирование на языке ассемблера

Радио и связь, 1991, 336 Шелдон

Язык Си для профессионалов И.В.К.-СОФТ, 1991, 383

Ключевое поле определяется методом TSortedCollection.KeyOf. Этот метод по заданному в качестве параметра обращения указателю на элемент коллекции возвращает указатель на ключевое поле. По умолчанию метод KeyOf возвращает указатель на весь элемент, однако Вы можете перекрыть его новым методом, возвращающим указатель на нужное ключевое поле. Пусть, например, нам требуется отсортировать каталог по году издания книг (поле Year). Добавим в описание объекта TSort перекрытие метода KeyOf:

type

TSort = object (TSortedCollection)

.....

Function KeyOf(Item: Pointer): Pointer; Virtual; 

end ;

Опишем новый метод следующим образом:

Functon TSort.KeyOf(Item: Pointer): Pointer; 

begin

KeyOf := PBook(Item).Yеаr 

end;

и изменим описание метода Compare:

Function TSort.Compare(Key1, Key2: Pointer): Integer; 

var

A: Integer absolute Key1; 

B: Integer absolute Key2; 

begin

if А < B then

Compare := -1

else if А = B then

Compare := 0 

else

Compare := 1

end; {TSort.Compare}

Теперь после запуска программы на экран будет выведено:

Йенсен К., Вирт Н.

Паскаль. Руководство для пользователя и описание языка

Финансы и статистика, 1982, 151

Джордейн Р.

Справочник программиста персональных компьютеров типа IBM PC,

XT и AT

Финансы и статистика, 1991, 544

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

Можно ли поместить в коллекцию два или больше элементов с одинаковыми полями? Turbo Vision позволяет сделать это: поле TSortedCollection.Duplicates по умолчанию содержит FALSE, что указывает на уникальность ключевого поля; если перед наполнением коллекции Вы поместите в это поле значение TRUE, коллекция не будет контролировать уникальность ключевых полей.



Изменим начало раздела исполняемых операторов главной программы следующим образом:

begin

Bookiist := New(PSort, Init(50,10) ) ;

with BookList do

begin

Duplicates := True; {Отменяем уникальность ключей}

......

end;

.....

end.

Теперь на экран будет выведено:

Йенсен К., Вирт Н.

Паскаль. Руководство для пользователя и описание языка Финансы и статистика, 1982, 151 Скэнлон Л.

Персональные ЭВМ IBM PC и XT. Программирование на языке ассемблера

Радио и связь, 1991, 336 Шелдон

Язык Си для профессионалов И.В.К.-СОФТ, 1991, 383 Джордейн Р.

Справочник программиста персональных компьютеров типа IBM PC, XT и AT Финансы и статистика, 1991, 544

Заметим, что, очередной элемент вставляется перед первым элементом с равным значением ключевого поля. Точно также поиск First.That вернет указатель на первый из нескольких элементов с одинаковыми ключевыми полями, а метод LastThat - на последний из них.


Полиформные коллекции


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

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

Для нашего примера можно создать следующий объект-родитель:

type

PGraphObject = TGraphObject;

TGraphObject = object (TObject)

X, Y: Integer; {Координаты характерной точки} 

Constructor Init; {Создание объекта} 

Procedure Draw; Virtual; {Вычерчивание}

end;

Объект TGraphObject содержит общие для всех потомков поля и методы. Заметим, что методы Init и Draw должны перекрываться в объектах-потомках, поэтому их содержимое не имеет значения. Однако полезно вынести в них некоторые общие для всех потомков части программы. Например, конструктор Init может помещать в поля X и Y заданные начальные значения; если этот метод наполнить конкретным содержанием, он может использоваться во всех объектах иерархии:

Constructor TGraphObject.Init;

{Присваивает случайные значения координатам X и Y} 

begin

X := Random(GetMaxX);

Y := Random(GetMaxY) 

end;

Здесь GetMaxX, GetMaxY - максимальные координаты графического экрана. Виртуальный метод Draw весьма специфичен: его конкретная программная реализация будет существенно зависеть от типа объекта. Поэтому объявим этот метод абстрактным:


Procedure TGraphObject.Draw;

{ Абстрактный метод для вычерчивания графического примитива} 

begin

Abstract 

end;

Как видим, тело этого метода содержит обращение к глобальной процедуре Abstract, которая аварийно завершает выполнение программы и выдает соответствующую диагностику, если в программе используется вызов метода TGraphObject.Draw. Таким стандартным способом Turbo Vision сообщает пользователю о некорректности программы. Вы можете сделать тело этого метода другим, если Вас не устраивают стандартные действия, однако во всех случаях имеет смысл предусмотреть возможность некорректного вызова абстрактного метода, даже если вновь создаваемая библиотека будет использоваться только Вами - это значительно облегчит отладку программы.

Создадим три потомка от TGraphObject:

type

PPoint =TPoint;

TPoint = object (TGraphObject) {Точка}

Procedure Draw; Virtual; 

end;

PCircle = TCircle; {Окружность} 

TCircle = object (TGraphObject)

R: Integer;

Constructor Init;

Procedure Draw; Virtual; 

end;

PRectangle = TRectangle; {Прямоугольник} 

TRectangle = object (TGraphObject)

W, H: Integer;

Constructor Init;

Procedure Draw; Virtual; 

end;

Объект TPoint (точка) не имеет новых полей и поэтому лишь перекрывает абстрактный метод TGraphObject.Draw:

Procedure TPoint.Draw; {Выводит точку на экран} 

begin

PutPixel (X, Y, White) 

end;

В объектах TCircle (окружность) и TRectangle (прямоугольник) инкапсулированы новые поля, поэтому перекрываются также и методы Init:

Constructor TCircle.Init;

{Создает окружность случайного радиуса в случайном месте}

begin

TGraphObject.Init; {Получаем координаты центра}

R := Random(GetMaxY div 2) {Получаем радиус} 

end;

Procedure TCircle.Draw; 

{Вычерчивает окружность} 

begin

Circle(X, Y, R) 

end;

Constructor TRectangle.init; 

{Создает случайный прямоугольник} 

begin

TGraphObject.Init;{Верхний левый угол} 

W := Random(GetMaxX div 2) {Ширина}



H := Random(GetMaxY div 2) {Высота} 

end;

Procedure TRectangle.Draw; 

{Вычерчивает прямоугольник} 

begin

Rectangle(X, Y, X+W, Y+H) 

end;

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

Procedure DrawAll (C: PCollection) ; 

{Выводит все элементы полиморфной коллекции} 

Procedure DrawItem(p: PGraphObject); far; 

begin

p.Draw {Это и есть полиморфизм в действии!} 

end; 

begin

С.ForEach(@DrawItem) 

end;

Как видим, в процедуре DrawItem полиморфизм используется дважды: во-первых, метод ForEach обращается к ней, передавая в качестве параметра обращения нетипизированный указатель на элемент коллекции; это позволяет трактовать параметр как указатель на любой объект, в том числе и на TGraphObject. Во-вторых, в процедуре используется обращение к виртуальному методу объекта-родителя Draw: поскольку этот метод перекрывается во всех потомках, каждый из них будет использовать свой метод Draw для вывода на экран.

Сформируем программу, поместив в нее вместо, точек уже рассмотренные фрагменты:

Uses Objects,Graph,CRT; 

type

.....

Constructor TGraphObject.Init;

.....

Procedure TGraphObject.Draw;

.....

Constructor TPoint.Init;

.....

Procedure TPoint.Draw;

.....

Constructor TCircle.Init;

.....

Procedure TCircle.Draw;

.....

Constructor TRectangle.Init;

.....

Procedure TRectangle.Draw;

.....

Procedure DrawAll(C: PCollection);

.....

var

a, r, k: Integer; 

List: PCollection; 

p: Pointer;

begin . a := 0;

{Инициируем графический режим работы экрана:} 

InitGraph(a, r, '\TP\BGI'); 

r := GraphResult; if r <> 0 then

WriteLn(GraphErrorMsg(r)) {Ошибка инициации} 

else 

begin

{Создаем коллекцию:}

List := New(PCollection, Init (20,5)); 

{Наполняем ее 20 элементами:} 

for k := 1 to 20 do 

begin

case k mod 3 of 

0: p := New(PPoint, Init); 

1: p := New(PCircle, Init);

2: p := New(PRectangle, Init)

end;

if p <> NIL then List.Insert(p) 

end ;

DrawAll(List) ; {Выводим на экран все элементы} 

While not KeyPressed do;{Ждем нажатия на любую клавишу} 

CloseGraph {Возвращаемся в текстовый режим} 

end 

end.

В этой программе предполагается, что драйвер графического экрана расположен в каталоге \TF\BGI на текущем диске. Если это не так, следует указать маршрут поиска

этого драйвера в качестве параметра обращения к процедуре InitGraph. Кроме того, каталог, содержащий стандартную графическую библиотеку Graph, должен быть указан опцией Options/Directories/Unit directories, если, разумеется, библиотека не содержится в текущем каталоге.


Создание коллекций


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

Uses Objects; 

type

PBook = TBook;

TBook = object (TObject)

Autor: PSbring; {Автор}

Title: PString; {Название}

PubHouse: PString; {Издательство}

Year: Word; {Год издания}

Pages: Word; {Количество страниц}

Constructor Init(A,T,PH: String; Y,P: Word);

Destructor Done; Virtual; 

end ;

Мы включили' в объект поля и два основных метода работы с ними: конструктор Init используется для размещения в динамической памяти очередной записи каталога, деструктор Done удаляет записи из кучи. Заметим, что в объекте задаются не сами текстовые строки, а указатели на них (тип PString в Turbo Vision описан как указатель на тип String). Такое размещение данных как правило дает значительную экономию памяти. Например:

Constructor TBook.Init(А,Т,РН: String; Y,P: Word); 

begin

Autor := NewStr(A); 

Title := NewStr(T); 

PubHouse := NewStr(PH); 

Year := Y; 

Pages := P 

end; {TBook.Init}

Используемые в конструкторе функции NewStr размещают в динамической памяти текстовую строку, выделяя под нее минимально необходимую память, что значительно выгоднее по сравнению с типичным описанием текстовых полей вида

type

TBook = object (TObject)

Autor, Title, PubHouse: String;

.....

end;

Для освобождения динамической памяти в деструкторе Done используется процедура DisposeStr:

Destructor TBook.Done;

begin

DisposeStr(Autor); 

DisposeStr(Title); 

DisposeStr (PubHouse)

end; {TBook.Init}

После того как тем или иным способом определены типы данных, создание коллекции не вызывает проблем. Например:

var

BookList: PCollection; 

begin

BookList := New(PCollection, Init(50,10)); 


with BookList do 

begin 

Insert(New(PBook, Init('Джордейн Р.',

'Справочник программиста персональных компьютеров'+

' типа IBM PC, XT и AT','Финансы и статистика',

1991,544))); 

Insert(New(PBook, Init('Шелдон',

'Язык Си для профессионалов','И.В.К.-СОФТ',1991,383))); Insert(New(PBook, Init('Скэнлон Л.',

'Персональные ЭВМ IBM PC и XT. '+

'Программирование на языке ассемблера',

'Радио и связь',1991,336))); 

Insert(New(PBook,

Init('Йенсен К., Вирт Н.',

'Паскаль. Руководство для пользователя '+

'и описание языка','Финансы и Статистика',1982,151))); 

end ;

.....

Dispose(BookList, Done); 

end;

Для создания коллекции мы обратились к методу TCollection.Init, указав ему начальную длину коллекции (50 элементов) и шаг наращивания (10 элементов). Руководствуясь этими указаниями, Turbo Vision зарезервирует в динамической памяти место для размещения 50 указателей. Если в ходе наполнения коллекции ее длина превысит начальную, Turbo Vision будет наращивать коллекцию порциями, каждая из которых достаточна для размещения 10 указателей.

Смысл параметров, передаваемых методу TCollection.Init, станет понятнее, если рассмотреть механизм создания и обновления коллекции. Вначале в куче резервируется участок памяти, достаточный для размещения массива из N0 указателей (N0 - начальный размер коллекции). Если в ходе наполнения коллекции ее длина превысит N0 элементов, резервируется новый участок памяти, достаточный для размещения массива из NO + DN указателей (DN - шаг наращивания коллекции), затем старый массив переносится на новое место, а память, выделенная под его размещение, возвращается в кучу. Таким образом, чем больше начальная длина коллекции и шаг ее наращивания, тем меньше суммарные потери времени на расширение коллекции, но и тем больше могут стать потери памяти, есди реальная длина коллекции окажется значительно меньше NO + k*DN (k = О, 1, 2,...).

Операторы Insert размещают в динамической памяти элементы коллекции. В реальной программе наполнение коллекции будет, судя по всему, осуществляться каким-то иным способом, чем простое программирование обращений к методу Insert (см., например, программу Notebook из гл.15). Для нас сейчас важно другое: мы нигде не говорили коллекции, какого типа объекты она будет хранить; для обеспечения нужных действий по размещению в памяти очередного элемента мы просто обращаемся к соответствующему методу Init, а уж он делает остальное - ведь он «знает» как это следует сделать.

Отметим, что обращение

Dispose(BookList, Done);

вызывает автоматическое обращение к методу TBook.Done перед уничтожением каждого элемента коллекции, после чего уничтожается экземпляр TCollection. Это стало возможным потому, что объект TBooh объявлен нами как потомок от TObject. Если бы мы его объявили независимым объектом

type

TBook = object

.....

end; 

мы должны были бы сами позаботиться об освобождении кучи, а обращение

Dispose(BookList, Done); 

привело бы к «зависанию» программы.


Использование потоков с произвольными данными


Хотя потоки спроектированы в основном для работы с объектами, Вы можете использовать их для хранения не только полей объектов, но и любых других данных. При этом не следует обращаться к методам Put и Get, так как они предполагают доступ к объектам. Вместо этого Вы должны обратиться к низкоуровневым процедурам Write и Read.

Следующая простая программа использует поток для сохранения десяти случайных целых чисел: 

Uses Objects; var

S: TBufStream; {Экземпляр потока}

k, j : Integer; 

begin

WriteLn('Запись в поток:');

S.lnit('Test.dat', stCreate, 512); {Создаем поток}

for k := 1 to 10 do

begin

j := Random(l00); {Получаем случайное целое} 

Write(j:8); {Выводим на экран} 

S.Write(j,2) {Помещаем в поток} 

end; 

Done; {Удаляем поток}

S.lnit('Test.dat', stOpenRead, 512); 

WriteLn;

WriteLn('Чтение из потока:'); 

for k := 1 to 10 do 

begin

S.Read(j,2); {Получаем целое из потока} 

Write (j:8) {Выводим на экран} 

end; 

S.Done; 

WriteLn 

end.

Для простоты в программу не включены средства контроля ошибок. В ходе прогона программы в текущем каталоге диска будет создан файл TEST.DАТ размером в 20 байт, а на экран будут выведены две строки с одинаковыми числами.



Методы Put и Get


Чтобы поместить объект в поток, нужно обратиться к методу Put, передав ему в качестве параметра инициированный экземпляр объекта. Например:

var

MyStream: TBufStream;{Экземпляр потока} 

MyWindow: TMyWindow;{Экземпляр объекта}

.....

MyStream.Put(MyWindow);{Помещаем объект в поток}

Предварительно объект должен быть зарегистрирован обращением к RegisterType, а поток - инициирован с помощью TXXXStream.Init.

Метод Put вначале отыскивает объект в регистрационном списке, создаваемом процедурой RegisterType, и получает из этого списка регистрационный номер объекта и адрес его метода Store. Затем в поток записывается регистрационный номер и вызывается метод Store, который делает остальное, т.е. копирует в поток все поля объекта.

По такой же схеме работает и метод Get: вначале он считывает из потока регистрационный номер объекта, затем отыскивает его в регистрационном списке и вызывает соответствующий конструктор Load. Конструктор размещает в динамической памяти экземпляр считываемого объекта, а затем считывает из потока все его поля. Результатом работы Get является нетипизированный указатель на вновь созданный и инициированный объект. Например:

type

MyStream: TBufStream;{Экземпляр потока} 

PWindow: PMyWindow;{Указатель на экземпляр объекта}

.....

PWindow := MyStream.Get;{Получаем объект из потока}

Заметим, что количество считываемых из потока данных и тип ТВМ, который назначен вновь созданному объекту, определяется не типом PWindow (см. выше), а регистрационным номером, полученным из потока. Вы можете ошибочно поместить в левой части оператора присваивания указатель на объект другого типа и Turbo Vision не сможет предупредить Вас об этом!

Методы Put и Get позволяют автоматически сохранять в потоке и получать из него сложные объекты (группы). Эта возможность реализуется внутри методов Store и Load.



Методы Store и Load


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

type

TMyDialog = object (TDialog) 

St: String;{Новое поле}

Procedure Store(var S: TStream); Virtual;

.....

end ;

то метод TMyDialog.Store может иметь такую реализацию:

Procedure TMyDialog.Store(var S: TStream); 

begin

TDialog.Store(S); {Сохраняем наследуемые поля} 

SA.Write(St, SizeOf(St)); {Сохраняем новое поле} 

end;

Аналогичным образом реализуется и конструктор Load: с помощью обращения к низкоуровневому методу TStream.Read он получает из потока только дополнительные поля и только в том порядке, как они были записаны в поток методом Store:

Constructor TMyDialog.Load(var S: TStream); 

begin

TDialog.Load(S); {Получаем наследуемые поля} 

S.Read(St, SizeOf(St)); {Получаем новое поле} 

end;

Вы должны тщательно следить за соответствием методов Store и Load: метод Load' должен прочитать ровно столько байт и строго в той последовательности, сколько байт и в какой последовательности поместил в поток метод Store. В Turbo Vision нет средств контроля За правильностью считываемых данных!

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

type

TMyDialog = object (TDialog)

St: String; {Текстовое поле}

PB: PButton; {Указатель на кнопку}

Procedure Store(var S: TStream); Virtual;

Constructor Load(var S: TStream);

.....

end;

Procedure TMyDialog.Store(var S: TStream); 

begin

TDialog.Store(S); {Сохраняем наследуемые поля} 

S.write(ST, SizeOf(St)); {Сохраняем текстовое поле} 

PutSubViewPtr(S, PB) ; {Сохраняем кнопку}

end;

Constructor TMyDialog.Load(var S: TStream);

begin

TDialog.Load(S); {Получаем наследуемые поля} 

S.Read(St, SizeOf(St)); {Получаем тестовое поле} 

GetSubViewPtr(S, PB); {Получаем кнопку}

end;



Обработка ошибок


При обнаружении ошибки поток вызывает свой метод TStream.Error, который определяет необходимую реакцию программы. По умолчанию этот метод просто записывает информацию об ошибке в поля TStream.Status и TStream.ErrorInfo.

Поле Status определяет тип ошибки, в соответствии со следующими константами модуля Objects:

const

stOk = 0; {Нет ошибки}

stError =-1; {Ошибка доступа}

stInitError =-2; {Ошибка инициации потока}

stReadError =-3; {Чтение за концом потока}

stWriteError =-4; {Нельзя расширить поток}

stGetError =-5; (Get для незарегистрированного объекта}

stPutError =-6; {Put для незарегистрированного объекта}

Поле ErrorInfo определено только для Status - -5 или Status - -6: в первом случае оно содержит регистрационный номер, полученный из потока и не обнаруженный в регистрационном списке; во втором - смещение ТВМ незарегистрированного объекта, который программа пытается поместить в поток.

Сразу после обнаружения ошибки Turbo Vision блокирует все операции с потоком до тех пор, пока аварийная ситуация не будет сброшена обращением к методу TStream.Reset.



Потоки


Сущность потоков

Регистрация объектов

Создание и удаление потока

Работа с потоком

Методы Put  и Get

Методы Store и Load

Обработка ошибок

Прямой доступ к потокам

Использование потоков с произвольными данными

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

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



Прямой доступ к потокам


Поток имеет методы, имитирующие файловые процедуры прямого доступа к дисковому файлу.

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

Метод Seek (Pos: LongInf) перемещает текущую позицию в потоке в байт Pos от начало потока.

Метод GetSize возвращает общий размер потока в байтах.

С помощью метода Truncate можно удалить из потока все данные, начиная с текущей позиции до конца потока.

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



Работа с потоком


Базовый объект TStream реализует три метода, используемых для непосредственной работы с потоком. Метод TStream.Put предназначен для передачи объектов в поток и выполняет приблизительно такие же функции, как стандартная файловая процедура Write. Метод TStream.Get используется для чтения объектов из потока, его аналогом является процедура Read. Наконец, с помощью метода TStream.Error анализируется состояние потока после

завершения некоторой операции: если обнаружена ошибка при обмене данными с потоком, вызывается этот метод, который по умолчанию просто устанавливает признаки ошибки в информационных полях TStream.Status и TStream.Errorlnfo. Приблизительным аналогом метода TStream.Error служит стандартная файловая функция IOResult.

Сразу же замечу, что в случае возникновения ошибки все последующие операции с потоком блокируются до тех пор, пока не будет вызван метод TStream.Reset.

Методы Put и Get практически никогда не перекрываются: для реализации операций с потоком они обращаются к виртуальным методам Store и Load, которые должны быть определены в каждом объекте, если только этот объект помещается в поток или считывается из него. Главное назначение методов Put и Get состоит в обеспечении полиморфизма потока за счет контроля регистрационных номеров объектов. Методы Load и Store никогда не вызываются прямо, но только из методов Put и Get, т.к. они ничего не знают о регистрационных номерах и не могут работать в полиморфных потоках.



Регистрация объектов


Для регистрации любого объекта используется обращение к глобальной процедуре Register-Type, определенной в интерфейсной части модуля Objects. Единственным параметром обращения к этой процедуре является запись типа TStreamRec, в которой группируются некоторые важные для Turbo Vision характеристики объекта:

type

PStreamRec = ATStreamRec; 

TStreamRec = record

ObjType: Word; {Регистрационный номер объекта}

VMTLink: Word; {Смещение таблицы виртуальных методов}

Load : Pointer; {Адрес метода чтения}

Store : Pointer; {Адрес метода записи}

Next : Word; {Связь в списке} 

end;

Для каждого объекта Вашей программы (как стандартного, так и нестандартного) должна быть создана своя запись типа TStreamRec, если только Вы собираетесь помещать соответствующий объект в поток или получать его из потока. Однако для стандартных объектов такие записи уже существуют и Вам нет нужды создавать их заново: по принятому в Turbo Vision соглашению запись TStreamRec для стандартного объекта имеет такое же имя, как имя объекта, с заменой начальной буквы Т на R. Например, для TWindow регистрационная запись называется RWindow, для TDialog -RDialog и т.д. Имеет смысл следовать этому соглашению, определяя идентификаторы регистрационных записей для нестандартных объектов.

Итак, для каждого нестандартного объекта Вы должны подготовить запись TStreamRec, причем фактически определяются только первые четыре поля этой записи, так как поле Next используется для организации связанного списка и заполняется внутри процедуры RegisterType.

В поле ObjType Вы должны поместить константу-идентификатор объекта. В Turbo Vision константы 0...99 уже используются в стандартных регистрационных записях RXXXX, таким образом, если Вы собираетесь использовать стандартные регистрационные записи, в Вашем распоряжении остаются константы в диапазоне от 100 до 65535. Выбор констант из этого диапазона может быть произвольным, однако Turbo Vision требует, чтобы каждый объект характеризовался уникальной константой. Заметим, что Turbo Vision следит за уникальностью регистрационных констант и аварийно завершает исполнение программы (код ошибки 212), если в программе делается попытка зарегистрировать два разных объекта с одинаковыми регистрационными номерами.


Поле VMTLink должно содержать смещение адреса таблицы виртуальных методов (ТВМ) объекта. Турбо Паскаль имеет стандартную функцию TypeOf (Type: object) , которая возвращает адрес ТВМ для указанного типа Туре объекта, поэтому поле VMTLink обычно заполняется значением Ofs (TypeOf (TName)^), где TName - имя нестандартного объекта.

Поля Load и Store должны содержать адреса соответствующих виртуальных методов (см. п.20.3), обеспечивающих чтение объектов из потока и их запись в поток.

Например, если в программе определен объект

type

TMyWindow = object (TWindow)

.....

Constructor Load(var S: TStream); 

Procedure Store(var S: TStream); 

end ; 

то для его регистрации создается запись

const

RMyWindow: TStreamRec = ( 

ObjType: 100;

VMTLink: Ofs(TypeOf(TMyWindow)^); 

Load : @TMyWindow.Load; 

Store : @TMyWindow.Store);

Обычно регистрация осуществляется в конструкторе Init вновь созданного объекта, например:

Constructor TMyWindow.Init; 

begin

RegisterType(RMyWindow);

.....

end;

Однако, если в программе предполагается помещать в поток (или считывать из потока) объекты разного типа, регистрацию объектов как правило реализуют отдельной процедурой, вызываемой в конструкторе Init программы или в любом другом удобном месте, но перед фактическим обращением к потоку. Например:

Procedure RegisterMyProgram;

begin

RegisterType(RWindow); {Регистрация стандартного объекта} 

RegisterType(RMyWindow); {Регистрация нового объекта}

.....

end;

Для упрощения регистрации стандартных объектов в модулях Turbo Vision предусмотрены процедуры RegisterXXXX, где ХХХХ - имя соответствующего модуля. Например, процедура RegisterDialogs осуществляет регистрацию всех неабстрактных объектов модуля Dialogs, т.е. TButton, TCluster, TInputLine и т.д.; процедура RegisterViews регистрирует объекты модуля Views и т.д.


Создание и удаление потока


Для работы с потоками в Turbo Vision предусмотрен абстрактный тип TStream и три его потомка - TDOSStream, TBufStream и TEMSStream.

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

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

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

Constructor TDOSStream.Init(FileName: FNameStr; Mode: Word);

Здесь FileName - имя дискового файла или файлового устройства, Mode - способ доступа к данным.

Параметр FileName может содержать полное имя файла с указанием диска и маршрута поиска. Параметр Mode определяет способ доступа к данным. Для задания этого параметра можно использовать следующие константы, определенные в модуле Objects:

const

stCreat = $ЗС00; {Создать файл}

stOpenRead = $3D00; {Открыть файл только для чтения}

stOpenWrite = $3D01; {Открыть файл только для записи}


stOpen = $3D02; {Открыть файл для чтения и записи}

Constructor TBufStream(FileName: FNameStr; Mode: Word; Size: Word);

Здесь FileName, Mode - см. выше; Size - размер буфера в байтах.

Размер буфера выбирается равным размеру дискового сектора (512 байт) или размеру кластера (п*512, и = 1, 2, 4, 8, ...). Минимальные потери времени обеспечивает размер буфера, равный размеру кластера диска (кластер - минимальная порция дискового пространства, выделяемая каждому файлу). При работе с гибкими дисками размер кластера обычно равен одному или двум секторам, для жесткого диска этот размер зависит от общей емкости диска и чаще всего равен 4 или 8 секторам. Если Вы не знаете размеры кластера диска, с которым будет связан поток, установите Size = 512.

Constructor TEMSStream.Init(MinSize, MaxSize: LongInt);

Здесь MinSize, MaxSize определяют соответственно минимальный и максимальный размеры блока, который будет передаваться в EMS-память. Параметр MaxSize имеет смысл только при использовании драйвера EMS-памяти, версии меньше 4.0: в этом случае попытка разместить в расширенной памяти блок, больше MaxSize, вызовет ошибку; при использовании версии драйвера 4.0 и выше в памяти можно разместить блок любого размера, в этом случае параметр MaxSize можно опускать.

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

Dispose(PMyStream, Done);

Здесь PMyStream - указатель на экземпляр потока, размещенный в куче. Если Вы не использовали указатель на поток (т.е. если экземпляр потока размещен в обычной переменной), для удаления потока используется вызов

MyStrearn.Done;

(MyStrem - экземпляр потока).

В ходе реализации процедуры TStream.Done очищается внутренний буфер (если использовался буферизованный поток); закрывается файл и уничтожается экземпляр потока.


Сущность потоков


Поток Turbo Vision - это коллекция объектов со специфичным способом хранения элементов: для этих целей коллекция-поток использует дисковый файл, отображаемую память (EMS-память) или файловое устройство (устройство ввода-вывода данных).

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

Как известно, в Турбо Паскале могут использоваться типизированные или нетипизированные файлы. При работе с типизированными файлами Турбо Паскаль осуществляет необходимый котроль за типом данных, предупреждая Вас о возможных ошибках; типизированные файлы как правило не обеспечивают высокую скорость доступа к данным. При обращении к нетипизированным файлам контроль возлагается целиком на программиста, но в этом случае обычно существенно увеличивается скорость работы с файлами. Потоки Turbo Vision обеспечивают в известном смысле компромисс между обоими способами хранения информации в файле: они позволяют компилятору осуществить проверку типов на уровне проверки объектов, но не контролируют сами данные, помещаемые в файл, т.к. в правильно сконструированной программе объект сам осуществляет необходимые операции с диском и делает это безошибочно и очень быстро.

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


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

Таким образом, для работы с потоками необходимо:

зарегистрировать объекты, которые будут помещаться в поток или считываться из него; отметим, что все стандартные объекты Turbo Vision уже имеют уникальные регистрационные номера и процедура их регистрации предельно проста (см. ниже);

инициировать (создать) поток; в ходе инициации указывается имя файла и некоторая другая информация, используемая для обеспечения доступа к файлу;

поместить данные в поток и/или прочитать их из него;

удалить поток.

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