Практическое занятие № 23

Тема: "Обработка данных в отношении многие ко многим"

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

 

Ход работы

 

Выполните задание в СУБД Access

 

Пусть имеется БД "Catalog" для хранения информации о книгах, читателях и выдачах книг.

- Books - таблица книг;

- Readers - таблица читателей;

- Uchet - таблица учета выдачи.

Создайте программу для работы с данными таблиц. При этом сверху отображается список всех книг, внизу слева - список читателей, внизу справа - список учета выдач книг

 

 

Указания: для формы задайте свойства: BorderStyle=bsSingle, BorderIcons=bsMaximized=false, Caption=Учет выдачи книг, Position=poDesktopCenter.

Для подключения БД нанесите на форму компонент ADOConnectuion (ADO) и с помощью свойства ConnectionString, подключите БД.

 

Отображение списка книг

 

Для отображения данных будем использовать компонент ADOTable. Нанесите на форму компонент ADOTable и задайте свойства: Connection=ADOConnection1, TableName=books. Для сортировки списка книг по названию задайте свойство IndexFieldNames=nazv_book. Двойным щелчком раскройте компонент, добавьте все поля таблицы, поля "id_book" и "label" сделайте невидимым, остальные поля переименуйте в сетке задайте подходящую ширину.

В событии OnCreate формы введите код подключения к таблице:

 

procedure TForm1.FormCreate(Sender: TObject);

begin

    //подключаем таблицу books

    ADOTable1.Active:=true;

end;

 

При удалении формы нужно разрывать связь с БД. Для этого в событии OnDestroy формы введите код:

 

procedure TForm1.FormCreate(Sender: TObject);

begin

    //закрываем соединение с БД   

    ADOConnection1.Connected=false;

end;

 

Нанесите на форму компонент DataSource (DataAccess) и задайте свойство: DataSet=ADOTable1.

Нанесите на форму компонент DBGrid (DataControls) и задайте свойство: DataSource=DataSource1.Чтобы при выходе из сетки в ней оставалось выделение, задайте свойство Options - dgAlwaysShowSelection=true.

 

Отображение списка читателей

 

Для отображения данных будем использовать компонент ADOTable. Нанесите на форму компонент ADOTable и задайте свойства: Connection=ADOConnection1, TableName=readers. Для сортировки списка читателей по фамилии, имени и отчеству задайте свойство IndexFieldNames=fam;imya;otch Двойным щелчком раскройте компонент, добавьте все поля таблицы, поле "id_reader" сделайте невидимым, остальные поля переименуйте в сетке и задайте подходящую ширину.

В событии OnCreate формы добавьте код подключения к таблице:

 

//подключаем главную readers

ADOTable2.Active:=true;

 

Нанесите на форму компонент DataSource (DataAccess) и задайте свойство: DataSet=ADOTable2.

Нанесите на форму компонент DBGrid (DataControls) и задайте свойство: DataSource=DataSource2.Чтобы при выходе из сетки в ней оставалось выделение, задайте свойство Options - dgAlwaysShowSelection=true.

 

Отображение списка учета выдачи книг

 

Список учета выдачи книг строится на основании запроса по таблицам "books" и "uchet". Нанесите на форму компонент ADOQuery и задайте свойства: Connection=ADOConnection1, SQL=введите текст запроса вида:

 

select id_uchet, id_book, uchet.id_reader, data_out, fam, imya, otch, data_in

from readers inner join uchet

on readers.id_reader=uchet.id_reader

where id_book=:id_book

order by data_out Desc

 

Обратите внимание, что поле id_reader в запросе указано с именем таблицы: uchet.id_readerЭто связано с тем, что данное поле имеется в обоих таблицах, участвующих в запросе: readers и uchet. Таким образом, мы указываем поле какой именно таблицы нас интересует.

В запросе используется параметр ":id_book", для которого обязательно задайте тип данных. Для этого в свойстве Parameters компонента ADOQuery1 выберите данный параметр и задайте свойства: DataType=Integer, Value - Type = Integer.

Двойным щелчком раскройте компонент ADOQuery, добавьте все поля запроса. Поля "id_uchet", "id_book", "id_reader" сделайте невидимым, остальные поля переименуйте в сетке.

Нанесите на форму компонент DataSource (DataAccess) и задайте свойство: DataSet=ADOQuery1.

Нанесите на форму компонент DBGrid (DataControls) и задайте свойство: DataSource=DataSource3.

В третьей сетке не предполагается редактирование данных на прямую. Поэтому для сетки задайте свойство Options - dgRowSelect = true (в сетке будут выделяться строки целиком. Данный режим автоматически блокирует сетку для ввода).

 

Организация корректного обновления данных в сетке с оценками

 

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

 

В событии AfterScroll компонента ADOTable1 введите код:

 

