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

         

Использование ресурса


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

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

var

StatusRes: TResourceFile; {Файл ресурсов}

type

PMyApp =TМуАрр; {Программа в Turbo Vision}

ТМуАрр = object (TApplication) Constructor Init; 

Procedure InitStatusLine; Virtual;

end;

Constructor TMyApp.Init; 

{Открывает поток, связанный с файлом ресурсов}

begin

StatusRes.Init(New(PBufStream,

Init('status.res',stOpen,1024)));

if StatusRes.StreamA.status <> 0 then 

Halt;

RegisterType(RStatusLine);

Inherited Init 

end;

Procedure TMyApp.initStatusLine; 



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

begin

StatusLine := PStatusLine(StatusRes.Get('Статус')) 

end; 

var

Prog: TMyApp;

begin

Prog.Init; 

Prog.Run; 

Prog.Done 

end.

Заметим, что если в файле ресурсов нет ресурса с указанным именем, метод Get возвращает значение NIL.

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

Для ускорения доступа к файлу ресурсов его можно связать с EMS-памятью или использовать для его размещения виртуальный диск.



Ресурсы Turbo Vision


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

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

Строковые ресурсы

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

Использование ресурсов открывает перед Вами ряд интересных возможностей.

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

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

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

В Turbo Vision ресурс реализуется с помощью объекта TResourceFile, который содержит поток и связанную с ним отсортированную коллекцию строк. С помощью метода Init Вы создаете ресурс, методом Put помещаете в поток нужные Вам объекты, а с помощью метода Get получаете из него объект по его имени или индексу.



Создание ресурса


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

Вот как, например, можно создать ресурс, содержащий строку статуса с именем (ключом) «Статус»:

Uses Drivers, Objects, Views, App, Menus; 

var

StatusRes: TResourceFile; {Файл ресурсов}

PStatusStrm: PBufStream; {Буферизованный поток} 

Procedure CreatestatusLine;

{Создает строку статуса и помещает ее в файл ресурсов} 

var

R: TRect;

PStatus: PStatusLine; 

begin

R.Assign(0,24,80,25) ;

PStatus := New(PStatusLine, Init(R,

NewStatusDef(0, $FFFF,

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

NewStatusKey('Демонстрация строки статуса',О,0, NIL)),

NIL)) ) ;

StatusRes.Put(PStatus, 'Статус');

Dispose(PStatus, Done) 

end; 

begin

PStatusStrm := New(PBufStream,Init(

'status.res',stCreate,1024));

StatusRes.Init(PStatusStrm); {Создаем поток}

RegisterType(RStatusLine); {Регистрируем строку статуса}

CreateStatusLine; {Помещаем строку статуса в поток}

StatusRes.Done 

end.

В программе создается поток, связанный с указателем PStatusStrm, и в него с помощью процедуры CreateStatusLine помещается строка статуса. Этой строке присваивается ключ Статус.



Строковые ресурсы


Списки строк используются в программах Turbo Vision очень часто, поэтому специально для этого вида ресурсов разработаны два особых объекта - TStrListMaker и TStringList. С помощью TSrtListMaker создается строковый ресурс, а с помощью TStringList ранее созданный строковый ресурс становится доступен программе. Предполагается, что создание строкового ресурса и его использование осуществляется в разных программах, поэтому обоим объектам в Turbo Vision присвоен один и тот же регистрационный номер. По той же причине TStrListMaker имеет метод Put, но не имеет метода Get, a TStringList, наоборот, имеет Get и не имеет Put.

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

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

Program CreateStingRes;

{Эта программа создает строковый ресурс}

Uses Objects;

const

SizeStrRes = 2000; {Общая длина всех строк в ресурсе}

NStr = 40; {Общее количество строк} 

var

f: text; {Файл с текстом программы}

ProgText: TResourceFile;

Strings : TStrListMaker; {Строковый ресурс}

k: Integer;

s: String; 

begin

s := copy(ParamStr(0),1,pos('.',ParamStr(0)));

Assign(f,s+'PAS');

Reset(f); {Открываем файл с текстом программы}

RegisterType(RStrListMaker); {Регистрируем объект}


