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

Создание формы с вкладками

< Лекция 4 || Лекция 5 || Лекция 6 >
Аннотация: Создается форма, в которой отображаются подробные сведения о члене семьи, выводится его жизнеописание и отображаются деревья предков и потомков. Форма содержит три вкладки. На первой странице размещается информация из базы данных, на второй отображаются деревья предков и потомков (деревья создаются в лекции 6), на третьей выводится текст. Текст можно загрузить из файла, отредактировать и сохранить. Эта же форма используется для добавления информации в базу данных (см. лекцию 7).

Создание формы

Ниже создается форма с владками, которая предназначена для использования в двух режимах — режиме просмотра информации о члене семьи и в режиме добавления сведений о члене семьи, информация о котором отсутствует в базе данных. В режиме просмотра не допускается изменение данных, в режиме добавления все поля доступны для ввода данных. В первом случае форма содержит три вкладки, во втором две.

Создадим форму tabForm. В редакторе формы выберем пользовательский элемент управления (Custom Control) в окне Controls, затем в окне выбора класса для элемента управления укажем имя класса tabControl. Растянем этот элемент управления на все поле формы (рис. 5.1 рис. 5.1).

Редактор формы tabForm

Рис. 5.1. Редактор формы tabForm

Для всех якорей привязки выберем значение True. Закроем редактор формы и выберем команду компиляции пакета tabForm.pack.

Форма с вкладками открывается при нажатии на кнопку "Открыть" окна familyForm. Она отображает информацию о выделенном в списке члене семьи, поэтому предварительно следует найти и запомнить указатель на объект класса person, в котором хранится информация об этом человеке.

Откроем редактор формы familyForm, выделим кнопку "Открыть", перейдем на вкладку Events окна свойств и добавим обработчик события нажатия на кнопку ClickResponder. Ниже приведено определение предиката onViewClick.

predicates
    onViewClick : button::clickResponder.
clauses
    onViewClick(_Source) = button::defaultAction:-
        Person = getSelectedPerson(),
        !,
        db:selectedPerson := some(Person),
        familyForm := This,
        Form = tabForm::display(getParent()),
        Form:setText(Person:legend()).
    onViewClick(_Source) = button::defaultAction.
Листинг 5.1. Определение предиката onViewClick

Предикат getParent в данном случае возвращает указатель на класса taskWindow. Вместо него можно использовать предикат applicationWindow::get/0, который всегда возвращает указатель на объект класса taskWindow.

Итак, при нажатии на кнопку "Открыть" сначала находится указатель на объект выделенного элемента списка, затем он запоминается, далее запоминается указатель на объект окна familyForm, после этого открывается форма с вкладкамии и в ее строку заголовка записывается имя выделенного члена семьи.

Далее создаются страницы вкладок.

Создание страницы информации

Выделим папку tabForm дерева проекта, с помощью команды меню New In New Package откроем диалоговое окно Create Project Item, выделим в нем элемент Control, в поле Name напишем название страницы: infoPage. Нажмем кнопку Create. В строке Title таблицы свойств введем текст закладки: Информация. Разместим на странице следующие элементы управления (рис. 5.2 рис. 5.2):

надписи (Static Text) "Имя", "Фамилия", "Отец", "Мать";

поля редактирования (Edit Control):

Name: name_ctl; Text: <пустое поле>;
Name: surname_ctl; Text: <пустое поле>;

групповой блок (Group Box):

Representation: Fact Variable; Name: sex_ctl; Text: Пол;

далее нужно выделить групповой блок и разместить в нем переключатель (Radio Button):

Name: male_ctl; Text: мужской; RadioState: checked;

затем нужно опять выделить групповой блок и разместить в нем еще один переключатель:

Name: female_ctl; Text: женский;

два выпадающих списка (List Button):

Name: father_ctl; Rows: 3;
Name: mother_ctl; Rows: 3;

надпись (Static Text):

Representation: Fact Variable; Name: spouseText_ctl; Text: Супруги;

список (List Box):

Name: spouses_ctl; MultiSelect: True;

пользовательский элемент управления (Custom Control):

Class: pictControl (или imageControl для пользователей CE); 

четыре кнопки (Push Button):

Name: left_ctl; Text: <; Enabled: False;
Name: right_ctl; Text: >; Enabled: False;
Name: del_ctl; Text: -; Enabled: False; Visible: False; TooltipText: tip("Удалить");
Name: browse_ctl; Text: …; Visible: False.

