Добрый день, при написании программы в Prologe появляется строчка getSpiral(_, _, _, _) = []. Но данный предикат нигде не описан и естественно программа выдае ошибку. Можно уточнить, что это за предикат и где и как его необходимо описать |
Приложение "Родственные отношения"
База данных
Создадим проект relatives (MDI). Подготовим текстовый файл так, как описано ниже, содержащий факты базы данных, и поместим его в папку Exe, а также bmp- и txt-файлы и разместим их в папках images и descriptions, которые должны быть созданы в директории Exe проекта (см. ниже).
Подготовка текстовых и bmp-файлов
Информация о членах семьи содержится во внутренней базе данных. Для ее хранения используется текстовый файл.
Создадим текстовый файл в директории Exe проекта, так чтобы он отображался в дереве проекта. Для этого выделим корень дерева проекта, с помощью команды (контекстного) меню New In New Package откроем диалоговое окно Create Project Item, выделим элемент Text File и заполним поля следующим образом:
- Name: family;
- Parent Directory: Exe.
Далее нужно нажать кнопку Create. В результате в директории Exe проекта будет создан файл family.txt. В него необходимо поместить факты базы данных, приведенные ниже.
clauses person(1, "Иван", "Петров", "м", 0, 0). person(2, "Анна", "Петрова", "ж", 0, 0). person(3, "Мария", "Иванова", "ж", 1, 2). person(4, "Павел", "Иванов", "м", 0, 3). person(5, "Петр", "Иванов", "м", 0, 3). person(6, "Елизавета", "Иванова", "ж", 0, 3). person(7, "Степан", "Иванов", "м", 5, 0). person(8, "Юлия", "Иванова", "ж", 0, 0). spouse(1, 2). spouse(4, 8). pict(1, "ivan1.bmp"). pict(1, "ivan2.bmp"). pict(2, "anna.bmp"). pict(3, "maria1.bmp"). pict(3, "maria2.bmp"). pict(6, "elizabeth.bmp"). descr(3, "maria.txt").Листинг 4.1. База данных. Файл family.txt
Предикат person/6 хранит идентификатор человека (порядковый номер), его имя, фамилию, пол и идентификторы родителей — отца и матери. Если сведения о каком-либо родителе отсутствуют, то вместо идентификатора указывается значение 0. Предикат spouse/2 хранит идентификаторы супругов — мужа и жены. Предикат pict/2 хранит идентификатор человека и название bmp-файла, содержащего его изображение. Для одного человека изображений может быть несколько (см. листинг 4.1 пример 4.1). Следует подготовить указанные bmp-файлы с изображениями, создать в директории Exe проекта папку images и поместить их в эту папку (рис. 4.1 рис. 4.1).
Предикат descr/2 используется для хранения сведений о жизнеописании человека (биографии). В фактах хранится идентификатор человека и имя текстового файла. В директории Exe проекта следует создать папку descriptions и поместить в него необходимые текстовые файлы (см. рис. 4.1 рис. 4.1).
Класс person
Для каждого человека из базы данных создается объект класса person, в котором размещается вся информация об этом человеке.
Создадим класс person с интерфейсом person. В интерфейсе person следует объявить свойства, а также предикаты legend и idLegend, которые используются для формирования надписей в списках и деревьях.
properties id : unsigned. name : string. surname : string. sex : string. idFather : unsigned. idMother : unsigned. children : unsigned*. spouses : unsigned*. pictures : string*. descriptions : string*. status : string. predicates legend: () -> string. idLegend: () -> string.Листинг 4.2. Объявление свойств и предикатов в интерфейсе person
В декларации класса person следует объявить конструктор.
constructors new: (unsigned Id).Листинг 4.3. Объявление конструктора в декларации класса person
Ниже приведено определение свойств и предикатов.
facts id : unsigned. name : string := "". surname : string := "". sex : string := "м". % dbrel::male idFather : unsigned := 0. idMother : unsigned := 0. children : unsigned* := []. spouses : unsigned* := []. pictures : string* := []. descriptions : string* := []. status : string := "просмотр". clauses new(Id):- id := Id. legend() = string::format("% %", name, surname). idLegend() = string::format("%2. %", id, legend()).Листинг 4.4. Определение предикатов в имплементации класса person
Взаимодействие с базой данных
Ниже создается класс dbrel, который обеспечивает взаимодействие с базой данных.
Создадим класс dbrel с интерфейсом dbrel. В интерфейсе dbrel следует объявить указанные ниже константы, свойства и предикаты.
constants images = "images". % назв. папки с изобр. descriptions = "descriptions". % назв. папки с заметками constants male = "м". % мужской пол female = "ж". % женский пол properties personList : person*. selectedPerson : optional{person}. predicates load: (). getPerson: (unsigned) -> person determ. getNewId: () -> unsigned.Листинг 4.5. Объявления в интерфейсе dbrel
Свойство personList используется для хранения указателей на объекты класса person. Свойство selectedPerson используется для хранения указателя на объект выделенного члена семьи. Домен optional{T} определен в классе core следующим образом:
domains optional{T} = none(); some(T).
Предикат load загружает базу данных, предикат getPerson/1 по идентификатору возвращает указатель на объект класса person. Предикат getNewId возвращает идентификатор для нового человека, сведения о котором будут записываться в базу данных.
Следующее объявление свойства и предикатов необходимо поместить в декларацию класса dbrel.
properties db : optional{dbrel}. % указатель на объект текущей БД constructors new: (string FileName). predicates imageFolder: () -> string. descriptionsFolder: () -> string.Листинг 4.6. Объявления в декларации класса dbrel
Свойство db используется для хранения указателя на объект текущей базы данных. Приложение предоставляет возможность одновременной работы с несколькими семьями. Конструктор new/1 создает по имени файла объект базы данных. Предикаты imageFolder и descriptionsFolder возвращают путь к папкам, содержащим изображения и описания.
В имплементации класса dbrel следует объявить базу данных, а также определить объявленные выше свойства и предикаты.
В раздел open имплементации класса следует добавить имя класса stdio.
open core, stdio facts filename : string. personList : person* := []. selectedPerson : optional{person} := none(). maxId : unsigned := 0. clauses new(FileName):- filename := FileName. class facts db : optional{dbrel} := none(). facts - rel person: (unsigned, string, string, string, unsigned, unsigned). spouse: (unsigned IdHusband, unsigned IdWife). pict: (unsigned Id, string BmpFileName). descr: (unsigned Id, string TextFileName). clauses load():- try file::consult(filename, rel) catch Error do writef("Error %. Unable to load the database from %\n", Error, filename) end try, setMaxId(), loadDb(). predicates setMaxId: (). clauses setMaxId():- IdList = [Id || person(Id, _, _, _, _, _)], IdList <> [], !, maxId := list::maximum(IdList). setMaxId(). predicates loadDb: (). clauses loadDb():- person(Id, Name, Surname, Sex, IdFather, IdMother), Person = person::new(Id), Person:name := Name, Person:surname := Surname, Person:sex := Sex, Person:idFather := IdFather, Person:idMother := IdMother, Person:children := getChildren(Id, Person:sex), Person:spouses := [I || spouse(Id, I); spouse(I, Id)], Person:pictures := [BmpFile || pict(Id, BmpFile)], Person:descriptions := [TxtFile || descr(Id, TxtFile)], personList := [Person | personList], fail. loadDb(). predicates getChildren: (unsigned Id, string Sex) -> unsigned* ChildrenList. clauses getChildren(Id, male) = [I || person(I, _, _, _, Id, _)]:- !. getChildren(Id, _) = [I || person(I, _, _, _, _, Id)]. clauses getPerson(Id) = Person:- Person in personList, Person:id = Id, !. clauses getNewId() = maxId:- maxId := maxId + 1. clauses imageFolder() = folder(images). descriptionsFolder() = folder(descriptions). class predicates folder: (string Name) -> string Path. clauses folder(Name) = string::concat(Path, Folder):- mainExe::getFileName(Path, _), Folder = string::format(@"%\", Name).Листинг 4.7. Определение в имплементации класса dbrel
Предикат loadDb для каждого члена семьи, информация о котором помещена в базу данных, создает объект класса person и записывает в него все сведения, имеющиеся о нем в базе данных. Предикат getFileName/2 возвращает путь к exe-файлу проекта.
Форма просмотра общих сведений
Ниже создается окно просмотра общих сведений. Окно содержит список, в котором пересчисляются члены семьи, поле для просмотра изображений и ряд кнопок (рис. 4.2 рис. 4.2).
Замечание.Пользователи версии Visual Prolog 7.x Commercial Edition могут не создавать окно pictControl, а вместо него использовать imageControl. Ниже показано, как это можно сделать.
Предварительно создадим окно pictControl, которое будет использоваться для отображения изображений (см. ниже).
Создание поля для просмотра изображений
Выделим корень дерева проекта, с помощью команды меню New in New Package откроем диалоговое окно Create Project Item, выделим в нем элемент Draw Control, в поле Name напишем название pictControl и нажмем кнопку Create. Затем закроем редактор окна.
В интерфейсе pictControl следует объявить предикаты drawPict/1 и clear. Первый предикат показывает изображение, второй очищает поле.
predicates drawPict: (string FileName). clear: ().Листинг 4.8. Объявление предикатов в интерфейсе pictControl
Определение объявленных предикатов в имплементации класса pictControl приведено ниже.
facts pict : picture := erroneous. pictRct : rct := erroneous. class facts % база загруженных изображений picture: (string FileName, picture, rct). clauses drawPict(FileName):- picture(FileName, Picture, PictRect), !, pict := Picture, pictRct := PictRect, invalidate(). drawPict(FileName):- Picture = loadPicture(FileName), !, pict := Picture, vpi::pictGetSize(Picture, PictWidth, PictHeight, _), pictRct := rct(0, 0, PictWidth, PictHeight), assert(picture(FileName, pict, pictRct)), invalidate(). drawPict(_FileName):- clear(). predicates loadPicture: (string FileName) -> picture determ. clauses loadPicture(FileName) = Picture:- FullName = string::concat(dbrel::imageFolder(), FileName), try Picture = vpi::pictLoad(FullName) catch Error do stdio::writef("Error %. Unable to load the picture from %\n", Error, FullName), fail end try. clauses clear():- pict := erroneous, invalidate().Листинг 4.9. Построение изображения
Далее для окна pictControl следует добавить обработчики событий PaintResponder, SizeListener и EraseBackgroundResponder.
Предикат onPaint либо отображает изображение, либо выводит надпись "No picture".
clauses onPaint(_Source, Rectangle, GDI):- not(isErroneous(pict)), !, GDI:pictDraw(pict, Rectangle, pictRct, rop_SrcCopy). onPaint(_Source, Rectangle, GDI):- GDI:clear(color_MediumTurquoise), GDI:drawTextInRect(Rectangle, "NO PICTURE", [dtext_center, dtext_singleline, dtext_vcenter]).Листинг 4.10. Определение предиката onPaint
Предикаты onSize и onEraseBackground определяются так же, как и ранее.
clauses onSize(_Source):- invalidate().Листинг 4.11. Определение предиката onSize
clauses onEraseBackground(_Source, _GDI) = noEraseBackground.Листинг 4.12. Определение предиката onEraseBackground
Создание основной формы
Создадим форму familyForm. В редакторе формы удалим из нее кнопки Cancel и Help. Ниже на форму добавляется ряд элементов управления и устанавливаются свойства для них с помощью окна свойств. Элемент управления следует выбрать на панели инструментов окна Controls и "перенести" на форму, а затем в таблице свойств изменить значения свойств, указанные ниже.
При выделении элемента управления в списке элементов, расположенном в верхней части окна свойств, или непосредственно на прототипе формы, в таблице свойств отображается список свойств этого элемента управления. Если на форме не выделен ни один из элементов управления или если в списке выделен элемент Form, то в таблице свойств отображаются свойства формы. На рис. 4.3 рис. 4.3 в верхнем списке окна свойств выделен элемент Form, поэтому в таблице свойств отображаются свойства окна.
С помощью панели инструментов Controls добавим следующие элементы управления (см. рис. 4.3 рис. 4.3):
cписок (List Box), свойству UseTabStop установим значение True;
надпись (Static Text), для которой установим следующие свойства:
Representation: Fact Variable; Name: status_ctl; Text:<пустое поле>
пользовательский элемент управления (Custom Control); в окне Choose Class Name for Custom Control, которое вызывается автоматически (рис. 4.4 рис. 4.4), выберем имя класса pictControl (пользователи Commercial Edition вместо него могут указать imageControl), затем установим свойства:
Right Anchor: True; Bottom Anchor: True;
пять кнопок (Push Button)
Name: view_ctl; Text: Открыть; Enabled: False; Name: new_ctl; Text: Добавить; Name: save_ctl; Text: Сохранить; Enabled: False; Name: del_ctl; Text: Удалить; Enabled: False; Name: table_ctl; Text: Таблица
Для всех новых кнопок следует установить следующие свойства:
Left Anchor: False; Top Anchor: False; Right Anchor: True; Bottom Anchor: True.
После этого следует закрыть редактор формы.
Свойство Anchor (якорь) определяет, привязан ли элемент к краю контейнера (формы). Если его значение равно True, то изменение размеров формы не влияет на расстояние от элемента до границы формы. В окне familyForm кнопки привязываются к правой и нижней границам формы, расстояние от них до указанных границ при изменении размеров формы остается неизменным. Элемент pictControl привязан ко всем границам формы, поэтому при изменении размеров формы будет изменяться и его размер.
Далее в интерфейсе familyForm необходимо объявить свойство, которое используется для хранения указателя на объект базы данных.
properties db : dbrel.Листинг 4.13. Объявление свойства в интерфейсе familyForm
В декларации класса familyForm следует объявить свойство, которое используется для !указателя на объект текущей формы (оно необходимо при одновременной работе с несколькими формами класса familyForm).
properties familyForm : familyForm.Листинг 4.14. Объявление свойства в декларации класса familyForm
В имплементации класса familyForm определим объявленные свойства и добавим предикаты legend и getSelectedPerson. Предикат legend/1 используется для формирования элемента списка. Предикат getSelectedPerson/1 для выделенного элемента списка возвращает указатель на объект класса person.
facts db : dbrel := erroneous. class facts familyForm : familyForm := erroneous. predicates legend: (person) -> string. clauses legend(Person) = string::format("%2. %-12\t%", Person:id, Person:name, Person:surname). predicates getSelectedPerson: () -> person determ. clauses getSelectedPerson() = db:getPerson(Id):- Index = listbox_ctl:tryGetSelectedIndex(), Item = listbox_ctl:getAt(Index), string::frontToken(Item, Tok, _), Id = tryToTerm(unsigned, Tok).Листинг 4.15. Определение в имплементации класса familyForm
Предикат tryGetSelectedIndex возвращает номер выделенного элемента списка (элементы нумеруются с нуля), предикат getAt/1 возвращает по номеру элемент списка.
Теперь в редакторе формы familyForm добавим обработчик событий ShowListener. При открытии окна формируется список членов семьи.
clauses onShow(_Source, _Data):- some(Db) = dbrel::db, !, db := Db, listbox_ctl:setTabStops([22]), listbox_ctl:addList([legend(Pers) || Pers in db:personList]). onShow(_Source, _Data).Листинг 4.16. Определение предиката onShow
Далее добавим обработчик события выделения элемента списка SelectionChangedListener. Для этого в редакторе формы нужно выделить список, перейти на вкладку Events окна свойств и выбрать для обработчика события SelectionChangedListener элемент onListBoxSelectionChanged.
При выделении элемента списка (члена семьи) находится указатель на объект класса person, появляется изображение (или удаляется предыдущее), отображается статус — "просмотр" или "добавление". Статус "просмотр" имеют члены семьи, сведения о которых уже записаны в базу данных, остальные персоны получают статус "добавление" (см. главу 6) "Деревья. Сводная таблица" . Кроме этого, включаются (т. е. делаются доступными) кнопки "Открыть" и "Удалить", а также включается или выключается кнопка "Сохранить", в зависимости от статуса члена семьи ("просмотр" или "добавление"). Ниже приведено определение предиката.
predicates onListboxSelectionChanged : listControl::selectionChangedListener. clauses onListboxSelectionChanged(_Source):- Person = getSelectedPerson(), !, if [FileName | _] = Person:pictures then pictControl_ctl:drawPict(FileName) else pictControl_ctl:clear() end if, status_ctl:setText(Person:status), view_ctl:setEnabled(true), del_ctl:setEnabled(true), save_ctl:setEnabled(toBoolean(Person:status <> "просмотр")). onListboxSelectionChanged(_Source).Листинг 4.17. Определение предиката onListBoxSelectionChanged
Замечание. Пользователи Commercial Edition, использующие imageControl, должны заменить строку pictControl_ctl:drawPict(FileName) следующим кодом:
FullName = string::concat(dbrel::imageFolder(), FileName), imageControl_ctl:setImageFile(FullName)
Вместо pictControl_ctl:clear() следует написать
imageControl_ctl:setNoImage().
Сделаем включенным пункт меню File -> New главного окна приложения (см. п. "Основные элементы графического интерфейса пользователя" ), добавим обработчик события вызова данной команды меню (см. п. 1.1.3 "Основные элементы графического интерфейса пользователя" ) и определим его так, как показано ниже.
clauses onFileNew(_Source, _MenuTag):- mainExe::getFileName(StartPath, _Name), FileName = vpiCommonDialogs::getFileName( "*.txt", ["Текстовый файл", "*.txt"], "Открыть базу данных", [], StartPath, _), !, Db = dbrel::new(FileName), Db:load(), dbrel::db := some(Db), Form = familyForm::display(This), familyForm::familyForm := Form, Form:setText(fileName::getName(FileName)). onFileNew(_Source, _MenuTag).Листинг 4.18. Определение предиката onFileNew
При выборе пункта меню File -> New открывается окно "Открыть базу данных" (рис. 4.5 рис. 4.5).
В нем пользователь должен выбрать текстовый файл, содержащий базу данных. После этого загружается база данных и открывается окно familyForm, в строку заголовка которого помещается имя файла. Кроме этого, запоминаются указатели на объекты базы данных и формы.
Упражнения
4.1. Подготовьте еще одну базу данных с информацией о какой-либо семье или династии. Текстовый файл, содержащий факты базы данных, поместите в директорию Exe проекта.
4.2. Создайте bmp-файлы с изображениями членов семьи (см. упр. 4.1) и текстовые файлы, содержащие их жизнеописания. Поместите их в папки Exe\images и Exe\descriptions, соответственно.