procedure TForm1.ADOTable1AfterScroll(DataSet: TDataSet);

begin

    //если списка книг нет, то код не выполняется

    if ADOTable1.RecordCount=0 then Exit;

 

    //отключаем третью таблицу

    ADOQuery1.Active:=false;

    //передаем в запрос в качестве входящего параметра код книги

    ADOQuery1.Parameters.ParamByName('id_book').Value:=

                            ADOTable1.FieldByName('id_book').Value ;

    //подключаемся к таблице для отображения данных

    ADOQuery1.Active:=true;

    //после обновления набора данных в запросе нужно указать,

    //что главная таблица в нашем запросе - это uchet

    ADOQuery1.Properties['Unique Table'].Value:='uchet';

end;

Настройка параметров сеток

 

Заблокируем редактирование сеток с помощью стандартных клавиш (Tab, Insert, Ctrl+Delete).

Для отключения добавления новой записи с помощью клавиши Tab задайте для всех сеток свойство Options - dgTabs = false.

Для отключения в сетках реакции на нажатия указанных клавиш для первой сетки в событии OnKeyDown введите код:

 

procedure TForm1.DBGrid1KeyDown(Sender: TObject; var Key: Word;Shift: TShiftState);

begin

    //если нажата клавиша Insert

    if (Key=VK_Insert)

    //или нажаты клавишы Ctrl+Delete

    or (ssCtrl in Shift) and (Key=VK_Delete) then

        //нажатие игнорируется

        abort;

end;

 

Для присваивание этого же кода остальным сеткам в их событии OnKeyDown выберите заголовок данного обработчика.

 

По умолчанию сетки нельзя пролистывать с помощью колеса мыши. Для решения данной проблемы нанесите на форму компонент ApplicationEnents (Additional) и в событии OnMessage введите код:

 

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;var Handled: Boolean);

var

    i: SmallInt;

begin

    if Msg.message = WM_MOUSEWHEEL then

    begin

        Msg.message := WM_KEYDOWN;

        Msg.lParam := 0;

        i := HiWord(Msg.wParam);

        if i > 0 then

            Msg.wParam := VK_UP

        else

            Msg.wParam := VK_DOWN;

        Handled := False;

    end;

end;

 

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

Все операции реализуются с помощью кнопок.

 

Учет выдачи книг

 

Для записи факта выдачи книги необходимо выбрать книгу, выбрать читателя и щелкнуть на кнопке "Выдать книгу". После этого в третью таблицу вносятся данные:

 

"id_uchet", "id_book", "id_reader", "data_out" (дата выдачи книги)

 

Кроме записи о выдаче в таблицу "books" в поле "label" ставиться признак, что книга выдана (цифра 1).

 

Нанесите на форму компонент Button и задайте свойство Caption = "Выдать книгу". Для кнопки введите код:

 

procedure TForm1.Button1Click(Sender: TObject);

begin

    //если значение поля "label" = 1 (то есть книга уже выдана)

    if ADOTable1.FieldByName('label').asinteger=1 then

    begin

        //выдаем сообщение об ошибке

        Application.MessageBox('Книга уже выдана',

                               'Операция отменена',

                               mb_OK+mb_iconstop);

        //завершаем работу кода

        Exit;

    end;
 

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

    ADOQuery1.Append;

    //записываем код книги

    ADOQuery1.fieldbyname('id_book').Value:=ADOtable1.FieldByName('id_book').Value;

    //записываем код читателя

    ADOQuery1.fieldbyname('id_reader').Value:=ADOtable2.FieldByName('id_reader').Value;

    //записываем дату выдачи (текущая)

    ADOQuery1.fieldbyname('data_out').Value:=FormatDateTime('dd/MM/YYYY',Date);

    //сохраняем запись в БД

    ADOQuery1.Post;

   

    //далее в таблице books нужно поставить отметку, что книга выдана

    //переводим запись о книге в режим редактирования

    ADOTable1.Edit;

    //в поле label ставим отметку (цифра 1)

    ADOTable1.FieldByName('label').Value:=1;

    //сохраняем запись о книге

    ADOTable1.Post;

end;

Обновление данных о текущей записи

По умолчанию после добавления новой записи о выдаче в сетке не отображаются поля из связанной таблицы readers.

Проблему можно решить обновлением запроса с помощью метода ADOQuery1.Requery(). Однако такой метод является плохим способом, так как обновляет весь набор данных, что может потребовать некоторого времени на операцию, и ставит курсор в начало набора, что вынуждает разработчика вначале запоминать текущую позицию в наборе, а после обновления набора эту позицию восстанавливать.

Существует способ, который обновляет только текущую запись, не требуя перезагрузки всего набора и не теряя текущей позиции. Данный код пишут сразу после задания главной таблицы в запросе на основе нескольких таблиц.