По умолчанию свойство Representation групповых блоков (Group Box) и надписей (Static Text) имеет значение Variable. Для группового блока "Пол" и надписи "Супруги" его значение следует изменить на Fact Variable (см. выше).

Редактор окна infoPage

Рис. 5.2. Редактор окна infoPage

Переключатель (Radio Button) может находиться в двух состояниях — checked (включен) и unchecked (выключен). Из набора переключателей, объединенных в одну группу, может быть включен только один переключатель.

Первые две кнопки используются для просмотра изображений. Третья кнопка предназначена для удаления изображения, четвертая кнопка — для добавления изображений. В режиме просмотра видимыми являются только две кнопки, предназначенные для просмотра изображений. Свойству Visible остальных кнопок устанавливается значение False (по умолчанию оно равно True). Для третьей кнопки создана подсказка (tooltip), которая появляется при наведении курсора на кнопку (аналогичные подсказки можно сделать для других элементов управления).

Для отображения родителей используются выпадающие списки, так как в режиме добавления в них помещаются списки членов семьи, из которых затем выбираются родители.

Надпись "Супруги" и список (супругов) становятся невидимыми в режиме просмотра, если в базе данных отсутствуют сведения о супругах члена семьи. Надпись меняется на "Супруг" или "Супруга", если член семьи имел (или имеет) ровно одного супруга или супругу.

Далее нужно закрыть редактор окна.

Аналогично следует создать в отдельных пакетах еще две страницы вкладок — treePage (Title: Деревья) и descrPage (Title: Описание). Эти страницы будут заполнены позднее.

В интерфейсе tabForm следует объявить свойство familyForm. Оно используется для хранения указателя на объект окна, из которого открывается форма с вкладками (может быть открыто несколько таких окон).

properties
    familyForm : familyForm.
Листинг 5.2. Объявление свойства в интерфейсе tabForm

Теперь необходимо изменить определение конструктора new/1 в имплементации класса tabForm. В нем создаются страницы вкладок.

clauses
    new(Parent):-
        formWindow::new(Parent),
        generatedInitialize(),
        %
        familyForm := familyForm::familyForm,
        Page1 = tabPage::new(),
        Tab1 = infoPage::new(Page1:getContainerControl()),
        Page1:setText(Tab1:getText()),
        tabControl_ctl:addPage(Page1),
        
        if some(_) = familyForm:db:selectedPerson then
            Page2 = tabPage::new(),
            Tab2 = treePage::new(Page2:getContainerControl()),
            Page2:setText(Tab2:getText()),
            tabControl_ctl:addPage(Page2)
        end if,
        
        Page3 = tabPage::new(),
        Tab3 = descrPage::new(Page3:getContainerControl()),
        Page3:setText(Tab3:getText()),
        tabControl_ctl:addPage(Page3),
        
        infoPage := Tab1.
        
facts
    familyForm : familyForm.
    infoPage : infoPage.
Листинг 5.3. Изменение определения конструктора

Страница с деревьями отображается только в режиме просмотра сведений.

Заполнение страницы информации

Заполним первую страницу вкладок. В интерфейсе infoPage следует объявить свойство, которое хранит указатель на объект базы данных, а также предикат updatePicture, который инициирует перерисовку изображения при изменении размеров формы.

properties
    db : dbrel.
    
predicates
    updatePicture: ().
Листинг 5.4. Объявления в интерфейсе infoPage

В имплементации infoPage следует внести изменения в определение конструктора new, объявить необходимые факты-переменные и определить предикат updatePicture.

clauses
    new():-
        userControlSupport::new(),
        generatedInitialize(),
        db := familyForm::familyForm:db.
        
facts
    db : dbrel := erroneous.	% указатель на объект БД
    pictures : string* := [].	% имена файлов с изображениями
    n : positive := 0.		% количество изображений
    k : positive := 0.		% номер текущего изображения
    
clauses
    updatePicture():-
        pictControl_ctl:invalidate().
Листинг 5.5. Основные параметры

Теперь следует добавить обработчик события ShowListener. Когда открывается окно, заполняются поля страницы.

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).
Листинг 5.6. Определение предиката onShow

Ниже приведено определение предикатов, которые размещают сведения в элементах управления.

predicates
    setSex: (string Sex).
    setCheckedState: (radioButton).
clauses
    setSex(dbrel::male):- !,
        setCheckedState(male_ctl).
    setSex(_):-
        setCheckedState(female_ctl).
        
    setCheckedState(RadioCtl):-
        RadioCtl:setRadioState(radioButton::checked).
        
