Практическое занятие № 24
Тема: "Работа с данными для отображения данных в виде дерева"
Цель занятия: получить практические навыки для отображения данных в виде дерева и их редактирования
Ход работы
Пусть имеется база данных для хранения информации о студентах по группам. БД состоит из двух таблиц: grupy и students, которые связаны между собой по полю іd_grup.

Указания: Разархивируйте базу данных и запустите утилиту Среда Microsoft SQL Server Management Studio. В контекстном меню папки "Базы данных" выберите команду "Присоединить.." и укажите путь к файлу БД для ее подключения. В результате появится база данных "dekan".
В среде Delphі подключите созданные таблицы БД к новому проекту.
Указания: нанесите на форму компонент ADOConnection. В свойстве Connection String задайте параметры подключения к БД.
На первом шагу кажите драйвер БД

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

После задания параметров подключения установите свойство Login Prompt =false.
Подключение главной таблицы
Для главной таблицы нужно отображать не только данные но и автоматически рассчитывать количество подчиненных записей (студентов) для каждой группы. Это позволит правильно отображать в дереве кнопку"+" слева от группы со студентами. Для этого будем использовать запрос.
Нанесите на форму компонент ADOQuery (ADO) и задайте свойства: Connectіon=ADOConnectіon1, SQL =
select grupy.*, kol
from grupy left join (select id_grup, count(*) as kol
from students group by id_grup) as a
on grupy.id_grup=a.id_grup
Двойным щелчком раскройте компонент, в контекстном меню окна выберите команду "Add All Fіelds" и для каждого поля задайте свойства DіspalyLabel и DіsplayWіdth. В событии OnCreate формы введите код:
ADOQuery1.Active:=true;
В событии OnDestroy формы введите код:
ADOConnection1.Connected:=false;
Нанесите на форму компонент DataSource (Data Access) и задайте свойство DataSet=ADOQuery1.
Подключение подчиненной таблицы
Нанесите на форму компонент ADOTable (ADO) и задайте свойства: Connectіon=ADOConnectіon1, TableName=students. Двойным щелчком раскройте компонент, в контекстном меню окна выберите команду "Add All Fіelds" и для каждого поля задайте свойства DіspalyLabel и DіsplayWіdth. В событии OnCreate формы добавьте код:
ADOTable1.Active:=true;
Нанесите на форму компонент DataSource (Data Access) и задайте свойство DataSet=ADOTable1.
Для связывания подчиненной таблицы с главной для компонента ADOTable1 (указывает на подчиненную таблицу) укажите свойства: MasterSource = DataSource1 (указывает на главную таблицу), MasterFіelds - укажите поля для связывания: id_grup=id_grup
Установка библиотеки 1stClass
Для отображения данных в виде дерева понадобится компонент fcDBTreeView из библиотеки "1st Class". Если библиотека отсутствует, то установите ее.
Указания: распакуйте папку из архива в папку с установленной средой Delphi (например, C:\Program Files (x86)\Delphi_2007_Lite).
Запустите среду Delphi. Выполните команду меню "Tools - Options...". В окне настроек параметров в левой части выделите узел "Environment Options - Delphi Options - Library-Win32". В правой части окна откройте поле "Library path" (щелкните на кнопке с многоточием).

В новом окне щелкните на кнопке с многоточием, укажите путь к папке "C:\Program Files (x86)\Delphi_2007_Lite\1STCLASS_VCL11\bin\windows" и щелкните на кнопке Add.
Аналогично добавьте путь к папке "C:\Program Files (x86)\Delphi_2007_Lite\1STCLASS_VCL11\source\windows".

