Опубликован: 08.08.2015 | Доступ: свободный | Студентов: 272 / 36 | Длительность: 09:22:00
Лекция 6:

Деревья. Сводная таблица

< Лекция 5 || Лекция 6 || Лекция 7 >
Аннотация: Создаются деревья и списки. Дерево предков и дерево потомков отображаются на второй странице вкладок. Список, отображающий сводные сведения в виде таблицы, создается в отдельном окне. Используются элементы управления treeViewControl и listViewControl.
Ключевые слова: дерево, базы данных

Отображение деревьев

Для создания деревьев можно использовать два подхода — строить дерево в виде терма или использовать объектную модель (последнее возможно только для пользователей 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).

Изображения для терминальной вершины: (a) обычной; (b) выделенной

Рис. 6.1. Изображения для терминальной вершины: (a) обычной; (b) выделенной

Аналогично следует создать изображения (bitmap) terminalSel, closed, closedSel, opened, openеdSel. Изображение terminalSel показано на рис. 6.1 (b) рис. 6.1.

Построение дерева в виде терма

Откроем редактор страницы treePage и поместим на нее надписи "Дерево предков" и "Дерево потомков" (рис. 6.2 рис. 6.2).

Редактор страницы treePage

Рис. 6.2. Редактор страницы treePage

Кроме этого, добавим пользовательский элемент управления (Custom Control), который будет использоваться для отображения дерева предков. В окне выбора класса для элемента управления следует указать имя класса treeViewControl (Name: anctree_ctl). Поле treeControlCE) сразу создавать не следует (см. рис. 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.

Деревья предков и потомков

Рис. 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

Рис. 6.4. Редактор формы listViewForm

Закроем редактор окна и выберем команду компиляции пакета 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

Рис. 6.5. Окно listViewForm

Добавим в редакторе формы 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).

Способы отображения списка: (a) список; (b) значки; (c) плитка

Рис. 6.6. Способы отображения списка: (a) список; (b) значки; (c) плитка

Создадим всплывающее меню, которое будет использоваться для изменения способа отображения списка (см. рис. 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).

< Лекция 5 || Лекция 6 || Лекция 7 >
Харламп Бикс
Харламп Бикс
Россия
Данибор Микалюкин
Данибор Микалюкин
Россия