predicates
    setParent: (unsigned Id, listButton).
clauses
    setParent(ParentId, ListCtl):-
        Person = db:getPerson(ParentId),
        !,
        ListCtl:addAt(0, Person:legend()),
        ListCtl:selectAt(0, true).
    setParent(_ParentId, _ListCtl).
    
predicates
    setSpouses: (person).
clauses
    setSpouses(Person):-
        L = [S:legend() || I in Person:spouses, S = db:getPerson(I)],
        L <> [],
        !,
        spouses_ctl:clearAll(),
        spouses_ctl:addList(L),
        spouseText_ctl:setText(spouseText(Person, L)).
    setSpouses(_Person):-	
        spouses_ctl:setVisible(false),
        spouseText_ctl:setText("").
        
predicates
    spouseText: (person, string*) -> string.
clauses
    spouseText(_Person, SpouseList) = "Супруги":-
        [_, _| _] = SpouseList,
        !.
    spouseText(Person, _SpouseList) = "Супруга":-
        dbrel::male = Person:sex,
        !.
    spouseText(_Person, _SpouseList) = "Супруг".
    
predicates
    setPictures: (person).
clauses
    setPictures(Person):-
        pictures := Person:pictures,
        n := list::length(pictures),
        right_ctl:setEnabled(toBoolean(n > 1)),
        [FileName | _] = pictures,
        !,
        pictControl_ctl:drawPict(FileName).
    setPictures(_Person).
    
predicates
    setEnable: (string Status).
clauses
    setEnable("просмотр"):- !,
        name_ctl:setReadOnly(),
        surname_ctl:setReadOnly(),
        sex_ctl:setEnabled(false).
    setEnable(_):-
        remove_ctl:setVisible(true),
        browse_ctl:setVisible(true),
        del_ctl:setVisible(true).
Листинг 5.7. Заполнение полей страницы информации

Если окно открывается в режиме просмотра, то сведения, которые в нем отображаются, изменять нельзя (см. определение предиката setEnable/1).

Как отмечалось выше, если сведения о супругах отсутствуют в базе данных, то список не отображается (рис. 5.3 рис. 5.3).

Просмотр первой страницы вкладок

Рис. 5.3. Просмотр первой страницы вкладок

Просмотр изображений

Для члена семьи в базе данных может храниться несколько изображений (см. п. 4.1.1 "Приложение "Родственные отношения"" ). Просмотр изображений выполняется с помощью кнопок ">" (вперед) и "<" (назад). Включение и выключение этих кнопок определяется количеством изображений.

Добавим обработчики событий нажатия на кнопки left_ctl и right_ctl. Определение этих предикатов приведено ниже.

clauses
    onLeftClick(_Source) = button::defaultAction:-
        k > 0,
        !,
        k := k - 1,
        pictControl_ctl:drawPict(list::nth(k, pictures)),
        if 0 = k then
            left_ctl:setEnabled(false)
        end if,
        if n – 2 = k, n > 1 then
            right_ctl:setEnabled(true)
        end if.
    onLeftClick(_Source) = button::defaultAction.
Листинг 5.8. Определение предиката onLeftClick
clauses
    onRightClick(_Source) = button::defaultAction:-
        k < n - 1,
        !,
        k := k + 1,
        pictControl_ctl:drawPict(list::nth(k, pictures)),
        if n - 1 = k then
            right_ctl:setEnabled(false)
        end if,
        if 1 = k, n > 1 then
            left_ctl:setEnabled(true)
        end if.
    onRightClick(_Source) = button::defaultAction.
Листинг 5.9. Определение предиката onRightClick

Наконец, в редакторе формы tabForm следует добавить обработчик событий SizeListener и определить его так, как показано ниже.

clauses
    onSize(_Source):-
        infoPage:updatePicture().
Листинг 5.10. Обновление изображения при изменении размеров

Текстовый редактор

Ниже приводится пример использования класса sciLexer для просмотра и редактирования заметок о членах семьи.

Вторая страница вкладок будет заполнена позднее (в главе 6 "Деревья. Сводная таблица" ). Приступим к заполнению третьей страницы (рис. 5.4 рис. 5.4).

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

Рис. 5.4. Редактор страницы descrPage

Откроем редактор страницы descrPage и разместим следующие элементы управления (см. рис. 5.4 рис. 5.4):