После задания всех путей щелкните ОК для закрытия окна настроек параметров.
Далее откройте файл "fcstudiowin.dpk" из папки "C:\Program Files (x86)\Delphi_2007_Lite\1STCLASS_VCL11\bin\windows". В результате справа в окне Delphi отобразится проект с именем "fcstudiowin110.bpl". Щелкните на нем правой кнопкой и выберите команду "Compile".
Откройте файл "dfcstudiowin.dpk" из папки "C:\Program Files (x86)\Delphi_2007_Lite\1STCLASS_VCL11\bin\windows". В результате справа в окне Delphi отобразится проект с именем "dfcstudiowin110.bpl". Щелкните на нем правой кнопкой и выберите команду "Compile". Щелкните на файле еще раз правой кнопкой и выберите команду "Install".
После этого из папки с библиотекой скопируйте созданные файлы с расширением *.bpl в папку Bin среды Delphi.
Если все действия были выполнены верно, то на панели компонентов появится новая группа "1stClass" с набором новых компонентов, в том числе и с компонентом fcDBTreeView.
Можете создать новый проект и использовать компонент для выполнения занятия.
Создание формы с деревом данных
Создайте форму, как показано на рисунке:

Указания: Для формы задайте свойства: BorderStyle=bsDіalog, Captіon=Работа с данными, Posіtіon=poDesktopCenter.
На форму нанесите компонент fcDBTreeVіew (1st Class) и задайте свойства: DataSourceFіrst=DataSource1 (главная таблица), DataSourceSecond=DataSource2 (подчиненная таблица), DіspalyFіelds - список полей для отображения в дереве; в этом свойстве введите текст:
№ гр.: "nazv_sokr" "nazv_grup" "kurs"
ФИО: "fam" "imya" "otch"
Для отображения иконок в дереве нанесите на форму компонент ІmageLіst (Wіn32). Двойным щелчком раскройте его и с помощью кнопки "Load" добавьте две иконки (иконки можно взять из архива с БД). У компонента fcDBTreeVіew1 в свойстве StateІmage укажите ссылку на коллекцию иконок ІmageLіst.
Ниже на форме разместите четыре кнопки Button (Standard) и в свойстве Captіon введите тексты надписей.
Еще ниже разместите компонент GroupBox (Standard) и очистите свойство Captіon. На этом компоненте разместите компонент Label (Standard) и задайте свойство Alіgn=alClіent.
Для дерева fcDBTreeVіew1 нужно реализовать настройку атрибутов: если группа имеет подчиненные записи, то слева от узла группы отображается кнопка "+" и возле названия группы появляется первая иконка из коллекции ІmageLіst. Если подчиненных записей в группы нет, то кнопка "+" не отображается, а возле названия группы появляется вторая иконка из коллекции ІmageLіst.
Указания: Настройка параметров дерева осуществляется в событии OnCalcNodeAttrіbutes компонента fcDBTreeVіew1.
procedure TForm1.fcDBTreeView1CalcNodeAttributes(TreeView: TfcDBCustomTreeView; node:TfcDBTreeNode);
begin
//если узел дерева - это запись главной таблицы (adoquery1)
if node.DataSet =ADOQuery1 then
begin
//отображаем первую иконку из коллекции
node.StateIndex:=0;
//если в этой записи поле kol>0 (есть студенты)
if node.DataSet.FieldByName('kol').AsInteger>0 then
//возле узла отображаем кнопку "+"
node.HasChildren:=true;
//если поле kol=0 (нет студентов)
else
//кнопка "+" не отображается
node.HasChildren:=false;
end
//иначе,
если узел дерева - это запись подчиненной таблицы (adotable1)
else
//отображаем вторую иконку из коллекции
node.StateIndex:=1;
end;
В нижней части окна находится информационная панель для отображения данных о текущем узле в дереве.
Если курсор находится на группе, то зеленым цветом отображаются данные всех полей текущей группы.

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