ProgText.Init(New(PBufStream,

InitC prog, res' , stCreate, 1024) )) ;

Strings.Init(SizeStrRes,Nstr);

k := 0;

while not EOF(f) do 

begin

ReadLn(f,s); {Читаем строку программы} 

inc(k) ; {k - порядковый номер строки}

Strings.Put(k,s) {Помещаем строку в ресурс} 

end;

Close (f); {Закрываем файл с текстом программы}

{Помещаем ресурс в поток}

ProgText.Put(@Strings,'Программа');

Strings.Done; {Закрываем ресурс}

ProgText. Done {Закрываем поток} 

end.

В программе UseStringRes текст, полученный из созданного строкового ресурса, читается и выводится на экран «задом наперед», начиная с последней строки текста.

Program UseStingRes;

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

Uses Objects; 

var

ProgText: TResourceFile; {Файл ресурсов}

PStrings : PStringList; {Строковый ресурс}

k,N: Integer;

begin

RegisterType(RStringList); {Регистрируем объект}

ProgText.Init(New(PBufStream, {Создаем поток}

Init('prog.res',stOpenRead,1024)));

PStrings := {Получаем из потока ресурс}

PStringList(ProgText.Get('Программа'));

N := 1;

while (PStrings.Get(N))<>'' do

inc(N); {N-1 = общее количество строк} 

for k := N-l downto 1 do

WriteLn(PStrings.Get(k)); {Получаем и выводим строки} 

PStringsA.Done; {Закрываем ресурс} 

ProgText.Done {Закрываем поток} 

end.

Заметим, что индексы, по которым осуществляется доступ к строковому ресурсу, могут быть произвольными целыми числами в диапазоне от 0 до 65535. Чтобы определить общее количество строк в строковом ресурсе, используется то обстоятельство, что при обращении к TStringList.Get с недействительным индексом метод выдает пустую строку. На практике этот способ нельзя считать надежным (в строковом ресурсе могут храниться и пустые строки), однако других способов определения этого параметра в TStringList не существует.

Перед обращением к TSrtListMaker.Put и TSrtmgList.Get соответствующие объекты должны быть зарегистрированы с помощью RegisterType.


Контроль текстового ввода


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

var

InpLine: PInputLine;

Validator: PRangeValidator; 

begin

{Создаем строку ввода}

InpLine := New(PInputLine,Init(...));

{Создаем объект-контролер} 

Validator := New(PRangeValidator,Init(0,10));

{Связываем контролер с редактором} 

InpLineA.SetValidator(Validator) ;

.....

end;

Можно объединить оба действия в одном операторе:

InpLine^.SetValidator(New(PRangeValidator,Init(0,10))

При работе совместно с объектом TInputLine контролер может активно воздействовать на ввод пользователя. Это относится к объектам TFilterValidator и TPXPicture-Validator: они контролируют ввод каждого символа и игнорируют нажатие пользователем клавиши, если очередной символ не соответствует требуемому. Объект TPXPictureValidator, кроме того, способен автоматически дополнять ввод пользователя неспециальными символами шаблона. Если, например, шаблон задан следующим образом:

InpLine^.SetValidator(New(PPXPictureValidator, Init('#/#/#',True)))

то нажатие пользователем любых клавиш, кроме цифровых, будет игнорироваться, а сразу после нажатия первой (второй) цифровой клавиши в строке ввода появится символ «/». Вставкой неспециальных символов шаблона управляет второй параметр обращения к методу TPXPictureValidator.Init (True - разрешить вставку, False - запретить).



Объекты - контролеры


Типы объектов-контролеров

TPXPictureValidator

TFilterValidator

TRangeValidator

TLookupValidator

TStringLookupValidator

Использование объектов-контролеров

Контроль текстового ввода

Проверка других объектов

Реализация нестандартного контроля

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

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



Проверка других объектов


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



Реализация нестандартного контроля


Возможно, Вам понадобится сконструировать собственный объект-контролер. В этом случае Вы должны помнить, что за контроль отвечают четыре метода базового объекта TValidator: Valid, IsValidInput, IsValid и Error, причем абстрактные методы Is Valid и Error перекрываются в потомках, вызываются другими методами объекта-контролера и не предназначены для вызова извне.

Метод Valid получает строку, введенную пользователем, осуществляет ее контроль и возвращает True или False в зависимости от результатов контроля (True означает правильный ввод). Метод IsValidlnput также получает строку символов ввода, однако, в отличие от Valid, он получает ее после каждого изменения пользователем входной строки. Метод IsValidlnput имеет два параметра обращения:

Function TValidator.IsValidlnput(var S: String; SuppressFill: Boolean): Boolean

Параметр SuppressFill указывает, должен ли метод автоматически форматировать строку перед ее проверкой. Если этот параметр имеет значение True, метод должен дополнить подготовленный пользователем ввод S нужными в этом месте символами и только после этого производить контроль. Поскольку параметр S определен как параметр-переменная, метод может вставлять дополнительные символы непосредственно в строку ввода, но ни в коем случае не должен удалять ошибочно введенные символы из нее. Из всех стандартных контролеров только TPXPictureValidator использует SuppressFill = True.I



TFilterValidator


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

MyFilter := New(PFilterValidator,Init(['0'..'9'])) 

выбирает из ввода только цифры.



Типы объектов-контролеров


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



TLookupValidator


Абстрактный объект TLookupValidator предназначен для контроля соответствия введенной пользователем строки набору эталонных строк. Фактически он модифицирует поведение базового объекта TValidator на случай, когда проверяется произвольная текстовая строка. В его потомках должен перекрываться метод Lookup, осуществляющий нужный контроль и возвращающий True только в том случае, когда подготовленная пользователем строка соответствует одному из желаемых значений. Например, с помощью объекта

type

TMyLookupValidator = object(TLookupValidator)

Function Lookup(const S: String): Boolean; Virtual; 

end;

Function TMyLookupValidator.Lookup; 

const

Textl = 'Turbo';

Text2 = 'Pascal'; 

begin

Lookup := (S=Textl) or (S=Text2) or (S=Textl+' '+Text2)

end;

можно потребовать от пользователя ввода только одной из трех возможных строк: Turbo, Pascal или Turbo Pascal.



TPXPictureValidator


Объект TPXPictureValidator проверяет введенную пользователем строку на соответствие некоторому шаблону ввода. Шаблон ввода определяется с помощью специальных символов, подобно тому, как задается допустимый ввод в базе данных Paradox корпорации Borland. В шаблоне можно использовать следующие специальные символы:

Любой другой символ шаблона представляет сам себя, т.е. не является управляющим. Символ «;», предшествующий специальному символу, отменяет его специальное назначение. Чтобы в шаблон вставить собственно символ «;», его нужно удвоить.

Перечисленные символы позволяют формировать шаблоны любой сложности. Например:

### - трехзначное число; примеры правильного ввода: 123, 566, 080; неправильный ввод: 12 (нет одной цифры), -12 (первый символ - не цифра);

*# - число с произвольным количеством цифр; допустимый ввод: 1, 12, 12345678987654321; неправильный ввод:-12, 0334А;

[+,-]*#- произвольное целое число; допустимый ввод: +123, -56, 7890; недопустимый ввод:+-123, 1.23, 12345Е+02;

{#[#]}-{#[#]}-{##[##]}- дата в формате ДД-ММ-ГТ; допустимый ввод: 1-2-33, 01-02-1933; недопустимый ввод: 1/2/33, 1 фев 33;

#;**#- число в диапазоне 0...9 умножается (символ *) на произвольное положительное целое число; правильный ввод: 2*2, 3*12345; неправильный ввод: 2/2, 123*12345.



TRangeValidator


Объект TRangeValidator порожден от TFilterValidator. Он преобразует символьный ввод в целое число и проверяет, находится ли оно в диапазоне указанных значений Min...Max. Его метод IsValid вернет значение True только в том случае, когда, во-первых, пользователь введет правильное число, и, во-вторых, это число будет принадлежать нужному диапазону. Например, объект

MyRange := New(PRangeValidator,Init(0,100))

позволяет вводить числа в диапазоне от 0 до 100 включительно.



TStringLookupValidator


Этот объект порожден от TLookupValidator и сравнивает введенную пользователем строку с набором допустимых строк, хранящихся в отсортированной коллекции. Чтобы объект осуществлял сравнение с нужным набором строк, он инициируется следующим образом:

var

MyCollection: PStringCollection; MyStrVal: PStringLookupValidator; 

begin

{Создаем коллекцию строк} 

MyCollection := New(PStringCollection,Init(1,1));

{Наполняем коллекцию - размещаем строки в куче} 

MyCollection.Insert(NewStr('Турбо')); 

MyCollection.Insert(NewStr('Паскаль')) ;

{Создаем объект-контролер}

MyStrCol := New(PStringLookupValidator,Init(MyCollection))

end;



Деструктор


Вызывайте наследуемый метод после реализации дополнительных действий:

Procedure MyObject.Done; 

begin

{Реализация дополнительных действий} 

{Вызов наследуемого деструктора Done} 

end;

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



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


На рис.23.2 показан вид диалогового окна, которое создается и используется в рассматриваемом ниже примере.

Рис.23.2. Диалоговое окно со строкой ввода и кнопками

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

Команда 'Не изменять',

если диалог завершен по клавише Esc, на экран не выводится никаких сообщений.

Пример иллюстрирует использование строки ввода, зависимых и независимых кнопок и нестандартных команд.

Для задания начальных параметров и чтения параметров, установленных в результате диалога, используется поле TDialog.Data. Это поле содержит данные, используемые в ходе диалога, в виде записи, поля и тип которой строго соответствуют порядку и типу вставляемых в окно терминальных элементов. В нашем примере (см. текст программы) первым в окно вставляется статический текст, однако этот терминальный элемент не имеет параметров, которые можно было бы изменить в ходе диалога, поэтому в записи Data ему не отводится место. Второй по счету в окно вставляется строка ввода TInputLine. Этот объект характеризуется длиной L строки, указываемой вторым параметром обращения к TInputLine.Init, поэтому для него в Data выделяется поле, длинойL+1 байт. Каждому кластеру с кнопками выделяется поле WORD, что дает возможность задать в кластере до 16 независимых кнопок и до 65536 зависимых: каждая независимая кнопка связана с соответствующим разрядом 16-битного поля (первая кнопка - с младшим разрядом), а каждой зависимой кнопке соответствует свое число (первой кнопке соответствует число 0, второй - 1 и т.д.). Установка данных в поле TDialog.Data осуществляется методом TDialog.SetData, получить данные после диалога можно с помощью метода TDialog.GetData.

{$Х+}

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

type

MyApp = object (TApplication)

Procedure InitStatusLine; Virtual; 


Procedure HandleEvent ( var Event: Tevent) ; Virtual; 

Procedure GetDialog; 

end;

PMyDialog = TMyDialog; 

TMyDialog = object (TDialog)

Procedure HandleEvent (var Event: Tevent); Virtual; 

end; 

const

cm0 = 200; 

cm1 = 201; 

cm2 = 202;

{-------------------}

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,cm0, NIL)),

NIL) ) ) 