пользовательский элемент управления (Class: sciLexer; Border: True);

три кнопки:

Name: open_ctl; Text: Открыть;
Name: save_ctl; Text: Сохранить;
Name: saveInDb_ctl; Text: Записать.

Закроем редактор формы.

Откроем файл descrPage.pro и изменим определение конструктора new так, как показано ниже. Кроме этого, объявим факты-переменные для хранения указателя на объект базы данных, имени файла и указания на систему кодирования символов в этом файле (Unicode или нет).

clauses
    new():-
        userControlSupport::new(),
        generatedInitialize(),
        %
        db := familyForm::familyForm:db,
        postAction({ :-
            sciLexer_ctl:wrapMode := sciLexer_native::sc_wrap_word
        }).
        
facts
    db : dbrel.
    filename : string := "".
    isUnicode : boolean := false.
Листинг 5.11. Изменение определения конструктора

Предикат postAction используется в данном случае для того, чтобы после выведения текста к нему была применена операция автоматического переноса слова на следующую строку, если оно не помещается в поле окна целиком.

Далее следует добавить в редакторе окна descrPage обработчик события ShowListener и обработчики нажатия на кнопки "Открыть" и "Сохранить".

Ниже приведено определение предиката onShow. Когда открывается страница "Описание", в ней отображается текст из файла, указанного в базе данных.

clauses
    onShow(_Source, _Data):-
        some(Person) = db:selectedPerson,
        [FileName | _] = Person:descriptions,
        FilePath = dbrel::descriptionsFolder(),
        FullName = fileName::setPath(FileName, FilePath),
        try Text = file::readString(FullName, _)
        catch Error do
            stdio::writef("Error %. Unable to read from %.\n", 
                Error, FullName),
            fail
        end try,
        !,
        sciLexer_ctl:text := Text.
    onShow(_Source, _Data).
Листинг 5.12. Определение предиката onShow

Свойство text текстового редактора хранит текущий текст.

Текстовый редактор обладает большим количеством свойств (см. в Help страницу Interface sciLexerBase). Например, установить свойство "Только для чтения" можно следующим образом:

sciLexer_ctl:readOnly := true.

При нажатии кнопки "Открыть" открывается окно "Открыть файл". Содержимое выбранного в нем текстового файла отображается в окне.

clauses
    onOpenClick(_Source) = button::defaultAction:-
        StartPath = dbrel::descriptionsFolder(),
        FileName = vpiCommonDialogs::getFileName("*.txt",
            ["Текстовый файл", "*.txt", " Document Word", "*.doc"],
            "Открыть файл", [], StartPath, _),
        !,
        Text = file::readString(FileName, Mode),
        isUnicode := Mode,
        filename := FileName,
        sciLexer_ctl:text := Text.
    onOpenClick(_Source) = button::defaultAction.
Листинг 5.13. Определение предиката onOpenClick

При нажатии кнопки "Сохранить" открывается окно "Сохранить файл". Содержимое поля записывается в выбранный файл.

clauses
    onSaveClick(_Source) = button::defaultAction:-
        Text = string::trim(sciLexer_ctl:text),
        Text <> "",
        StartPath = dbrel::descriptionsFolder(),
        FileName = vpiCommonDialogs::getFileName("*.txt",
            ["Текстовый файл", "*.txt", "Document Word", "*.doc"],
            "Сохранить как", [dlgfn_save], StartPath, _),
        !,
        file::writeString(Filename, Text, isUnicode),
        filename := FileName.
    onSaveClick(_Source) = button::defaultAction.
Листинг 5.14. Определение предиката onSaveClick

Считать текст можно также следующим образом:

Text = sciLexer_ctl:getText().

Предикат обработки события нажатия на кнопку "Запомнить " будет определен позднее.

Упражнения

5.1. Создайте окно, содержащее два списка и кнопку. В первом списке должны быть перечислены названия родственных отношений: "родитель ", "брат " и т. д. Второй список должен быть пуст. Кроме этого, следует создать кнопку на форме familyForm, при нажатии на которую должно открываться это окно. После того, как в первом списке будет выделено название родственного отношения, во втором списке должен отображаться список соответствующих родственников выделенного (в окне familyForm) члена семьи.

5.2. Создайте окно для поиска в базе данных с помощью регулярных выражений (см. класс regEx). Регулярное выражение вводится в поле редактирования. Поиск ведется в строках вида "<имя> <фамилия> ". Результаты должны выводиться в списке.

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