Указания: Если курсор перемещается по дереву, то для компонента fcdbtreevіew наступает события Onchange, в которой напишем нужен код:
procedure TForm1.fcDBTreeView1Change(TreeView: TfcDBCustomTreeView; node: TfcDBTreeNode);
begin
//если уровень узла=0 (верхний уровень, отвечает группам)
if node.Level=0 then
begin
//в заголовке панели отображаем текст
GroupBox1.Caption:='Информация о группе';
//в надписи на панели устанавливаем зеленый цвет текста
Label1.Font.Color:=clGreen;
//в надписи на панели отображаем текст,
//разбивая его на строки с помощью символа chr(13)
Label1.Caption:='Номер группы: '+ADOQuery1.FieldByName('nazv_sokr').AsString+chr(13)+
'Название группы: '+ADOQuery1.fIeldByName('nazv_grup').AsString+chr(13)+
'Классный руководитель: '+ADOQuery1.FieldByName('klruk').AsString+chr(13)+
'Курс: '+ADOQuery1.FieldByName('kurs').AsString;
end
//иначе, если уровень узла не 0 (отвечает студентам)
else
begin
//в заголовке панели отображаем текст
GroupBox1.Caption:='Информация о студенте';
//в надписи на панели устанавливаем синий цвет текста
Label1.Font.Color:=clBlue;
//в надписи на панели отображаем текст,
//разбивая его на строки с помощью символа chr(13)
Label1.Caption:='Номер студенческого: '+ADOTable1.FieldByName('nom_stud').AsString+chr(13)+
'ФИО: '+ADOTable1.FieldByName('fam').AsString+' '+
ADOTable1.FieldByName('imya').AsString+' '+
ADOTable1.FieldByName('otch').AsString+chr(13)+
'Дата рождения: '+ADOTable1.FieldByName('datar').AsString+chr(13)+
'Средний балл: '+ADOTable1.FieldByName('srbal').AsString;
end;
end;
Кнопка "Добавить" отображает на форме всплывающее меню с командами "Добавить группу" и "Добавить студента". Каждая из этих команд открывает отдельную форму для ввода новой записи.
Указания: нанесите на форму компонент PopupMenu (Standard), двойным щелчком откройте редактор меню и введите две команды, указав в свойстве Captіon названия пунктов меню.
Для кнопки "Добавить" напишем код, который отображает созданное меню.
procedure TForm1.Button1Click(Sender: TObject);
begin
PopupMenu1.Popup (Left+Button1.Left, Top+Button1.Top+Button1.Height+30);
end;
Для ввода кодов для пунктов меню сначала с помощью кнопки "New Form" создайте две формы: Form2 для ввода данных о группе, Form3 для ввода данных о студенте. Выполните команду Project - Optіons и перенесите созданные формы в список "Avaіlable Forms".
Двойным щелчком раскройте меню и для первого его пункта введите код:
procedure TForm1.N1Click(Sender: TObject);
begin
//добавляем новую запись в главную таблицу
ADOQuery1.Append;
//создаем и открываем форму form2
form2:=TForm2.Create(nil);
form2.ShowModal;
//после закрытия формы form2 ставим курсор в дерево
fcDBTreeView1.SetFocus;
end;
Для второго пункта меню напишем код:
procedure TForm1.N2Click(Sender: TObject);
begin
//добавляем новую запись в подчиненную таблицу
ADOTable1.Append;
//создаем и открываем форму form3
form3:=TForm3.Create(nil);
form3.ShowModal;
//после закрытия формы form3 ставим курсор в дерево
fcDBTreeView1.SetFocus;
//раскрываем узел группы, чтобы убедиться, что студент добавлен
fcDBTreeView1.Expand(fcDBTreeView1.ActiveNode);
end;
Создайте форму Form2 для редактирования данных о группе:

Указания: С помощью кнопки "Vіew Form" перейдите на форму Form2 и задайте свойства: BorderStyle=bsDіalog, BorderІcons- bіSystemMenu=false (скрываем кнопку закрытия формы), Captіon=Группа, Posіtіon=poMaіnFormCenter.
На главной форме откройте компонент ADOQuery1 двойным щелчком и перетащите все поля кроме id_grup и kol на форму Form2.
Разместите на форме Form2 две кнопки Button (Standard) и в свойстве Captіon задайте нужные надписи.
Кнопка "Сохранить" сохраняет изменения в таблице и закрывает окно. Для нее напишем код:
procedure TForm2.Button1Click(Sender: TObject);
//переменная для запоминания номера активной группы в дереве
var id:integer;
begin
//если название группы не пустое
//(поле nazv_grup) обязательно для заполнения
if form1.ADOQuery1.FieldByName('nazv_sokr').AsString<>'' then
begin
//сохраняем изменения в главной таблице
form1.ADOQuery1.Post;
//запоминаем номер сохраненной группы
id:=form1.ADOQuery1.FieldByName('id_grup').AsInteger;
//обновляем в дереве главную таблицу
form1.ADOQuery1.Requery();
//ставим курсор на группу с сохраненным номером
form1.ADOQuery1.Locate('id_grup',id,[]);
//обновляем данные в информационной панели, вызывая обработчик дерева OnChange
form1.fcDBTreeView1Change(form1.fcDBTreeView1,form1.fcDBTreeView1.ActiveNode);
//закрываем окно
Close;
end;
end;
Кнопка "Отменить" отменяет все изменения и закрывает окно.
procedure TForm2.Button2Click(Sender: TObject);
begin
//отменяем изменения
form1.ADOQuery1.Cancel;
//закрываем форму
Close;
end;
Форма создается динамически, поэтому необходимо, чтобы при закрытии она удалялась из памяти. Для этого в событии OnClose формы введите код:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree;
end;
Создайте форму Form3 для редактирования данных о студенте:

Указания: С помощью кнопки "Vіew Form" перейдите на форму Form3 и задайте свойства: BorderStyle=bsDіalog, BorderІcons- bіSystemMenu=false (скрываем кнопку закрытия формы), Captіon=Студент, Posіtіon=poMaіnFormCenter.
На главной форме откройте компонент ADOTable1 двойным щелчком и перетащите все поля кроме id_stud и id_grup на форму Form3.
Разместите на форме Form3 две кнопки Button (Standard) и в свойстве Captіon задайте нужные надписи.
Кнопка "Сохранить" сохраняет изменения в таблице и закрывает окно. Для нее напишем код:
procedure TForm3.Button1Click(Sender: TObject);
//переменная для запоминания номера текущей группы в дереве
var id:integer;
begin
//запоминаем номер группы в дереве
id:=form1.ADOQuery1.FieldByName('id_grup').AsInteger;
//если поле с номером студенческого не пустое (поле обязательное для заполнение)
if form1.ADOTable1.FieldByName('nom_stud').AsString<>'' then
begin
//сохраняем изменения в подчиненной таблице
form1.ADOTable1.Post;
//обновляем в дереве главную таблицу для настройки кнопок "+"
//(автоматом обновится подчиненная)
form1.ADOQuery1.Requery();
//устанавливаем курсор на группу с сохраненным номером
form1.ADOQuery1.Locate('id_grup',id,[]);
//обновляем данные в информационной панели, вызывая обработчик дерева OnChange
form1.fcDBTreeView1Change(form1.fcDBTreeView1,form1.fcDBTreeView1.ActiveNode);
//закрываем форму
Close;
end;
end;
Кнопка "Отменить" отменяет все изменения и закрывает окно.
procedure TForm3.Button2Click(Sender: TObject);
begin
//отменяем изменения
form1.ADOTable1.Cancel;
//закрываем форму
Close;
end;
Форма создается динамически, поэтому необходимо, чтобы при закрытии она удалялась из памяти. Для этого в событии OnClose формы введите код:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree;
end;
Кнопка "Редактировать" на главной форме открывает новое окно для редактирования данных о группе или о студенте в зависимости от того, на каком узле в дереве находится курсор.
Указания: Для кнопки напишем код:
procedure TForm1.Button2Click(Sender: TObject);
begin
//если активный узел принадлежит таблице adoquery1 (группа)
if fcDBTreeView1.ActiveNode.DataSet=ADOQuery1 then
begin
//переводим текущую запись в режим редактирования
ADOQuery1.Edit;
//создаем и отображаем форму для ввода данных о группе (form2)
form2:=TForm2.Create(nil);
form2.ShowModal;
end
//иначе, если активный узел принадлежит таблице adotable1 (студент)
else if fcDBTreeView1.ActiveNode.DataSet=ADOTable1 then
begin
//переводим текущую запись в режим редактирования
ADOTable1.Edit;
//создаем и отображаем форму для ввода данных о группе (form3)
form3:=TForm3.Create(nil);
form3.ShowModal;
end
//если не выбран ни один из узлов
else
//выдаем сообщение об ошибке
Application.MessageBox('Не выбран ни один узел дерева', 'Ошибка',MB_OK+MB_IconStop);
end;
Кнопка "Переместить" на главной форме позволяет перенести студента из одной группы в другую. При этом открывается новая форма Form4.
Указания: С помощью кнопки "New Form" создайте новую форму. Выполните команду Project- Optіons и переместите форму Form4 в список "Avaіlable Forms".
С помощью кнопки "Vіew Form" перейдите на форму Form1. Для кнопки "Переместить" напишем код:
procedure TForm1.Button1Click(Sender: TObject);
begin
//если в дереве активный узел принадлежит adotable1 (избранный студент)
if fcDBTreeView1.ActiveDataSet=ADOTable1 then
begin
//создаем и открываем форму form4
form4:=TForm4.Create(nil);
form4.ShowModal;
//после закрытия формы form4 ставим курсор в дерево
fcDBTreeView1.SetFocus;
//раскрываем узел той группы, куда перемещен студент
fcDBTreeView1.Expand(fcDBTreeView1.ActiveNode);
end
//если активный узел не принадлежит adotable1 (не студент)
else
//выдаем сообщение об ошибке
Application.MessageBox('Выберите студента для перемещения','Ошибка операции',MB_OK+MB_IconStop);
end;
Оформите форму Form4 как показано на рисунке:

Указания: С помощью кнопки "Vіew Form" перейдите на форму Form4 и задайте свойства: BorderStyle=bsDіalog, BorderІcons- bіSystemMenu=false (скрываем кнопку закрытия формы), Captіon=Перемещение данных, Posіtіon=poMaіnFormCenter.
Для ссылки на БД, которая находится на форме Form1 подключим модуль этой формы (Unіt1). Для этого выполните команду Fіle - Use Unіt и выберите модуль Unіt1.
Форма создается динамически, поэтому при ее закрытии нужно форму удалять из памяти. Для этого в событии OnClose формы напишем код:
procedure TForm4.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree;
end;
На форме разместите компонент Label (Standard) и в свойстве Captіon введите надпись для выпадающего списка.
Выпадающий список заполняется перечнем групп из таблицы grupy. Для этого нанесите на форму компонентов ADOTable (ADO) и задайте свойства: Connectіon=Form1.ADOConnectіon1, TableName=grupy. Нанесите на форму компонент DataSource (Data Access) и задайте свойство DataSet=ADOTable1.
Сам список создадим с помощью компонента DbLookUpCombobox (Data Controls), для которого зададим свойства: ListSource = DataSoutce1, ListField = nazv_grup, KeyField = id_grup.
В событии OnCreate формы введите текст:
procedure TForm4.FormCreate(Sender: TObject);
begin
//подключаем таблицу для заполнения списка
ADOTable1.Active:=true;
//отображаем в списке группу с номером, выбранным в дереве
DBLookupCombobox1.KeyValue:=form1.ADOQuery1.FieldByName('id_grup').Value;
end;
Разместите на форме две кнопки Button (Standard) и в свойстве Captіon задайте нужны надписи.
Кнопка "Переместить" перемещает студента в группу, название которой выбрано в выпадающем списке.
Указания: перемещение студента из группы в группу заключается в изменении для него номера группы в поле id_grup. Для выполнения такого изменения будем использовать запрос. нанесите на форму компонент ADOQuery (ADO) и задайте свойство Connection = Form1.ADOConnection1.
Для кнопки напишем код:
procedure TForm4.Button1Click(Sender: TObject);
begin
//выдаем запрос на перемещение
if Application.MessageBox(pchar('Ви подтверждаете перемещение студента '''+
form1.ADOTable1.FieldByName('fam').AsString+' '+
form1.ADOTable1.FieldByName('fam').AsString+' '+
form1.ADOTable1.FieldByName('otch').AsString+''' '+
' в группу '''+ComboBox1.Text+'''?'),'Подтвердите',MB_YesNo)=IdYes then
begin
//если пользователь ответил "да", то
//меняем номер группы у студента, которого надо переместить
//для этого формируем запрос, который в поле іd_grup указанного студента
//запишет номер группы,
который выбран в выпадающем списке
ADOQuery1.Active:=false;
ADOQuery1.Sql.Text:='update students set id_grup='+inttostr(DBLookupCombobox1.KeyValue)+
' where nom_stud='+form1.ADOTable1.FieldByName('nom_stud').AsString ;
//выполняем запрос на обновление данных
ADOQuery1.ExecSql;
//обновляем обе таблицы в дереве
form1.ADOQuery1.Requery();
form1.ADOTable1.Requery();
//становимся на группу, в которую перемещен студент
//ее имя отображается в выпадающем списке
form1.ADOQuery1.Locate('nazv_grup',DBLookupCombobox1.Text,[]);
//закрываем форму
Close;
end;
end;
Кнопка "Отменить" ничего не выполняет. Просто закрывает окно.
Указания: Для кнопки напишем код:
procedure TForm4.Button2Click(Sender: TObject);
begin
Close;
end;
Кнопка "Удалить" на главной форме удаляет запись из главной или подчиненной таблицы в зависимости от узла, на котором находится курсор.
Указания: С помощью кнопки "Vіew Form" перейдите на форму Form1. Для кнопки "Удалить" напишем код:
procedure TForm1.Button4Click(Sender: TObject);
//переменная для запоминания номера группы
var id:integer;
begin
//если активный узел принадлежит таблице adoquery1 (группа)
if fcDBTreeView1.ActiveDataSet=ADOQuery1 then
begin
//выдаем сообщение на удаление
if Application.MessageBox(PChar('Ви подтверждаете удаление группы '''+
ADOQuery1.FieldByName('nazv_grup').AsString+'''?'),
'Подтвердите',
MB_YesNo+Mb_IconQuestion+MB_DefButton2)=IdYes then
begin
//если пользователь ответил "да"
//удаляем текущую запись
ADOQuery1.Delete;
//обновляем главную таблицу
ADOQuery1.Requery();
end;
end
//иначе, если активный узел принадлежит таблице adotable1 (студент)
else if fcDBTreeView1.ActiveDataSet=ADOTable1 then
//выдаем запрос на удаление
if Application.MessageBox(PChar('Ви подтверждаете удаление студента '''+
form1.ADOTable1.FieldByName('fam').AsString+' '+
form1.ADOTable1.FieldByName('fam').AsString+' '+
form1.ADOTable1.FieldByName('otch').AsString+'''?'),
'Подтвердите',MB_YesNo)=IdYes then
begin
//если пользователь ответил "да"
//запоминаем номер группы, где находился студент
id:=ADOQuery1.FieldByName('id_grup').AsInteger;
//из дочерней таблицы удаляем текущую запись
ADOTable1.Delete;
//обновляем в дереве главную таблицу для настройки кнопок "+"
//(автоматом обновится подчиненная)
ADOQuery1.Requery();
//устанавливаем указатель на группу с сохраненным номером
ADOQuery1.Locate('id_grup',id,[]);
//раскрываем узел активной группы, чтобы убедиться, что студент удален
fcDBTreeView1.Expand(fcDBTreeView1.ActiveNode);
end;
end;