end {MyApp.InitStatusLine} ;

{-----------------}

Procedure MyApp.HandleEvent; 

{Обрабатывает нестандартную команду cm0} 

begin

Inherited HandleEvent (Event) ;

case Event . Command of 

cm0 : GetDialog

else

ClearEvent (Event)

end 

end {MyApp . HandleEvent} ;

{------------------}

Procedure MyApp.GetDialog;

{Создает и использует диалоговое окно} 

var

R: TRect; {Координаты элементов} 

D: PMyDialog; {Диалоговое окно} 

I: PInputLine; {Строка ввода} 

RB: PRadioButtons; {Зависимые кнопки} 

СВ: PCheckBoxes; {Независимые кнопки} 

s : String; {Для вывода сообщения} 

const

L = 120; {Длина строки ввода} 

type

TDialogData = record {Параметры диалогового окна} 

I_Data: String [L]; {Текст в строке ввода} 

CB_data: Word; {Независимые кнопки} 

RB_Data: Word {Зависимые кнопки} 

end; 

const

st: array [0..2] of String = ('Первое','Второе','Третье '); 

Data : TDialogData =( {Начальные параметры диалога} 

I_Data : 'Начальный текст';

CB_Data: 3; {1-я и 2-я кнопка} 

RB_Data: 2); {3-е продолжение} 

begin

R.Assign(5,3,75,18); {Координаты диалогов ого окна} 

D := New(PMyDialog,Init(R,'Пример диалогового окна')); 

with D do 

begin

R.Assign(1,1,69,3);

