Добрый день, при написании программы в Prologe появляется строчка getSpiral(_, _, _, _) = []. Но данный предикат нигде не описан и естественно программа выдае ошибку. Можно уточнить, что это за предикат и где и как его необходимо описать |
Добавление и удаление сведений
Режим добавления
Ниже новыми называются члены семьи, информация о которых в базе данных отсутствует, старыми — для которых она присутствует.
Для того чтобы добавить нового члена семьи в базу данных, необходимо нажать на кнопку "Добавить" окна familyForm. В результате открывается форма tabForm. В режиме добавления она содержит две страницы вкладок (рис. 7.1 рис. 7.1).
Добавим обработчик события нажатия на кнопку "Добавить" окна familyForm. Ниже приведено его определение.
clauses onNewClick(_Source) = button::defaultAction:- db:selectedPerson := core::none(), familyForm := This, Win = tabForm::display(getParent()), Win:setText("Добавление"), if Index = listbox_ctl:tryGetSelectedIndex() then listbox_ctl:selectAt(Index, false) end if.Листинг 7.1. Предикат onNewClick
Начальное состояние страницы информации
При открытии формы tabForm выпадающие списки "Отец" и "Мать", а также список "Супруги" заполняются списками имен мужского и женского пола из базы данных (см. рис. 7.1 рис. 7.1). Кроме этого, становятся видимыми кнопки, которые не используются в режиме просмотра. Это кнопки служат для добавления или удаления изображений.
Определим предикат getList/1, который формирует список мужчин или женщин, предикат fillList/2, который заполняет выпадающий список списком элементов и выделяет первый элемент, а также предикат getSex/1, который возращает пол по состоянию переключателя "женский". В список родителей добавляется пустой элемент, который используется в случае, когда сведения о родителе отсутствуют в базе данных.
constants emptyItem = "< >". predicates getList: (string Sex) -> string* ItemList. clauses getList(Sex) = [Person:idLegend() || Person in db:personList, Person:sex = Sex]. predicates fillList: (string* ItemList, listButton). clauses fillList(ItemList, ListCtl):- ListCtl:addList(ItemList), ListCtl:addAt(list::length(ItemList), emptyItem), ListCtl:selectAt(0, true). predicates getSex: (radioButton::state Female) -> string Sex. clauses getSex(radioButton::checked) = dbrel::female:- !. getSex(_) = dbrel::male.Листинг 7.2. Формирование списка мужчин или женщин
Изменим определение предиката onShow страницы infoPage так, как показано ниже.
clauses onShow(_Source, _Data):- % режим просмотра some(Person) = db:selectedPerson, !, name_ctl:setText(Person:name), surname_ctl:setText(Person:surname), setSex(Person:sex), setParent(Person:idfather, father_ctl), setParent(Person:idmother, mother_ctl), setSpouses(Person), setPictures(Person), setEnable(Person:status). onShow(_Source, _Data):- % режим добавления FemaleList = getList(dbrel::female), fillList(FemaleList, mother_ctl), MaleList = getList(dbrel::male), fillList(MaleList, father_ctl), spouses_ctl:clearAll(), spouses_ctl:addList(FemaleList), setEnable("добавление").Листинг 7.3. Определение предиката onShow
Изменение состояния переключателя
Если новый член семьи — женщина, то в потенциальный список супругов помещается список мужчин, а если мужчина — то женщин. В список супругов заносятся идентификаторы выделенных элементов потенциального списка супругов.
Добавим обработчик событий StateChangedListener для переключателя "мужской".
Ниже приведено определение предиката onMaleStateChanged. Список супругов обновляется при изменении состояния переключателя.
predicates onMaleStateChanged : radioButton::stateChangedListener. clauses onMaleStateChanged(_Source, _OldState, NewState):- spouses_ctl:clearAll(), spouses_ctl:addList(getList(getSex(NewState))).Листинг 7.4. Изменение состояния переключателя
Добавление и удаление изображений
Для нового члена семьи можно записать в базу данных сразу несколько изображений. Эти изображения должны быть заранее подготовлены. Файлы, содержащие изображения, копируются в папку images, которая находится в директории Exe, если они не были размещены в ней заранее. Файлы, расположенные в одной и той же папке, могут добавляться списком. Добавление изображений производится с помощью кнопки "…". Для удаления изображения предназначена кнопка "-". Добавим обработчики событий нажатия на эти кнопки.
Ниже определяются предикаты onBrowseClick и getFileList/3. При нажатия кнопки "…" откроется окно "Открыть файл", с помощью которого можно выбрать сразу несколько файлов (изначально открывается папка images), используя клавиши Shift или Ctrl.
clauses onBrowseClick(_Source) = button::defaultAction:- StartPath = dbrel::imageFolder(), FileName = vpiCommonDialogs::getFileName( "*.bmp", ["Файл bmp", "*.bmp"], "Выберите файлы", [dlgfn_multisel], StartPath, FileNameList), !, Path = fileName::getPath(FileName), FileList = getFileList(FileNameList, Path, StartPath), pictures := list::union(pictures, FileList), n := list::length(pictures), k := 0, pictures == [FirstFile | _], pictControl_ctl:drawPict(FirstFile), del_ctl:setEnabled(true), right_ctl:setEnabled(toBoolean(n > 1)), left_ctl:setEnabled(false). onBrowseClick(_Source) = button::defaultAction.Листинг 7.5. Добавление изображений
predicates getFileList: (string*, string Path, string StartPath) -> string*. clauses getFileList(FileNameList, Path, StartPath) = [FileName || FullName in FileNameList, FileName = filename::getNameWithExtension(FullName), if not(string::equalIgnoreCase(Path, StartPath)), ImageFile = fileName::setPath(FullName, StartPath), not(file::existFile(ImageFile)) then file::copy(FullName, ImageFile) end if].Листинг 7.6. Копирование изображений
При нажатии кнопки "-" текущее изображение, которое отображается в окне, удаляется из списка изображений. Ниже определяется предикат onDelClick.
clauses onDelClick(_Source) = button::defaultAction:- n > 0, pictures := list::remove(pictures, list::nth(k, pictures)), !, n := n - 1, k := if n > 0, k < n then k elseif n > 0 then n - 1 else 0 end if, if n > 0 then pictControl_ctl:drawPict(list::nth(k, pictures)) else pictControl_ctl:clear() end if, right_ctl:setEnabled(toBoolean((n > 1, k < n - 1))), left_ctl:setEnabled(toBoolean((n > 1, k > 0))), del_ctl:setEnabled(toBoolean(n > 0)). onDelClick(_Source) = button::defaultAction.Листинг 7.7. Удаление изображения
Добавление описания
Для любого члена семьи, нового или старого, в базу данных можно добавить заметку (жизнеописание). Для этого нужно ввести текст на странице descrPage в текстовое поле или вставить его из файла. После этого текст необходимо сохранить в текстовом файле в папке descriptions, с помощью нажатия на кнопку "Сохранить". Затем нужно нажать на кнопку "Записать". Позднее потребуется сохранить изменения в базе данных (см. ниже).
В редакторе страницы descrPage добавим обработчик события нажатия на кнопку "Записать". Ниже определяется предикат onSaveInDbClick.
clauses onSaveInDbClick(_Source) = button::defaultAction:- some(Person) = db:selectedPerson, filename <> "", Text = string::trim(sciLexer_ctl:text), Text <> "", FileName = fileName::getNameWithExtension(filename), not((P in db:personList, FileName in P:descriptions)), !, file::writeString(filename, Text, isUnicode), Person:descriptions := [Filename | Person:descriptions], db:addDescription(Person, Filename), stdio::write("Описание добавлено в базу данных.\n"). onSaveInDbClick(_Source) = button::defaultAction.Листинг 7.8. Сохранение описания в базе данных
Предикат addDescription/2 необходимо объявить в интерфейсе dbrel и определить в имплементации класса dbrel.
predicates addDescription: (person, string). Листинг 7.10. Определение предиката в имплементации класса dbrel clauses addDescription(Person, FileName):- Person:status <> "добавление", not(descr(Person:id, Filename)), !, assert(descr(Person:id, Filename)). addDescription(_, _).Листинг 7.9. Объявление предиката в интерфейсе dbrel
Добавление сведений
Сведения о новом члене семьи, помещенные на страницу информации окна tabForm, добавляются при нажатии кнопки Ok формы tabForm (рис. 7.2 рис. 7.2).
Добавим обработчик события нажатия на кнопку Ok. Его определение приведено ниже.
clauses onOkClick(_Source) = button::defaultAction:- Person = infoPage:getNewData(), !, familyForm:addPerson(Person). onOkClick(_Source) = button::defaultAction.Листинг 7.11. Определение предиката onOkClick
После нажатия на кнопку Ok, создается объект класса person, в который записывается информация, считанная с первой страницы вкладок. Затем имя нового члена семьи сразу отображается в списке окна familyForm.
В интерфейс infoPage cледует добавить объявление предиката getNewData.
predicates getNewData: () -> person determ.Листинг 7.12. Интерфейс infoPage
Ниже приведено определение этого предиката. Новый объект класса person создается только в том случае, если заполнены поля "Имя" и "Фамилия".
clauses getNewData() = Person:- Name = getName(name_ctl), Surname = getName(surname_ctl), !, Id = db:getNewId(), Person = person::new(Id), Person:name := Name, Person:surname := Surname, Person:sex := getSex(), Person:idFather := getParent(father_ctl, Id), Person:idMother := getParent(mother_ctl, Id), Person:spouses := getSpouses(), Person:pictures := pictures, Person:status := "добавление", db:personList := [Person | db:personList]. getNewData() = _:- vpiCommonDialogs::error("Поля 'Имя' и 'Фамилия' " "обязательны для заполнения"), fail. predicates getName: (editControl) -> string determ. clauses getName(Ctl) = Name:- Name = string::trim(Ctl:getText()), Name <> "". predicates getId: (string Item) -> unsigned Id determ. clauses getId(Item) = Id:- string::frontToken(Item, Tok, _), Id = tryToTerm(unsigned, Tok). predicates getSex: () -> string. clauses getSex() = getSex(female_ctl:getRadioState()). predicates getParent: (listButton, unsigned IdPerson) -> unsigned IdParent. clauses getParent(Ctl, Id) = IdParent:- Index = Ctl:tryGetSelectedIndex(), Item = Ctl:getAt(Index), IdParent = getId(Item), Parent = db:getPerson(IdParent), !, Parent:children := [Id | Parent:children]. getParent(_Ctl, _Id) = 0. predicates getSpouses: () -> unsigned* IdList. clauses getSpouses() = [Id || Item in L, Id = getId(Item)]:- L = spouses_ctl:getSelectedItems().Листинг 7.13. Предикаты считывания данных
Объявим предикат addPerson в интерфейсе familyForm.
predicates addPerson: (person).Листинг 7.14. Объявление предиката в интерфейсе familyForm
Ниже приведено определение предиката addPerson в имплементации класса familyForm.
clauses addPerson(Person):- listbox_ctl:addAt(-1, legend(Person)).Листинг 7.15. Определение в имплементации класса familyForm
Первым аргументом предиката addAt/2 является индекс элемента списка. Если вместо него указывается значение (– 1), то элемент либо добавляется в конец списка, в случае если его элементы не сортируются автоматически (автоматическая сортировка устанавливается по умолчанию — см. таблицу свойств для списка), либо он вставляется в список так, чтобы тот оставался упорядоченным.
Запись в базу данных. Удаление сведений
Информация о новом члене семьи не записывается в базу данных автоматически. Операции записи и удаления этой информации выполняются с помощью кнопок окна familyForm.
Запись и удаление информации
Для того чтобы записать сведения о члене семьи в базу данных, следует выделить его имя в списке окна familyForm и затем нажать на кнопку "Сохранить". Если он имел статус "добавление", то информация о нем запишется в базу данных. Удаление информации о члене семьи, как уже записанной в базу данных, так и еще не записанной в нее, выполняется при нажатии на кнопку "Удалить". Добавим обработчики событий нажатия на эти кнопки.
Ниже приведено определение предиката onSaveClick.
clauses onSaveClick(_Source) = button::defaultAction:- Person = getSelectedPerson(), "добавление" = Person:status, !, db:savePerson(Person), status_ctl:setText(Person:status). onSaveClick(_Source) = button::defaultAction.Листинг 7.16. Сохранение сведений о новой персоне
Далее определяется предикат onDelClick.
clauses onDelClick(_Source) = button::defaultAction:- Person = getSelectedPerson(), db:deletePerson(Person), Index = listbox_ctl:tryGetSelectedIndex(), !, listbox_ctl:delete(Index), status_ctl:setText(""), pictControl_ctl:clear(), view_ctl:setEnabled(false), del_ctl:setEnabled(false). onDelClick(_Source) = button::defaultAction.Листинг 7.17. Удаление персоны из базы данных
Предикаты savePerson/1 и deletePerson/1 следует объявить в интерфейсе dbrel.
predicates savePerson: (person). deletePerson: (person).Листинг 7.18. Объявление предикатов добавления и удаления персоны
Ниже приведено определение этих предикатов в имплементации класса dbrel. Предикат savePerson записывает информацию в базу данных. Предикат deletePerson удаляет информацию из базы данных, а также из объектов класса person.
clauses savePerson(Person):- assert(person(Person:id, Person:name, Person:surname, Person:sex, Person:idfather, Person:idmother)), list::forAll(Person:spouses, { (I):- getSpouses(Person, I, IdH, IdW), assert(spouse(IdH, IdW))}), list::forAll(Person:pictures, {(BmpFile):- assert(pict(Person:id, BmpFile))}), list::forAll(Person:descriptions, {(TxtFile):- assert(descr(Person:id, TxtFile))}), Person:status := "просмотр". predicates getSpouses: (person, unsigned, unsigned [out], unsigned [out]). clauses getSpouses(Person, IdW, Person:id, IdW):- male = Person:sex, !. getSpouses(Person, IdH, IdH, Person:id). clauses deletePerson(Person):- removeFromDb(Person), removeFromPersonList(Person). predicates removeFromDb: (person). clauses removeFromDb(Person):- "добавление" = Person:status, !. removeFromDb(Person):- retractAll(person(Person:id, _, _, _, _, _)), retractAll(spouse(Person:id, _)), retractAll(spouse(_, Person:id)), retractAll(pict(Person:id, _)), retractAll(descr(Person:id, _)). predicates removeFromPersonList: (person). clauses removeFromPersonList(Person):- personList := list::remove(personList, Person), P in personList, P:spouses:= list::remove(P:spouses, Person:id), P:children := list::remove(P:children, Person:id), if P:idfather = Person:id then P:idfather := 0 end if, if P:idmother = Person:id then P:idmother := 0 end if, fail. removeFromPersonList(_Person).Листинг 7.19. Определение предикатов добавления и удаления
Сохранение изменений в базе данных
Текущее состояние базы данных не сохраняется автоматически в файле. Для этого пользователь должен ответить положительно на вопрос программы о сохранении изменений в базе данных (см. ниже). Добавим для формы familyForm обработчик событий DestroyListener. Его определение приведено ниже.
predicates onDestroy : window::destroyListener. clauses onDestroy(_Source):- 0 = vpiCommonDialogs::ask("Сохранение изменений", "Сохранить изменения в базе данных?", ["Да", "Нет"]), !, db:copy(), db:save(), stdio::writef("Изменения сохранены в базе данных.\n"). onDestroy(_Source).Листинг 7.20. Определение предиката onDestroy
После закрытия окна familyForm появляется диалоговое окно Ask (рис. 7.3 рис. 7.3).
Если пользователь нажимает кнопку "Да", то создается новый файл, в который копируется содержимое файла, хранящего факты базы данных до ее загрузки. Затем новое состояние базы данных сохраняется в старом файле. Предикат ask/3 возвращает номер выбранной кнопки. Кнопка "Да" имеет номер 0, кнопка "Нет" — номер 1 (см. код).
Предикаты copy и save следует объявить в интерфейсе dbrel.
predicates copy: (). save: ().Листинг 7.21. Объявление предикатов сохранения базы данных
Ниже приведено определение предикатов в имплементации класса dbrel.
clauses copy():- Time = time::new(), S = Time:formatDateTime("ddMMyy", "HHmmss"), Name = fileName::getName(filename), NewName = string::concat(Name, S), NewFileName = fileName::setExtension(NewName, "dbp"), file::copy(filename, NewFileName). save():- file::save(filename, rel).Листинг 7.22. Определение предикатов сохранения базы данных
Имя файла, в который копируется старое состояние базы данных, генерируется автоматически с помощью приписывания к имени файла, в котором хранится база данных, текущих даты и времени. Ему присваивается расширение dbp.
Упражнения
7.1. Добавьте проверку корректности перед добавлением родителей и супругов (они не должны быть родственниками новому члену семьи в текущем состоянию базы данных, родители не должны быть моложе детей и т. д.).
7.2. Добавьте режим редактирования сведений о персоне. Страница информации формы tabForm должна открываться так же, как в режиме добавления, но поля, содержащие имя, фамилию, пол и изображения должны быть уже заполнены. В списках родителей должны быть выделены строки с именами родителей персоны или пустые строки, если сведения о родителях отсутствуют в базу данных. В списке супругов должны быть выделены имена супругов. После нажатия кнопки Ok, обновленная информация о персоне должна быть записана в объект класса person.
7.3. Измените определение обработчика событий нажатия на кнопку "Сохранить" так, чтобы в режиме редактирования персоны (см. упр. 7.2) из базы данных была удалена старая информация об этом человеке и была записана новая информация. Добавьте соответствующие предикаты в класс dbrel.