|
Добрый день, при написании программы в 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).