Подключите модуль ADOInt.

Для таблицы ADOTable1 в событии AfterScroll добавьте команды вида:

 

//задаем дополнительные параметры перед обновлением

//задаем «главную» таблицу в наборе данных

ADOQuery1.Properties['Unique Table'].Value:='uchet';

ADOQuery1.Properties['Update Resync'].Value:= adResyncAll;

//к тексту запроса добавляем условие в виде:

//ключевое поле «главной таблицы» = ?

ADOQuery1.Properties['Resync Command'].Value:= AdoQuery1.Sql.Text+' where id_uchet=?';

 

//далее выполняем обновление записи

ADOQuery1.UpdateCursorPos;

ADOQuery1.Recordset.Resync(adAffectCurrent, adResyncAllValues);

ADOQuery1.Resync([]);

 

Теперь в сетке учета выдачи книг будет обновляться только текущая запись.

 

Учет возврата книг

 

Для записи факта возврата книги необходимо выбрать запись о факте выдачи книги и и щелкнуть на кнопке "Вернуть книгу". После этого в третьей таблице заполняется поле "data_in" для записи даты возврата книги.

 

Кроме записи о выдаче в таблицу "books" в поле "label" ставиться признак, что книгу вернули (0).

 

Нанесите на форму компонент Button и задайте свойство Caption = "Вернуть книгу". Для кнопки введите код:

 

procedure TForm1.Button2Click(Sender: TObject);

//переменная для сохранения позиции курсора в сетке

var pos:integer;

begin

//если книга уже возвращена (поле data_in заполнено)

    if ADOQuery1.FieldByName('data_in').AsString<>'' then

    begin

        //выдаем сообщение об ошибке

        Application.MessageBox('Книга указанным читателем уже возвращена',

                               'Операция отменена',

                               mb_OK+mb_iconstop);

        //завершаем работу кода

        Exit;

    end;

 

    //запоминаем позицию курсора в сетке с данными о выдаче

    pos:=ADOQuery1.FieldByName('id_uchet').AsInteger;

    //переводим запись о книге в режим редактирования

    ADOQuery1.Edit;

    //записываем дату возврата (текущая)

    ADOQuery1.FieldByName('data_in').Value:=FormatDateTime('dd/MM/YYYY',date);

    //сохраняем запись в БД

    ADOQuery1.Post;

    //устанавливаем курсор на сохраненное значение

    ADOQuery1.Locate('id_uchet',pos,[]);

 

    //далее в таблице books нужно поставить отметку, что книга возвращена

    //переводим запись о книге в режим редактирования

    ADOTable1.Edit;

    //в поле label ставим отметку (цифра 0)

    ADOTable1.FieldByName('label').Value:=0;

    //сохраняем запись о книге

    ADOTable1.Post;

end;

Удаление записи о выдаче книги

Нанесите на форму компонент Button и задайте свойство Caption = "Удалить". Для кнопки введите код:

 

procedure TForm1.Button3Click(Sender: TObject);

begin

    //если данных о выдаче книги нет, то код не выполняется

    if ADOQuery1.RecordCount=0 then Exit;

 

    //если поле "data_in" пустое,

    //т.е. книга еще не возвращена

    if ADOQuery1.FieldByName('data_in').AsString='' then

    begin

        //выдаем сообщение об ошибке

        Application.MessageBox('Нельзя удалить запись о невозвращенной книге',

                               'Отмена операции',

                               MB_OK+MB_IconStop);

        //завершаем работу кода

        Exit;

    end;

 

    //выдаем запрос на удаление
        if Application.MessageBox('Удалить данные о выдаче?',

                              'Подтвердите операцию',

                              mb_yesno+mb_iconquestion+mb_defbutton2)=idYes then

        //удаляем запись

        ADOQuery1.Delete;

end;

 

Для удобства пользователя будем выполнять условное форматирование данных в сетке: выданные книги помечаются другим цветом. После возврата книги цветовая метка снимается.

 

Указания:  в событии OnDrawColumnCell первой сетки введите код:

 

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);

begin

    //если книга не выдана (label=0)

    if ADOTable1.FieldByName('label').AsInteger=0 then

        //задаем цвет фона по умолчанию

        DBGrid1.Canvas.Brush.Color:=clWindow

    //если книга выдана (label<>0)

    else

        //задаем другой цвет фона (светло-красный)

        DBGrid1.Canvas.Brush.Color:=$009A96D4;

 

    //если ячейка является активной в сетке

    if gdSelected in State then

        //задаем для нее другой цвет

        DBGrid1.Canvas.Brush.Color:=clMenuHighLight;

 

    //закрашиваем сетку заданными цветами

    DBGrid1.Canvas.FillRect(rect);

    //после закраски выводим текст в сетке

    DBGrid1.Canvas.TextOut(rect.left+2, rect.Top+2, Column.Field.Text);