Insert(New(PStaticText, {Вставляем статический текст} 

Init(R,#3'Это статический текст'))); 



R.Assign(20,3,60,4) ; 

I := New(PInputLine, Init(R, L)); 

Insert (I); {Вставляем строку ввода} 

R.Assign(1,3,20,4);

Insert(New(PLabel, {Вставляем метку строки ввода} 

Init(R,'~l~ Строка ввода:',I)));

R.Assign(60,3,62,4);

Insert(New(PHistory, {Вставляем список ввода} 

Init(R,I,0))) ; 

R.Assign(10,6,30,9) ; 

CB := New (PCheckBoxes, Init(R, 

NewSItem('Первая кнопка', 

NewSItem('Вторая кнопка',

NewSItem('Третья кнопка', NIL)))));

Insert(CB); {Вставляем независимые кнопки} 

R.Assign(6,5,30,6);

Insert(New(PLabel, {Вставляем метку кнопок} 

Init(R,'~2~ Независимые кнопки',CB)));

R.Assign(40,6,63,9); 

RB := New(PRadioButtons, Init(R, 

NewSItem('Первое продолжение', 

NewSItem('Второе продолжение', 

NewSItem('Третье продолжение', NIL))))); 

Insert(RB); {Вставляем зависимые кнопки} 

R.Assign(36,5,63,6) ;

Insert(New(PLabel, {Вставляем метку кнопок} 

Init(R,'~3~ Зависимые кнопки',RB))); 

R.Assign(14,11,32,13) ;

Insert(New(PButton, {Вставляем кнопку "Установить"} 

Init(R,'~4~ Установить',cm1,bfNormal))); 

R.Assign(40,11,58,13);

Insert (New(PButton, {Вставляем кнопку "Не изменять"} 

Init(R,'~5~ He изменять',cm2,bfNormal))); 

SetData(Data) {Устанавливаем начальные значения} 

end;

{ Помещаем окно на экран и получаем команду завершения} 

case DeskTop.ExecView(D) of 

cm1:

begin {Была команда "Установить":} 

D.GetData(Data); {Получаем новые значения} 

with Data do

begin {Готовим сообщение} 

s := #3'Параметры диалогового окна:'+

#13'Текст :'+I_Data+#13'Кнопки: '; 

if CB_Data and 1 <> 0 then

s := s+' Первая'; 

if CB_Data and 2 <> 0 then

s := s+' Вторая'; 

if CB_Data and 4 <> 0 then

s := s+' Третья';

s :=s+#13'Продолжение: '+st[RB_Data] 

end 

end ;

cm2: s := #3'Команда "Не изменять"'; 

else

s := ' ' ; 



end; {case} 

if s <> '' then

MessageBox(s,NIL,0) 

end {MyApp.GetDialog};

{--------------}

Procedure TMyDialog.HandleEvent; 

{Обрабатывает нестандартные команды cm1 и cm2} 

begin

Inherited HandleEvent(Event);

if Event.What = evCommand then 

case Event.Command of

cm1:EndModal (cm1);{Завершить с командой cm1}

cm2:EndModal (cm2){Завершить с командой cm2}

end;

ClearEvent (Event) {Очистить другие события} 

end {TMyDialog.HandleEvent} ;

{---------------}

var

P: MyApp; 

begin

P.Init;

P. Run;

P . Done

end.

Для использования нестандартных команд cm0, cm1 и cm2 перекрываются обработчики событий строки статуса и диалогового окна. Чтобы завершить диалог с выдачей нестандартной команды, в обработчике событий окна вызывается метод EndModal, в результате чего метод ExecView возвращает значение соответствующей команды. Заметим, что стандартная для Turbo Vision команда cmCancel (закрыть окно по клавише Esc) обрабатывается стандартным методом TDlalog.HandleEvent.


Другие методы


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

Procedure MyObject.HandleEvent(var Event: TEvent); 

begin

{Изменение наследуемых свойств} 

{Вызов наследуемого обработчика} 

{Добавление новых свойств} 

end;

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

Procedure TDialog.HandleEvent(var Event: TEvent); 

begin

Inherited HandleEvent(Event);

case Event.what of

evKeyDown:

.....

evCommand:

.....

end ; 

end;

Этот обработчик перехватывает все события от клавиатуры и мыши, в том числе и нажатие на клавишу Tab. Если Вы хотите обработать событие от клавиши Tab особым способом, Вы должны перехватить это событие до вызова стандартного обработчика. Например:

Procedure TNoTabsDialog.HandleEvent(var Event: TEvent);

begin

if (Event.What = evKeyDown) then 

if (Event.KeyCode = kbTab) or

(Event.KeyCode = kbShiftTab) then 

ClearEvent(Event); Inherited HandleEvent(Event); 

end;



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


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

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

Заметим, что размеры всех основных оверлейных модулей - Арр, Objects, Views, Menus приблизительно одинаковы и составляют около 50 Кбайт. С учетом Ваших собственных объектов, обеспечивающих интенсивное взаимодействие с пользователем и порожденных от TWindow или TDialog, типичный размер оверлейного буфера составит не менее 64 КБайт. Если Вы хотите минимизировать потери времени на свопинг и в то же время создать достаточно компактную программу, Вам придется поэкспериментировать с размером оверлейного буфера и/или испытательной зоны. Вы можете также возложить на пользователя Вашей программы ответственность за выбор размера оверлейного буфера, предусмотрев соответствующий параметр в строке вызова программы.

В следующем примере показан возможный способ инициации оверлея.

{$F+,0+,S-}

{$М 8192,65536,655360}

Uses Overlay, Drivers, Memory, Objects, Views, Menus, 

Dialogs,istList, StdDlg, App; 

{Объявляем оверлейные модули:} 

{$0 App } 


{$O Dialogs } 

{$0 HistList } 

{$0 Memory } 

{$0 Menus } 

{$0 Objects } 

{$0 StdDlg } 

{$O Views } 

const

OvrBufDisk=96*1024;{Размер буфера без EMS-памяти}

OvrBufEMS=72*1024;{Размер буфера при использовании EMS-памяти}

type

ТМуАрр = object (TApplication) 

Constructor Init; Destructor Done; Virtual;

.....

end; {TMyApp}

Procedure InitOverlays; 

var

FileName: String [79] ; 

begin

FileName := ParamStr(0); 

Ovrlnit(FileName) ; 

if OvrResult <> 0 then 

begin

PrintStr('Невозможно открыть оверлейный файл ', FileName); 

Halt; 

end;

OvrinitEMS; 

if OvrResult = 0 then OvrSetBuf(OvrBufEMS) 

else 

begin

OvrSetBuf(OvrBufDisk) ; 

OvrSetRetry(OvrBufDisk div 2); 

end

end; {InitOverlays} 

Constructor TMyApp.Init; 

begin

InitOverlays; 

TApplication.Init;

.....

.....

end; {TMyApp.Init} 

Destructor TMyApp.Done; 

begin

.....

.....

TApplication.Done; 

end; {TMyApp.Done} 

var

MyApp: TMyApp; 

begin

MyApp.Init;

MyApp.Run;

MyApp. Done; 

end.

В этой программе используется механизм размещения оверлеев в исполняемом EXE-файле. Для этого после компиляции программы используйте команду ДОС

copy/b MyProg.exe+MyProg.ovr MyProg.exe

Чтобы определить маршрут поиска EXE-файла, в процедуре InitOverlays проверяется параметр вызова с номером 0. Заметим, что в этот параметр автоматически помещается маршрут доступа к загруженной программе только в том случае, когда Ваш ПК работает под управлением MS-DOS версии 3.0 и выше.

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


Конструктор


Вызывайте наследуемый Метод до реализации дополнительных действий:

Procedure MyObject.Init(.....); 

begin

{Вызов наследуемого конструктора Init} 

{Реализация дополнительных действий} 

end;

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

type

MyObject = object (TWindow) 

Value: Word; 

Ok : Boolean; 

Constructor Init(var Bounds: TRect; ATitle: TTitleStr;

AValue: Word; AOk: Boolean); 

end;

Constructor MyObject.Init; 

begin

Inherited Init(Bounds, ATitle, wnNoNumber); 

Value := 16; 

Ok := True; 

end;

то дополнительные поля Value и Ok получат нужные значения 16 и True. Однако, если обращение TWindow.Init (Bounds, ATitle, wnNoNumber); поставить после оператора Ok := True, в них будут помещены значения 0 и False. Из этого правила существует одно исключение, связанное с загрузкой коллекции из потока конструктором Load. Дело в том, что в наследуемом методе TCollection.Load реализуется следующий цикл:

Constructor TCollection.Load (var S: TStream); 

begin

.....

for I := 0 to Count - 1 do

AtPut(I, GetItem(S)); 

end;

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

type

PDataCollection = ATDataCollection; 

TDataCollection = object (TStringCollection)

ItemSize: Word;

Constructor Load(var S: TStream);

Function GetItem(var S: TStream): Pointer; Virtual;

.....

end;

Constructor TDataCollection.Load(var S: TStream); 

begin

S.Read(ItemSize, SizeOf(ItemSize));

Inherited Load(S); 

end;

Function TDataCollection.GetItem(var S: TStream): Pointer; 

var

Item: Pointer; 

begin

GetMem(Item, ItemSize);

S.Read(Item, ItemSize);

GetItem := Item; 

end;

В этом примере конструктор Load сначала загружает из потока поле ItemSize, содержащее длину читаемого элемента. Затем вызывается конструктор TCollection.Load, в котором осуществляется вызов GetItem. Новый GetItem использует поле ItemSize, чтобы определить размер читаемых данных, и резервирует нужный буфер в динамической памяти. Разумеется, запись полиморфных коллекций в поток должна происходить в том же порядке, т.е. сначала записывается длина очередного элемента, а уже потом - его данные.



Контроль за динамической памятью


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

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

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

В следующем примере создается простое диалоговое окно:

Uses Memory,...;{Функция LowMemory определена в модуле Memory}

.....

R.Assign(20,3,60,10);

D := New(Dialog, Init(R, 'Диалоговое окно')); 

with D do 

begin

R.Assign(2,2,32,3);

Insert(New(PStaticText, Init(R, 'Сообщение-вопрос'))); 

R.Assign(5,5,14,7);

Insert(New(PButton, Init(R, '~Y~es (Да)', cmYes))); 

RAssign(16,5,25,7);

Insert(New(PButton, Init(R, ' ~N~o (Нет)', cmNO))) 


end;

if LowMemory then 

begin

Dispose(D,Done); {Нет памяти: удаляем распределение} 

OutOfMemory; {Сообщаем об этом} 

DoIt := False {Признак ошибки} 

end 

else

Dolt := DeskTop.ExecView(D)=cmYes;

Если Вы используете вызов LowMemory сразу после динамического размещения объекта, то в ходе самого размещения не должен произойти аварийный останов, связанный с нехваткой памяти. Таким образом, размер пула надежности должен быть достаточным для размещения всего объекта. Переменная LowMemSize задает размер пула надежности в параграфах (участках, длиной по 16 байт). По умолчанию она имеет значение 4096 div 16 = 256, т.е. размер пула надежности составляет 4 Кбайт.

На практике вместо прямого обращения к LowMemory чаще используется вызов метода TProgram.ValidView (P: Pointer): Pointer. Этот метод получает в качестве параметра обращения указатель Р на динамический объект и осуществляет следующие действия:

если Р = NIL, метод возвращает NIL;

если LowMemory = True, метод освобождает память, связанную с Р, вызывает метод TProgram.OutOfMemory и возвращает NIL;

если обращение к методу TView. Valid (cm Valid) дает False (см. ниже), объект Р удаляется из кучи и метод ValidView возвращает NIL;

в противном случае считается, что размещение осуществлено успешно, и метод возвращает значение указателя Р.

Метод TProgram.ValidView осуществляет стандартные действия по контролю надежности использования кучи. Обычно его используют перед тем, как поместить новый видимый элемент в группу, например:

DeskTop.Insert(ValidView(New(TMyWindow, Init(...))));

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

В ряде случаев может оказаться полезной глобальная функция Function MemAlloc (Size: Word): Pointer, которая осуществляет те же действия, что и New или GetMem, но в отличие от них не распределяет пул надежности. Функция возвращает указатель на выделенную область кучи или NIL, если в куче нет свободного блока нужного размера. Аналогичные действия осуществляет функция MemAllocSeg, отличающаяся от MemAlloc только тем, что выделяет память, выровненную на границу параграфа (на границу сегмента).


Меню


Стандартная программа содержит поле MenuBar типа ТМепиВаr. По умолчанию Метод TApplicatlon.InitMenuBar устанавливает это поле в NIL, что означает отказ от меню. Если Вы хотите использовать меню в Вашей программе, необходимо перекрыть этот метод. В следующем примере создается двухуровневое меню, показанное на рис.23. 1.

Рис. 23.1. Двухуровневое меню

Опцию «Подменю...» можно выбрать следующими способами:

нажатием клавиш F10 - <смещение указателя> - Enter;

командой Alt-S;

отметкой мышью.

Опции «Первый выбор» и «Второй выбор» можно выбрать клавишами F1 и F2 без развертывания подменю. После развертывания подменю можно использовать те же клавиши, а также использовать клавиши с цифрами 1 и 2, отметку мышью или смещение указателя к нужной опции и Enter. Опция «Третий выбор» доступна только после развертывания подменю. Выбор каждой из этих трех опций приводит к появлению на экране окна с сообщением. Кроме того, опция «Третий выбор» попеременно запреща-ет или разрешает действие команд cm1, cm2 и cmQuit.

{$X+}

Uses Objects,App,Menus,Drivers,Views,MsgBox; 

type

MyApp = object (TApplication) 

Procedure InitMenuBar; Virtual;

Procedure HandleEvent(var Event: TEvent); Virtual; 

end;

const

cm1 = 201; 

cm2 = 202; 

cm3 = 203;

{----------------}

Procedure MyApp. InitMenuBar; 

var

R: TRect; 

begin

GetExtent (R) ;

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

MenuBar := New ( PMenuBar , Init(R,

NewMenu ( {Главная полоса меню}

NewSubMenu ( {Первый элемент главного меню}

' ~S~ Подменю. ..', hcNoContext,

NewMenu ( {Определить выпадающее подменю}

NewItem( '~1~ Первый выбор ' , ' F1 ' , kbF1, cm1, 0,

NewItem('~2~ Второй выбор' , ' F2 ' , kbF2, cm2, 0,

NewLine( {Определить разделяющую линию}

NewItem('~3~ Третий выбор' , ' ' , 0, cm3, 0,

NIL) ) ) ) ) ,

NewItem( {Второй элемент главного меню}

'-ESO Выход' , '~ESC~' , kbEsc,cmQuit, 0, NIL))))); end {MyApp. InitMenuBar} ;

{------------------}

Procedure MyApp . HandleEvent ; 

const

Flag: Boolean = True; 

cms = [cm1, cm2, cmQuit] ; 

begin

Inherited HandleEvent (Event) ; 

case Event . Command of

cm1: MessageBox (#3 'Первый выбор', NIL,0); 

cm2 : MessageBox (#3 'Второй выбор', NIL,0); 

cm3 : 

begin

MessageBox (#3' Третий выбор', NIL,0); 

if Flag then

DisableCommands (cms) 

else

EnableCommands (cms) ; 

Flag := not Flag 

end 

end 

end {MyApp .HandleEvent } ;

{------------------}

var

P: MyApp;

begin

P.Init; 

P.Run; 

P.Done 

end.



Обработка ошибок инициации и модальных состояний


Каждый видимый элемент наследует виртуальный метод TView.Valid (Command: Word): Boolean. С помощью этого метода решаются две задачи: если параметр обращения Command = cmValid = О, метод должен проверить правильность инициации объекта и выдать True, если инициация прошла успешно; при обращении с параметром Command о cmValid метод возвращает True только тогда, когда модальное состояние диалогового элемента можно завершить командой Command. По умолчанию метод Valid возвращает True. Вы должны перекрыть этот метод, если хотите автоматизировать контроль за инициацией объекта и/или за завершением работы модального элемента.

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

Типичный метод Valid имеет такой вид:

Function TMyView. Valid (Command: Word): Boolean; 

begin

Valid := True;

{Проверяем корректность инициации:} 

if Command = cmValid then 

if not Correctlylnit then 

begin

ReportErrorlnit; {Сообщяем о некорректной инициации} 

Valid := False 

end 

else

{Проверяем корректность завершения:} 

else if Command <> EnableCommand then

begin 

ReportErrorEnd {Сообщяем о некорректном выходе}

Valid := False 

end 

end;

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


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

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


Окно с текстом


В следующей программе на экране создается несколько окон, содержащих один и тот же текст - текст программы (см. рис.23. 3).

Рис.23.3. Окна с текстом программы

Каждое новое окно открывается с помощью клавиши Ins. Активное окно можно удалить клавишей Del или распахнуть на весь экран клавишей F5. С помощью мыши Вы можете перемещать активное окно по экрану и/или изменять его размеры.

Uses Objects,App,Views,Drivers,Menus; 

const

cmNewWin = 200;

cmDelWin = 201;

MaxLine = 22; {Количество текстовых строк} 

var

Lines: array [0.. MaxLine] of String [80]; 

type

MyApp = object (TApplication)

WinNo : Word;

Constructor Init;

Procedure InitStatusLine; Virtual;

Procedure HandleEvent (var Event: Tevent) ; Virtual;

Procedure NewWindow; 

end;

PInterior = Tinterior; 

TInterior = object (TView)

Constructor Init(R: TRect);

Procedure Draw; Virtual; 

end ;

{----------------}

Constructor MyApp. Init;

{Открывает и читает файл с текстом программы}

var

f: text;

s: String;

k: Integer; 

begin

Inherited Init;

WinNo := 0 ; {Готовим номер окна }

for К := 0 to MaxLine do

Lines [k] := ' ' ; {Готовим массив строк}

s := copy(ParamStr(0),1,pos ('.',ParamStr(0)))+'PAS';

{$I-}

Assign (f,s) ; 

Reset (f); 

if IOResult <> 0 then

exit; {Файл нельзя открыть} 

for k := 0 to MaxLine do

if not EOF(f) then ReadLn(f, Lines [k] ); 

Close(f) 

{$I+} 

end {MyApp.Init} ;

{----------------}

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 ( ' ~Ins~ Открыть новое' , kbIns, cmNewWin, 

NewStatusKey (' ~Del~ Удалить активное' , kbDel, cmClose, 

NewStatusKey (' ~F5~ Распахнуть ', kbF5, cmZoom, NIL)))), NIL))) 

end {MyApp. InitStatusLine} ;

{---------------------}

Procedure MyApp. HandleEvent;

{Обработка нестандартных команд cmNewWin, cmDelWin}


begin

Inherited HandleEvent (Event) ; 

case Event. Command of 

cmNewWin: 

begin

ClearEvent (Event) ; 

NewWindow; 

end ;

cmDelWin: Event . Command := cmClose; 

end;

ClearEvent(Event) 

end {MyApp.HandleEvent } ;

{-------------------}

Procedure MyApp.NewWindow ; 

{Открывает новое окно} 

var 

R: TRect;

W: PWindow; 

begin

Inc(WinNo); {Номер окна} 

{Задаем случайные размеры и положение окна : }

R. Assign (0, 0,24+Random(10) ,7+Random(5) ) ;

R. Move (Random ( 80 -R. В. X) ,Random(24-R.B.Y) ) ;

W := New (PWindow, Init (R, ' ' ,WinNo) ) ;

W^.GetClipRect (R) ; {Получаем в R границы окна}

R.Grow( - 1, -1) ; {Размер внутренней части окна} 

{Инициируем просмотр текста : }

W. Insert (New (PInterior, Init(R)));

DeskTop . insert (W) ; {Помещаем окно на экран} 

end {MyApp.NewWindow} ;

{-------------------}

Constructor TInterior.Init; 

{ Инициация просмотра текста во внутренней части окна} 

begin 

Inherited Init (R) ;

GrowMode := gfGrowHiX+gfGrowHiY 

end {Tinterior.Init} ;

{-----------}

Procedure TInterior. Draw; 

{Вывод текста в окне} 

var

k: Integer; 

В: TDrawBuffer; 

begin

for k := 0 to pred(Size.Y) do 

begin

MoveChar(B,' ',GetColor(1),Size.X); 

MoveStr(B, copy(Lines[k],1,Size.X),GetColor(1)); 

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

end

end {TInterior.Draw}; 

{-------------------}

var

P: MyApp; 

begin 

P.Init; 

P.Run; 

P.Done 

end.

В программе объявляется тип TInterior, предназначенный для создания изображения во внутренней части окон. Его метод Init определяет способ связи объекта TInterior со стандартным объектом TWindow: оператор

GrowMode := gfGrowHiX+gfGrowHiY

задает автоматическое изменение размеров объекта TInterior при изменении размеров окна так, чтобы вся внутренняя часть окна была всегда заполнена текстом. Метод TInterior.Draw заполняет внутреннюю часть окон текстовыми строками, которые в ходе выполнения конструктора TMyApp.Init предварительно считываются из файла с исходным текстом программы в глобальный массив Lines. Для вывода текста сначала с помощью метода MoveChar буферная переменная В типа TDrawBuffer заполняется пробелами, затем методом MoveStr в нее копируется нужный текст, а с помощью WriteLine содержимое переменной В помещается в видеопамять. Такая последовательность действий стандартна для вывода текстовых сообщений в Turbo Vision. Заметим, что функция GetColor (1) возвращает номер элемента палитры, связанный с обычным текстом; для выделения тестовых строк можно использовать вызов GetColor (2).


Окно со скроллером


Скроллером называется специальное окно, обеспечивающее просмотр (скроллинг) текста. Типичный скроллер - это окно редактора интегрированной среды системы Турбо Паскаля; его поведение Вам, очевидно, хорошо знакомо. Средства Turbo Vision обеспечивают стандартные функции скроллера для окна, создаваемого в приводимой даже программе. В частности, это окно (см. рис.23.4) управляется мышью, реагирует на клавиши смещения курсора, оно может изменять размеры и свое положение на экране, его можно «распахнуть» на весь экран.

Рис.23.4. Окно со скроллером

Uses Objects,App,Drivers,Menus,Views; var

Lines: PCollection; {Коллекция для хранения текстовых строк}

type

ТМуАрр = object (TApplication)

Procedure Run; Virtual; 

end;

PInterior =TInterior;

TInterior = object (TScroller)

Constructor Init(R: TRect; SX,SY: PScrollBar);

Procedure Draw; Virtual; 

end;

Procedure TMyApp.Run;

{Читает строки из, текстового файла и обеспечивает их просмотр}

var

R: TRect;

W: PWindow;

s,name: String;

f: text; 

begin

{Получаем в NAME имя файла с текстом программы:}

name := copy(ParamStr(0),1,pos('.',Paramstr(0)))+'PAS';

{Создаем коллекцию текстовых строк:}

Lines := New(PCollection, Init(10,5));

assign(f,name);

{$I-}

reset (f);

{$I+}

if IOResult = 0 then

begin {Файл успешно открыт} 

with Lines do while not EOF(f) do

begin

ReadLn ( f , s ) ; 

Insert (NewStr (s) ) 

end;

Close (f) 

end 

else {Файл не был открыт}

Lines . Insert (NewStr (' Нет доступа к файлу '+name)); 

{Создаем окно со скроллером: } 

DeskTop.GetExtent (R) ;

W := New (PWindow, Init (R, 'Просмотр файла '+name,0)); 

with W do 

begin

GetClipRect(R) ; 

R.Grow(-1, -1) ;

Insert (New (PInterior , Init (R, StandardScrollBar ( 

sbHorizontal+ sbHandleKeyboard) , 

StandardScrollBar (sbvertical+sbHandleKeyboard) ) ) ) 

end ;

DeskTop . Insert (W) ; 

{Ждем действий пользователя:} 

Inherited Run end {TMyApp.Run} ;

{----------------}


Constructor TInterior.Init; 

{Создает окно скроллера} 

begin

Inherited Init (R, SX, SY) ;

GrowMode := gfGrowHiX+gfGrowHiY;

SetLimit(128, Lines .count- 1) 

end {TInterior.Init};

{----------------}

Procedure TInterior.Draw;

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

var

Y: Integer; 

В: TDrawBuffer; 

S: String; 

begin

for Y := 0 to pred(Size.Y) do 

begin

MoveChar(B,' ' ,GetColor (1) , Size.X) ; 

if (Y+Delta.Y < Lines. Count) and

(Lines. At (Y+Delta.Y) <> NIL) then 

begin

S := PString (Lines. At (Y+Delta.Y) ); 

MoveStr (В, copy (s, Delta. X+1, Length (s) -

Delta. X), GetColor(1) ) 

end;

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

end 

end {TInterior.Draw} ;

{----------}

var

P : TMyApp ; 

begin

P.Init;

P. Run;

P. Done 

end.

В программе перекрывается метод TApplication.Run. В потомке TMyApp этот метод вначале считывает текстовые строки из файла с текстом программы в коллекцию Lines и создает на экране окно со скроллером. После этого вызывается стандартный метод TApplication.Run.

Метод TInterior.Draw обеспечивает вывод нужных строк в окно скроллера. Для определения порядкового номера выводимых строк и их положения относительно границ скроллера используется поле TScroller.Delta. Обратите внимание: если в коллекцию помещается «пустая» строка, т.е. строка нулевой длины, глобальная функция NewStr возвращает значение NIL. В методе TInterior.Draw оператор

if (Y+Delta.Y < Lines. count) and 

(Lines.At(Y+Delta.Y) <> NIL) then ...

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


Отладка программ


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

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

Может случиться, что установленная контрольная точка не будет реагировать вообще или, наоборот, будет активизироваться слишком часто. Если точка не активизируется, это означает, что Ваш обработчик событий просто «не видит» событие. В этом случае необходимо убедиться в том, что поле EventMask видимого объекта содержит маску, позволяющую ему реагировать на событие нужного вида. Другой причиной «исчезновения» события может быть его перехват (и обработка) другим видимым элементом. Это может быть вызвано различными обстоятельствами. Например, Вы могли ошибочно связать две разные команды с одной константой или используете команду, которую использует также другой видимый элемент. Кроме того, обычно в наследуемых методах HandleEvent вызывается обработчик событий объекта-родителя, который может «украсть» событие у Вашего обработчика. В таких ситуациях бывает достаточно сделать вызов родительского метода после того, как событие будет обработано Вами.

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


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

освобождается динамический объект, который входил в состав ранее освобожденной динамической группы;

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

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

Uses Objects,Views; 

var

G1, G2: PGroup;

R: TRect; 

begin

R.Assign(10,5,70,20) ;

Gl := New(PGroup, Init(R));

R.Grow(-10, -3) ;

G2 := New(PGroup, Init(R));

G1.Insert(G2);

Dispose(G1, Done);

Dispose(G2, Done) {Здесь программа "зависнет"!} 

end.

Заметим, что перестановка операторов Dispose местами приводит к корректному варианту, т.к. метод G1.Done умеет контролировать освобождение своего подэлемента G2 и не освобождает его вторично. Во всех случаях оператор Dispose (G2, Done) излишен: освобождение группы вызывает автоматическое освобождение всех ее подэ-лементов.

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

Unit HeapView;

Interface

Uses Dialogs,Objects;

type

PHeapView = THeapView;

THeapView = object(TStaticText) 

Constructor Init(var R: TRect); 

Procedure Update;

end;

Implementation 

Constructor THeapView.Init; 

var

S: String; 

begin

Str(MemAvail,S);

Inherited lnit(R,#3+S) 

end;

Procedure THeapView.Update; 



var

S: String; 

begin

Str(MemAvail,S);

DisposeStr(Text);

Text := NewStr(#3+S);

Draw 

end; 

end.

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

Uses Objects,Views,App, HeapView; 

var

H: PHeapView;{Окно для MemAvail}

W: PWindow; 

G: PGroup; 

R: TRect; 

P: TApplication;{Стандартная программа} 

begin 

P.Init;

R.Assign(70,0,80,1);{Верхний правый угол}

New(H, Init(R));{Создаем окно контроля} 

P.Insert(H);{Помещаем его на экран}

ReadLn; {Пауза - показываем начальный размер кучи}

R.Assign(10,5,70,20);

W := New(PWindow,Init(R,'',0)); {Создаем окно}

R.Assign(5,3,55,12);

G := New(PGroup, Init(R));

W.Insert(G); {Вставляем в окно группу}

DeskTop.Insert(W); {Выводим на экран}

Н.Update; {Обновляем окно контроля}

ReadLn; {Пауза - размер кучи перед освобождением}

Dispose(W, Done); {Освобождаем окно и группу}

НА.Update; {Обновляем окно контроля} 

ReadLn; {Пауза - размер после освобождения} 

Р.Done 

end.

Для получения текущего значения общего размера кучи используется вызов метода THeapView.Update в нужных местах программы. Вы можете автоматизировать обновление окна контроля, если включите вызов Update в перекрываемый метод TProgramIdle. В следующем варианте показан способ отображения MemAvail в фоновом режиме. Кроме того, в программе иллюстрируется возможное использование функции MessageBox.

{$Х+} {Используется расширенный синтаксис вызова функции MessageBox} 

Uses Objects,Views,App,HeapView,MsgBox; 

type

MyApp = object (TApplication) 

Procedure Idle; Virtual;

end; 

var

H: PHeapView; 

Procedure MyApp.Idle; 

begin

H^.Update 

end; 

var

W: PWindow;

G: PGroup;

R: TRect;

P: MyApp; 

begin

P.Init;

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

New(H,Init(R));

P.Insert(H);

MessageBox(#3'Размер кучи до размещения',NIL,0);

R.Assign(10,5,70,20) ;

W := New(PWindow, Init(R,'',0));

R.Assign(5,3,55,12) ;

G := New(PGroup, Init(R));

WA.lnsert(G);

DeskTop.Insert(W);

MessageBox(#3'Размер кучи после размещения', NIL,0);

Dispose(W, Done);

MessageBox(#3'Размер кучи после освобождения', NIL,0);

Р.Done 

end.

Константа #3 вставляется в начало строки сообщения в том случае, когда требуется центрировать эту строку (расположить ее симметрично относительно границ окна сообщения).


Порядок вызова наследуемого метода


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



Практика использования


Контроль за динамической памятью

Обработка ошибок инициации и модальных состояний

Отладка программ

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

Порядок вызова наследуемого метода

Конструктор

Деструктор

Другие методы

Примеры программных реализаций

Строка статуса

Меню

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

Окно с текстом

Окно со скроллером

Просмотр списка файлов

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

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



Примеры программных реализаций


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



Просмотр списка файлов


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

Для реализации просмотра списка файлов и выбора из этого списка нужного файла в Turbo Vision предусмотрен объект TListBox. Этот объект создает специальное окно скроллера, содержащее одну вертикальную полосу и указатель на текущий элемент. Имена файлов помещаются в коллекцию строк, указатель на которую передается объекту с помощью метода TListBox.NewList.

В программе используются две глобальные переменные, содержащие указатель на коллекцию L и номер выбранного элемента Foc. В объекте TApplication перекрываются методы Run и Done. Новый метод TMyApp.Run создает коллекцию и окно просмотра. Метод TMyApp.Done перед завершением работы программы формирует окно, в котором сообщается имя выбранного из списка файла. Заметим, что это имя помещается в переменную Foc в момент выхода из программы с помощью перекрываемого метода TListBox. Valid.

Рис. 23.5. Окно выбора файлов

{$Х+}

Uses DOS,Objects,App,Views,Dialogs,Drivers,MsgBox; 

var

L: PStringCollection; {Коллекция имен файлов} 

Foc: String; {Выбранный файл} 

type

ТМуАрр = object (TApplication) 

Procedure Run; Virtual; 

Destructor Done; Virtual; 

end ;

PMyListBox =^TMyListBox; 

TMyListBox = object (TListBox)

Function Valid(Command: Word): Boolean; Virtual; 

end ; 

{------------------}

Procedure TMyApp.Run; {Создает диалоговое окно с TListBox} 

var

R,RR: TRect; 

W: PDialog;

S: SearchRec;

B: PScrollBar; 

P: PListBox; 

begin {Создаем коллекцию имен файлов:}

L := New(PStringCollection, Init(50,10)); 

FindFirst('\games\fl9\*.*',Archive,S); 

While DosError = 0 do with S,L^ do 

begin

Insert(NewStr(Name)); 

FindNext(S) 

end;


{Создаем окно:} 

R.Assign (17, 4 ,63, 14 );

W := New(PDialog, Init (R, 'Текущий каталог:')); 

{Вставляем в окно TListBox:} 

with W do 

begin

RR.Assign(44,1,45,9) ;

В := New(PScrollBar, Init(RR));

Insert (B) ;

R.Assign (1, 1,44,9) ;

P:= New(PMyListBox, Init (R, 3 ,B) ) ;

P.NewList(L) ;

Insert (P) 

end ;

DeskTop . Insert (W) ; {Помещаем окно на экран} 

Inherited Run {Ждем команду Alt-X} 

end; {TMyApp.Run}

{-------------------}

Function TMyListBox. Valid;

{Помещает в Foc имя выбранного файла}

begin

Foc := PString(L.At (Focused));

Valid := True

end ; {TMyL stBox .Valid}

Destructor TMyApp.Done;

{Выводит имя выбранного файла} 

var

R: TRect;

begin

R.Assign(20, 15, 60,22) ;

MessageBoxRect(R,#3' Выбран файл '+Foc, NIL, $402);

Inherited Done 

end {TMyApp.Done};

{-----------------}

var

P: TMyApp; 

begin

P. Init;

P. Run;

P. Done 

end.

Окно TListBox управляется мышью и клавишами. В частности, клавишами смещения курсора можно выбрать нужный файл, клавишами PgUp, PgDn листать окно со списком. Работают также клавиши End, Home, Ctrl-PgUp, Ctrl-PgDn.

В момент обращения к методу TMyApp.Done вызывается функция TMyListBox. Valid, которая определяет номер вьщеленного файла (этот номер хранится в поле TListBox.Focused) и переписывает имя этого файла из коллекции в глобальную переменную Foc.


Строка статуса


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

Esc Выход F1 Сменить контекст на 1 

Однажды в студеную, зимнюю пору

либо

ESC Выход F2 Сменить контекст на 0 

Я из лесу вышел. Был сильный мороз...

Переключение строки осуществляется клавишами F1 и F2, для выхода из программы используется клавиша Esc.

Uses Objects,App,Menus,Drivers,Views; 

type

PMyStatusLine = TMyStatusLine; 

TMyStatusLine = object (TStatusLine)

Function Hint(Cntx: Word): String; Virtual; 

end; 

MyApp = object (TApplication)

StatLine: PMyStatusLine; 

Constructor Init;

Procedure InitStatusLine; Virtual; 

Procedure HandleEvent(var Event: Tevent); Virtual; 

end; 

const

cmCntxl =200; 

cmCntx2 = 201;

{-----------------}

Constructor MyApp.Init ; 

begin

Inherited Init;

Insert (StatLine) {Использовать нестандартную строку статуса} 

end {MyApp .Init} ;

Procedure MyApp. Ini tstatusLine ;

{Инициация нестандартного поля MyApp. StatLine}

var

R: Trect; 

begin

GetExtent (R) ;

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

StatLine := New(PMyStatusLine, Init(R,

NewStatusDef (0, 0, {Первый вариант строки}

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

NewStatusKey (' ~F1~ Сменить контекст на 1', kbF1 , cmCntxl , NIL) ) ,

NewStatusDef (1, 1, {Второй вариант строки}

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

NewStatusKey (' ~F2~ Сменить контекст на 0 ' , kbF2 , cmCntx2 , NIL)) ,

NIL) ) ) ) ; end {MyApp. Ini tstatusLine} ;

{--------------------}

Procedure MyApp. HandleEvent;

{Переключение контекста и обновление строки статуса} 

begin

Inherited HandleEvent (Event) ; 

case Event . Command of 

cmCntxl: HelpCtx := 1; 

cmCntx2: HelpCtx := 0; 

else

ClearEvent (Event) ; 

end;

if Event. What <> evNothing then 

begin

StatLine. Update; 

ClearEvent (Event) 

end 

end {MyApp . HandleEvent } ;

{---------------------}

Function TMyStatusLine. Hint (Cntx: Word):String;

{Переключение поля подсказки}

const

Prompt: array [0..1] of String =(

'Однажды в студеную, зимнюю пору',

'Я из лесу вышел. Был сильный мороз...');

begin

Hint : = Prompt [Cntx] 

end {TMyStatusLine.Hint} ;

{---------------------}

var

P : MyApp ; 

begin

P.Init;

P . Run ;

P . Done 

end .