begin
{Подготовить работу программы}
{Выполнить необходимые действия}
{Завершить исполнение программы}
end.
Если Вы попытаетесь выполнить эту программу, ничего не произойдет - ведь мы
еще никак не конкретизировали необходимые действия. Так обстоит дело в Турбо
Паскале, но не так - в Turbo Vision! Для любой прикладной программы Turbo Vision
сразу же создает некоторую минимальную программную реализацию, которую Вы можете
Uses Арр; {Используется модуль АРР библиотеки Turbo Vision}
var
Notebook: TApplication;
begin
Notebook.Init; {Подготовить работу программы}
Notebook.Run; {Выполнить необходимые действия}
Notebook.Done {Завершить исполнение программы}
end.
В этой программе объявляется использование стандартного для Turbo Vision модуля Арр (от application - приложение, прикладная программа). Такое объявление открывает доступ прикладной программе к мощным возможностям Turbo Vision. Чтобы использовать эти возможности, мы объявили переменную Notebook (notebook - записная книжка) типа TApplication. Как Вы вскоре заметите, на букву Т в Turbo Vision начинаются идентификаторы объектов. Таким образом, Notebook - это экземпляр объекта TApplication, т.е. объединение данных (полей) и методов обработки этих данных (процедур, функций, конструкторов, деструкторов). В объекте TApplication предусмотрены методы Init, Run и Done. Вызов этих методов и составляет исполняемую часть нашей программы. Если Вы подготовите и запустите программу, на экране ПК появится изображение, показанное на рис. 15.1.
Рис.15.1. Вид экрана для простейшей программы
Для выхода из программы необходимо, как это следует из надписи в левом нижнем углу экрана, нажать Alt-X или подвести к этой надписи указатель мыши (если, разумеется, Ваш ПК оснащен этим устройством) и нажать ее левую кнопку.
Как видите, даже простейшая программа «знает», как создать экран, распознает команду Alt-X и может работать с мышью. Совсем не плохо для трех исполняемых операторов, не так ли? Такие возможности доступны потому, что в объекте TApplication предусмотрены соответствующие методы. В этом смысле использование объектов напоминает использование подпрограмм из библиотек. Однако в отличие от подпрограммы любой объект имеет все необходимые ему данные. Говоря об объектах, я часто буду использовать такие слова, как «знает», «умеет», «может», подчеркивая тем самым главную отличительную особенность объектов от традиционных подпрограмм - их «разумность»: последовательное проведение в жизнь принципа инкапсуляции (объединения) данных и всех необходимых для их обработки методов придает объекту определенную независимость от других элементов программы; объекты как бы «живут» в программе своей независимой жизнью.
Простейшая программа не может выполнять никаких других действий, кроме уже перечисленных, так как именно эти действия запрограммированы в методах Init и Run объекта TApplication. В ходе их выполнения на экране создается изображение, имеющее три зоны: верхняя строка, нижняя строка и вся остальная часть экрана. Верхняя строка обычно используется для размещения опций главного меню (не забывайте, что Turbo Vision - это оболочка для диалоговых программ!). Нижняя строка - строка статуса: в ней указываются так называемые командные клавиши, т.е. клавиши или комбинации клавиш, которые вызывают нужные действия без перехода к промежуточному диалогу. Вся остальная часть экрана составляет «рабочий стол» программы - сюда будут помещаться сообщения, здесь будут размещаться окна, «выпадающие» меню (меню нижнего уровня) и т.п.
Alt-X Exit
на русифицированное
Alt-X Выход
Таким образом, нам необходимо модифицировать стандартное поведение объекта Notebook. Для этого мы должны отыскать в типе TApplication метод, ответственный за создание строки статуса. Если мы обратимся к прил.П6, то обнаружим, что объект типа TApplication содержит методы Init и Done, с помощью которых создаются и уничтожаются экземпляры объекта, но в нем нет метода, ответственного за строку статуса. Однако из таблицы наследования нетрудно определить, что этот метод (InitStatusLine) он наследует от своего родителя TProgram. Как изменить работу метода? В рамках объектно-ориентированной библиотеки для этого поступают следующим образом: объявляется объект-потомок от стандартного объекта, поведение которого необходимо изменить, и в новом объекте описывается свой метод, ответственный за это поведение.
Изменим программу следующим образом;
Uses Арр, Objects, Menus, Drivers, Views;
type
TNotebook = object (TApplication) {Создаем объект-потомок от TApplication}
Procedure InitStatusLine; Virtual; {Перекрываем старый метод InitStatusLine новым}
end;
{-----------}
Procedure TNotebook. InitStatusLine;
{Описание нового метода, с помощью которого создается строка статуса}
var
R: TRect; {Границы строки статуса}
begin
GetExtent (R) ; {Получаем в R координаты всего экрана}
R.A.Y := pred(R.B.Y) ; {Помещаем в R координаты строки статуса}
{Создаем строку статуса:}
StatusLine := New(PStatusLine, Init(R,
{Определяем один вариант строки статуса:}
NewStatusDef (0, $FFFF, {Устанавливаем для этого варианта максимальный диапазон контекстной справочной службы}
{Определяем единственную клавишу Alt-X: }
NewStatusKey('~Alt-X~ Выход' , kbAltX, cmQuit,
NIL), {Нет других клавиш}
NIL) {Нет других строк статуса}
))
end; {TNotebook. InitStatusLine}
{---------}
var
Notebook: TNotebook;{Изменен тип переменной!}
begin
Notebook. Init;
Notebook. Run;
Notebook. Done
end .
Как видим, программа сразу же усложнилась. Во-первых, в ней используются идентификаторы, которые определены в других модулях Turbo Vision, - эти модули мы перечислили в предложении Uses. Во-вторых, нам потребовалось объявить новый объект TNotebook как потомок от объекта TApplication. Объект-потомок наследует от своего объекта-родителя все поля и методы и при необходимости может их дополнять своими полями и методами, а также перекрывать методы родителя. Как раз для того, чтобы перекрыть унаследованный от TProgram стандартный метод InitStatusLine, ответственный за создание строки статуса, нам и понадобилось объявление нового типа TNotebook. Строка
Procedure InitStatusLine; Virtual;
в объявлении этого типа указывает, что новый объект будет пользоваться одноименным, но иным, чем объект-родитель, методом. Возможность замены методов на одноименные, но с другим содержанием называется полиморфизмом.
Процедура TNotebookJnitStatusLine раскрывает суть нового метода. В ней используется обращение к методам NewStatusDef vi NewStatusKey, с помощью которых создается динамический объект типа TStatusLine. Программа TApplication обращается к методам этого объекта для обслуживания строки статуса.
Turbo Vision позволяет определять несколько вариантов строки статуса. Каждый вариант создается с помощью метода NewStatusDef. В зависимости от текущего состояния программы (от контекста программы) Turbo Vision автоматически помещает в строку статуса нужный вариант. Так как в нашей программе используется единственный вариант строки статуса, мы указали максимально возможный диапазон контекста программы при обращении к методу NewStatmDef.
С помощью метода NewStatusKey в строке статуса определяется очередная командная клавиша. При обращении к методу сначала указывается текст, высвечиваемый в строке статуса, причем символом «~» выделяется та часть сообщения, которая будет подсвечена в строке статуса другим цветом: таким способом в Turbo Vision указываются командные клавиши. Идентификатор kbAltX задает комбинацию клавиш, а cmQuit - связанную с ней команду.
В программе объявляется переменная R типа TRect. С помощью такого типа переменных программист задает координаты прямоугольного участка экрана. Эта переменная необходима нам для указания того места на экране, где будет помещено сообщение
Alt-X Выход
определяющее командные клавиши Alt-X. Для правильного задания этих координат мы использовали два предложения:
GetExtent(R);
R.A.Y := pred(R.B.Y);
В первом вызывается стандартный метод Turbo Vision GetExtent, с помощью которого в R помещаются координаты доступной в данный момент части экрана. Во втором - номер той строки (строки статуса), куда будет выводиться сообщение. В Turbo Vision тип TRect объявляется в виде следующей записи:
type
TRect = record
A: record {Координаты верхнего левого угла}
X: Byte; Y: Byte
end;
В: record {Координаты правого нижнего угла}
X: Byte; Y: Byte
end
end;
Таким образом, второе предложение лишь уменьшает на единицу вертикальную координату самой нижней доступной строки и устанавливает полученное значение в поле R.A. Y (это поле задает вертикальную координату верхнего левого угла прямоугольного участка). Заметим, что в Turbo Vision минимальные координаты задаются значением 0, в то время как в стандартном модуле CRT Турбо Паскаля минимальные координаты имеют значение 1.
Обратите внимание на характерный прием, широко используемый в Turbo Vision: при обращении к методам NewStatusDef и NewStatusKey последним параметром указывается переменная типа Pointer. Внутри методов эта переменная трактуется как ссылка на новый метод, что позволяет организовать цепочку последовательных определений. Вложенная последовательность вызовов заканчивается зарезервированной константой NIL, указывающей на конец цепочки. Если бы мы, например, захотели добавить в строку статуса определение клавиши F10, связав ее с закрытием активного окна, мы могли бы использовать такую конструкцию:
NewStatusDef(0, $FFFF,
NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit,
NewStatusKey('~F10~ Закрыть окно', kbFl0, cmClose,
NIL)), {Нет других клавиш}
NIL) {Нет других определений}
Описанный пример позволяет нам сделать очень важный вывод:
Чтобы модифицировать стандартное поведение объекта, необходимо создать объект-потомок от этого объекта и перекрыть в нем нужный метод.
const
{Команды для обработчиков событий:}
cmWork = 203; {Обработать данные}
cmDOS = 204; {Временно выйти в ДОС}
WinComl: TCommandSet = [cmSave,cmWork]; {Множество временно недоступных команд}
Эти строки следует вставить сразу после предложения Uses; они определяют коды команд, которые будут затем использоваться для вызова соответствующих частей программы. Кроме того, объявление объекта TNotebook нужно дополнить строкой
type
TNotebook = object (TApplication)
.......
Procedure InitMenuBar; Virtual; {Перекрываем стандартный метод InitMenuBar}
end;
в которой перекрывается прежний метод InitMenuBar, ответственный за формирование строки меню. И, наконец, в разделе объявлений программы следует поместить описание метода InitMenuBar и видоизменить описание метода InitStatusLine:
Procedure TNotebook.Ini tMenuBar;
{Создание верхнего меню}
var
R: TRect;
begin
GetExtent (R) ;
R.B.Y := succ(R.A.Y) ; {R - координаты, строки меню}
MenuBar := New ( PMenuBar , Init(R, NewMenu ( {Создаем меню}
{Первый элемент нового меню представляет собой подменю (меню второго уровня) . Создаем его} NewSubMenu( '~F~/ Файл', hcNoContext,
{Описываем элемент главного меню}
NewMenu ( {Создаем подменю}
NewItem( {Первый элемент}
'~1~/ Открыть ', 'F3' , kbF3,cmOpen, hcNoContext,
NewItem( {Второй элемент}
'~2~/ Закрыть ', 'F2', kbF2, cmSave,hcNoContext,
NewItem( {Третий элемент}
'~3~/ Сменить диск' , ' ' , 0, cmChangeDir,hcNoContext,
NewLine ( {Строка-разделитель}
NewItem('~4~/ Вызов ДОС' , ' ' , 0, cmDOSShell,
hcNoContext,
NewItem('~5~/ Конец работы' , 'Alt-X' ,
kbAltX, cmQuit, hcNoContext,
NIL) ) ) ) ) ) {Нет других элементов подменю} ),
{Создаем второй элемент главного меню}
NewItem( '~W~/ Работа', ' ', kbF4, cmWork, hcNoContext,
NIL) {Нет других элементов главного меню} ))))
end; {TNotebook. InitMenuBar}
{---------}
Procedure TNotebook. InitStatusLine;
{Формирует строку статуса}
var
R: TRect; {Границы строки статуса}
begin
GetExtent (R,) ; {Получаем в R координаты всего экрана}
R.A.Y := pred(R.B.Y) ; StatusLine := New(PStatusLine,
Init(R, {Создаем строку статуса}
NewStatusDef (О, $FFFF, {Устанавливаем максимальный диапазон контекстной справочной службы}
NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit,
NewStatusKey('~F2~ Закрыть', kbF2, cmSave,
NewStatusKey('~F3~ Открыть', kbF3,cmOpen,
NewStatusKey('~F4~ Работа', kbF4,cmWork,
NewStatusKey('~F10~ Меню', kbF10,cmMenu, NUL))))),{Нет других клавиш}
NUL){Нет других определений}
));
DisableCommands(WinComl) {Запрещаем недоступные команды}
end; {TNotebook.InitStatusLine}
В новом варианте программы мы продвинулись дальше по пути конкретизации ее действий. Если Вы запустите программу и нажмете клавиши Alt-F (вызов опции «Файл» главного меню), на экране появится изображение, показанное на рис. 15,2.
Рис.15.2. Вид окна с развернутым меню опции Файл
Определение опций меню во многом напоминает определение командных клавиш в строке статуса. Отличие заключается лишь в том, что с любой опцией меню может быть при необходимости связана встроенная справочная служба. В нашей программе мы не используем эту возможность, для чего задаем стандартный идентификатор hcNoContext (нет контекстно-зависимой справки) при описании каждой опции.
Подобно клавишам строки статуса командные клавиши меню выделяются символом «~». Заметим, что не имеет смысла назначать в качестве командных клавиш клавиши кириллицы, так как при их анализе Turbo Vision игнорирует коды 128...255. Если бы, например, мы задали в качестве командной клавиши для опции «Файл» клавишу «Ф», нажатие Alt-Ф не вызвало бы развертывания подменю, связанного с этой опцией (как и в Турбо Паскале, в Turbo Vision опции главного меню вызываются комбинацией А1t-<клавиша>, а опции меню нижнего уровня - просто нажатием нужной командной клавиши).
Что же такое команды Turbo Vision? Внимательный анализ предыдущего варианта программы показывает, что.эти коды еще никак не используются, они понадобились лишь для синтаксически правильного обращения к стандартным методам инициации строк меню и статуса. В новом варианте программной реализации Вы можете вызвать любую опцию главного меню или нажать любую командную клавишу - это не приведет ни к каким последствиям: пока работает только команда Alt-X, завершающая работу программы, и клавиши F2, F3 и F10. Происходит это потому, что эти клавиши мы связали со стандартными командами cmQuit, cmSave, cmOpen и стМепи и обрабатываются они где-то внутри Turbo Vision. Новые команды не известны системе, и их обработку мы должны взять на себя.
Как мы увидим дальше, в Turbo Vision есть средства контроля командных клавиш. Эти средства определяют факт нажатия на клавишу с помощью генерации кода соответствующей команды, который (код) будет в этом случае передан нашей программе. Таким образом, обработка команд заключается в расшифровке получаемых от Turbo Vision кодов и передаче управления соответствующим частям программы.
Для шифровки команд в Turbo Vision используется 16-разрядное слово, что позволяет определить до 65535 различных команд. Некоторые из этих кодов зарезервированы для использования внутри Turbo Vision, остальные доступны программисту:
Код команды | Зарезервировано | Можно запретить |
0. . .99 | Да | Да |
100.. .255 | Нет | Да |
256. . .999 | Да | Нет |
1000. . .65535 | Нет | Нет |
Команды с кодами от 0 до 255 при необходимости могут быть временно запрещены, остальные команды запретить нельзя - вот почему используется два диапазона доступных для программиста кодов команд. Временное запрещение команд связано с тем очевидным свойством диалоговых программ, что отнюдь не любая команда может исполняться в каждом возможном состоянии программы. Например, бессмысленно использовать команду «Закрыть файл», если файл еще не открыт. Наоборот, если файл уже открыт, команда «Открыть файл» может стать временно недоступной пользователю. Механизм маскирования (временного запрещения) команд позволяет избавиться от многочисленных проверок контекстуальной корректности тех или иных команд: программист может их запретить впредь до наступления какого-либо события, а запрещенные команды игнорируются средствами Turbo Vision и в программу пользователя не передаются.
В нашей программе имеет смысл запретить команды cmSave и cmWork до тех пор, пока пользователь не откроет нужный файл с данными. Запрет команд достигается обращением к стандартной процедуре DisableCommands (см. предыдущий вариант программы). Указанные в обращении к ней команды задаются в виде множества кодов (мощность любого множества в Турбо Паскале не может превышать 256, вот почему могут быть запрещены только первые 256 команд) и становятся недоступны впредь до обращения к процедуре EnableCommands (разрешить команды).
Запрещенные опции меню (как и временно недоступные командные клавиши) выделяются на экране оттенком (пониженной яркостью).
Назад | Начало | Вперед |
Turbo Vision поддерживает два возможных способа действия пользователя - с помощью клавиш клавиатуры и с помощью мыши. Любое такое действие пользователя с точки зрения Turbo Vision приводит к появлению события, т.е. к созданию небольшого информационного пакета, описывающего вновь возникшую ситуацию. События распространяются от одной части программы к другой до тех пор, пока не обнаружится подпрограмма, ответственная за обработку данного события. Эта подпрограмма обычно очищает информационный пакет и таким образом блокирует дальнейшее перемещение события.
Пожалуй, именно механизм событий кардинально отличает Turbo Vision от других библиотек Турбо Паскаля. На первых порах это может вызвать определенные трудности, связанные с отладкой программ. Принцип независимости обработки событий от процесса создания видимых элементов приводит фактически к появлению двух параллельных процессов в рамках одной программы: процесса создания видимых элементов и процесса обработки событий. Говоря о программах Turbo Vision, следует помнить, что эти программы управляются событиями. Их трассировка (прослеживание работы) в среде Турбо Паскаль обычно достигается установкой и использованием контрольных точек.
Подпрограммы, ответственные за обработку действий пользователя, называются обработчиками событий. Любой стандартный для Turbo Vision объект, обеспечивающий создание видимого элемента, имеет собственный обработчик событий (виртуальный метод HandleEvent), который Вы можете перекрыть своим собственным методом, если Вас не устраивает стандартная реакция объекта на то или иное событие. Существует такой метод и в объекте TNotebook. По умолчанию этот объект использует обработчик событий, унаследованный им от объекта-родителя TApplication. Стандартный обработчик знает, как реагировать на команды cmQuit и стМепи, но ему не известны новые команды cmWork, cmOpenFile и другие. Чтобы программа смогла правильно обработать эти команды, мы должны перекрыть стандартный метод HandleEvent объекта TNotebook новым. Добавим в описание объекта TNotebook еще одну строку
type
TNotebook = object (TApplication)
.......
Procedure HandleEvent(var Event: TEvent); Virtual;
end;
и поместим в раздел объявлений текст новой подпрограммы:
Procedure TNotebook.HandleEvent(var Event: TEvent);
{Обработчик событий программы}
begin {TNotebook.HandleEvent}
Inherited HandleEvent(Event);{Обработка стандартных команд cmQuit и cmMenu}
if Event.What = evCommand then
case Event.Command of
{Обработка новых команд:}
cmOpen : FileOpen; {Открыть файл}
cmSave:FileSave; {Закрыть файл}
cmChangeDir:ChangeDir; {Сменить диск}
cmDOSShell:DOSCall; {Временный выход в ДОС}
cmWork:Work; {Обработать данные}
else
exit {Не обрабатывать другие команды}
end;
ClearEvent (Event) {Очистить событие после обработки}
end; {TNotebook.HandleEvent}
Чтобы новый вариант программы можно было выполнить, следует предусмотреть «заглушки» для несуществующих пока процедур FileOpen, FileSave и т.д. Например:
Procedure FileOpen;
begin
end;
Поведение вновь созданного варианта программы внешне ничем не отличается от предыдущего: также будут созданы меню, строка статуса и основное поле экрана, программа по-прежнему будет распознавать команды Alt-X и F10. Однако теперь она будет реагировать и на новые команды. Чтобы убедиться в этом, установите контрольные точки в заглушках FileOpen и FileSave и запустите программу вновь: нажатие на клавишу F3 вызовет останов в контрольной точке FileOpen - ведь именно с этой клавишей мы связали команду cmOpen в процедуре InitStatusLine, в то время как нажатие на клавишу F2 не приведет к срабатыванию контрольной точки FileSave, поскольку команда cmSave пока еще запрещена и обработчик HandleEvent ее просто не «увидит».
Чтобы использовать нестандартные команды меню или строки статуса, мы должны перекрыть обработчик событий программы, в новом обработчике выделить из потока событий команды и распознать их коды.
Чтобы стали более понятны действия обработчика событий, отметим, что тип TEvent в Turbo Vision определен как запись такого вида:
type
TEvent = record
What: Word; {Определяет тип события}
case Word of {"Пустое" событие}
evMouse: ( {Событие от мыши:}
Buttons: Byte; {Состояние кнопок}
Double: Boolean;{Признак двойного нажатия кнопки мыши}
Where: TPoint); {Координаты курсора мыши}
evKeyDown: ( {Событие от клавиатуры:}
case Integer of
0: (KeyCode: Word);{Код клавиши}
1: (CharCode: Byte; 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;
Стандартная маска evCommand позволяет выделить из потока событий только те, которые связаны с передачей команд между различными обработчиками событий. Именно таким способом стандартный обработчик TApplication.HandleEvent сообщает новому обработчику TNotebookHandleEvent о возникновении события, связанного с вновь определенной командой. Если бы мы не предусмотрели вызов стандартного обработчика с помощью оператора
Inherited HandleEvent(Event);
нам пришлось бы самим анализировать положение мыши или нажатую клавишу и интерпретировать их как соответствующие команды. Включение вызова TApplication.HandleEvent в тело нашего обработчика событий избавляет нас от этой рутинной работы.
В конце обработчика мы вызвали стандартную процедуру ClearEvent, с помощью которой в переменную Event помещается сообщение Nothing («пустое» событие). Это событие игнорируется всеми обработчиками, так что программа будет повторять проверку состояния мыши и клавиатуры до тех пор, пока не произойдет нового события. Фактически тело процедуры TApplication.Run (см. раздел исполняемых операторов нашей программы) состоит из бесконечно повторяющегося цикла проверки мыши и клавиатуры и передачи событий по цепи обработчиков событий. После получения любого события обработчик должен либо обработать это событие и очистить переменную Event, либо просто вернуть управление обработчику верхнего уровня, если эта команда не предназначена для него, либо, наконец, сформировать и передать новое событие для реализации команд, которые распознаны им, но которые он выполнять не умеет или не должен.
Начнем с процедуры FileOpen. Ее задача - выбрать один из возможных файлов с данными и подготовить его к работе. Конечно, программу можно было бы сделать менее гибкой, раз и навсегда «привязав» ее к какому-то одному файлу, скажем, с именем notebook.dat. Но даже и в этом случае следует решить проблему с местоположением файла данных, а также определить, что должна делать программа, если нужный файл не найден. Наша программа будет весьма гибкой в этом отношении: она позволит указать интересующий нас файл мышью или клавишами курсора, либо ввести имя файла с помощью клавиатуры или взять его из буфера ранее введенных имен. Иными словами, поведение нашей программы будет в точности повторять поведение среды Турбо Паскаль в момент нажатия на клавишу F3.
Если Вы когда-либо программировали подобные действия в Турбо Паскале, Вы по достоинству оцените простоту их реализации в Turbo Vision:
Procedure FileOpen; {Открывает файл данных}
var
PF: PFileDialog; {Диалоговое окно выбора файла}
Control: Word;
s: PathStr;
begin
{Создаем экземпляр динамического объекта:}
New(PF, Init('*.dat','Выберите нужный файл:','Имя файла',fdOpenButton,0));
{С помощью следующего оператора окно выводится на экран и результат работы пользователя с ним помещается в переменную Control:}
Control := DeskTop.ExecView(PF);
{Анализируем результат запроса:}
case Control of
StdDlg. cmFileOpen, cmOk:
begin {Пользователь указал имя файла:}
PF.QetFileName(s) ; {s содержит имя файла}
{-----------} {Открыть файл}
end;
end; {case Control}
Dispose (PF, Done) {Уничтожаем экземпляр}
end; {FileOpen}
Для реализации этого фрагмента необходимо указать имя модуля StdDlg в предложении Uses - в этом модуле описан тип PFileDialog и предусмотрены все необходимые методы для работы с ним. Кроме того, в программе используется переменная S типа PathStr. Этот тип описан в модуле DOS - сошлитесь также и на него. Сделайте нужные изменения в тексте программы, не раскрывая пока сущности действий
{Открыть файл}
запустите программу на счет и нажмите клавишу F3 - экран приобретет вид, показанный на рис.15.3.
Тип PFileDialog - это указатель на объект TFileDialog, создающий и обслуживающий стандартное диалоговое окно выбора файлов. Все действия по созданию и использованию диалогового окна, показанного на рис.15.3, реализуются двумя операторами:
NewfPF, Init('*.dat','Выберите нужный файл:',
'Имя файла',fdOpenButton, 0));
Control := DeskTopA.ExecView(PF);
Puc. 15.3. Диалоговое окно выбора файлов
Первый оператор инициирует новый экземпляр объекта TFileDialog. Три строковых параметра обращения к конструктору Init этого объекта задают, соответственно, маску выбираемых файлов ('*.dat'), заголовок диалогового окна ('Выберите нужный файл:') и заголовок окна ввода ('Имя файла'). Параметр fdOpenButton указывает на необходимость включить в диалоговое окно кнопку Open. Последним параметром задается идентификатор протокола ввода. Доступ к этому протоколу открывается кнопкой [|] справа от окна ввода. Сам протокол хранится в куче в виде последовательности вводившихся ранее текстовых строк. Идентификатор протокола ввода позволяет при необходимости использовать один и тот же протокол в разных диалоговых окнах.
Второй оператор
Control := DeskTop.ExecView(PF);
помещает вновь созданное окно в основное поле экрана программы (ссылка DeskTop) и инициирует диалог с пользователем. Результат диалога возвращается в переменной Control, значение этой переменной анализируется оператором
case Control of
.......
end;
Если Control содержит коды команд cmOk или cmFileOpen, то с помощью метода GetFileName объекта TFileDialog в переменную S записывается полное имя файла (с предшествующим путем).
В методе TFileDialog.GetFileName (var Name: Pathstr) параметр обращения должен иметь тип PathStr. Этот тип определен в модуле DOS,- вот почему нам понадобилось сослаться на этот модуль в предложении Uses. Если указать компилятору на необходимость смягчить проверку строковых типов (директива компилятора {$V-}), то при обращении к GetFileName можно использовать переменнуюлюбого строкового типа, в том числе String.
Перед выходом из процедуры FileOpen экземпляр объекта TFileDialog уничтожается (удаляется из кучи) обращением к деструктору Done.
По описанной схеме в Turbo Vision создаются и используются любые другие диалоговые окна.
Для реализации диалогового запроса необходимо создать диалоговое окно и с помощью функции ExecView объекта-владельца (программы) инициировать диалог с пользователем. Результат, возвращаемый этой функцией, будет содержать выбранную пользователем команду.
Чтобы запрограммировать действия, связанные с открытием файла, следует вначале решить, какие именно данные он будет содержать. Напомню, что мы разрабатываем диалоговую программу управления «записной книжкой». Структура типичной записи в такой книжке состоит из трех полей: имя, телефон, адрес. Учитывая это, будем считать, что данные в файле хранятся в виде следующих записей:
const
LName = 25;{Длина поля Name}
LPhone= 11;{Длина поля Phone}
LAddr =40;{длина поля Addr}
type
DataType = record {Тип данных в файле}
Name : String[LName]; {Имя}
Phone: String[LPhone] {Телефон}
Addr : String[LAddr] {Адрес}
end;
Поместим эти строки в начале программы, а перед описанием процедуры FileOpen вставим определения следующих глобальных переменных:
var
DataFile: file of DataType; {Файловая переменная}
OpFileF : Boolean; {Флаг открытого файла}
Дополним текст процедуры FileOpen такими строками:
case Control of
StdDlg.cmFileOpen,cmOk:
begin
PFA.GetFileName(s);
Assign(DataFile,s); {Отсюда начинаются новые строки}
{$I-}
Reset(DataFile);
if lOResult <> 0 then
Rewrite{DataFile); OpFileF := IOResult=0;
{$I+}
if OpFileF then
begin
DisableCommands(WinCom2);
EnableCommands(WinComl)
end
end;
end;
С помощью оператора DisableCommands мы временно запрещаем набор команд, указанный в константе WinComl. Эта константа в нашем случае должна содержать команду стОреn; ее определение нужно включить сразу за определением константы WinComl:
const
WinComl: TCommandSet = [cmSave, cmWork];
WinCom2: TCommandSet = [cmOpen];
Обращение к процедуре EnableCommands разрешает использовать команды cmSave vicmWork.
В Turbo Vision все новые процедуры обычно инкапсулируются в объекты., если в них необходимо получить доступ к специфическим средствам этих объектов.
Поскольку процедура FileOpen вызывается из обработчика событий объекта TNotebook, нам следует включить ее в виде нового метода этого объекта:
type
TNotebook = object(TApplication)
.......
Procedure FileOpen;
Procedure FileSave;
Procedure ChangeDir;
Procedure DOSCall;
Procedure Work;
end;
В этом фрагменте мы инкапсулировали в объект все методы, используемые обработчиком событий. Разумеется, необходимо соответствующим образом изменить заголовок процедуры FileOpen, поскольку она теперь стала методом объекта TNotebook:
Procedure TNotebook.FileOpen;
Аналогичным образом следует изменить и заголовки других инкапсулированных процедур. Теперь трансляция пройдет успешно, а после открытия файла станет недоступна команда F3.
Тексты двух других новых методов объекта TNotebook не нуждаются в особых комментариях:
Procedure TNotebook.FileSave;
{Закрывает файл данных}
begin
Close(DataFile);
OpFileF := False;
EnableCommands(WinCom2); {Разрешаем открыть файл}
DisableCommands(WinComl) {Запрещаем работу и сохранение}
end; {TNotebook.FileSave}
{-----------}
Procedure TNotebook.ChangeDir;
{Изменяет текущий каталог}
var
PD: PChDirDialog; {Диалоговое окно смены каталога/диска}
Control: Word;
begin
New(PD, Init(cdNormal,0));{Создаем диалоговое окно}
Control := DeskTop.ExecView(PD){Используем окно}
ChDir(PD.Dirlnput.Data);{Устанавливаем новый каталог}
Dispose(PD, Done){Удаляем окно из кучи}
end; {TNotebook.ChangeDir}
Несколько слов по поводу реализации процедуры TNotebook. ChangeDir. В ней используется объект TChDirDialog, входящий в модуль StdDlg. С помощью этого объекта создается диалоговое окно, позволяющее выбрать новый диск или каталог. После создания и использования экземпляра объекта TChDirDialog в его поле
Dirlnput.Data
устанавливается строка типа PathStr, задающая новый каталог (и, возможно, новый диск).
Чуть сложнее обстоит дело с процедурой DOSCall, которая должна реализовать временный выход в ДОС. Дело в том, что перед выходом необходимо сохранить в куче текущее состояние программы, а после возврата нужно восстановить состояние программы, в том числе и вид экрана. Чтобы реализовать имеющиеся в Turbo Vision средства сохранения и восстановления программы, в предложение Uses необходимо добавить ссылку на модуль Memory. Вот текст метода TNotebooLDOSCall:
Procedure TNotebook.DOSCall;
{Временный выход в ДОС}
const
txt ='Для возврата введите EXIT в ответ'+' на приглашение ДОС...';
begin
DoneEvents;{Закрыть обработчик событий}
DoneVideo;{Закрыть монитор экрана}
DoneMemory;{Закрыть монитор памяти}
SetMemTop(HeapPtr) ;{Освободить кучу}
WriteLn(txt);{Сообщить о выходе}
SwapVectors;{Установить стандартные векторы}
{Передать управление командному процессору ДОС:}
Exec(GetEnv('COMSPEC'),'');
{Вернуться из ДОС:}
SwapVectors; {Восстановить векторы)
SetMemTop(HeapEnd); {Восстановить кучу}
InitMemory; {Открыть монитор памяти}
InitVideo;{Открыть монитор экрана}
InitEvents;{Открыть обработчик событий}
InitSysError;{Открыть обработчик ошибок}
Redraw {Восстановить вид экрана}
end; {DOSCall}
Процедуры DoneXXXX завершают работу отдельных частей Turbo Vision, а процедуры InitXXXXосуществляют обратные действия. С помощью процедуры SetMemTop в ДОС передается информация о фактически используемой динамической памяти (по умолчанию программе предоставляется вся доступная память). Этот вызов освобождает неиспользуемую в данный момент часть кучи для размещения в ней командного процессора COMMAND.COM. После возврата из ДОС вызов SetMemTop используется еще раз - для того, чтобы зарезервировать за программой всю ранее выделенную ей память. Процедура Redraw восстанавливает все видимые элементы экрана.
При работе с электронной записной книжкой хотелось бы, чтобы на экране появилось сразу несколько записей, отсортированных в алфавитном порядке. Пользователь должен иметь возможность «листать» книжку, отыскивать в ней нужную запись, добавлять новые и исключать ненужные записи, редактировать их (вносить изменения). Таким образом, ядром диалога должно стать окно с текстом. При необходимости пользователь может смещать текст в окне в ту или иную сторону, перемещать само окно относительно границ экрана, менять его размеры. Все эти возможности типичны для многочисленных текстовых редакторов, систем программирования, систем управления базами данных и т.п.
Для реализации этих действий в Turbo Vision предусмотрен специальный объект TWindow, экземпляры которого отображаются на экране в виде прямоугольного окна с рамкой и стандартными кнопками изменения размера и закрытия окна. Попробуем создать такое окно в нашей программе. Для этого изменим текст процедуры Work следующим образом:
Procedure TNotebook.Work;
{Работа с данными}
var
R: TRect;
begin
R.Assign(0,0,80,23);
Desktop.Insert(New(PWindow,Init(R,'',0)))
end; {Work}
После запуска программы нажмите клавишу F3, укажите в диалоговом окне имя несуществующего файла (файл данных пока еще не создан), нажмите клавиши Enter и F4 - экран приобретет вид, показанный на рис. 15.4.
Если Ваш ПК оснащен устройством ввода типа мышь, Вы можете перемещать это окно по экрану (надо «схватить» мышью верхнюю рамку окна, т.е. подвести к ней указатель мыши, нажать левую кнопку и, удерживая кнопку нажатой, перемещать мышь), изменять его размеры («схватить» правый нижний угол), использовать стандартные кнопки изменения размера (справа на верхней рамке) и закрытия окна (слева). Ничего другого окно не умеет. А как загрузить в него текст? Как получить хорошо знакомые по среде Турбо Паскаль полосы-указатели и управлять с их помощью положением текста? Для этих целей можно было бы использовать объект TScroller, представляющий собой окно с текстом и с двумя полосами-указателями. Однако по умолчанию такое окно не имеет рамки, а потому не может изменять своего размера, в нем нет стандартных кнопок изменения размера и закрытия окна. Таким образом, и объект TScroller не решает всех проблем. Каков же выход? Нужно создать новый объект, объединяющий в себе свойства и TWindow, и TScroller! В терминах Turbo Vision такие составные объекты называются группами.
Рис. 15.4. Окно просмотра данных
Введем в программу следующий объект:
type
PWorkWin =TWorkWin;
TWorkWin = object (TWindow)
Constructor Init(Bounds: TRect);
end;
Новый объект является потомком TWindow и, следовательно, наследует все свойства родителя, в том числе рамку и способность перемещения по экрану. Дополнительные свойства ему должен придать новый конструктор TWorkWin.Init, которому мы в качестве параметра передаем начальное положение и размеры создаваемого окна:
Constructor TWorkWin.Init(Bounds: TRect);
{Создание окна данных}
var
HS,VS: PScrollBar; {Полосы-указатели}
Interior: PScroller; {Указатель на управляемое текстовое окно}
begin
TWindow.Init(Bounds,'',0); {Создаем новое окно с рамкой}
GetClipRect(Bounds){Получаем в BOUNDS координаты минимальной перерисовываемой части окна}
Bounds.Grow(-1,-1){Устанавливаем размеры окна с текстом}
{Включаем стандартные по размеру и положению полосы-указатели:}
VS := StandardScrollBar(sbVertical+sbHandleKeyBoard);
HS := StandardScrollBar(sbHorizontal+sbHandleKeyBoard);
{Создаем текстовое окно:}
Interior := New(PScroller,Init(Bounds, HS, VS));
Insert(Interior) {Включаем его в основное окно}
end; {TWorkWin.Init}
С помощью вызова процедуры GetClipRect мы получаем размеры минимального прямоугольника, который следует обновлять при любых перемещениях окна или изменениях его размера. Такой вызов позволяет до минимума сократить время вывода. Процедура Bounds.Grow изменяет вертикальный и горизонтальный размеры прямоугольника Bounds: при положительном параметре соответствующий размер увеличивается, при отрицательном - уменьшается. Параметры -1,-1 учитывают рамку основного окна. Функция StandardScrollBar создает указатель на управляющую полосу стандартного размера. При обращении к ней параметр sbVertical (sbHorizontal) определяет положение полосы, а параметр sbHandleKeyboard разрешает использование клавиатуры для управления ею (если этот параметр не включить, полоса будет управляться только с помощью мыши). Наконец, процедура Insert включает вновь созданное окно TScrollBar в основное окно TWindow, так что теперь оба окна будут функционировать как одно целое.
Для создания группы необходимо в объект-потомок от TGroup (обычно - это объект TWindow или потомок от него) вставлять нужные элементы с помощью метода Insert.
Осталось лишь нужным образом изменить процедуру Work:
Procedure TNotebook.Work;
{Работа с данными}
var
R: TRect;
PW: PWorkWin;
begin
R.Assign(0,0,80,23) ;
PW := New(PWorkWin, Init(R));
DeskTop.Insert(PW)
end; {Work}
Если исполнить подготовленную таким образом программу, на экране появится изображение, показанное на рис. 15.5.
Рис.15.5. Окно с полосами прокрутки
В объекте TScroller для вывода текста предусмотрен абстрактный метод Draw. Абстрактным он называется потому, что не выполняет никакой полезной работы. Однако именно к этому методу обращается обработчик событий объекта TScroller всякий раз, когда понадобится обновить на экране вид окна. Чтобы объект выполнял все заложенные в него функции, нам необходимо перекрыть этот метод новым. Мы уже знаем, что для этого нужно объявить новый объект:
type
PInterior =ATInterior; TInterior = object (TScroller)
Constructor Init(var Bounds: TRect; HS,VS: PScrollBar);
Procedure Draw; virtual;
Procedure ReadFile;
end;
Мы перекрыли абстрактный метод Draw, стандартный конструктор Init и инкапсулировали в объект новый метод ReadFile. Новый конструктор предназначен для инициации экземпляра объекта TScroller. Кроме того, с помощью метода ReadFile он должен прочитать все записи файла данных и подготовить соответствующий массив строк - это сократит время на обновление текста процедурой Draw.
Перед тем, как двигаться дальше, подумаем о способе хранения строк для процедуры Draw. Если все необходимые действия по чтению нужной записи из файла и преобразования ее к текстовому формату возложить на процедуру Draw, наша программа станет слишком медленной, в особенности, если файл данных записан на дискете. Поэтому предусмотрим такие глобальные переменные:
const
MaxLine = 300; {Максимальная длина массива}
LLine = LName+LPhone+LAddr; {Длина строки}
var
NLines: Word; {Истинная длина массива строк}
Lines: array [1..MaxLine] of String [LLine]; {Массив строк}
Теперь нетрудно подготовить процедуру ReadFile:
Procedure TInterior.ReadFile;
{Читает содержимое файла данных в массив Lines}
var
k: Integer; s: String;
Data: DataType;
begin
seek(DataFile, 0) ;
NLines := FileSize(DataFile);
if NLines > MaxLine then
NLines := MaxLine;
for k ':= 1 to NLines do
begin
Read(DataFile, data);
with data do
begin
s := Name;
while Length(s) < LName do
s := s+' ';
s := s+Phone;
while Length(s) < LName+LPhone do
s := s+' ';
s := s+Addr
end;
Lines[k] := s
end;
end; {ReadFile}
В этой процедуре из записей файла данных готовится массив строк Lines, причем начало каждого поля выравнивается так, чтобы поля образовали колонки - такая форма вывода поможет легко найти на экране каждое поле.
Теперь займемся процедурой Draw:
Procedure TInterior.Draw;
{Выводит данные в окно просмотра}
var
n, {Текущая строка экрана}
k: Integer; {Текущая строка массива}
В: TDrawBuffer; Color: Byte;
begin
Color := GetColor(l); {Использовать цвет основного текста}
for n := 0 to pred(Size.Y) do
{Size.Y - количество строк окна}
begin
k := Delta.Y+n+1; {Delta.Y - номер первой выводимой строки}
MoveChar(B,' ',Color,Size.X);
MoveStr(B, Copy(Lines[k],Delta.X+l,Size.X),Color);
WriteLine(0,N,Size.X,l,B)
end
end; {TInterior.Draw}
Работа процедуры основана на использовании текущих размеров и положения текстового окна относительно текста. Эти параметры хранятся в полях Size и Delta объекта TScroller и обновляются всякий раз, когда пользователь манипулирует полосами управления или изменяет размеры окна. Для вывода текста используются три процедуры: MoveChar, MoveStr, WriteLine. Каждая из них оперирует переменной В типа TDrawBuffer, представляющей собой последовательности кодов выводимых символов и их атрибутов. Процедура MoveChar заполняет переменную В указанным символом (' ') и атрибутом (Color). Процедура MoveStr копирует строку в переменную В, а с помощью WriteLine осуществляется вывод буфера В на экран.
Для вывода изображений (текста) перекрывайте и используйте метод Draw объекта-владельца нужной части экрана. Это обеспечит автоматическое изменение изображения и его прорисовку при изменении границ или положения поля вывода.
Рис.15.6. Палитра объекта TScroller
Числа 6 и 7 в этой палитре указывают не конкретные цвета, а номера позиций в палитре объекта-владельца. Для нашего случая объектом-владельцем будет TWindow. Таким образом, цвет номер 1 палитры TScroller лишь указывает на шестое по счету цветовое сочетание в палитре TWindow (рис. 15.7).
Рис. 15.7. Связь палитр TScroller и TWindow
Шестой элемент палитры TWindow в свою очередь ссылается на 13-й элемент палитры своего владельца - TProgram. Объект TProgram - это начальный видимый элемент любой программы в Turbo Vision. На нем заканчивается любая цепочка ссылок, т.е. его палитра содержит конкретные атрибуты символов. Тринадцатый элемент этой палитры содержит значение $1Е, что соответствует выводу желтого символа на синем фоне - именно таким образом отображается нормальный текст в окне TScroller, если это окно вставлено в TWindow. Если бы объект TScroller был помещен непосредственно на панель экрана, то значение 6 в первом элементе палитры TScroller указывало бы на 6-й элемент палитры TProgram, содержащий атрибуты $28 (темно-серые символы на зеленом фоне). Цветовые палитры в Turbo Vision содержат такие значения по умолчанию, чтобы любая комбинация цветов давала приятную цветовую гамму. При необходимости пользователь может изменить любую цветовую палитру. Вернемся к нашему примеру и рассмотрим реализацию конструктора Init:
Constructor TInterior.Init(var Bounds: TRect;
HS,VS: PScrollBar);
{Создает окно для данных}
begin
Inherited Init(Bounds, HS, VS) ;
ReadFile;
GrowMode := gfGrowHiX + gfGrowHiY;
SetLimit(LLine,NLines)
end; {Tinterior.Init}
Объект TScroller имеет поле GrowMode, которое определяет, как элемент будет изменять свои размеры, если пользователь потребует этого. Параметр gfGrowHiX предписывает окну TScroller изменяться таким образом, чтобы правая его граница всегда находилась на постоянном расстоянии от правой границы владельца. Точно также gfGrowHiY задает неизменным расстояние нижней границы окна TScroller от нижней границы владельца. Таким образом, окно TScroller всегда будет занимать всю внутреннюю часть окна-владельца TWindow. С помощью процедуры SetLimit (X, Y) мы задаем горизонтальную Х и вертикальную У границы перемещения окна относительно текста. Эти границы будут выдерживаться при управлении окном с помощью клавишей или мыши: какими бы не были текущие размеры окна, нажатие на клавишу End, например, смещает его вправо так, чтобы самым правым видимым символом был Х-й символ текста. Нажатие на клавиши Ctrl-PgDn смещает окно вниз по тексту таким образом, чтобы самая нижняя строка окна соответствовала Y-й строке текста. Иными словами, параметры X и Y задают координаты правого нижнего угла виртуального (воображаемого) экрана неограниченных размеров, на котором находится текст и по которому «скользит» окно. Левый верхний угол виртуального экрана всегда имеет координаты (0,0).
Осталось отредактировать конструктор TWorkWinJnit: нужно изменить тип переменной Interior
var
.......
Interior: PInterior;
и обращение к конструктору:
Interior := New(PInterior, Init(Bounds, HS, VS));
Но не спешите запускать программу на счет: ведь файла данных пока еще нет, а поэтому Вы ничего не увидите на экране. Чтобы все-таки оценить достигнутые результаты, измените текст процедуры ReadFile - добавьте в него следующие строки:
Procedure TInterior.ReadFile;
{Читает содержимое файла данных}
var
.......
f: text;
begin
s := copy(ParamStr(0),l,pos('.',ParamStr(0)))+'pas';
assign(f,s);
reset(f); {Открываем файл с текстом программы}
NLines := 0;
while not EOF(f) and (NLines < MaxLine) do
begin
inc(NLines);
ReadLn(f/Lines[NLines])
end;
close (f) ;
exit;
.......
end; {ReadFile}
Добавленные строки заставят процедуру прочитать в массив Lines текст самой программы (если Вы будете запускать программу из среды Турбо Паскаль, не забудьте установить компиляцию в дисковый файл опцией COMPILE/DESTINATION, иначе оператор
s:=copy(ParamStr(0),l,pos('.',ParamStr(0)))+'pas';
не сможет установить в S правильное имя файла с текстом Вашей программы). После запуска программы нажмите клавишу F3, задайте имя несуществующего файла, нажмите клавиши Enter и F4 - на экране появится изображение, показанное на рис. 15.8.
Это окно откликается на нажатие клавиш управления курсором, команды PgUp, PgDn, Ctrl-PgUp и т.д. подобно тому, как ведет себя окно редактора в среде Турбо Паскаль. С помощью мыши Вы можете перемещать его по экрану, изменять размеры, закрывать - все эти действия реализует стандартный обработчик событий объекта TScroller.
Рис.15.8. Окно с текстом программы.
Коллекции обладают целым рядом новых свойств. В частности, к любой коллекции можно применить метод ForEach, который осуществит заданные Вами действия над каждым элементом коллекции. Таким способом можно, например, быстро отыскать элемент, удовлетворяющий заданным требованиям. Наконец, в Turbo Vision определены отсортированные коллекции, элементы которых упорядочиваются по заданному ключу. Все это делает коллекции более предпочтительным способом хранения данных, чем массивы Турбо Паскаля.
Попробуем заменить массив Lines на отсортированную коллекцию. Введем в объект TInterior новое поле PS:
type
TInterior = object (TScroller)
PS: PStringCollection;
.......
end;
Тип PStringCollection в Turbo Vision определен как указатель на экземпляр объекта TStringCollection, представляющий собой отсортированную коллекцию строк. Сортировка строк осуществляется по обычным правилам сравнения строк по ASCII-кодам. Если вновь помещаемая строка уже существует в коллекции, она не дублируется (при желании программист может разрешить дублирование одинаковых строк), поэтому в общем случае количество элементов коллекции может оказаться меньшим количества помещенных в нее строк.
Для создания коллекции удалите ненужные теперь глобальные объявления MaxLine, Lines и NLines (в коллекции есть другие средства доступа к элементам) и измените метод ReadFile следующим образом :
Procedure TInterior.ReadFile;
var
.....
begin
PS := New(PStringCollection, Init(100,10));
s := copy(ParamStr(0),1,pos('.',ParamStr(0)))+'pas';
assign(f,s);
reset (f); {Открыть файл с текстом программы}
while not (EOF(f) or LowMemory) do
begin
ReadLn(f,s);
if s <> ' ' then PS.Insert(NewStr(s))
end;
Close(f);
exit;
Seek(DataFile,0);
while not (EOF(DataFile) or
LowMemory) do
begin
Read(DataFile, data);
with data do
begin
end;
if s<>''then PS.Insert(NewStr(s))
end;
end; {ReadFile}
В приведенном фрагменте мы предусмотрительно изменили только ту часть программы, которая стоит после оператора Exit и которая зависит от удаленных глобальных определений. Вы должны сделать эти изменения (они все равно нам пригодятся) или закомментировать эту часть текста, чтобы получить синтаксически правильный вариант программы.
С помощью оператора
PS := New(PStringCollection, Init(100,10));
инициируется экземпляр коллекции, причем параметр 100 определяет начальный размер коллекции, а параметр 10 - шаг наращивания коллекции, если ее размер превысит 100 элементов. Оператор
if s<> ' ' then PS.Insert(NewStr(s))
вставляет очередную непустую строку в коллекцию. Заметим, что коллекции РЗЛ передается не строка 5, а лишь указатель на нее, т.к. функция NewStr размещает строку в куче и возвращает ее адрес. Функция NewStr не может разместить в куче пустую строку, поэтому мы вставляем в коллекцию только непустые строки.
Функция LowMemory используется для контроля за размерами динамической памяти: она возвращает значение True, если в куче осталось менее 4 Кбайт.
В последний оператор метода Interior.Init внесите следующее изменение:
Constructor TInterior.Init(var Bounds: TRect; HS,VS: PScrollBar);
begin
SetLimit(LLine,PSA.Count)
end; {TInterior.Init}
Другим станет также и реализация метода TInterior.Draw:
Procedure TInterior.Draw;
var
n,k: Integer;
B: TDrawBuffer;
p: PString;
Color: Byte;
begin
Color := GetColor(1);
for n := 0 to pred(Size.Y) do
begin
k := Delta.Y+n;
MoveChar(B,' ',Color,Size.X);
if k < pred(PS.Count) then
begin
p := PS.At(k);
MoveStr(B,Copy(р,Delta.X+1,Size.X),Color)
end;
WriteLine(0,N,Size.X,1,B)
end
end; {TInterior.Draw}
Элементы коллекции нумеруются, начиная с номера 0. Длина коллекции (общее количество ее элементов) хранится в поле PS. Count. Функция PS.At(k) возвращает указатель на k-й элемент коллекции.
Созданная коллекция размещается в динамической памяти, поэтому после использования ее следует удалить из кучи. Для этого перекроем стандартный деструктор Done:
type
TInterior = object (TScroller)
.......
Destructor Done; Virtual;
end;
Destructor TInterior.Done;
begin
Dispose(PS, Done); {Удаляем коллекцию}
Inherited Done {Выполняем стандартный деструктор}
end;
Еще раз хочу обратить Ваше внимание на особенность программирования в среде Turbo Vision: Вы определяете метод, но не указываете, когда он должен быть выполнен. Правильно сконструированный объект уже «знает», когда он ему понадобится! Так было в случае правила Draw, так же обстоит дело и с деструктором Done: обработчик событий окна TWindow вызовет этот метод, как только он получит событие cmCancel (закрыть окно). Чтобы убедиться в этом, установите контрольную точку в строке
Dispose(PS, Done); {Удаляем коллекцию}
и запустите программу. Останов в контрольной точке произойдет только в том случае, если Вы загрузите окно с текстом и попытаетесь выйти из программы. Если из программы выйти сразу после ее запуска, контрольная точка не сработает.
Вид экрана с окном просмотра отсортированного файла показан на рис. 15.9.
Puc.15.9. Окно с отсортированным тестом программы
type
TInterior = object (TScroller)
Location: Word;
.......
end;
Поле Location будет хранить номер той строки,которая отождествляется с выбранной строкой и которая на экране должна выделяться цветом.Добавьте в конце метода ReadFile строку
Location:=0;
и измените метод Draw:
Procedure TInterior.Draw;
{Выводит данные в окно просмотра}
var
n,k: Integer;
В: TDrawBuffer;
р: PString;
Color: Byte;
begin
if Delta.Y > Location then
Location := Delta.Y;
if Location > Delta.Y+pred(Size.Y) then
Location := Delta.Y+pred(Size.Y);
for n := 0 to pred(Size.Y) do
begin
k := Delta.Y+n;
if k=Location then
Color := GetColor(2)
else
Color := GetColor(1);
end
end; {TInterior.Draw}
Вначале проверяется, попадает ли строка с номером, хранящимся в Location, в число выводимых строк. Если это не так, значит пользователь изменил размеры окна или сдвинул его относительно текста; в этом случае нужным образом корректируется значение Location. Такая проверка гарантирует, что в окне всегда будет выводиться текущая строка. Перед выводом очередной строки сравнивается значение ее номера с величиной Location и, если величины совпадают, строка выводится цветом 2 из палитры TScroller (темно-синими символами на сером фоне).
Создав указатель в окне, нужно предусмотреть и средства воздействия на него. Для этого нам понадобится проверять действия пользователя с мышью и клавиатурой и изменять положение указателя. Вы не забыли, что все действия программы в Turbo Vision выполняются с помощью обработчика событий? Перекроем стандартный метод HandleEvent в объекте TInterior:
type
TInterior,. = object (TScroller)
.......
Procedure HandleEvent(var Event: TEvent); Virtual;
end;
Procedure TInterior.HandleEvent(var Event: TEvent);
{Обработчик событий для окна данных}
var
R: TPoint;
begin
Inherited HandleEvent(Event);
case Event.What of evMouseDown: {Реакция на щелчок мышью}
begin
MakeLocal(MouseWhere, R){Получаем в R локальные координаты указателя мыши}
Location := Delta.Y+R.Y;
Draw
end;
evKeyDown: {Реакция на клавиши + -}
case Event.KeyCode of
kbGrayMinus: if Location > Delta.Y then
begin
dec(Location);
Draw
end;
kbGrayPlus: if Location < Delta.Y+pred(Size.Y) then
begin
inc(Location);
Draw
end;
end
end
end; {TInterior.HandleEvent}
В новом методе вначале вызывается унаследованный обработчик событий TScroller.HandleEvent, с помощью которого обрабатываются все стандартные действия с окном (смещение текста, изменение размеров и т.д.). Затем обрабатываются события от нажатия кнопки мыши и от нажатия клавиш «+» и «-» из зоны цифровых клавиш (на клавиатуре ПК они выделяются серым цветом). С клавишей «+» связывается действие «Сместить указатель вниз на одну строку», с клавишей «-» - «Сместить вверх». Выбор серых клавиш «+» и «-» для смещения указателя вызван тем, что клавиши управления курсором используются для смещения окна и обрабатываются стандартным обработчиком событий. Заметим, что нажатие кнопки мыши будет обрабатываться в TScroller.HandleEvent только в том случае, если указатель мыши находится на рамке окна или на полосах управления. Если указатель сместить внутрь окна, нажатие на кнопку мыши будет преобразовано в событие evMouseDone и передано в наш обработчик. В этом случае глобальная переменная MouseWhere содержит абсолютные координаты указателя мыши (т.е. координаты относительно левого верхнего угла экрана). Чтобы получить номер соответствующей строки текста, мы сначала с помощью оператора
MakeLocal(MouseWhere, R) ;
получаем в переменной R локальные координаты мыши относительно границ окна TScroller. Оператор
Location := Delta.Y+R.Y;
устанавливает в поле Location номер той строки текста, на которой располагается указатель мыши.
Procedure TNotebook.HandleEvent(var Event: TEvent);
{Обработчик событий программы}
begin
Inherited HandleEvent(Event);
if Event.What = evCommand then
case Event.Command of
cmOpenFile:
begin
FileOpen;
if OpFileF then Work
end;
.......
end; {TNotebook.HandleEvent}
Как из режима просмотра данных перейти к другим режимам? Возможно несколько решений. Я предлагаю для этих целей воспользоваться командой cmClose (закрыть окно просмотра): в момент, когда пользователь в режиме просмотра данных нажмет клавишу Esc или воздействует мышью на кнопку «Закрыть окно», на экране должно раскрыться диалоговое окно выбора режима, предлагающее одно из пяти возможных продолжений:
закрыть окно просмотра; удалить текущую запись; искать нужную запись; редактировать текущую запись; добавить запись (записи).Для реализации этой идеи в уже созданный нами обработчик событий TInterior.HandleEvent следует ввести обработку события cmClose:
const
{Команды для обработчиков событий:}
.......
cmCan=205;
cmDelete=206;
cmSearch = 207;
cmEdit = 208;
cmAdd = 209;
Function Control: Word; {Создает и использует диалоговое окно выбора режима работы)
begin
Control := cmCan
end; {Control}
{-----------------}
Procedure TInterior.HandleEvent (var Event: TEvent) ;
{Обработчик событий для окна данных}
Procedure DeleteItem;
{Удаляет указанный в Location элемент данных}
begin
end; {DeleteItem}
{-----------------}
Procedure AddItem(Edit: Boolean);
{Добавляет новый или редактирует старый элемент данных}
begin
end; {AddItem}
{-----------------}
Procedure SearchItem;
{Ищет нужный элемент}
begin
end; {SearchItem}
{-----------------}
var
R: TPoint; label Cls;
begin {TInterior.HandleEvent}
Inherited HandleEvent (Event) ;
case Event. What of evCommand:
case Event . Command of
cmClose:
begin
Cls:
case Control of{Получить команду из основного диалогового окна}
cmCan,
cmCancel:EndModal (cmCancel) ;
cmEdit:AddItem (True);
cmDelete:DeleteItem;
cmSearch:SearchItem;
cmAdd:AddItem (False);
end
end;
cmZoom: exit;
end;
evMouseDown: {Позиционировать мышью}
.....
evKeyDown: {Позиционировать клавишами + -}
case Event.KeyCode of
kbEsc: goto Cls;
kbGrayMinus: if Location > Delta.Y then
.....
end; {TInterior.HandleEvent}
В этом фрагменте мы расширили набор нестандартных команд (константы стпСап, ..., cmAdd), ввели новую функцию Control и предусмотрели необходимые процедуры в теле обработчика событий. Заметим, что режимы редактирования записи и добавления новой записи очень схожи по организации диалога с пользователем, поэтому он» реализуются в рамках одной процедуры AddItem и управляются параметром обращения к ней.
Функция Control используется для создания диалогового окна выбора продолжения. В качестве значения этой функции будет возвращаться одна из пяти новых команд. В начальном варианте функция возвращает команду стСап, что интерпретируется обработчиком событий как указание на завершение работы с диалоговым окном. Поэтому, если Вы вставите указанный текст в программу и запустите ее, поведение программы останется прежним.
Займемся реализацией функции Control. Она должна создать диалоговое окно выбора режима, получить с его помощью команду, идентифицирующую выбранный режим, и вернуть эту команду в качестве своего значения:
Function Control: Word;
{Получает команду из основного диалогового окна}
const
X = 1;
L = 12;
DX= 13;
But: array [0. .4]of String [13] = {Надписи на кнопках:}
('~1~ Выход ','~2~Убрать ','~3~ Искать ',
'~4~ Изменить ','~5~ Добавить ') ;
Txt: array [0..3]of String [52] = (
{Справочный текст:}
'Убрать - удалить запись, выделенную цветом', 'Искать - искать запись, начинающуюся нужными буквами', 'Изменить - изменить поле (поля) выделенной записи', 'Добавить - добавить новую запись'); var
R: TRect;
D: PDialog;
k: Integer;
begin
R.Assign(7,6,74,15) ;
D := New{PDialog,Init(R, 'Выберите продолжение:'));
with D do
begin
for k := 0 to 3 do {Вставляем поясняющий текст}
begin
R.Assign(l,l+k,65,2+k);
Insert(New(PStaticText,Init(R,#3+Txt[k])))
end;
for k := 0 to 4 do {Вставляем кнопки:}
begin
R.Assign(X+k*DX,6,X+k*DX+L,8);
Insert(New(PButton, Init(R,But[k],cmCan+k,bfNormal)))
end;
SelectNext(False); {Активизируем первую кнопку}
end;
Control := DeskTopA.ExecView(D); {Выполняем диалог}
end; {Control}
Сначала создается диалоговое окно с заданными размерами (чтобы программе стал доступен тип TDialog, укажите в предложении Uses модуль Dialogs). Затем в цикле
for k := 0 to 3 do
в окно вставляется поясняющий текст (см. рис.15.10).
Рис.15.10. Диалоговое окно функции Control
Этот текст не связан с диалогом и называется статическим. Для вставки статической строки в любой видимый элемент используется конструктор TStaticTextJnit, которому в качестве параметров передаются координаты строки и сама строка. Как Вы уже могли заметить, идентификаторы объектов в Turbo Vision начинаются на букву Т, а идентификаторы типов-указателей на экземпляры этих объектов начинаются на букву Р. Таким образом, PStaticText - это тип-указатель на экземпляр объекта TStaticText, поэтому оператор
Insert(New (PStaticText, Init(R,'Текст'))
помещает строку «Текст» на место, заданное координатами переменной R. Отметим, что если строка начинается на символ #3, то при выводе на экран она будет размещаться в центре прямоугольника R. Мы используем это соглашение и дополняем каждую выводимую строку этим символом. В цикле
for k := 0 to 4 do {Вставить кнопки:}
в окно вставляются пять кнопок. При их инициации используется то обстоятельство, что определенные нами команды cmCan, ..., cmAdd образуют непрерывное множество [205..209].
Особо следует остановится на операторе
SelectNext(False); {Активизируем 1-ю кнопку}
Дело в том, что по умолчанию активизируется тот элемент диалогового окна, который задан (вставлен в окно) последним. Чтобы изменить активность по умолчанию, используется вызов процедуры SelectNext, которая смещает активность к следующему элементу. Так как элементы образуют замкнутую цепь (от последнего элемента активность переходит к первому), параметр обращения к этой процедуре указывает направления смещения: если он имеет значение False, активным станет следующий в цепи элемент, если True - предыдущий.
Прежде, чем Вы попробуете запустить эту программу на счет, внесем в нее несколько изменений. Во-первых, пора убрать имитацию данных, показываемых в окне просмотра. Для этого в процедуре TInterior.ReadFile необходимо удалить строки
s := copy(ParamStr(O),1,pos('.',ParamStr(0)))+'pas';
assign(f,s);
.....
exit;
Надеюсь, что Вы заблаговременно подготовили остальной текст этого метода, если это не так, вставьте операторы
seek (DataFile, 0);
while not (EOF (DataFile) or LowMemory) do
begin
.....
end;
Location := 0
Во-вторых, обратили ли Вы внимание на то, что в процедуре TNotebook. Work указатель PW инициируется оператором
PW := New(PWorkWin, Init(R));
а динамическая память, выделенная для размещения экземпляра объекта TWorkWin, не возвращается обратно в кучу? Если да, то у Вас есть хорошие шансы избежать многих неприятностей при программировании в среде Turbo Vision. Конечно же, нам следовало где-то в программе позаботиться об удалении ненужного нам экземпляра объекта. Чтобы не усложнять программу, я не стал этого делать: если вставить оператор
Dispose(PW, Done)
сразу за оператором
DeskTop.Insert(PW)
то вновь созданное окно будет тут же удалено с экрана, поэтому оператор Dispose нужно разместить в обработчике событий TNotebook. HandleEvent (подумайте, где именно).
После включения диалогового окна в цепочку действий, связанных с инициацией PW, появилась возможность приостановить исполнение программы в процедуре Work: вместо оператора
DeskTop.Insert(PW)
вставьте следующие строки:
Control := DeskTop.ExecView(PW);
Dispose(PW, Done)
и добавьте описание переменной Control:
var
.....
Control: Word;
В отличие от процедуры Insert процедура ExecView не только помещает видимый элемент на экран, но и приостанавливает дальнейшее исполнение программы Work до тех пор, пока не закончится диалог с пользователем.
И, наконец, еще одно усовершенствование. Работа с программой станет удобнее, если сразу после чтения файла с данными она перейдет к их показу. Реализовать это очень просто: добавьте вызов процедуры Work в процедуру FileOpen следующим образом:
Procedure TNotebook.FileOpen;
..... begin
.....
if OpFileF then
begin
.....
Work{Переходим к работе}
end;
.....
end; {FileOpen}
Если Вы внесете в программу все описанные изменения и запустите ее на счет , то при попытке выйти из режима просмотра на экране будет развернуто диалоговое окно, показанное на рис. 15.10. «Нажатие» на любую кнопку этого окна не приводит ни к каким последствиям - наше окно пока откликается только на стандартную команду cmClose, связанную с клавишей Esc.
Файл с данными DataType пока еще не существует. Чтобы программа смогла нормально работать, в диалоговом окне открытия файла укажите произвольное имя, например MYDATA. После завершения работы программы будет создан пустой файл MYDATA.DAT.
Введем новый объект TDlgWin как потомок объекта TDialog и перекроем его метод HandleEvent:
type
PDlgWin =ATDlgWin;
TDlgWin = object (TDialog)
Procedure HandleEvent(var Event: TEvent); Virtual;
end;
В новом методе следует сначала вызвать стандартный обработчик, а затем проанализировать событие: если оно не очищено и содержит команду, значит была нажата какая-то командная кнопка, и нам следует заставить обработчик закрыть окно и вернуть эту команду как результат диалога с пользователем:
Procedure TDlgWin.HandleEvent(var Event: TEvent);
{Обработчик событий для основного диалогового окна}
begin
Inherited HandleEvent(Event);
if Event.What = evCommand then
EndModal(Event.Command) {Закрыть окно и вернуть команду}
end;
Метод EndModal используется для того, чтобы завершить работу с диалоговым окном и вернуть команду в программу, использующую это окно. Измените описание переменной D в функции Control на
var
.....
D: PDlgWin;
и обращение к методу Init:
D := New(PDlgWin, Init(...));
и вновь запустите программу: теперь нажатие на любую кнопку диалогового окна приведет к его закрытию.
Режим редактирования отличается от режима добавления записей двумя обстоятельствами: во-первых, в режиме редактирования поля ввода данных окна должны содержать текст, взятый из редактируемой записи, а в режиме ввода эти поля пусты. Во-вторых, режим редактирования завершается сразу после нажатия на клавишу Enter, в то время как в режиме ввода нажатие на эту клавишу означает добавление к файлу текущей записи и переход к вводу следующей: режим ввода завершается командой cmClose (клавиша Esc). С учетом этого оба режима реализуются в рамках одной процедуры AddItem (Edit), а параметр Edit указывает нужный режим: если Edit = True, реализуется режим редактирования, если False - режим добавления записей. Вот текст этой процедуры:
Рис.15.11. Окно ввода/редактирования записей
Procedure AddItem(Edit: Boolean);
{Добавляет новый или редактирует старый элемент данных}
const
у = 1;
dy= 2;
L -= LName+LPhone+LAddr;
var
Data: DataType;
R: TRect;
InWin: PDialog;
BName,BPhone,BAddr: PInputLine;
Control: Word;
OldCount: Word;
s: String;
р: PString;
begin
Seek(DataFile,FileSize(DataFile));{Добавляем записи в конец файла}
repeat {Цикл ввода записей}
if Edit then {Готовим заголовок}
s := 'Редактирование:'
else
begin
Str(FileSize(DataFile)+1,s);
while Length(s) < 3 do
s := '0'+s;
s :- 'Вводится запись N '+s
end;
FillChar(Data,SizeOf(Data),' ');{Заполняем поля пробелами}
R.Assign(15,5,65,16) ;
InWin := New(PDialog, Init(R, s));{Создаем окно}
with InWin do
begin{Формируем окно:}
R.Assign(2,y+1,2+LName,y+2);
BName := New(PInputLine, Init(R,LName));
Insert(BName); {Поле имени}
R.Assign(2,y,2+LName,y+1);
Insert(New(PLabel,
Init(R, 'Имя',BName)));
R.Assign(2,y+dy+1,2+LPhone,y+dy+2);
BPhone := New(PInputLine, Init(R,LPhone));
Insert(BPhone); {Поле телефона}
R.Assign (2,y+dy, 2+LPhone,y+dy+1) ;
Insert (New(PLabel,.
Init(R, 'Телефон',BPhone)));
R.Assign(2,y+2*dy+1,2+LAddr,y+2*dy+2);
BAddr := New(PInputLine, Init(R,LAddr));
Insert(BAddr); {Поле адреса}
R.Assign(2,y+2*dy,2+LAddr,y+2*dy+1);
Insert(New(PLabel,
Init(R, 'Адрес',BAddr)));
{Вставляем две командные кнопки:}
R.Assign(2,y+3*dy+1,12,y+3*dy+3);
Insert(New(PButton,
Init(R, 'Ввести',cmOK,bfDefault)));
R.Assign(2+20,y+3*dy+1,12+20,y+3*dy+3);
Insert(New(PButton,
Init(R, 'Выход',cmCancel,bfNormal)));
SelectNext(False) {Активизируем первую кнопку}
end; {Конец формирования окна}
if Edit then with Data do
begin {Готовим начальный текст:}
р :=PS.At(Location); {Читаем данные из записи)
S:=p;
Name := copy(s,1,LName);
Phone:= copy(s,succ(LName),LPhone);
Addr := copy(s,succ(LName+LPhone),LAddr);
InWin.SetData(Data) {Вставляем текст в поля ввода}
end;
Control := DeskTop.ExecView(InWin); {Выполняем диалог}
if Control=cmOk then with Data do
begin
if Edit then
DeleteItem; {Удаляем старую запись}
Name := BName.Data;
Phone:= BPhone.Data;
Addr := BAddr.Data;
s[0] := chr(L) ;
FillChar(s[1],L,' ');
move(Name[1],s[1],Length(Name)) ;
move(Phone[1],s[succ(LName)],Length(Phone));
move(Addr[1],s[succ(LName+LPhone)],Length(Addr));
OldCount := PS.Count; {Прежнее количество записей}
РS.insert(NewStr(s)); {Добавляемв коллекцию}
{Проверяем добавление}
if OldCount <> РS.Count then
Write(DataFile,Data) {Да - добавляем в файл}
end
until Edit or (Control=cmCancel);
Draw
end; {AddItem}
Вначале указатель файла смещается в самый конец, подготавливая добавление записей (судя по всему, режим добавления будет использоваться гораздо чаще, чем режим редактирования). Затем формируется заголовок окна и само окно. Операторы
if Edit then with Data do
begin {Готовим начальный текст:}
.......
end;
готовят начальное состояние полей ввода в режиме редактирования. Оператор
InWin. SetData (Data)
помещает подготовленный текст в нужные поля. При обращении к процедуре SetData данные должны быть предварительно подготовлены в строгом соответствии с порядком создания диалоговых полей в окне и типом их данных. Поскольку в нашем случае формат данных в полях ввода окна совпадает с форматом файловых данных, мы можем использовать одну и ту же переменную как для работы с файлом, так и для установки начальных значений диалоговых полей.
В самом общем случае пользователь должен объявить новый тип, соответствующий формату помещаемых в окно данных, и использовать выражение этого типа в качестве параметра обращения к процедуре SetData. Например, если бы в нашем окне было предусмотрено только одно поле ввода «Телефон», то установку данных можно было бы осуществить таким оператором:
InWin. SetData (DataType . Phone)
где DataType.Phone - выражение типа String [LPhone].
Контроль за соответствием типа устанавливаемых данных порядку объявления и типу данных диалоговых полей полностью возлагается на программиста. В операторах
if Control=cmOk then with Data do
begin
.....
end
данные, полученные из диалогового окна, помещаются сначала в отсортированную коллекцию, а затем - в файл. С помощью оператора
if OldCount <>PS. Count then
проверяется изменение количества данных в коллекции (напомню, что в отсортированную коллекцию можно поместить только уникальную запись). Если количество записей в коллекции изменилось, значит новая запись не совпадает ни с одной из уже имеющихся и ее следует поместить в файл.
Операторы
if Edit then
DeleteItem; {Удаляем старую запись}
предварительно удаляют старую запись с помощью обращения к процедуре DeleteItem.
Procedure DeleteItem;
{Удаляет указанный в Location элемент данных}
var
D: Integer;
PStr: PString;
s: String;
Data: DataType;
begin
PStr := PS.At(Location){Получаем текущую запись}
s := copy(PSr,1,LName)
seek(DataFile,0);
D := -1;{D - номер записи в файле}
repeat{Цикл поиска по совпадению поля Name:}
inc(D);
read(DataFile,Data);
with Data do while Length(Name) < LName do
Name := Name+' '
until Data.Name=s;
seek(DataFile,pred(FileSize(DataFile)));
read(DataFile,Data); {Читаем последнюю запись}
seek(DataFile,D);
write(DataFile,Data); {Помещаем ее на место удаляемой}
seek(DataFile,pred(FileSize(DataFile)));
truncate(DataFile); {Удаляем последнюю запись}
with РS do D := IndexOf(At(Location));
PS.AtFree(D); {Удаляем строку из коллекции}
Draw {Обновляем окно}
end; {DeleteItem}
Рис.15.12. Окно ввода шаблона поиска
С помощью этого окна пользователь может задать несколько начальных букв, используемых как ключ для поиска записи. Получив данные из этого окна, процедура SearchItem организует поиск первой от начала коллекции строки, для которой не выполняется условие
Pattern >= Item
где Pattern - образец поиска, Item - текущая строка коллекции. Найденная строка указывается как текущая в поле Location и организуется вывод соответствующего текста в окне просмотра.
В реализации процедуры SearchItem указанная проверка осуществляется для строк, предварительно преобразованных к прописным буквам с помощью внутренней процедуры UpString, т.е. поиск игнорирует возможную разницу в высоте букв шаблона и строк коллекции.
Procedure SearchItem;
{Ищет нужный элемент}
Function UpString(s: String): String;
{Преобразует строку в верхний регистр}
var
k: Integer; begin
for k := 1 to Length(s) do
if s[k] in ['a'-.'z'] then
s[k] := chr(ord('A')+ord(s[k])-ord('a'))
else if s[k] in ['a'..'n']. then
s[k] := chr(ord('A')+ord(s[k])-ord('a'))
else if s[k] in ['р'..'я'] then
s[k] := chr(ord('P')+ord(s[k])-ord('p'));
UpString := s
end; {UpString}
var
InWin: PDialog;
R: TRect;
s: String;
p: PInputLine;
k: Word;
begin {SearchItem}
R.Assign(15,8,65,16);
InWin := New(PDialog,
Init(R,'Поиск записи:'));
with InWin do
begin
R.Assign(2,2,47,3);
p := New(PInputLine, Init(R,50));
Insert(p);
R.Assign(l,l,40;2);
Insert(New(PLabel, Init(R, 'Введите образец для поиска:',р)));
R.Assign(10,5,20,7);
Insert(New(PButton, Init(R,'Ввести',cmOk,bfDefault)));
R.Assign(25,5,35,7);
Insert(New(PButton, Init(R,'Выход',cmCancel,bfNormal)));
SelectNext(False)
end;
if DeskTop.ExecView(InWin) = cmCancel then
exit; s := p.Data;
Location := 0;
while (UpString(s) >= UpString(PString(PS.At(Location))))
and (Location < pred(PS.count)) do
inc(Location); if (Location < Delta.Y) or
(Location > Delta.Y+pred(Size.Y)) then
ScrollTo(Delta.X,Location)
else
Draw
end; {SearchItem}
Наша программа, насчитывающая всего около 600 строк, обеспечивает весьма высокий уровень диалога с пользователем: в ней используются командные клавиши, «выпадающие» меню, удобные диалоговые окна, поддержка мыши. Думаю, что вряд ли каким-либо другим способом мы смогли бы создать столь сложную диалоговую среду программой такого объема. Таким образом, Turbo Vision является превосходным инструментом для разработки диалоговых программ, ориентированных на текстовый режим работы экрана. Использование этой среды в Ваших программах резко сократит сроки их создания и повысит качество.
Разумеется, созданная программа далека от совершенства, однако даже в этом виде она, как показывает мой собственный опыт, может быть достаточно полезной. При желании ее можно взять за основу создания более сложной информационно-поисковой системы.