Использование коллекций


Для вывода текста мы использовали глобальный массив Lines. Как известно, длина любого массива в Турбо Паскале не может превышать длину сегмента данных (64 Кбайт). Это ограничение можно убрать, если воспользоваться еще одним механизмом Turbo Vision - коллекциями. Подобно массивам, коллекции представляют собой набор элементов, в которых можно хранить любые данные, включая экземпляры любых объектов. К элементам коллекции можно обращаться по индексу, однако, в отличие от массива, коллекция размещается в куче, поэтому ее суммарная длина ограничивается всей доступной памятью и может быть больше 64 Кбайт. Кроме того, размер коллекции не лимитируется при ее создании и может динамически изменяться в процессе работы программы.
Коллекции обладают целым рядом новых свойств. В частности, к любой коллекции можно применить метод 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); {Удаляем коллекцию}
и запустите программу. Останов в контрольной точке произойдет только в том случае, если Вы загрузите окно с текстом и попытаетесь выйти из программы. Если из программы выйти сразу после ее запуска, контрольная точка не сработает.
Вид экрана с окном просмотра отсортированного файла показан на  9.
Указатель на элемент списка
Как уже отмечалось, с помощью процедуры 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: она поддерживает командные клавиши, работу с мышью, может сменить каталог или диск, выбрать нужный файл и загрузить его в окно просмотра. Не так плохо для 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
в окно вставляется поясняющий текст (см.  10).
Этот текст не связан с диалогом и называется статическим. Для вставки статической строки в любой видимый элемент используется конструктор 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}
Если Вы внесете в программу все описанные изменения и запустите ее на счет , то при попытке выйти из режима просмотра на экране будет развернуто диалоговое окно, показанное на  10. «Нажатие» на любую кнопку этого окна не приводит ни к каким последствиям - наше окно пока откликается только на стандартную команду cmClose, связанную с клавишей Esc.
Файл с данными DataType пока еще не существует. Чтобы программа смогла нормально работать, в диалоговом окне открытия файла укажите произвольное имя, например MYDATA. После завершения работы программы будет создан пустой файл MYDATA.DAT.
Обработка команд пользователя
Обработчик событий диалогового окна поддерживает только стандартные команды 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(...));
и вновь запустите программу: теперь нажатие на любую кнопку диалогового окна приведет к его закрытию.
 
Редактирование и добавление записей
Для редактирования и добавления записей создадим окно, показанное на  11.
Режим редактирования отличается от режима добавления записей двумя обстоятельствами: во-первых, в режиме редактирования поля ввода данных окна должны содержать текст, взятый из редактируемой записи, а в режиме ввода эти поля пусты. Во-вторых, режим редактирования завершается сразу после нажатия на клавишу Enter, в то время как в режиме ввода нажатие на эту клавишу означает добавление к файлу текущей записи и переход к вводу следующей: режим ввода завершается командой cmClose (клавиша Esc). С учетом этого оба режима реализуются в рамках одной процедуры AddItem (Edit), а параметр Edit указывает нужный режим: если Edit = True, реализуется режим редактирования, если False - режим добавления записей. Вот текст этой процедуры:
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.
  
Удаление записи
При реализации режима удаления записи нам нужно учесть тот факт, что порядок следования записей в файле и коллекции может быть различным. Поэтому в процедуре 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}
 
Режим поиска записи
Для поиска нужной записи сформируем диалоговое окно, показанное на  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 механизмов объектно-ориентированного программирования.
Наша программа, насчитывающая всего около 600 строк, обеспечивает весьма высокий уровень диалога с пользователем: в ней используются командные клавиши, «выпадающие» меню, удобные диалоговые окна, поддержка мыши. Думаю, что вряд ли каким-либо другим способом мы смогли бы создать столь сложную диалоговую среду программой такого объема. Таким образом, Turbo Vision является превосходным инструментом для разработки диалоговых программ, ориентированных на текстовый режим работы экрана. Использование этой среды в Ваших программах резко сократит сроки их создания и повысит качество.
Разумеется, созданная программа далека от совершенства, однако даже в этом виде она, как показывает мой собственный опыт, может быть достаточно полезной. При желании ее можно взять за основу создания более сложной информационно-поисковой системы.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика