Добрый день, при написании программы в Prologe появляется строчка getSpiral(_, _, _, _) = []. Но данный предикат нигде не описан и естественно программа выдае ошибку. Можно уточнить, что это за предикат и где и как его необходимо описать |
Деревья. Сводная таблица
Отображение деревьев
Для создания деревьев можно использовать два подхода — строить дерево в виде терма или использовать объектную модель (последнее возможно только для пользователей Commercial Edition). Сначала мы построим дерево предков в виде терма. Затем для построения дерева потомков применим объектную модель (для пользователей Personal Edition снова строится дерево в виде терма). Пользователи Personal Edition могут пропустить п. 6.1.3. "Деревья. Сводная таблица"
Подготовка изображений
Подготовим изображения, которые будут размещаться в вершинах дерева — для терминальных вершин, для открытых и закрытых узлов, а также для выделенных вершин (терминальных, открытых и закрытых), всего шесть изображений.
Выделим папку treePage дерева проекта и, с помощью команды меню New in Existing Package, откроем диалоговое окно Create Project Item. Выделим элемент Bitmap и напишем название terminal в поле Name. Создадим изображение размером 16 х 16 для терминальной вершины (рис. 6.1 (a) рис. 6.1).
Аналогично следует создать изображения (bitmap) terminalSel, closed, closedSel, opened, openеdSel. Изображение terminalSel показано на рис. 6.1 (b) рис. 6.1.
Построение дерева в виде терма
Откроем редактор страницы treePage и поместим на нее надписи "Дерево предков" и "Дерево потомков" (рис. 6.2 рис. 6.2).
Кроме этого, добавим пользовательский элемент управления (Custom Control), который будет использоваться для отображения дерева предков. В окне выбора класса для элемента управления следует указать имя класса treeViewControl (Name: anctree_ctl). Поле treeControl (в CE) сразу создавать не следует (см. рис. 6.2 рис. 6.2). Пользователям Personal Edition следует сразу создать еще один элемент управления (Custom Control) для отображения дерева потомков (Class: treeViewControl; Name: descendtree_ctl).
Закроем редактор страницы и выберем команду компиляции пакета treePage.pack.
В раздел open имплементации класса treePage слeдует добавить имена классов resourceIdentifiers и treeViewControl:
open core, vpiDomains, resourceIdentifiers, treeViewControl
Далее необходимо изменить определение конструктора new/0 и добавить объявление констант.
clauses new():- userControlSupport::new(), generatedInitialize(), % db := familyForm::familyForm:db. facts db : dbrel. constants closed : bitmapId = resid(idb_closed). closedSel : bitmapId = resid(idb_closedsel). opened : bitmapId = resid(idb_opened). openedSel : bitmapId = resid(idb_openedsel). terminal : bitmapId = resid(idb_terminal). terminalSel : bitmapId = resid(idb_terminalsel). ancestors = 0. descendants = 1.Листинг 6.1. Изменение конструктора. Основные параметры
Добавим обработчик событий ShowListener. Ниже приведено его определение.
clauses onShow(_Source, _Data):- some(Person) = db:selectedPerson, !, AncTree = createTree(Person:id, ancestors), anctree_ctl:insertTree(wcc_null, sorted(), AncTree), DescTree = createTree(Person:id, descendants), % для PE descendtree_ctl:insertTree(wcc_null, sorted(), DescTree). onShow(_Source, _Data).Листинг 6.2. Определение предиката onShow
Предикат insertTree/3 размещает дерево в окне treeViewControl. Параметр sorted означает, что поддеревья упорядочиваются по надписям, помещенным в корнях этих поддеревьев.
Предикат createTree/2 строит дерево. Дерево строится в виде терма — шестиместной структуры с функтором tree (см. ниже). Первый аргумент хранит идентификатор, второй — список свойств узла (например, он может быть развернутым — expanded), третий и четвертый — изображения для невыделенных и выделенных вершин, пятый — текст (надпись) и шестой — список поддеревьев.
predicates createTree: (unsigned, positive AncOrDesc) -> tree. clauses createTree(Id, Type) = tree(uncheckedConvert(itemId, Id), [], Pict, PictSel, Person:legend(), list::map(ChildList, {(I) = createTree(I, Type)})):- Person = getPerson(Id), ChildList = childList(Person, Type), nodeBitmap(toBoolean([] = ChildList), true, Pict, PictSel). predicates getPerson: (unsigned Id) -> person. clauses getPerson(Id) = Person:- Person = db:getPerson(Id), !. getPerson(_) = _:- exception::raise_error(). predicates childList: (person, positive Type) -> unsigned* IdList. clauses childList(Person, descendants) = Person:children:- !. childList(Person, _) = list::filter([Person:idFather, Person:idMother], {(I):- I > 0}). predicates nodeBitmap: (boolean IsTerminal, boolean IsNotExpanded, bitmapId NewBmp [out], bitmapId NewSelBmp [out]). clauses nodeBitmap(true, _, terminal, terminalSel):- !. nodeBitmap(_, true, closed, closedSel):- !. nodeBitmap(_, _, opened, openedSel).Листинг 6.3. Построение дерева
Предикат nodeBitmap/4 возвращает изображения для вершины (невыделенной и выделенной).
Сделаем так, чтобы при открывании или закрывании узла дерева менялось изображение. В редакторе страницы treePage выделим в списке окна Properties элемент anctree_ctl и добавим обработчик событий ExpandBeginResponder. Ниже приведено его определение.
predicates onAnctreeExpandBegin : treeViewControl::expandBeginResponder. clauses onAnctreeExpandBegin(Source, ItemId, IsExpanded) = treeViewControl::acceptExpand:- Source:getItem(ItemId, Text, StateList, Bmp, _SelBmp), nodeBitmap(toBoolean(terminal = Bmp), IsExpanded, NewBmp, NewSelBmp), Source:setItem(ItemId, Text, StateList, NewBmp, NewSelBmp), Source:unselect().Листинг 6.4. Определение предиката onAnctreeExpandBegin
Далее (в PE) следует выделить в редакторе формы элемент descend_ctl и на вкладке Events окна свойств выбрать в качестве обработчика событий ExpandBeginResponder предикат onAncTreeExpandBegin, определенный выше. Результат показан слева на рис. 6.3 рис. 6.3.
Объектная модель дерева
Для построения дерева потомков в Commercial Edition используем объектную модель. Откроем редактор страницы treePage и поместим на форму пользовательский элемент управления (Custom Control). В окне выбора класса для элемента управления укажем имя класса treeControl (см. рис. 6.2 рис. 6.2). В таблице свойств установим следующие значения для приведенных ниже свойств:
- Name: desctree_ctl;
- @Node: treeNode_std;
- Border: True.
Изменим определение конструктора new/0 так, как показано ниже.
clauses new():- userControlSupport::new(), generatedInitialize(), % db := familyForm::familyForm:db, descTreeControl_ctl:imageList := imageList::new(16, 16), closedIdx := desctree_ctl:imageList:addResource(idb_closed), closedSelIdx := desctree_ctl:imageList:addResource(idb_closedsel), terminalIdx := desctree_ctl:imageList:addResource(idb_terminal), terminalSelIdx := desctree_ctl:imageList:addResource(idb_terminalsel), if some(Person) = db:selectedPerson then Root = treeNode_std::new(Person:legend()), Root:bitmapIdx := closedIdx, Root:selectedBitmapIdx := closedSelIdx, getTree(Root, Person:id, descendants), TreeModel = treeModel_std::new(), TreeModel:addTree(Root), desctree_ctl:model := TreeModel, desctree_ctl:nodeRenderer := TreeModel:nodeRenderer end if.Листинг 6.5. Изменение определения конструктора
Ниже приведено определение предиката getTree/3, который строит дерево, а также объявление фактов-переменных.
facts closedIdx : integer := 0. closedSelIdx : integer := 0. terminalIdx : integer := 0. terminalSelIdx : integer := 0. predicates getTree: (treeNode_std, unsigned, positive Type). clauses getTree(ParentNode, Id, Type):- Person = getPerson(Id), List = childList(Person, Type), Idx = if [] = List then terminalIdx else closedIdx end if, SelIdx = if []= List then terminalSelIdx else closedSelIdx end if, ParentNode:bitmapIdx := Idx, ParentNode:selectedBitmapIdx := SelIdx, list::forAll(List, {(IdChild):- Child = getPerson(IdChild), Node = treeNode_std::new(ParentNode, Child:legend()), getTree(Node, IdChild, Type)}).Листинг 6.6. Построение дерева
При выделении вершины будем изменять цвет надписи. Добавим для элемента управления treeControl обработчики событий SelectBeginResponder и SelectEndListener.
Ниже приведено определение предиката onDesctreeSelectBegin.
predicates onDesctreeSelectBegin : treeControl{treeNode_std}::selectBeginResponder. clauses onDesctreeSelectBegin(_Source, _Unselected, Selected) = treeControl{treeNode_std}::acceptSelect:- Selected = treeControl{treeNode_std}::node(Node), !, Node:textColor := color_DarkBlue, desctree_ctl:refresh(Node). onDesctreeSelectBegin(_Source, _Unselected, _Selected) = treeControl{treeNode_std}::acceptSelect.Листинг 6.7. Выделение текста
Далее приведено определение предиката onDesctreeSelectEnd.
predicates onDesctreeSelectEnd : treeControl{treeNode_std}::selectEndListener. clauses onDesctreeSelectEnd(_Source, Unselected, _Selected):- Unselected = treeControl{treeNode_std}::node(Node), !, Node:textColor := color_Black, desctree_ctl:refresh(Node). onDesctreeSelectEnd(_Source, _Unselected, _Selected).Листинг 6.8. Возвращение исходных атрибутов текста
Результат показан справа на рис. 6.3 рис. 6.3.
Отображение данных в виде списка
В настоящем параграфе создается форма, в которой в виде таблицы отображаются сводные сведения из базы данных.
Создание формы
Cоздадим форму listViewForm и поместим в нее следующие элементы управления (рис. 6.4 рис. 6.4):
пользовательский элемент управления (Custom Control):
Class: listViewControl; Right Anchor: True, Bottom Anchor: True;
поле редактирования (Edit Control):
Text: <пустое поле> Top Anchor: False, Right Anchor: True, Bottom Anchor: True; VScroll: True, AutoVScroll: True, AutoHScroll: False.
Закроем редактор окна и выберем команду компиляции пакета listViewForm.pack.
Затем в пакете listViewForm создадим изображения (Bitmap) male (idb_male) и female (idb_female) размером 16 х 16.
В раздел open имплементации класса listViewForm добавим имена классов resourceIdentifiers и listViewControl:
open core, vpiDomains, resourceIdentifiers, listViewControl
После этого изменим определение конструктора, а также определим факты-переменные.
clauses new(Parent):- formWindow::new(Parent), generatedInitialize(), % db := familyForm::familyForm:db, ImageList = imageList::new(16, 16), maleIdx := ImageList:addResource(idb_male), femaleIdx := ImageList:addResource(idb_female), listViewControl_ctl:imageList := ImageList. facts db : dbrel. maleIdx : integer := 0. femaleIdx : integer := 0.Листинг 6.9. Предикаты заполнения полей таблицы
Далее следует определить предикаты, которые используются для формирования строк в ячейках таблицы.
predicates getLegend: (unsigned Id) -> string Name. clauses getLegend(Id) = Person:legend():- Person = db:getPerson(Id), !. getLegend(_Id) = "". predicates getPict: (person) -> integer Idx. clauses getPict(Person) = maleIdx:- dbrel::male = Person:sex, !. getPict(_Person) = femaleIdx. predicates getSpouses: (person) -> string. clauses getSpouses(P) = string::concatWithDelimiter( [S:legend() || Id in P:spouses, S = db:getPerson(Id)], ", ").Листинг 6.10. Предикаты заполнения полей таблицы
Предикат getLegend/1 возвращает строку, состоящую из имени и фамилии родителя, либо пустую строку, если сведения о родителе отсутствуют в базе данных. Предикат getPict/1 возвращает изображение, которое отображается в строке таблицы. Предикат getSpouses/1 собирает строку, состоящую из имен и фамилий супругов.
Создание таблицы
Таблица создается при открытии окна listViewForm. Она содержит шесть столбцов: "Имя", "Фамилия", "Пол", "Отец", "Мать" и "Супруги". К каждой строке прикрепляется изображение male или female (рис. 6.5 рис. 6.5). Изначально строки упорядочиваются по идентификаторам.
Добавим в редакторе формы listViewForm обработчик событий ShowListener. Его определение приведено ниже.
clauses onShow(_Source, _Data):- ColumnList = [ column("Имя", 100, alignLeft), column("Фамилия", 80, alignLeft), column("Пол", 50, alignCenter), column("Отец", 100, alignLeft), column("Мать", 110, alignLeft), column("Супруги", 120, alignLeft) ], Comparator = {(X, Y) = compare(X:id, Y:id)}, PersonList = list::sortBy(Comparator, db:personList), ItemList = [ item( uncheckedConvert(itemId, Person:id), Person:name, Pict, [], [Person:surname, Person:sex, Father, Mother, Spouses]) || Person in PersonList, Pict = getPict(Person), Father = getLegend(Person:idFather), Mother = getLegend(Person:idMother), Spouses = getSpouses(Person) ], listViewControl_ctl:insertColumnList(1, ColumnList), listViewControl_ctl:insertItemList(ItemList), listViewControl_ctl:setLVType(lvs_report), listViewControl_ctl:setStyle([lvs_singlesel]).Листинг 6.11. Создание таблицы
Список ColumnList содержит параметры столбцов таблицы — название, ширину столбца и тип выравнивания элементов в столбце.
Строка таблицы хранится в виде терма item/5. Первый аргумент содержит идентификатор, второй — значение в первом столбце, третий — изображение, четвертый — список свойств, пятый — список значений в остальных столбцах. Если изображение не требуется, то значение параметра Pict можно положить равным (– 1).
Форма listViewForm открывается при нажатии на кнопку "Таблица" окна familyForm. В редакторе формы familyForm добавим обработчик события нажатия на эту кнопку. Ниже приведено его определение.
clauses onTableClick(_Source) = button::defaultAction:- familyForm := This, _ = listViewForm::display(getParent()).Листинг 6.12. Определение предиката onTableClick
События выделения строки или столбца
Добавим операцию сортировки списка по столбцу при выделении столбца. Для этого следует открыть редактор формы listViewForm, выделить элемент listViewControl и добавить обработчик события ColumnClickListener. Он определяется ниже.
predicates onListViewControlColumnClick : listViewControl::columnClickListener. clauses onListViewControlColumnClick(S, ColumnNumber):- S:sortItems(listViewControl::ascending(), ColumnNumber).Листинг 6.13. Сортировка по столбцу
В результате строки таблицы будут упорядочены по значениям элементов выделенного столбца.
При выделении строки таблицы, описание о члене семьи из файла, который указан в базе данных (первым), должно отобразиться в поле редактирования (см. рис. 6.5 рис. 6.5). Если файл с описанием отсутствует в базе данных, то в поле редактирования должно отобразиться имя человека. В редакторе формы listViewForm добавим для списка обработчик событий SelectBeginResponder.
predicates onListViewControlSelectBegin : listViewControl::selectBeginResponder. clauses onListViewControlSelectBegin(S, ItemId, true) = listViewControl::acceptSelect:- item(_, Name, _, _, [Surname | _]) = S:getItem(ItemId), Id = uncheckedConvert(unsigned, ItemId), Person = db:getPerson(Id), !, Text = getDescription(Person, Name, Surname), edit_ctl:setText(Text). onListViewControlSelectBegin(_, _, _) = acceptSelect:- edit_ctl:setText("").Листинг 6.14. Событие выделения строки
Ниже приведено определение предиката getDescription/3.
predicates getDescription: (person, string Name, string Surname) -> string. clauses getDescription(Person, _, _) = Text:- [FileName | _] = Person:descriptions, FilePath = dbrel::descriptionsFolder(), FullName = fileName::setPath(FileName, FilePath), file::existFile(FullName), try Text = file::readString(FullName, _) catch _ do fail end try, !. getDescription(_, Name, S) = string::concat(Name, " ", S).Листинг 6.15. Событие выделения строки
Способы отображения списка
Существует четыре способа отображения списка — таблица (см. выше), список, значки и плитка (рис. 6.6 рис. 6.6).
Создадим всплывающее меню, которое будет использоваться для изменения способа отображения списка (см. рис. 6.5 рис. 6.5).
В папке listViewForm создадим меню listViewPopUpMenu с пунктами:
- Таблица (id_report);
- Список (id_list);
- Значки (id_icon);
- Плитка (id_smallicon).
После этого добавим обработчик событий MouseRightClickListener для списка и обработчик событий MenuItemListener для формы. Ниже приведено определение предиката onListViewControlMouseRightClick.
predicates onListViewControlMouseRightClick : listViewControl::mouseRightClickListener. clauses onListViewControlMouseRightClick(_Source, Point):- Menu = vpi::menuGetRes(id_listviewpopupmenu), menuPopUp(Menu, Point, align_left).Листинг 6.16. Создание всплывающего меню
Далее определяется предикат onMenuItem.
predicates onMenuItem : window::menuItemListener. clauses onMenuItem(_Source, id_report):- !, listViewControl_ctl:setLVType(lvs_report). onMenuItem(_Source, id_list):- !, listViewControl_ctl:setLVType(lvs_list). onMenuItem(_Source, id_icon):- !, listViewControl_ctl:setLVType(lvs_icon). onMenuItem(_Source, id_smallicon):- !, listViewControl_ctl:setLVType(lvs_smallicon). onMenuItem(_Source, _MenuTAG).Листинг 6.17. Команды меню
Высплывающее меню можно создать непосредственно в окне списка. Для этого нужно изменить определение предиката onListViewControlMouseRightClick/2 следующим образом (см. листинг 6.16 пример 6.16):
onListViewControlMouseRightClick(_Source, Point):- Menu = vpi::menuGetRes(id_listviewpopupmenu), listViewControl_ctl:menuPopUp(Menu, Point, align_left).
В этом случае придется использовать другой обработчик событий выбора пункта меню. В редакторе окна listViewForm нужно выделить список listViewControl и для него добавить обработчик событий menuItemListener. Предикат onListViewControlMenuItem/2 следует определить так же, как и предикат onMenuItem/2 (см. листинг 6.17 пример 6.17).
Упражнения
6.1. Создайте отдельное окно для просмотра изображения выделенного члена семьи. Оно должно открываться с помощью двойного щелчка по изображению на форме familyForm и на форме tabForm.
6.2. Создайте окно для поиска ответов на запросы к базе данных на языке, близком к естественному языку. Для вывода ответа на запрос используйте список (listBox или listViewControl).