end;

 

Для ускорения ввода данных реализуем интерактивный поиск книги или читателя по шифру. Для такого поиска на форме имеются два поля. По мере ввода цифр шифра в полое происходит автоматический переход курсора в сетке на найденную запись. В полях реализуем технологии PlaceHolder полей HTML: если поле пустое, то в нем отображается текст подсказка.

 

Указания: нанесите на форму компонент Edit (Standart) и задайте свойства: Text = "Введите шифр книги...", Font - Color = clGray.

По умолчанию в поле отображается текст подсказка. Если при входе в поле там находится данный текст, то считается, что поле пустое. В событии OnEnter поля введите код:

 

procedure TForm1.Edit1Enter(Sender: TObject);

begin

    //если текст в поле равен начальному (данные не введены)

    if Edit1.Text='Введите шифр книги...' then

        //поле очищается

        Edit1.Text:='';

    //цвет текста становится черным

    Edit1.Font.Color:=clBlack;

end;

 

При выходе из поля, если поле осталось пустым, то в поле отображается текст подсказка и текст отображается серым цветом. В событии OnExit поля введите код:

 

procedure TForm1.Edit1Exit(Sender: TObject);

begin

    //если поле пустое 

    if Edit1.Text= '' then

    begin

        //в поле отображаем текст по умолчанию       

        Edit1.Text:='Введите шифр книги...';

        //текст в поле делаем серым       

        Edit1.Font.Color:=clGray;

    end;

end;

 

При вводе текста в поле, указатель в сетке автоматически переходит на найденную запись. В событии OnChange поля введите код:

 

procedure TForm1.Edit1Change(Sender: TObject);

begin

    //в поле "shifr" таблицы "books" ищем текст из поля Edit1

    ADOTable1.Locate('shifr',Edit1.Text,[loPartialKey]);

end;

 

Аналогично реализуем поиск читателя по шифру.

Нанесите на форму компонент Edit (Standart) и задайте свойства: Text = "Введите шифр читателя...", Font - Color = clGray.

По умолчанию в поле отображается текст подсказка. Если при входе в поле там находится данный текст, то считается, что поле пустое. В событии OnEnter поля введите код:

 

procedure TForm1.Edit2Enter(Sender: TObject);

begin

    //если текст в поле равен начальному (данные не введены)

    if Edit2.Text='Введите шифр читателя...' then

        //поле очищается

        Edit2.Text:='';

    //цвет текста становится черным

    Edit2.Font.Color:=clBlack;

end;

 

При выходе из поля, если поле осталось пустым, то в поле отображается текст подсказка и текст отображается серым цветом. В событии OnExit поля введите код:

 

procedure TForm1.Edit2Exit(Sender: TObject);

begin

    //если поле пустое 

    if Edit2.Text= '' then

    begin

        //в поле отображаем текст по умолчанию       

        Edit2.Text:='Введите шифр читателя...';

        //текст в поле делаем серым       

        Edit2.Font.Color:=clGray;

    end;

end;

 

При вводе текста в поле, указатель в сетке автоматически переходит на найденную запись. В событии OnChange поля введите код:

 

procedure TForm1.Edit2Change(Sender: TObject);

begin

    //в поле "shifr" таблицы "readers" ищем текст из поля Edit2

    ADOTable2.Locate('shifr',Edit2.Text,[loPartialKey]);

end;

 

Выполните задание в СУБД SQL Server

 

Пусть имеется БД "Catalog" для хранения информации о книгах, читателях и выдачах книг.

- Books - таблица книг;

- Readers - таблица читателей;

- Uchet - таблица учета выдачи.

Скачайте готовую БД и разместите ее на сервере SQL Server.

 

Указания: распакуйте архив с файлами БД. Запустите утилиту "Среда SQL Server Management Studio". В левой части щелкните правой кнопкой на узле "Базы данных" и выберите команду "Присоединить". В новом окне в помощью кнопки "Добавить" укажите файд БД и щелкните ОК.

 

Скопируйте папку с проектом и внесите в него изменения для работы с БД SQL Server.

 

Указания: скопируйте папку с проектом. Откройте копию проекта и внесите ряд изменений.

Для подключения БД SQL Server у компонента ADOConnectuion (ADO) откройте свойство ConnectionString. В новом окне щелкните на кнопке "Build", затем выберите драйвер "Microsoft OLE DB Provider for SQL Server" и щелкните "Далее". Укажите имя сервера в формате "имя_компьютера, 1433", Пользователь = sa, Пароль = 111, База данных = catalog.

 

После выполненных изменений ваш проект будет работать с БД в формате SQL Server.