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

Добавление и удаление сведений

< Лекция 6 || Лекция 7 || Лекция 8 >
Аннотация: Реализуется процесс внесения изменений в базу данных — добавления новых членов семьи и удаления старых. Для добавления новых данных используется окно tabForm. Для записи информации в базу данных и для удаления ее из базы данных используется окно familyForm.

Режим добавления

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

Для того чтобы добавить нового члена семьи в базу данных, необходимо нажать на кнопку "Добавить" окна familyForm. В результате открывается форма tabForm. В режиме добавления она содержит две страницы вкладок (рис. 7.1 рис. 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).

Добавление новой персоны

Рис. 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("Поля &apos;Имя&apos; и &apos;Фамилия&apos; "
            "обязательны для заполнения"),
        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

Рис. 7.3. Диалоговое окно Ask

Если пользователь нажимает кнопку "Да", то создается новый файл, в который копируется содержимое файла, хранящего факты базы данных до ее загрузки. Затем новое состояние базы данных сохраняется в старом файле. Предикат 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.

< Лекция 6 || Лекция 7 || Лекция 8 >
Алексей Роднин
Алексей Роднин
Россия
Роман Гаранин
Роман Гаранин
Беларусь, Брест