Цветовая палитра
В процедуре Draw переменная Color задает атрибуты (цвет символов и цвет фона) символов, выводимых с помощью методов MoveChar и MoveStr. С помощью функции GetColor она устанавливается таким образом, чтобы символы на экране отображались цветовым сочетанием с номером 1. В Turbo Vision используется гибкая система установки цвета отдельных видимых элементов. Элемент изображения связывается не с каким-то конкретным цветом, а с индексом в таблице цветов, называемой палитрой. Количество элементов палитры зависит от количества цветовых сочетаний, используемых при выводе элемента изображения. Например, в TScroller используется двухэлементная палитра цветов: первый элемент устанавливает цвет нормального текста, второй - выделенного текста (рис. 15.6).
![](15_6.jpg)
Рис.15.6. Палитра объекта TScroller
Числа 6 и 7 в этой палитре указывают не конкретные цвета, а номера позиций в палитре объекта-владельца. Для нашего случая объектом-владельцем будет TWindow. Таким образом, цвет номер 1 палитры TScroller лишь указывает на шестое по счету цветовое сочетание в палитре TWindow (рис. 15.7).
![](15_7.jpg)
Рис. 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.
Диалоговое окно выбора режима
Подведем некоторые итоги. Мы создали программу, которая погружает пользователя в среду объектно-ориентированного диалога Turbo Vision: она поддерживает командные клавиши, работу с мышью, может сменить каталог или диск, выбрать нужный файл и загрузить его в окно просмотра. Не так плохо для 300 строк программного текста! Наша дальнейшая задача - реализовать другие режимы работы (поиск нужной строки, добавление и уничтожение строк, их изменение). Для двух из них (уничтожение и редактирование строки) в программе необходимо каким-то образом указать ту строку, с которой будет работать пользователь. Мы уже реализовали эту возможность, предусмотрев в окне просмотра текста управляемый указатель. Поэтому режим просмотра можно принять в качестве основного режима работы с данными. В связи с этим следует несколько изменить метод TNotebook-HandleEvent, предусмотрев в нем автоматический переход в режим просмотра данных в случае успешного открытия файла с данными:
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.jpg)
Рис.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.
Формирование меню
Вернемся к смысловой части нашего примера и подумаем о том, какие еще свойства следует придать программе. Поскольку мы предполагаем работу с файлом, можно включить в программу код, реализующий строку меню с опцией «Файл», связав с этой опцией такие действия, как открытие уже существующего файла данных и/или создание нового. Здесь же можно предусмотреть возможность альтернативного выхода из программы. Кроме того, в главное меню следует поместить еще одну опцию, назовем ее «Работа». Эта опция должна открыть доступ к содержательной части программы. С учетом сказанного программу нужно дополнить следующими строками:
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.jpg)
Рис.15.2. Вид окна с развернутым меню опции Файл
Определение опций меню во многом напоминает определение командных клавиш в строке статуса. Отличие заключается лишь в том, что с любой опцией меню может быть при необходимости связана встроенная справочная служба. В нашей программе мы не используем эту возможность, для чего задаем стандартный идентификатор hcNoContext (нет контекстно-зависимой справки) при описании каждой опции.
Подобно клавишам строки статуса командные клавиши меню выделяются символом «~». Заметим, что не имеет смысла назначать в качестве командных клавиш клавиши кириллицы, так как при их анализе Turbo Vision игнорирует коды 128...255. Если бы, например, мы задали в качестве командной клавиши для опции «Файл» клавишу «Ф», нажатие Alt-Ф не вызвало бы развертывания подменю, связанного с этой опцией (как и в Турбо Паскале, в Turbo Vision опции главного меню вызываются комбинацией А1t-<клавиша>, а опции меню нижнего уровня - просто нажатием нужной командной клавиши).
Формирование строки статуса
Стандартный вид экрана, показанный на рис. 15.1, можно изменять. Попробуем придать ему некоторый специфический для нашей программы вид. Например, заменим в строке статуса стандартное сообщение
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) {Нет других определений}
Описанный пример позволяет нам сделать очень важный вывод:
Чтобы модифицировать стандартное поведение объекта, необходимо создать объект-потомок от этого объекта и
перекрыть в нем нужный метод.
Инкапсуляция новых полей и методов
При попытке откомпилировать полученный вариант программы Турбо Паскаль сообщит о неизвестном идентификаторе DisableCommands. На первый взгляд это кажется странным - ведь аналогичное обращение в обработчике событий TNotebook.HandleEvent не вызывало проблем! Все дело в том, что мы работаем с объектами, а следовательно, здесь очень важным становится контекст программ. Обработчик TNotebook-HandleEvent - это метод объекта TNotebook, который унаследовал от своих родителей многие свойства, в том числе и метод DisableCommands. Процедура FileOpen не является потомком объектов Turbo Vision и не имеет доступа к их полям и методам.
В 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 восстанавливает все видимые элементы экрана.
Использование коллекций
Для вывода текста мы использовали глобальный массив Lines. Как известно, длина любого массива в Турбо Паскале не может превышать длину сегмента данных (64 Кбайт). Это ограничение можно убрать, если воспользоваться еще одним механизмом Turbo Vision - коллекциями. Подобно массивам, коллекции представляют собой набор элементов, в которых можно хранить любые данные, включая экземпляры любых объектов. К элементам коллекции можно обращаться по индексу, однако, в отличие от массива, коллекция размещается в куче, поэтому ее суммарная длина ограничивается всей доступной памятью и может быть больше 64 Кбайт. Кроме того, размер коллекции не лимитируется при ее создании и может динамически изменяться в процессе работы программы.
![](15_8.jpg)
Рис.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.
![](15_9.jpg)
Puc.15.9. Окно с отсортированным тестом программы
мы завершили создание диалоговой программы,
Итак, мы завершили создание диалоговой программы, обслуживающей электронную «записную книжку». В ходе ее реализации Вы познакомились с некоторыми возможностями диалоговой среды Turbo Vision. Я не ставил себе целью дать здесь подробное описание всех или даже использованных в программе средств Turbo Vision -этому посвящены остальные главы этой части книги. В частности, вне рамок примера остались такие важные механизмы, как потоки, ресурсы, препроцессорные и постпроцессорные события и многие другие возможности Turbo Vision. Однако уже рассмотренные средства свидетельствуют о том, что программирование в Turbo Vision существенно отличается от традиционных методов создания программ, и это отличие является следствием широкого использования в Turbo Vision механизмов объектно-ориентированного программирования.
Наша программа, насчитывающая всего около 600 строк, обеспечивает весьма высокий уровень диалога с пользователем: в ней используются командные клавиши, «выпадающие» меню, удобные диалоговые окна, поддержка мыши. Думаю, что вряд ли каким-либо другим способом мы смогли бы создать столь сложную диалоговую среду программой такого объема. Таким образом, Turbo Vision является превосходным инструментом для разработки диалоговых программ, ориентированных на текстовый режим работы экрана. Использование этой среды в Ваших программах резко сократит сроки их создания и повысит качество.
Разумеется, созданная программа далека от совершенства, однако даже в этом виде она, как показывает мой собственный опыт, может быть достаточно полезной. При желании ее можно взять за основу создания более сложной информационно-поисковой системы.
Команды
Необходимо пояснить назначение вновь введенных констант стХХХХ. Это так называемые команды, точнее их коды (шифр). Сразу же замечу, что префикс cm в идентификаторах команд не является следствием каких-либо требований со стороны Turbo Vision, просто он принят для предопределенных (стандартных) команд, таких как cmQuit и cmClose. Вновь вводимые команды не являются предопределенными, при их описании я заимствовал стандартный префикс только по соображениям стилистики.
Что же такое команды Turbo Vision? Внимательный анализ предыдущего варианта программы показывает, что.эти коды еще никак не используются, они понадобились лишь для синтаксически правильного обращения к стандартным методам инициации строк меню и статуса. В новом варианте программной реализации Вы можете вызвать любую опцию главного меню или нажать любую командную клавишу - это не приведет ни к каким последствиям: пока работает только команда Alt-X, завершающая работу программы, и клавиши F2, F3 и F10. Происходит это потому, что эти клавиши мы связали со стандартными командами cmQuit, cmSave, cmOpen и стМепи и обрабатываются они где-то внутри Turbo Vision. Новые команды не известны системе, и их обработку мы должны взять на себя.
Как мы увидим дальше, в Turbo Vision есть средства контроля командных клавиш. Эти средства определяют факт нажатия на клавишу с помощью генерации кода соответствующей команды, который (код) будет в этом случае передан нашей программе. Таким образом, обработка команд заключается в расшифровке получаемых от Turbo Vision кодов и передаче управления соответствующим частям программы.
Для шифровки команд в Turbo Vision используется 16-разрядное слово, что позволяет определить до 65535 различных команд. Некоторые из этих кодов зарезервированы для использования внутри Turbo Vision, остальные доступны программисту:
Команды с кодами от 0 до 255 при необходимости могут быть временно запрещены, остальные команды запретить нельзя - вот почему используется два диапазона доступных для программиста кодов команд. Временное запрещение команд связано с тем очевидным свойством диалоговых программ, что отнюдь не любая команда может исполняться в каждом возможном состоянии программы. Например, бессмысленно использовать команду «Закрыть файл», если файл еще не открыт. Наоборот, если файл уже открыт, команда «Открыть файл» может стать временно недоступной пользователю. Механизм маскирования (временного запрещения) команд позволяет избавиться от многочисленных проверок контекстуальной корректности тех или иных команд: программист может их запретить впредь до наступления какого-либо события, а запрещенные команды игнорируются средствами Turbo Vision и в программу пользователя не передаются.
В нашей программе имеет смысл запретить команды cmSave и cmWork до тех пор, пока пользователь не откроет нужный файл с данными. Запрет команд достигается обращением к стандартной процедуре DisableCommands (см. предыдущий вариант программы). Указанные в обращении к ней команды задаются в виде множества кодов (мощность любого множества в Турбо Паскале не может превышать 256, вот почему могут быть запрещены только первые 256 команд) и становятся недоступны впредь до обращения к процедуре EnableCommands (разрешить команды).
Запрещенные опции меню (как и временно недоступные командные клавиши) выделяются на экране оттенком (пониженной яркостью).
Обработка команд пользователя
Обработчик событий диалогового окна поддерживает только стандартные команды cmClose, cmOk, cmCancel. Как заставить его реагировать на команды пользователя? Вы правы: нужно перекрыть стандартный обработчик событий.
Введем новый объект 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(...));
и вновь запустите программу: теперь нажатие на любую кнопку диалогового окна приведет к его закрытию.
Программирование диалоговых запросов
В обработчике событий TNotebook.HandleEvent мы предусмотрели вызовы нескольких процедур, с помощью которых реализуются конкретные действия программы. Настала пора запрограммировать эти действия.
Начнем с процедуры 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);
![](15_3.jpg)
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
Работа большинства прикладных программ проходит в три этапа: подготовка к работе, собственно работа и, наконец, ее завершение. В нашем случае к подготовительному этапу можно отнести такие действия, как анализ существования файла данных и его (файла) подготовка к работе. На этапе завершения мы должны обеспечить необходимые действия по сохранению файла/Все остальные действия относятся к среднему этапу. С учетом этого можно написать следующую простейшую программу:
begin
{Подготовить работу программы}
{Выполнить необходимые действия}
{Завершить исполнение программы}
end.
Если Вы попытаетесь выполнить эту программу, ничего не произойдет - ведь мы еще никак не конкретизировали необходимые действия. Так обстоит дело в Турбо Паскале, но не так - в Turbo Vision! Для любой прикладной программы 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.jpg)
Рис.15.1. Вид экрана для простейшей программы
Для выхода из программы необходимо, как это следует из надписи в левом нижнем углу экрана, нажать Alt-X или подвести к этой надписи указатель мыши (если, разумеется, Ваш ПК оснащен этим устройством) и нажать ее левую кнопку.
Как видите, даже простейшая программа «знает», как создать экран, распознает команду Alt-X и может работать с мышью. Совсем не плохо для трех исполняемых операторов, не так ли? Такие возможности доступны потому, что в объекте TApplication предусмотрены соответствующие методы. В этом смысле использование объектов напоминает использование подпрограмм из библиотек. Однако в отличие от подпрограммы любой объект имеет все необходимые ему данные. Говоря об объектах, я часто буду использовать такие слова, как «знает», «умеет», «может», подчеркивая тем самым главную отличительную особенность объектов от традиционных подпрограмм - их «разумность»: последовательное проведение в жизнь принципа инкапсуляции (объединения) данных и всех необходимых для их обработки методов придает объекту определенную независимость от других элементов программы; объекты как бы «живут» в программе своей независимой жизнью.
Простейшая программа не может выполнять никаких других действий, кроме уже перечисленных, так как именно эти действия запрограммированы в методах Init и Run объекта TApplication. В ходе их выполнения на экране создается изображение, имеющее три зоны: верхняя строка, нижняя строка и вся остальная часть экрана. Верхняя строка обычно используется для размещения опций главного меню (не забывайте, что Turbo Vision - это оболочка для диалоговых программ!). Нижняя строка - строка статуса: в ней указываются так называемые командные клавиши, т.е. клавиши или комбинации клавиш, которые вызывают нужные действия без перехода к промежуточному диалогу. Вся остальная часть экрана составляет «рабочий стол» программы - сюда будут помещаться сообщения, здесь будут размещаться окна, «выпадающие» меню (меню нижнего уровня) и т.п.
Редактирование и добавление записей
Для редактирования и добавления записей создадим окно, показанное на рис. 15.11.
Режим редактирования отличается от режима добавления записей двумя обстоятельствами: во-первых, в режиме редактирования поля ввода данных окна должны содержать текст, взятый из редактируемой записи, а в режиме ввода эти поля пусты. Во-вторых, режим редактирования завершается сразу после нажатия на клавишу Enter, в то время как в режиме ввода нажатие на эту клавишу означает добавление к файлу текущей записи и переход к вводу следующей: режим ввода завершается командой cmClose (клавиша Esc). С учетом этого оба режима реализуются в рамках одной процедуры AddItem (Edit), а параметр Edit указывает нужный режим: если Edit = True, реализуется режим редактирования, если False - режим добавления записей. Вот текст этой процедуры:
![](15_11.jpg)
Рис.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.
Режим поиска записи
Для поиска нужной записи сформируем диалоговое окно, показанное на рис.15.12.
![](15_12.jpg)
Рис.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}
События и их обработка
Весьма важным принципом Turbo Vision является принцип отделения процесса создания видимых изображений от процесса обработки данных. Это означает, что все действия по созданию разнообразных окон, меню и прочих видимых элементов можно осуществлять, не заботясь о тех командах (действиях пользователя), которые будут связаны с ними. Именно так мы поступили при определении меню и строки статуса -коды команд дают возможность распознать соответствующие действия пользователя, однако сами эти действия пока еще никак не раскрыты. И наоборот, мы можем разрабатывать части программы, ответственные за обработку действий пользователя, не связывая прямо эти части с созданием нужных видимых элементов.
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, либо просто вернуть управление обработчику верхнего уровня, если эта команда не предназначена для него, либо, наконец, сформировать и передать новое событие для реализации команд, которые распознаны им, но которые он выполнять не умеет или не должен.
Создание и использование групп
Пора заняться основной содержательной частью нашей программы - процедурой Work. Прежде всего следует продумать способ взаимодействия пользователя с данными (интерфейс пользователя). От удачного выбора интерфейса во многом зависит успех разработки диалоговых программ: неудобный способ доступа, связанный с необходимостью ввода каких-либо команд или ответов на многочисленные вопросы, надолго отобьет у пользователя всякое желание работать с программой. Turbo Vision предоставляет в Ваше распоряжение все необходимые средства для разработки современного объектно-ориентированного диалога. В ходе такого диалога пользователь видит на экране объекты, о которых идет речь, он может указать на любой объект и выбрать те действия, которые нужно осуществить над ним.
При работе с электронной записной книжкой хотелось бы, чтобы на экране появилось сразу несколько записей, отсортированных в алфавитном порядке. Пользователь должен иметь возможность «листать» книжку, отыскивать в ней нужную запись, добавлять новые и исключать ненужные записи, редактировать их (вносить изменения). Таким образом, ядром диалога должно стать окно с текстом. При необходимости пользователь может смещать текст в окне в ту или иную сторону, перемещать само окно относительно границ экрана, менять его размеры. Все эти возможности типичны для многочисленных текстовых редакторов, систем программирования, систем управления базами данных и т.п.
Для реализации этих действий в 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.jpg)
Рис. 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.jpg)
Рис.15.5. Окно с полосами прокрутки
Удаление записи
При реализации режима удаления записи нам нужно учесть тот факт, что порядок следования записей в файле и коллекции может быть различным. Поэтому в процедуре 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}
Указатель на элемент списка
Как уже отмечалось, с помощью процедуры Draw можно выводить обычный текст и выделенный текст. Попробуем использовать это обстоятельство для того, чтобы поместить в окно просмотра указатель на текущий элемент данных. Для этого добавим в TInterior еще одно поле:
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 номер той строки текста, на которой располагается указатель мыши.
Введение в Turbo Vision
Простейшая программа в Turbo Vision
Формирование строки статуса
Формирование меню
Команды
События и их обработка
Программирование диалоговых запросов
Инкапсуляция новых полей и методов
Создание и использование групп
Вывод текста
Цветовая палитра
Использование коллекций
Указатель на элемент списка
Диалоговое окно выбора режима
Обработка команд пользователя
Редактирование и добавление записей
Удаление записи
Режим поиска записи
Итоги
В этой главе мы попробуем разработать программу, которая использует некоторые возможности Turbo Vision. Пусть, например, нам необходимо создать простейшую информационную систему - нечто вроде электронной записной книжки. Предполагается, что данные, используемые этой системой, будут храниться в виде записей в дисковом файле. Наша задача - разработать удобную диалоговую программу, облегчающую доступ к файловым данным.
Разработка программы, разумеется, не является самоцелью - ведь для нас это только повод для конкретного знакомства с Turbo Vision. Поэтому мы будем создавать программу постепенно, каждый раз фиксируя достигнутые результаты. Если Вас интересует собственно информационная программа, используйте ее окончательный вариант, приведенный в прил.П.5.4
Вывод текста
По сравнению с рис. 15.4 мы добились немногого, ведь пока еще не решена главная проблема - вывод нужного текста. Разумеется, в Вашем распоряжении всегда имеется процедура WRITELN, однако вывод текста «в лоб» с помощью этой процедуры практически никогда не используется в Turbo Vision, так как в этом случае выведенный текст не будет связан с окнами.
В объекте 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 объекта-владельца нужной части экрана. Это обеспечит автоматическое изменение изображения и его прорисовку при изменении границ или положения поля вывода.
Абстрактные объекты и методы
Объект TObject никогда не используется самостоятельно и служит основой для начинающегося с него дерева объектов. В Turbo Vision есть и другие объекты, не предназначенные для самостоятельного использования в программах. Такие объекты называются абстрактными. В правильно сконструированной программе никогда не создаются и не используются экземпляры абстрактных объектов. Эти объекты просто объединяют в себе некоторые общие для всех своих потомков поля и методы.
Во многих объектах Turbo Vision используются абстрактные методы, т.е. методы, которые не содержат исполняемых операторов и служат заготовками для аналогичных методов в объектах-потомках. Абстрактные методы всегда являются виртуальными и должны перекрываться в потомках. Например, абстрактными являются деструктор TObject.Done, используемый для удаления экземпляра объекта из кучи, метод TSortedCollection.Compare, с помощью которого осуществляется сортировка коллекций, метод TCluster.Press, ответственный за обработку события, связанного с выбором элемента управления в диалоговом окне. В большинстве абстрактных методов предусмотрен вызов глобальной процедуры Abstract, предназначенной для аварийного завершения работы программы и выдачи соответствующего диагностического сообщения.
Некоторые методы не являются абстрактными, но предоставляют лишь минимум возможностей и поэтому почти всегда перекрываются в потомках. Такие методы называются псевдоабстрактными. Примером псевдоабстрактного метода может служить метод TView.Draw, ответственный за перерисовку видимого элемента: по умолчанию он ничего не делает и оставляет выделенную ему часть экрана без изменений.
![](16_2.jpg)
Puc.16.2. Иерархия объектов Turbo Vision
Функциональность объектов
В функциональном отношении все потомки объекта TObject делятся на две группы: видимые элементы и невидимые объекты (рис. 16.3).
Видимые элементы могут быть терминальными видимыми объектами или группами видимых элементов. Любой элемент группы в свою очередь может быть группой элементов, однако все группы в конце концов заканчиваются терминальными видимыми объектами.
![](16_3.jpg)
Рис. 16.3. Видимые и невидимые объекты
Все терминальные видимые объекты (или просто видимые объекты) являются потомками объекта TView, который придает им специальные свойства, отсутствующие у невидимых объектов. Объект TView - это прямой потомок TObject (см. рис. 16.2), представляющий собой пустую прямоугольную область экрана. В программе почти никогда не используются экземпляры этого объекта, однако от него прямо или косвенно порождаются все видимые элементы - как стандартные для Turbo Vision, так и создаваемые программистом. Каждый порождаемый от TView видимый объект владеет прямоугольной частью экрана и наследует его псевдоабстрактные методы Draw и HandleEvent. Метод Draw является основой специализированных методов, обеспечивающих создание изображений. Метод HandleEvent - это центральный метод, с помощью которого реализуется цепочка связанных друг с другом обработчиков событий. Каждый видимый элемент может обрабатывать связанные с ним события - это является одним из важнейших отличительных свойств видимых элементов.
Семейство невидимых объектов включает в себя потоки, ресурсы, контролеры, коллекции и списки строк. Невидимые объекты по существу представляют собой обобщение таких важных механизмов Турбо Паскаля, как файлы и массивы, учитывающее особенности объектно-ориентированных программ. Вы можете использовать невидимые объекты в любых программах, в том числе и не пользующихся изобразительными возможностями Turbo Vision. В частности, они могут оказаться полезными при разработке программ, рассчитанных на графический режим работы дисплея (видимые объекты Turbo Vision рассчитаны исключительно на текстовый режим работы).
Группы видимых элементов
Абстрактная группа
Абстрактная группа - это объект TGroup. Этот объект, вообще говоря, не является абстрактным, однако его экземпляры практически никогда не используются в программах: основное назначение объекта - служить основой иерархии родственных объектов пользователя. Хотя группа владеет прямоугольным участком экрана, она становится видимой только за счет визуализации своих элементов. Элементы группы организуются в связанный список: каждый элемент имеет поле Owner, содержащее указатель на своего владельца, и поле Next, с помощью которого указывается следующий элемент списка.
Панель экрана
Панель экрана - это объект TDesktop, создающий фоновое изображение рабочей зоны экрана. TDesktop является прямым потомком TGroup и, следовательно, может владеть произвольным набором видимых элементов. Заполнение панели экрана осуществляется другими видимыми элементами, такими как TWindow, TDialog и т.п. Обычно владельцем группы TDesktop является программа - объект TApplication или его потомки.
Программы
Все программы, использующие средства Turbo Vision, должны порождаться от
объекта TProgram или его единственного потомка TApplication. Оба объекта создают на экране стандартное изображение строки меню, панели- экрана и строки статуса, т.е. .являются владельцем группы, состоящей из TMenuBar, TDesktop и TStatusLine
(рис. 16.4).
TApplication отличается от своего родителя только методами Init и Done, с помощью которых осуществляется инициация основных подсистем Turbo Vision и их закрытие:
Constructor TApplication.Init;
begin
InitMemory;
InitVideo;
InitEvents;
InitSysError;
InitHistory;
TProgam.Init
end;
Destructor TApplication.Done;
begin
TProgram.Done;
DoneHistory;
DoneSysError;
DoneEvents;
DoneVideo;
DoneMemory end;
![](16_4.jpg)
Рис.16.4.
Стандартная группа TProgram
Обычно программа пользователя связывается с экземпляром TApplication или его потомка. Если Вас не устраивает стандартная последовательность инициации и закрытия подсистем Turbo Vision, Вы должны создать и использовать потомка от TProgram.
Окна
Для создания и использования окон в Turbo Vision предусмотрен объект TWindow. Обычно этот объект владеет объектом TFrame и поэтому очерчивается прямоугольной рамкой со стандартными кнопками изменения размера и закрытия. Вы можете перемещать окно, изменять его размеры и закрывать, используя методы TView или перекрывая их собственными методами. Если окно имеет номер от 1 до 9, его можно сделать активным (выбрать) командными клавишами Alt-N, где N - номер окна. Если окно имеет несколько видимых элементов, его обработчик событий интерпретирует нажатие на клавиши Tab и Shift-Tab как команду активизации следующего (предыдущего) видимого элемента.
Диалоговые окна
Объект TDialog порожден от TWindow и предназначен для реализации взаимодействия с пользователем. В отличие от TWindow диалоговое окно не может изменять свой размер (но может перемещаться по экрану). Его обработчик событий генерирует команду cmCancel в ответ на нажатие клавиши Esc (или воздействие мышью на кнопку закрытия) и команду cmDefault в ответ на нажатие Enter.
Коллекции
TCollection реализует набор элементов, подобный массивам языка Турбо Паскаль. В отличие от массивов, содержащих элементы одного какого-либо типа, коллекции обладают свойством полиморфизма, т.е. могут хранить данные разного типа, в том числе и разные объекты. Коллекции размещаются в динамической памяти, поэтому их размер может быть больше 64 Кбайт. Кроме того, размер коллекции может динамически изменяться в ходе работы программы. К каждому элементу коллекции можно обратиться по его номеру (индексу).
Отсортированные коллекции
С помощью объектов TSortedCollection организуются коллекции, элементы которых упорядочены (отсортированы) по какому-либо признаку. Объект содержит абстрактный метод Compare, с помощью которого осуществляется упорядочение. Этот метод должен перекрываться для задания нужного способа сортировки элементов коллекции. Метод Insert обеспечивает вставку новых элементов в коллекцию с учетом принятого упорядочения. Метод Search осуществляет поиск заданного элемента методом двоичного поиска.
Коллекции строк
TStringCollection представляет собой модификацию своего родителя TSortedCollection, обеспечивая лексикографическое упорядочение строк. Метод FreeItem удаляет нужную строку, сохраняя упорядочение строк. Для записи новых строк в коллекцию и чтения строк из нее объект имеет методы PutItem и GetItem.
Коллекции ресурсов
Объект TResourceCollection порожден от TStringCollection и используется в объекте TResourceFile (файл ресурсов) для реализации коллекции ресурсов. При использовании коллекции ресурсов создается и поддерживается индексная служба в виде отсортированных строк, т.е. метод Compare этого объекта поддерживает лексикографическое упорядочение строк.
Контроллеры
Абстрактный объект TValidator и его потомки образуют группу объектов-контролеров. Общее назначение этих объектов - осуществление контроля за клавиатурным вводом пользователя. Они связываются с объектами типа TEditor и активизируются при выполнении TEditor.Valid. Получив управление, контролеры проверяют соответствие введенных данных определенным требованиям и блокируют завершение ввода, если обнаружена ошибка. Объекты-контролеры избавляют программиста от рутинной работы, связанной с программированием контроля наиболее популярных форматов вводимых данных.
TValidator
Абстрактный объект TValidator инкапсулирует основные свойства всех объектов-контролеров. Его метод Valid вызывается из одноименного метода редактора TEditor и обращается к абстрактному методу TValidator.Is Valid, чтобы осуществить необходимый контроль.
TPXPicture Validator
Объект TPXPictureValidator проверяет введенную пользователем строку на соответствие некоторому эталонному образцу ввода. Образец задается в виде текстовой строки, подготовленной в соответствии со спецификацией СУБД Paradox корпорации Borland.
TFilterValidator
Этот объект проверяет все символы, введенные пользователем, на соответствие набору допустимых символов. Если хотя бы один введенный символ не соответствует ни одному из символов эталонного набора, метод TFilterValidator.IsValid вернет значение False и таким образом будет блокирована попытка пользователя закрыть объект TEditor.
TRangeValidator
Объект TRangeValidator порожден от TFilterValidator. Он преобразует символьный ввод в целое число и проверяет, находится ли оно в заданном диапазоне Min...Max значений. Его метод IsValid возвращает True только в том случае, когда введенные пользователем символы соответствуют внешнему представлению целых чисел, а само введенное число N удовлетворяет условию Мах> =N> =Min.
TLookupValidator
Абстрактный объект TLookupValidator предназначен для контроля соответствия введенной пользователем строки набору эталонных строк. Фактически он модифицирует поведение TValidator в случае, когда проверяется произвольная текстовая строка. В его потомках должен перекрываться метод Lookup, осуществляющий нужный контроль.
TStringLookupValidator
Объект TStringLookupValidator порожден от TLookupValidator и осуществляет контроль введенной пользователем строки, сравнивая ее с набором допустимых строк из отсортированной коллекции строк.
Общая характеристика объектов
Структура объектов
Абстрактные объекты и методы
Функциональность объектов
Обзор видимых элементов
Группы видимых объектов
Терминальные видимые объекты
Невидимые элементы
Потоки
Коллекции
Списки строк
Контролеры
Все свойства Turbo Vision заключены в полях и методах входящих в библиотеку объектов. Каждый объект предназначен для тех или иных целей, поэтому, если Вы хотите использовать какие-то возможности Turbo Vision, Вы должны создать и использовать экземпляры объектов с нужными Вам свойствами. Этот процесс не представляет никакой сложности: вспомните обычные переменные или константы Турбо Паскаля; если, например, в программе понадобится осуществить преобразование строковых данных, Вы объявляете переменную типа String, для реализации математических вычислений - переменную типа Real и т.д. Точно также обстоит дело и с объектами: для создания окна Вы можете объявить переменную типа TWindow, для использования отсортированной коллекции строк - переменную типа TStringCollection и т.д. Например:
var
MyWindow': TWindow;
MyCollection: TStringCollection;
Однако для многих практических применений Вы можете не найти среди объектов Turbo Vision такого, который бы имел все нужные Вам свойства. В этом случае Вы должны создать новый объект как потомок от какого-либо стандартного объекта Turbo Vision и наделить его дополнительными свойствами. Если, например, вновь создаваемое окно предназначено для вывода отсортированных текстовых строк, Вы можете изменить его конструктор Init, обеспечив в нем создание и использование экземпляра объекта TStringCollection:
type
MyWindowType = object (TWindow)
MyCoHection: PStringCollection;
Constructor Init;
end;
Constructor MyWindowType.Init;
.....
begin
.....
MyCollection := New(PStringCollection, Init(...));
.....
end;
Чтобы вновь создаваемый объект был по возможности простым в реализации, он должен наследовать от объекта-родителя максимальное количество нужных ему полей и методов. Таким образом, ключом к использованию Turbo Vision является знание тех свойств, которыми обладают ее стандартные объекты. Изучению объектов посвящена фактически вся эта часть книги, в этой главе мы дадим наиболее общую их характеристику.
Обзор видимых элементов
Видимый объект - это любой объект, который является потомком TView и может быть изображен на экране в текстовом режиме работы дисплея. Важным свойством видимых объектов является возможность объединения нескольких видимых объектов
в группу, рассматриваемую как единое целое. Любым элементом группы в свою очередь может быть новая группа, в которую могут входить другие группы и т.д., однако любая группа видимых элементов всегда заканчивается терминальными видимыми объектами.
Потоки
Поток - это обобщение процедур ввода/вывода данных. Потоки имеют все необходимые методы, позволяющие им правильно обрабатывать любые наборы данных, в том числе - экземпляры объектов Turbo Vision. Базовый абстрактный объект TStream служит основой для специализированных потоков. Он имеет поле Status, определяющее режим доступа к данным (только чтение, только запись или и чтение и запись одновременно). В объекте предусмотрено 7 абстрактных методов: Flush, GetPos, GetSize, Read, Seek, Truncate и Write. Это методы должны перекрываться в потомках TStream для того, чтобы придать потокам необходимые свойства. Перед использованием любого вновь созданного объекта в потоке его необходимо зарегистрировать. В ходе регистрации объекта ему приписывается уникальный идентификатор, позволяющий Turbo Vision автоматически распознавать тип данных, циркулирующих в потоке. Каждому стандартному объекту Turbo Vision уже приписан уникальный номер в диапазоне от 0 до 99. Для регистрации вновь создаваемых объектов программист может использовать номера от 100 до 65535.
Потоки DOS
TDOSStream - это специализированный поток, реализующий небуферизованный ввод/вывод. Метод Init этого объекта позволяет создать новый или открыть уже существующий дисковый файл, задав его имя и режим доступа. В объекте перекрываются все абстрактные методы TStream за исключением TStream.Flush. В большинстве программ удобнее использовать буферизованный поток TBufStream, порожденный от TDOSStream.
Буферизованные потоки
TBufStream реализует буферизованную версию TDOSStream. Наличие внутреннего буфера существенно увеличивает скорость доступа к данным. Объект перекрывает абстрактный метод TStream.Flush, используемый для выталкивания данных из буфера. Выталкивание данных означает чтение (запись) данных из буфера перед закрытием потока и очисткой буфера.
Потоки EMS
Объект TEMSStream реализует обмен данными с так называемой EMS-памятью (EMS - от Expanded Memory Specification - спецификация расширенной памяти, т.е. оперативная память, превышающая основные для ДОС 640 Кбайт). Новые поля этого объекта содержат обработчик EMS, число страниц, размер потока и текущую позицию внутри него.
Ресурсы
Файл ресурсов (объект TResourceFile) - это специальный вид потока, элементы которого могут индексироваться с помощью строковых ключей. При сохранении в потоке очередного элемента (объекта) ему приписывается текстовая строка, которая идентифицирует этот элемент. Для быстрого поиска нужного элемента поток сохраняет в дисковом файле строки-индексы в виде отсортированной коллекции строк вместе с указателем на положение в файле индексируемого элемента и его размером.
Основное назначение файла ресурсов - обеспечение относительно простого способа доступа к диалоговым элементам. Например, вместо того, чтобы создавать диалоговое окно в точке ветвления программы, можно прочитать это окно из заранее подготовленного файла ресурсов.
Списки строк
Объект TStringList реализует специальный вид строкового ресурса, в котором к строкам можно обращаться с помощью числового индекса. Поле Count содержит число строк в объекте. Этот объект упрощает создание многоязычных диалоговых программ, т.к. с его помощью можно обращаться к любой текстовой строке по ее индексу. В объекте предусмотрен метод Get, осуществляющий чтение нужной строки. Для создания списка строк и добавления к нему новых строк используется объект TStrListMaker.
Структура объектов
Все множество объектов Turbo Vision строится на базе трех примитивных объектов: TPoint, TRect и TObject. Примитивными они называются потому, что не являются потомками каких-либо других объектов, но используются или могут использоваться как основа порождаемых от них деревьев родственных типов (рис.16.1).
![](16_1.jpg)
Рис. 16.1. Примитивные объекты Turbo Vision
Объекты TPoint и TRect используются для задания координат точки (TPoint) и прямоугольника (TRect) на экране. В Turbo Vision эти объекты не имеют потомства, но используются всеми другими объектами, связанными с созданием изображений на экране ПК. Объект TObject служит основой иерархии всех других объектов Turbo Vision, кроме TPoint и TRect (см. рис. 16.2).
Объект TObject не имеет данных (полей) и содержит три метода: Init, Free и Done. Конструктор Init осуществляет распределение памяти и является основой всех других конструкторов Turbo Vision. Метод Free освобождает память, распределенную конструктором. Деструктор Done не выполняет никакой работы - это абстрактный метод, который всегда перекрывается в объектах-потомках. .
Терминальные видимые объекты
Рамки
Для создания рамки окна и кнопок изменения размера окна и его закрытия предназначен объект TFrame (рис. 16.5).
Этот объект никогда не используется самостоятельно, но только в группе, связанной с объектом TWindow. Последний имеет поле Frame с указателем на ассоциированный объект TFrame.
![](16_5.jpg)
Рис. 16.5. Рамка окна (объект TFrame)
Кнопки
Объект TButton - это прямоугольник с надписью, имитирующий кнопку панели управления. Обычно TButton является элементом группы TDialog и «нажатие» на кнопку инициирует событие, связанное с какой-либо стандартной командой или командой пользователя.
Кластеры
Кластеры представляют собой прямоугольные видимые элементы, имитирующие несколько зависимых или независимых кнопок (см. рис. 16.6).
![](16_6.jpg)
Рис. 16.6. Примеры кластеров: а) с зависимыми кнопками; б) с независимыми кнопками
Для создания и использования кластера предусмотрен абстрактный объект TCluster и его потомки TRadioButtons, TCheckBoxes и TMultiCheckBoxes. Объект TCluster имеет 16-разрядное поле Value, каждый разряд которого связан со своей кнопкой в кластере. Его потомки отличаются способом изменения состояния этих разрядов: TRadioButtons устанавливает содержимое разряда выбранной кнопки в 1, предварительно очистив поле Value, a TCheckBoxes переключает его содержимое, сохраняя остальные разряды неизменными. Многопозиционная кнопка TMultiCheckBoxes может иметь от одного до 8 связанных с ней разрядов в поле Value. Количество разрядов зависит от количества возможных состояний кнопки, которых может быть до 256.
Таким образом, кластер может содержать до 16 кнопок и выбирать один из возможных вариантов (объект TRadioButtons) или несколько вариантов (TCheckBoxes и TMultiCheckBoxes). Для переключения кнопок используется мышь или клавиатура. В последнем случае клавишей Tab выбирается нужный кластер, а клавишами смещения курсора - нужная кнопка; состояние выбранной кнопки меняется нажатием на клавишу Пробел.
Меню
Для выбора одного из нескольких возможных продолжений в диалоговых программах широко используются меню. Объект TMenuView и его потомки ТМепиВаr и ТМепиВох существенно упрощают создание и использование меню в среде Turbo Vision (рис. 16.7).
Любая программа в Turbo Vision всегда владеет строкой меню. Перекрывая TApplication.InitMenuBar, Вы можете использовать методы NewSubMenu, NewItem и NewLine, чтобы создать и использовать свою систему вложенных меню.
![](16_7.jpg)
Рис. 16.7. Пример меню
Строки ввода
Для ввода различных текстовых строк с клавиатуры ПК в Turbo Vision используется тип TInputLine (рис. 16.8). Использование этого объекта дает в распоряжение пользователя мощные возможности встроенного редактора, обрабатывающего алфавитно-цифровые клавиши, клавиши перемещения курсора влево/вправо, а также клавиши Backspace, Del, Insert, Home, End.
![](16_8.jpg)
Рис. 16.8. Пример строки ввода
Протокол ввода
Абстрактный объект THistory реализует механизм выбора строки ввода из списка ранее введенных строк (из протокола ввода). Объект THistory обычно ассоциируется с объектом TInputLine и становится доступным с помощью клавиши смещения курсора вниз (см. рис. 16.8). Протоколы ввода запоминаются в динамической памяти в виде связанных списков и имеют идентификатор (поле HistoryID), позволяющий использовать один и тот же протокол в нескольких объектах TInputLine.
Скользящие окна (скроллеры)
С помощью объекта TScroller реализуется так называемое скользящее окно, т.е. окно с текстом, положение которого (текста) относительно границ окна может меняться. Условимся для краткости такие объекты в дальнейшем называть скроллерами, а процесс смещения текста в окне - скроллингом. В общем случае размеры окна скрол-лера могут быть значительно меньше полных размеров просматриваемого текста: средства скроллера дают возможность вывести на экран любой фрагмент текста. Обычно скроллеры объединяются в одну группу с объектами TScrollBar, представляющими собой горизонтальную или вертикальную полосу. Эти полосы (полосы скроллинга) указывают положение окна относительно полных границ текста и используются для реализации скроллинга с помощью мыши. Скроллинг текста осуществляется также в ответ на нажатие клавиш смещения курсора и клавиш PgUp, PgDn, Home, End, Ctrl-PgUp, Ctrl-PgDn.
Потомки TScroller - объекты TTextDevice и TTerminal облегчают реализацию скользящих окон специального вида: объекты содержат методы, обеспечивающие запись текстовых строк в скроллер и чтение этих строк из него. Объект TTerminal отличается от своего родителя наличием внутреннего кольцевого буфера, предназначенного для запоминания текстовых строк, и соответствующих методов доступа к буферу. Размер буфера не может быть больше 64 Кбайт. Типичное применение этих объектов - реализация так называемых драйверов текстовых устройств. Например, можно связать существующий текстовый файл с объектом TTerminal и обеспечить непосредственное считывание строк файла в окно скроллера.
Просмотр списков
Абстрактный объект TListViewer предоставляет в Ваше распоряжение средства просмотра списка строк и выбора из этого списка нужной строки. Списки строк выводятся в окне, управляемом полосами скроллинга. Типичное применение объектов TListViewer - просмотр списка файлов. Обычно для этих целей используется потомок TListViewer - объект TListBox, с помощью которого списки файлов выводятся в одну или несколько колонок.
Статический текст
Объекты TStaticText - это терминальные видимые объекты, используемые для вывода текстовых сообщений. В отличие от строк, созданных непосредственным обращением к процедуре WriteLn, текстовая строка объекта TStaticText может входить в группу видимых элементов (например, окно) и управляться этой группой. Статический текст игнорирует любые события, посланные к нему. Типичное применение TStaticText - создание различного рода информационных окон или поясняющего текста в диалоговых окнах.
Строки статуса
Самая нижняя строка экрана в Turbo Vision представляет собой строку статуса. Эта строка создается с помощью объекта TStatusLine. Строка статуса обычно содержит список наиболее важных командных клавиш. Элементы строки можно также выбирать мышью. Содержание строки статуса может изменяться в зависимости от контекста программы. Для этих целей в объекте предусмотрено поле Defs, содержащее указатель на связанный список типа TStatusDef. В каждом элементе этого списка есть два поля Min и Мах, задающие диапазон контекста для данного варианта строки. Контекст программы определяется полем TView.HelpCtx.
Активные элементы
Внутри любой группы видимых элементов в каждый момент времени может быть выбран (активизирован) один и только один элемент. Даже если в программе открыто несколько окон с текстом, активным считается только то окно, с которым Вы работаете в данный момент. Более того, поскольку окно представляет собой группу, в нем будет активным лишь один элемент. Если, например, Вы воздействуете мышью на полосу скроллера, будет активна именно эта полоса. Рис. 17.7 иллюстрирует сказанное: на нем показано дерево видимых элементов для двух открытых окон, причем активные элементы выделены двойной рамкой.
![](17_7.jpg)
Puc. 17.7. Цепочка активности видимых элементов просмотра текста
Цепочка активности видимых элементов используется при обработке событий (см. гл.18).
Активный элемент обычно выделяется на экране тем или иным способом. Например, выбранное окно очерчивается двойной рамкой, а остальные - одинарной; внутри диалогового окна активный элемент выделяется яркостью (цветом). С помощью метода Select видимый элемент можно сделать активным по умолчанию в момент его создания. При активизации группы активизируется ее подэлемент, указанный как активный по умолчанию. Пользователю может потребоваться изменить текущий активный видимый элемент. Он может это сделать, манипулируя мышью, или нажав командную клавишу (если элемент связан с командной клавишей), или, наконец, с помощью клавиши Tab.
Заметим, что существуют видимые элементы, которые нельзя сделать активными. Например, не может быть активным видимый элемент TBackground (фон рабочей зоны экрана). В момент создания элемента с помощью признака ofSelectable Вы можете указать, будет ли этот элемент выбираемым, т.е. можно ли его сделать активным в ходе работы программы. Однако, если Вы объявите выбираемым тот же элемент TBackground, он все равно не сможет активизироваться, так как знает, что на самом деле активизация ему недоступна. Точно также на сможет активизироваться рамка окна (заметим, что указать на рамку мышью можно, и программа может, например, перемещать окно с рамкой, однако это еще не означает активизации рамки: рамка не может быть объектом диалога с пользователем). Обычно сброс признака ofSelectable используется для того, чтобы запретить элементу стать активным, он, в принципе, может активизироваться, но его активизация в программе не нужна. Таким способом можно, например, сделать неактивной метку в диалоговом окне и, следовательно, превратить ее в статический поясняющий текст.
Цвет изображения
Все цвета в Turbo Vision определяются системой палитр: за каждым стандартным видимым элементом закреплен свой набор условных номеров цветов, называемый палитрой. Размер палитры (количество определенных для видимого элемента цветов) зависит от функциональности элемента: чем сложнее элемент, чем больше функций он выполняет, тем богаче его палитра (каждому элементу палитры приписывается некоторая функциональность: один элемент ответственен за фон изображения, другой - за текст, третий выделяет специальные символы и т.д.). Например, для скроллера палитра состоит всего из двух элементов: первый элемент определяет цвет основного текста, второй - цвет выделенного текста. Обычно скроллер входит в качестве терминального видимого объекта в группу, палитра которой будет больше. Например, часто скроллер помещается в окно TWindow, палитра которого насчитывает уже 8 элементов (см. рис. 17.2).
![](17_2.jpg)
Рис. 17.2. Связь палитры TScroller с палитрой TWindow
Палитры образуют систему связанных друг с другом ссылок: каждый элемент палитры содержит не какой-то конкретный цвет или его код, а целое число, указывающее на номер элемента в палитре своего владельца. Если владелец входит в группу, содержимое его палитры устанавливает связь с нужными элементами палитры этой группы и т.д. Ссылки завершаются на «владельце всех владельцев», т.е. на программе: только палитра TProgram и его потомков содержит не ссылки, а сами байты цветовых атрибутов.
Пусть, например, при формировании изображения в методе Draw скроллера выбран первый элемент палитры (нормальный текст). Этот элемент содержит число 6, указывающее номер шестого элемента палитры владельца TScroller. Если владельцем скроллера является объект TWindow, это число означает ссылку на шестой элемент палитры TWindow, который содержит число 13 как ссылку на тринадцатый элемент владельца окна (рис. 17.2). Если, наконец, владельцем окна является программа, то число 13 - это ссылка на тринадцатый элемент палитры TProgram, который содержит байт атрибутов $1Е, т.е. символы будут выводиться желтым цветом на синем фоне (рис. 17.3).
Чтобы получить цвет элемента, используется обращение к функции GetColor. Эта функция просматривает всю цепочку ссылок от текущего видимого элемента до программы и найденный таким образом байт атрибутов из палитры TProgram возвращает в качестве результата. Параметром обращения к функции является номер элемента палитры видимого объекта. Если указан номер несуществующего в данной палитре элемента, функция вернет атрибуты $CF и изображение будет выводиться мигающими белыми символами на красном фоне. Такого сочетания цветов нет ни в одной стандартной палитре, поэтому появление мигающих бело-красных символов на экране
сигнализирует о непредусмотренном разработчиками Turbo Vision функциональном использовании элемента. Если, например, вставить кнопку TButton в текстовое окно TWindow, то окажется, что первый элемент палитры TButton (нормальный текст кнопки) ссылается на 10-й элемент палитры владельца, в то время как в палитре TWindow только 8 элементов.
![](17_3.jpg)
Рис. 17.3. Фрагмент палитры TProgram
Чтобы изменить цвет изображения, нужно либо изменить ссылку в палитре элемента или его владельца, либо сменить атрибут цвета в палитре TProgram. На практике обычно меняют палитру TProgram, т.к. она определяет цвет всех родственных элементов. Например, если Вы измените элемент палитры, ответственный за цвет основного текста в окне, одновременно все окна изменят свой цвет нужным образом, что, по всей видимости, будет логически правильным.
Палитры Turbo Vision задаются в виде обычных текстовых строк. Это дает возможность применять к палитрам все операции и преобразования, которые используются при работе со строковыми данными. Для изменения k-го элемента палитры TProgram следует изменить k-ый символ в строке, указатель на которую возвращает функция GetPalette. Пусть, например, нам нужно, чтобы во всех окнах скроллера стандартный цвет текста (желтый на голубом фоне) был заменен на белый на черном фоне. Тогда можно использовать такой прием:
Uses Арр,...;
type
TMyProgram = object (TApplication)
Constructor Init;
.....
end;
Constructor TMyProgram.Init;
begin
GetPaletteA[13] := #$0F; { Задаем белый цвет на черном фоне}
TApplication.Init {Инициируем программу}
end;
В этом фрагменте в конструкторе TMyProgram.Init осуществляется замена 13-го элемента палитры: этот элемент отвечает за цвет основного текста скроллера (см. рис. 17.3). После такого изменения во всех скроллерах программы основной текст будет выводиться белыми символами на черном фоне.
Для изменения палитры видимого элемента только одного типа нужно перекрыть его метод GetPalette. Допустим нам необходимо, чтобы скроллер рисовал основной текст таким же цветом, как полосы скроллера. В этом случае мы должны посмотреть,
каким по счету элементом в палитре окна-владельца скроллера определяется цвет полос: в нашем примере это элемент с номером 5. Таким образом, палитра скроллера должна содержать значения 5 и 7 вместо прежних 6 и 7 (см. рис. 17.2). Создадим новый объект:
type
TMyScroller = object (TScroller)
Function GetPalette: PPalette; Virtual;
end;
Function TMyScroller.GetPalette: PPalette;
const
NewColors = #5#7;
NewPalette: String [2] = NewColors;
begin
GetPalette := @NewPalette
end;
Существует и менее универсальный, но более простой путь изменения цвета только в одном видимом элементе. Как мы знаем, изображение этого элемента в конечном счете формируется в его методе Draw; если этот метод перекрывается в Вашей программе, а в некоторых случаях, например в скроллере, он должен обязательно перекрывается, то можно воздействовать на цвет непосредственно при обращениях к процедурам MoveChar и MoveStr. Например:
type
MyScroller = object (TScroller)
Procedure Draw; Virtual;
end;
Procedure MyScroller.Draw;
var
Color: Byte;
.....
begin
(* Color := GetColor(l); {Стандартный цвет скроллера} *)
Color := $0F; {Задаем белые символы на черном фоне}
.....
MoveChar(...,...,Color,...);
MoveStr(...,...,Color);
.....
end;
В этом примере в строке комментария (* ..... *) указывается обычный способ получения стандартного цвета основного текста скроллера. Вместо этого желаемый цвет задается нужными атрибутами в переменной Color, которая затем используется при обращениях к процедурам MoveChar и MoveStr.
Палитра TProgram насчитывает 63 элемента и учитывает все возможные функциональные действия, осуществляемые видимыми элементами (см. прил.П6). Более того, этот объект на самом деле имеет три 63-элементных палитры: CColor (цветная палитра), CBlackWhite (черно-белая) и CMonoChrome (монохромная). В установочной секции модуля Views на основе тестирования аппаратных средств ПК из этих палитр выбирается рабочая палитра, которая затем и будет использоваться при формировании изображений. При необходимости Вы можете переустановить палитру TProgram с помощью глобальной процедуры SetVideoMode, например:
Program MyProgram; Uses
Views,....;
var
Main: TApplication;
.....
begin {Начало основной программы}
SetVideoMode(smBW80); {Выбрать черно-белую палитру}
Main.Init; {Инициация программы}
.....
end;
Обращение к SetVideoMode должно предшествовать инициации основной программы, работающей в среде Turbo Vision. Параметром обращения к этой процедуре может быть одна из следующих констант:
const
smBW80 = $002; {Черно-белый режим работы цветного адаптера}
smCO80 = $003; {Цветной режим работы}
smMono = $007; {Монохроматический адаптер}
Эти константы можно дополнять константой
const
smFont8x8 = $100; {Задает 43/50 строк для экрана EGA/VGA.}
для задания режима вывода 43 или 50 строк на экране дисплея, оснащенного адаптером EGA или VGA. Например:
SetVideoMode(smC080+smFont8x8);
Группы
Замечательным свойством видимых элементов Turbo Vision является их способность образовывать группы. Все группы являются потомками абстрактного объекта TGroup. Любая программа в конечном счете наследуется от TProgram или TApplication и, следовательно, является потомком TGroup, т.е. представляет собой группу.
Группа - это просто пустое окно. Главная особенность группы заключается в том, что она может управлять входящими в нее элементами. Как и любой другой видимый элемент, группа должна уметь заполнять нужным изображением выделенную ей часть экрана и обрабатывать все связанные с этой областью события. Однако в большинстве случаев группа обращается за выполнением требуемых действий к своим элементам. Например, визуализация группы происходит исключительно с помощью методов Draw тех элементов, которые образуют группу. С другой стороны, некоторые команды могут относиться к группе в целом. Например, группе может адресоваться команда cmClose (закрыть), в результате которой будет уничтожено изображение группы, т.е. очищен выделенный ей участок экрана. Для реализации этой команды группа будет последовательно передавать ее всем входящим в нее элементам.
Важно помнить, что группа обладает способностью включать в себя видимые подэлементы динамически, в ходе исполнения программы. Как правило, состав группы определяется действиями пользователя: если, например, он нажал командную клавишу, связанную с раскрытием опции главного меню, группа TDesktop, ответственная за рабочую зону экрана, обогащается дополнительными видимыми подэлементами «выпавшего» меню. После нажатия клавиши Esc эти элементы будут удалены из состава группы.
Изменение свойств элемента
Каждый видимый элемент Turbo Vision имеет 5 полей, которые определяют его поведение в диалоговой среде и которые можно использовать для того, чтобы изменить свойства элемента. К этим полям относятся Options, GrowMode, DragMode, State и EventMask. Поле EventMask активно используется в обработчиках событий и описано в гл.18. Поля Options, GrowMode и DragMode доступны как для чтения, так и для записи. Поле State доступно только для чтения и изменить его состояние можно с помощью обращения к методу SetState.
Модальные видимые элементы
Все программы в среде Turbo Vision рассчитаны на диалоговый способ взаимодействия с пользователем, а следовательно, в них активно используются точки ветвления, управляемые командами пользователя (точки диалога). В точке диалога создается активный видимый элемент, называемый модальным элементом.
Примером модального элемента является диалоговое окно. Когда в программе создается и активизируется модальный элемент, только этот элемент и его подэлементы могут взаимодействовать с пользователем. Любая часть дерева видимых элементов, не являющаяся модальным элементом или не принадлежащая ему, не может быть активна в этот момент. Если, например, на экране развернуто диалоговое окно, любые отметки мышью вне его пределов или нажатие на не предусмотренные в этом окне командные клавиши будут игнорироваться.
Единственным исключением из этого правила являются командные клавиши и соответствующие поля для мыши, определенные в строке статуса. Эти клавиши (поля) всегда доступны пользователю и нажатие на них (отметка их мышью) обрабатывается модальным элементом точно также, как если бы они были определены в нем, хотя этот элемент может и не владеть строкой статуса.
Чтобы временно исключить из списка активных команду (или группу команд), определенную в строке статуса, используется метод DisableCommands. После завершения работы модального элемента можно восстановить активность команд методом EnableCommands. Параметром обращения к этим методам служит произвольное множество типа TCommandSet, содержащее до 256 кодов команд. В Turbo Vision тип TCommandSet определен следующим образом:
type
TCommandSet = set of Byte;
Таким образом запрещать (и разрешать) можно только те команды, коды которых принадлежат диапазону 0...255.
Временно запрещенные команды выделяются в строке статуса пониженной яркостью (оттенком).
Поле DragMode
Поле DragMode размером в один байт определяет, как ведет себя видимый элемент при перемещении владельца. Напомню, что Turbo Vision позволяет перемещать окно на экране с помощью мыши, если «схватить» мышью верхнюю рамку окна.
Должен заметить, что мне не удалось добиться сколько-нибудь заметных результатов при различных установках поля DragMode. По умолчанию Turbo Vision устанавливает DragMode = 32 = dmLimitLoY.
На рис. 17.10 указаны разряды поля DragMode.
![](17_10.jpg)
Puc. 17.10. Разряды поля Drag
dmDragMove
Установленный флаг dmDragMode разрешает перемещать окно с помощью мыши.
dmDragGrow
Если флаг установлен, окно может изменять свои размеры.
dmLimitLoX
Если флаг установлен, левая сторона видимого элемента не может выходить за границы своего владельца.
dmLimitLoY
Если флаг установлен, верхняя часть видимого элемента не может выходить за границы своего владельца.
dmLimitHiX
Если флаг установлен, правая сторона видимого элемента не может выходить за границы своего владельца.
dmLimitHiY
Если флаг установлен, нижняя часть видимого элемента не может выходить за границы своего владельца.
dmLimitAll
Если флаг установлен, ни одна часть видимого элемента не может выходить за границы своего владельца.
Поле GrowMode
Восьмиразрядное поле GrowMode определяет, как должен изменяться видимый элемент, когда его владелец (группа) изменяет свои размеры. Разряды этого поля показаны на рис. 17.9.
![](17_9.jpg)
Puc. 17.9. Разряды поля GrowMode
Среда Turbo Vision позволяет изменять размеры окон с помощью мыши: для этого надо «схватить» мышью правый нижний угол окна. Флаги GrowMode определяют, как будут вести себя в этой ситуации вставленные в окно элементы. Напомню, что изображение встроенного элемента всегда отсекается границами группы. Стандартное состояние элементов среды Турбо Паскаль соответствует установленным флагам gfGrowHiX и gfGrowHiY.
gfGrowLoX
Если флаг установлен, левая граница видимого элемента всегда располагается на одинаковом расстоянии от правой границы группы-владельца. Таким образом, при уменьшении горизонтального размера окна вставленный в него видимый элемент смещается влево и, возможно, отсекается левой границей окна.
gfGrowLoY
Если флаг установлен, верхняя граница видимого элемента всегда располагается на одинаковом расстоянии от нижней границы группы, т.е. уменьшение вертикального размера окна приводит к смещению элемента вверх.
gfGrowHiX
Если флаг установлен, правая граница видимого элемента всегда располагается на одинаковом расстоянии от левой границы группы, т.е. при уменьшении горизонтального размера окна видимое на экране положение его внутреннего элемента остается неизменным и, возможно, отсекается правой границей окна.
gfGrowHiY
Если флаг установлен, нижняя граница видимого элемента всегда располагается на одинаковом расстоянии от верхней границы группы, т.е. при уменьшении вертикального размера окна видимое на экране положение его внутреннего элемента останется неизменным. Стандартное состояние элементов среды Турбо Паскаль соответствует установленным флагам gfGrowHiX и gfGrowHiY.
gfGrowAll
Если разряды gfGrowAll установлены в 1, видимый элемент передвигается в процессе изменения размеров своего владельца, отсекаясь его левой и верхней границами.
gfGrowRel
Если флаг установлен, видимый элемент пропорционально изменяет свои размеры при изменении размеров владельца. Вы должны использовать эту опцию только с TWindow, или с наследниками от TWindow, которые присоединяются к панели экрана. В этом случае окна сохраняют свои относительные размеры и положения на экране при переходе от 25 строк к 43/50 строкам и обратно. Этот флаг не предназначен для использования с видимыми элементами внутри окна.
Поле Options
Поле Options представляет собой шестнадцатиразрядное слово, биты (разряды) которого показаны на рис. 17.8.
![](17_8.jpg)
Рис.17.8. Разряды поля Options
ofSelectable
Если этот флаг установлен (имеет значение 1), пользователь может выбрать видимый элемент мышью или клавишей Tab. Если Вы поместили на экран информационный элемент, Вам может потребоваться, чтобы пользователь не мог выбрать его. Например, статические текстовые объекты и рамки окон обычно не могут выбираться и имеют ofSelectable = 0.
ofTopSelect
Видимый элемент будет передвигаться наверх в смысле Z-упорядочения, пока не станет самым верхним среди других равных ему видимых элементов. Этот флаг в основном используется для окон в рабочей зоне экрана. Вы не должны использовать его для видимых элементов в группе.
ofFirstClick
Если флаг сброшен, отметка элемента мышью игнорируется, и наоборот - установленный флаг разрешает выбор элемента мышью. Если в диалоговое окно помещена кнопка, Вы наверняка захотите, выбрать ее мышью, поэтому кнопка имеет ofFirstClick установленным по умолчанию. Но если Вы создаете окно, Вы можете сбросить этот флаг, если хотите, чтобы оно не откликалось на выбор мышью.
ofFramed
Если флаг установлен, видимый элемент имеет рамку.
ofPreProcess
Если флаг установлен, видимый элемент будет получать и, возможно, обрабатывать активные события до того, как их получит и обработает активный элемент. См. раздел «Фаза» в гл.18.
ofPostProcess
Установленный в 1 флаг ofPostProcess позволяет видимому элементу обрабатывать события после того, как они были получены активным элементом, и при условии, что активный элемент не очистил событие. См. раздел «Фаза» в гл.18.
ofBuffered
Когда этот бит установлен, образ группы при первой ее визуализации автоматически сохраняется в буфере. В следующий раз, когда группе нужно будет визуализиро-ваться, она копирует образ из буфера на экран вместо прорисовки всех своих подэле-ментов, что ускоряет процесс создания изображений. Если программа испытывает недостаток в динамической памяти, монитор памяти Turbo Vision освобождает буферы групп до тех пор, пока запрос к динамической памяти не сможет быть выполнен.
Если группа имеет буфер, вызов метода Lock будет блокировать вывод изображения на экран до тех пор, пока не будет вызван метод Unlock. Сразу после вызова UnLock буфер группы целиком выводится на экран. Блокирование уменьшает мерцание во время сложных корректировок экрана. Например, TDesktop блокирует себя, когда выводит свои подэлементы каскадом или черепицей.
ofTileable
Объекг TDesktop может располагать связанные с ним окна каскадом или черепицей. Если окна располагаются каскадом, каждое следующее окно накладывается на предыдущее и почти полностью перекрывает его; при расположении окон черепицей каждое окно располагается так, чтобы не перекрывать другие окна. Если Вы хотите, чтобы окна располагались каскадом, Вам следует очистить флаг ofTileable, если черепицей - установить его в 1. Если для окна установлен режим вывода каскадом, оно будет занимать одно и то же место на экране, в то время как расположение окон черепицей приводит к изменению их размеров и положения при открытии каждого нового окна.
Расположение видимых элементов черепицей или каскадом 'выполняется в TApplication.HandleEvent очень просто:
cmTile:
begin
DeskTopA.GetExtent(R);
DeskTopA.Tile(R);
end;
cmCascade:
begin
DeskTop^.GetExtent(R);
DeskTop^.Cascade(R);
end;
ofCenterX
Этот флаг следует установить в 1, если Вы хотите, чтобы видимый элемент центрировался по горизонтали, т.е. вставлялся в группу так, чтобы слева и справа от него было приблизительно одинаковое расстояние до границ группы.
ofCenterY
Флаг обеспечивает центрирование элемента по вертикали. Если Вы хотите, чтобы Ваши окна выглядели одинаково хорошо в режимах 25 и 43/50 строк на экране, следует установить ofCenterY = 1.
ofCentered
Установка этих разрядов обеспечивает центровку видимого элемента относительно границ группы одновременно по горизонтальной и вертикальной осям.
Поле State
Шестнадцатиразрядное поле State хранит различные признаки, определяющие поведение или состояние видимого элемента. На рис. 17.11 показаны разряды поля State.
![](17_11.jpg)
Puc.17.1l. Разряды поля State
sjVisible
Разрешает визуализировать видимый элемент, если визуализируется его владелец. По умолчанию этот флаг установлен.
sfCursorVis
Разрешает показать курсор внутри видимого элемента. По умолчанию этот флаг очищен.
sfCursorlns
Если этот флаг установлен, курсор будет занимать всю высоту строки, если сброшен, курсор имеет стандартную высоту (две нижних строки развертки).
sfShadow
Если флаг установлен, видимый элемент имеет «тень».
sfActive
Указывает, является ли данный элемент активным окном или подэлементом активного окна.
sfSelected
Указывает, является ли данный элемент текущим (выбранным) видимым элементом. Каждый объект TGroup имеет поле Current, содержащее указатель на текущий выбранный подэлемент или NIL, если не выбран ни один подэлемент. В каждый момент времени в группе может быть выбран только один подэлемент.
sfFocused
Указывает, принадлежит ли данный элемент цепочке активности видимых элементов, т.е. выбран ли он и все его владельцы в данный момент времени.
sJDragging
Разрешает изменять размеры элемента.
sfDisable
Флаг запрещает выбирать данный видимый элемент. Если флаг сброшен, элемент можно выбрать мышью или клавишей Tab.
sfModal
Если флаг установлен, данный элемент - это модальный элемент. В программе Turbo Vision всегда имеется один и только один модальный элемент. Обычно это экземпляр TApplication или TDialog. Модальный видимый элемент активизируется вызовом метода ExecView и образует корень активного дерева событий: он перехватывает события и управляет ими до тех пор, пока не будет вызван его метод EndModal (см.гл.18). Модальный элемент может передавать события своим подэлементам и получать события от них, но он никогда не передает события своему владельцу (события локализуются в модальном элементе).
sfExposed
Указывает, виден ли элемент или любая его часть в данный момент времени (элемент может закрываться другими видимыми элементами). Если флаг установлен, метод Exposed данного элемента возвращает значение True.
Создание группы и изменение ее состава
Создание группы осуществляется за счет создания экземпляра объекта-наследника TGroup и присоединения к нему всех видимых элементов группы. Любой видимый объект, т.е. наследник TView, имеет два поля: Owner и Next. Поле Owner указывает на владельца этого элемента, а поле Next - на следующий равный ему подэлемент группы. При включении видимого элемента в группу его поля изменяются так, что Owner содержит ссылку на экземпляр группы-владельца, a Next имеет значение NIL. После добавления к группе еще одного элемента поле Next ранее включенного элемента изменяет свое значение и содержит ссылку на этот новый элемент. Таким образом создается связанный список подэлементов группы (рис. 17.4).
Для присоединения элемента должны использоваться методы Insert или ExecView. Метод Insert присоединяет очередной видимый элемент к списку подэлементов группы. В зависимости от набора связанных с подэлементом признаков этот элемент может размещаться в центре (признаки ofCenterX и/или ofCenterY), стать активным (ofSelectable) и, наконец, появиться на экране (sfVisible). После создания подэлемента управление передается следующему за обращением к Insert оператору программы.
Метод ExecView осуществляет те же действия, что и метод Insert, однако после создания видимого подэлемента управление будет передано в него и оператор, следующий за обращением к ExecView, получит управление только после уничтожения этого подэлемента. Более точно процесс взаимодействия с программой элемента, присоединенного к группе методом ExecView, можно описать следующим образом. Любой видимый элемент наследует виртуальный метод Valid, с помощью которого он может сигнализировать своему владельцу о том, насколько успешно он выполнил возложенные на него обязанности. Обычно Valid возвращает True, если успешно создан и размещен в динамической памяти экземпляр объекта, и False,- в противном случае. Объект может перекрыть метод Valid и возвратить False, если он хочет оставить управление у себя,- именно так, например, поступает объект TDialog. Метод ExecView приостанавливает исполнение следующего оператора программы до тех пор, пока все подэлементы группы не вернут Valid = True. Таким образом, метод ExecView используется в том случае, когда среди подэлементов группы есть хотя бы один элемент, реализующий диалог с пользователем.
Метод Delete удаляет подэлемент группы из связанного списка.
![](17_4.jpg)
Puc. 17.4. Связанный список элементов группы
Территориальность
Видимый элемент владеет прямоугольным участком экрана. Правильно сконструированный элемент обязан заполнять нужным изображением всю выделенную ему область, иначе на экране останется «мусор». Чтобы элемент мог заполнить область, он должен знать координаты закрепленного за ним участка. Эти координаты хранятся в двух полях - Origin и Size, каждое из которых имеет тип TPoint. Поле Origin задает координаты левого верхнего угла области, выделенной элементу, поле Size - размер этой области, т.е. показывает на каком расстоянии от Origin находится его правый нижний угол. Минимальный по размеру видимый элемент, который может хоть что-то вывести на экран, имеет Size.X = Size.Y=1.
Объект TPoint крайне прост, он только определяет координаты некоторой точки на экране и не имеет никаких методов:
type
TPoint = object
X: Integer; {Горизонтальная координата}
Y: Integer; {Вертикальная координата}
end;
Координаты в Turbo Vision имеют две особенности. Во-первых, они указывают позицию на экране, начиная с 0, а не с 1, как это принято в стандартных для Турбо Паскаля подпрограммах работы с текстовым экраном (например, GotoXY из модуля CRT). Во-вторых, все координаты задаются относительно границ той группы видимых элементов, в которой создается и используется новый элемент. В Turbo Vision любойвидимый элемент входит в состав какой-то группы, поскольку все элементы в конечном счете принадлежат программе, которая сама по себе является группой.
Для указания всех четырех координат видимого элемента и действий над ними используется тип TRect следующего вида:
type
TRect = object
A: TPoint; {Левый верхний угол }
В: TPoint; {Правый нижний угол}
Procedure Assign(XA,YA,XB,YB: Integer);
{Назначает значения параметров полям А и В}
Procedure Copy(R: Trect);
{Устанавливает все поля, равными прямоугольнику R}
Procedure Move(ADX,ADY: Integer);
{Смещает прямоугольник, добавляя ADX к А.Х, В.Х и
ADY к А. Y, B.Y}
Procedure Grow(ADX,ADY: Integer);
{Изменяет размер, вычитая ADX из А.Х и прибавляя ADX к В.Х; вычитая ADY из A.Y и прибавляя ADY к B.Y}
Procedure Intersect(R: TRect);
{Изменяет положение и размер прямоугольника до области,
определенной пересечением R и текущего положения элемента}
Procedure Union(R: Trect);
{Изменяет прямоугольник до области, определяемой
объединением R и текущего положения элемента}
Function Contains(P: TPoint): Boolean;
{ Возвращает TRUE, если точка принадлежит элементу}
Function Equals(R: Trect): Boolean;
{Возвращает True, если положение и размеры прямоугольника R и элемента одинаковы}
Function Empty: Boolean;
{Возвращает TRUE, если элемент пустой, т.е. если его поля Size.X и Size,У. равны нулю}
end;
С помощью полей Owner^.Origin и Owner^.size видимый элемент может определить положение и размер своего владельца, т.е. той группы, в состав которой он входит, а с помощью метода
Procedure GetExtend(var R: Trect)
получить в R свои текущие координаты (напомню, что положение и размеры большинства видимых элементов могут меняться в ходе работы программы). Обычно обращение к GetExtend используется перед тем, как задать максимально возможные координаты вновь создаваемого видимого элемента. Например, если внутри окна TWindow нужно поместить скроллер так, чтобы он занял всю внутреннюю часть окна, можно использовать такой фрагмент:
type
MyWindow = object (TWindow)
.....
Constructor Init;
.....
end ;
Constructor MyWindow.Init;
var
S: PScroller{Указатель на скроллер}
R: TRect;
HS, VS: PScrollBar;{Указатели на полосы скроллера}
.....
begin
.....
GetExtend(R) ;{Получаем координаты окна}
R.Grow(-1, -1) ;{Уменьшаем их на 1}
S := New(PScroller,
Init(S, HS, VS)){Создаем скроллер}
Insert(S);{Помещаем скроллер в окно}
.....
end;
Видимые элементы
Территориальность
Вывод изображения
Заполнение области
Цвет изображения
Группы
Создание группы и изменение ее состава
Z-упорядочение и дерево видимых элементов
Активные элементы
Модальные видимые элементы
Изменение свойств элемента
Поле Options
Поле GrowMode
Поле DragMode
Поле State
Воздействие на состояние поля State
Любой видимый элемент Turbo Vision имеет два важнейших свойства: он полностью контролирует изображение в пределах выделенного ему участка экрана и знает, как обрабатывать связанные с этим участком события - нажатие на клавиши или отметку мышью. Эти свойства определяются двумя псевдоабстрактными методами объекта TView (этот объект является родителем всех остальных видимых элементов Turbo Vision): Draw и HandleEvent. Метод Draw знает, как рисовать объект, но не знает, когда это нужно делать. Метод HandleEvent, наоборот, знает когда, но не знает как. Эти методы в наибольшей степени воплощают основной принцип программ, управляемых событиями: процесс создания изображений и процесс обработки событий - это два разных процесса в том смысле, что в первом случае мы сообщаем программе как создается изображение, а во втором - когда это нужно делать. Обработке событий посвящена следующая глава.
В этой главе мы рассмотрим некоторые детали технической реализации видимых элементов, которые дадут нам возможность разобраться в том, что именно делает видимый элемент и как он это делает. Эти сведения помогут Вам правильно использовать видимые элементы в Вашей программе.
Воздействие на состояние поля State
В отличие от других полей поле State доступно только для чтения (поля Options, DragMode и GrowMode доступны также и для записи). Это означает, что в программе не может использоваться оператор вида
State := NewState;
Установить новое состояние этого поля можно только с помощью метода SetState, доступного любому видимому элементу. Метод SetState объявлен в Turbo Vision следующим образом:
type
TView = object (TObject)
Procedure SetState(AState:Word; Enable: Boolean); Virtual;
.....
end;
При обращении к методу параметр AState должен содержать маску разрядов поля State, а признак Enable указывает, должны ли устанавливаться эти разряды (Enable = True) или очищаться (Enable = False).
Отметим, что Turbo Vision вызывает метод SetState всякий раз, когда элемент активизируется или выбирается. Это дает возможность программе устанавливать новое состояние элемента или реагировать на действия пользователя (например, на активизацию видимого элемента). В отличие от этого поля Options, DragMode и GrowMode активизируются только при инициации элемента и далее обычно не меняются.
Видимые элементы часто перекрывают метод SetState, чтобы гибко реагировать на действия пользователя. Например кнопка (объект TButton) просматривает флаги поля State и изменяет свой цвет на бирюзовый, когда становится активной. В следующем примере объект TButton вначале проверяет, принадлежит ли он активному окну, чтобы решить, рисовать себя или нет. Затем он проверяет, выбрана ли кнопка в окне и, если это так, вызывает свой метод MakeDefault, чтобы установить или сбросить (в зависимости от Enable) свою активность по умолчанию:
Procedure TButton.SetState(AState: Word; Enable: Boolean);
begin
TView.SetState(AState, Enable);
if (AState and (sfSelected + sfActive)) <> 0 then
DrawView;
if (AState and sfFocused)<> 0 then
MakeDefault(Enable);
end;
Другой пример: допустим, что Ваша программа включает в себя текстовый редактор и Вы хотите разрешить или запретить все команды редактирования в полосе меню в зависимости от того, открыт редактор или нет. В этом случае Вы можете создать такой метод SetState для текстового редактора:
Procedure TEditor.SetState(AState: Word; Enable: Boolean);
const
EditorCommands = [cmSearch, cmReplace,cmSearchAgain,
cmGotoLine, cmFindProc,cmFindError];
begin
TView.SetState(AState, Enable);
if AState and sfActive <> 0 then
if Enable then EnableCommands(EditorCommands)
else DisableCommands(EditorCommands);
end;
Z-упорядочение и дерево видимых элементов
Последовательное присоединение подэлементов к группе приводит к так называемому Z-упорядочению видимых элементов. Z-упорядочение - это трехмерная модель видимых элементов, в которой координаты X и Y определяют положение элементов на экране, а координата Z - порядковый номер элемента в группе. Например, на рис. 17.5 показана Z-модель элементов окна с рамкой и скроллером.
В этой модели каждый элемент можно представить в виде стеклянной пластины, накладывающейся на уже существующее изображение. То изображение, которое мы видим на экране, есть проекция трехмерной модели на плоскость XY. Трехмерный образ позволяет «взглянуть» на видимые элементы со стороны и увидеть порядок, в котором они присоединяются к группе.
Важно помнить, что любая группа визуализирует свои подэлементы в порядке, определяемом их Z-упорядочением. Для рис. 17.5 это означает, что сначала создается изображение рамки, очерчивающей все окно, затем на рамку накладываются полосы скроллера, потом - сам скроллер с текстом, а накрывает сверху все изображение и скрепляет его в единое целое прозрачная пластина TWindow.
Для того чтобы группа нашла связанный с ней список подэлементов, используется поле First, входящее в любой видимый объект. Это поле содержит NIL, если данный элемент - терминальный видимый объект; если этот элемент - группа, поле First содержит указатель на первый вставленный в группу подэлемент, т.е. на самый «нижний» элемент в смысле Z-упорядочения. Таким образом, цепочка ссылок First - Next образует дерево видимых элементов, так как каждый элемент Next может быть новой группой и в этом случае его поле First <> NIL.
![](17_5.jpg)
Рис.17.5. Z-модель видимых элементов окна
Программа Turbo Vision всегда владеет строкой меню, строкой статуса и рабочей зоной экрана, а следовательно, имеет дерево видимых элементов, показанное на рис. 17.6.
![](17_6.jpg)
Рис. 17.6. Основное дерево видимых элементов TApplication
Отметим, что деревья видимых элементов показывают принадлежность элементов, а не их иерархию в генеалогии объектов Turbo Vision, т.е. связи на рис. 17.6 определяют функциональную подчиненность экземпляров объектов Application, MenuBar, Desktop и StatusLine.
Деревья видимых элементов динамически изменяются в ходе работы программы. Они могут расти, если к программе присоединяются новые группы, или уменьшаться, если эти группы уничтожаются. В отличие от этого генеалогическое дерево объектов может только расти за счет создания потомков.
Все ветви дерева видимых элементов всегда заканчиваются терминальными видимыми объектами.
Заполнение области
Видимый элемент может быть частично или полностью перекрыт другими видимыми элементами. Turbo Vision позволяет располагать окна на экране в произвольном порядке, в том числе и накладывая их друг на друга. С помощью метода
Procedure GetClipRect(var R: TRect)
видимый элемент может получить координаты минимальной площади, которую он должен заполнить в данный момент. Обращение к GetClipRect обычно используется в методе Draw и позволяет до минимума сократить время обновления информации на экране.
Следует помнить, что в правильно построенной программе никакой другой видимый элемент не должен вторгаться в область владения данного элемента. Поэтому, если видимый элемент не заполнит всю выделенную ему область, этого за него не сделает никто другой, и незаполненная область окажется «замусоренной» предыдущим выводом в эту часть экрана.
Для вывода на экран не рекомендуется использовать стандартную процедуру Write (WriteLn), т.к. она заполняет только ту часть экрана, которая необходима для вывода, в то время как длина строки в видимом элементе может быть больше строки вывода. С другой стороны, эта процедура игнорирует границы видимого элемента и может «залезть» в чужую область.
Вывод в Turbo Vision основан на применении методов MoveChar, MoveStr и WriteLine. Все три метода используют переменную типа TDrawBuffer в качестве буфера видеопамяти. Метод MoveChar заполняет буфер нужным символом, например, пробелом или символом Char (#176) - этим символом заполняется фон панели экрана. Метод MoveStr переносит в буфер строку (подстроку), а метод WriteLine переносит буфер в видеопамять и таким образом осуществляет собственно вывод на экран.
Тип TDrawBuffer представляет собой массив слов:
type
TDrawBuffer = array [0..MaxViewWidth-1] of Word;
Константа MaxViewWidth определена в интерфейсной части модуля Views и устанавливает максимально возможную длину вывода (132 символа). Элементы массива задают двухбайтные последовательности, используемые в видеопамяти ПК для размещения кода выводимог/о символа (младший байт) и его атрибутов (старший байт). Байт атрибутов определяет цвет выводимого символа и цвет фона, а также содержит признак мерцания (рис. 17.1).
![](17_1.jpg)
Рис. 17.1. Байт атрибутов видеопамяти
При обращении к методам MoveChar и MoveStr байт атрибутов задается в качестве одного из параметров обращения. Его можно получить с помощью функции GetColor, параметр обращения к которой определяет нужный номер элемента палитры.