Лекция № 26
Тема: "Отображение связанных данных из таблиц в виде дерева"
План
1. Преимущества отображения данных в виде дерева
2. Организация БД для создания дерева данных
3. Компонент для создания дерева данных
4. Настройка параметров дерева данных
5. Редактирование данных в дереве
1. Преимущества отображения данных в виде дерева
Отображение данных из связанных таблиц в виде дерева имеет ряд преимуществ:
- данные представленные в более удобной для восприятия пользователем форме;
- данные представленные в компактном виде, не занимая много места на форме;
- в дереве можно отобразить любое количество последовательно связанных таблиц.
Например, если есть две таблицы "grupy" и "students", то их отображение можно выполнить с помощью сеток, которые занимают много места на форме.

Эту же информацию можно представить в виде дерева.

В одном компоненте представлено две таблицы. При этом четко видная иерархия подчиненности данных.
Представим более сложный пример. Пусть есть три таблицы: Отделение, Группы и Студенты. Для их представления в обычном виде понадобится три сетки, которые займут на форме еще больше места. А в дереве эта информация отображается так:

С помощью одного компонента отображается содержимое трех таблиц.
2. Организация БД для создания дерева данных
Если данные отображаются в виде дерева, то по умолчанию приняты некоторые условия: если узел имеет подчиненные элементы, то слева от узла отображается кнопка с символом «+»; если узел не имеет подчиненных элементов, то данная кнопка отсутствующая.
Для организации корректного отображения этого свойства таблица кроме основных полей также должна хранить количество подчиненных записей. Если число таких записей больше 0, то возле узла дерева отображается кнопка "+", иначе - не отображается.
Для решения этой задачи данные из таблицы отображают с помощью запроса вида:
select главная таблица.*, kol
from главная_таблица left join (select поле, count(*) as kol
from подчиненная_таблица group by поле) as a
on главная_таблица.поле=a.поле
где ПОЛЕ - это имена поля в главной и подчиненной таблице, по которым эти таблицы связаны.
3. Компонент для создания дерева данных
Для создания дерева данных по связанным таблицам можно использовать стандартные компоненты Delphi типа TreeView. Однако этот компонент не предназначен для работы с БД и не имеет специальных свойств для подключения к таблицам. Для его использования разработчику приходится "вручную" строить дерево и следить за его корректным обновлением.
Более простым вариантов будет использование внешних компонентов, предназначенных для поставленных целей. Одним из таких компонентов является fcDBTreeView, который входит в состав библиотеки 1st Class. Данной библиотеки нет по умолчанию в среде Delphi и ее установка описана в практическом занятии по данной теме.
Данный компонент имеет ряд свойств:
BorderStyle – задает тип границы компонента.
Color – цвет фона компонента.
DatasouceFirst – ссылка на компонент DataSource, связанный с главной таблицей.
DataSouceSecond – ссылка на компонент DataSource, связанный с подчиненной таблицей.
DataSources – используется, если в дереве планируется отобразить данные более чем из двух таблиц. В этом случае в свойстве нужно ввести имена компонентов DataSource через точку с запятой без пробелов.
Например, есть таблицы "Otdelen", "Grupy", "Students". Для отображения данных из этих таблиц в свойстве нужно ввести текст:
DataSource1;DataSource2;DataSource3
DisplayFields – если это свойство не задано, то по умолчанию в дереве отображается только первое поле из каждой таблицы. Для отображения других полей в данном свойстве нужно ввести имена нужных полей в двойных кавычках, а также любой вспомагательный текст без кавычек. При этом список полей каждой таблицы вводят на отдельной строке.
Например, если есть три таблицы "Otdelen", "Grupy", "Students" и свойство задано так:
"nazv_ot"
№ гр: "nazv_sokr" "nazv_grup"
ФИО: "fam" "imya" "otch"
Указанные поля отображаются, как показано на рисунке:

Imager – ссылка на компонент Imager (1st Class), который содержит изображение, отображаемое в дереве как фон.
InactiveFocusControl – цвет выделенного узла в дереве, если самое дерево не активно.
LineColor – цвет линий в дереве.
Options – список параметров, которые влияют на внешний вид дерева: dtvoKeysScrollLevelOnly – если False, то с помощью стрелок управления курсором можно перемещаться только в пределах одного узла; dtvoAutoExpandOnDSScroll – если True, то при переходе по записям в другом визуальном компоненте, автоматически в дереве раскрывается соответствующая ветви и отображается избранный элемент; dtvoExpandButtons3D – отображение кнопок со значком «+» в объемном виде; dtvoHideSelection – если True, то в дереве не отображается выделенный узел, если дерево не в фокусе; dtvoRowSelect – если True, то в дереве выделяется целая строка, если False, то только название узла; dtvoShowNodeHint – если True, то отображаются всплывающие подсказки для узлов дерева; dtvoShowButtons – если True, то отображаются кнопки для раскрытия нижних уровней; dtvoShowLines – если True, то отображаются линии в дереве; dtvoShowRoot – если True, то отображается главный узел в дереве; dtvoShowHorzScrollBar, dtvoShowVertScrollBar – если True, то отображаются полосы прокрутки; dtvoHotTracking – если True, то при наведении на узел в дереве, его текст отображается как гиперссылки.
StateImages – ссылка на компонент ImageList со списком иконок, отображаемых возле узлов дерева.
4. Настройка параметров дерева данных
Для корректного отображения кнопок раскрытия подчиненных уровней, а также для корректного отображения иконок возле узлов дерева можно написать код в событии OnCalcNodeAttributes компонента fcDBTreeView. Данное событие имеет параметр Node, который позволяет распознавать узел и задавать его параметры.
Корректное отображение кнопки «+»
Например. Пусть в дереве отображаются две таблицы: "Grupy" и "Students". Пусть главная таблица "Grupy" отображается с помощью приведенного выше запроса, у которого в поле "kol" хранится количество студентов в группе (количество подчиненных записей). Написать код, отображающий кнопку «+» возле узлов, которые имеют подчиненные записи (kol>0) или не отображает кнопку, если подчиненных записей нет (kol=0).
В событии OnCalcNodeAttributes компонента fcDBTreeView напишем код:
procedure TForm1.fcDBTreeView1CalcNodeAttributes(TreeView: TfcDBCustomTreeView; Node: TfcDBTreeNode);
begin
//если источник узла – это запрос ADOQuery1 (grupy)
if Node.DataSet =ADOQuery1 then
begin
//если значение поля kol_stud больше 0
if Node.DataSet.FieldByName('kold').AsInteger>0 then
//указываем, что узел имеет дочерние элементы (отображаем кнопку "+")
Node.HasChildren:=true
//если значение поля kol_stud равно 0
else
//указываем, что узел не имеет дочерних элементов (не отображаем кнопку "+")
Node.HasChildren:=false
end;
Отображение иконок возле узлов дерева
Если в свойстве StateImage компонента fcDBTreeView указан компонент ImageList со списком иконок, то для их отображения также нужно писать код в событии OnCalcNodeAttributes компонента fcDBTreeView.
Например. Пусть в дереве отображаются две таблицы: "Grupy" и "Students". Если узел принадлежит главной таблице, то возле него отображается первая иконка из списка, если узел принадлежит подчиненной таблице, то возле него отображается вторая иконка из списка.
В событии OnCalcNodeAttributes компонента fcDBTreeView напишем код:
procedure TForm1.fcDBTreeView1CalcNodeAttributes(TreeView: TfcDBCustomTreeView; Node: TfcDBTreeNode);
begin
//если узел принадлежит первой таблице
if Node.DataSet = ADOQuery1 then
//отображаем первую иконку из списка
Node.StateIndex:= 0
//иначе, если узел принадлежит второй таблице
else
//отображаем вторую иконку из списка
Node.StateIndex:= 1;
end;
5. Редактирование данных в дереве
Для редактирования данных в дереве можно использовать вспомогательные страничные формы.
Например, пусть есть две формы. Форма "form2" содержит поля для редактирования таблицы "grupy" и форма "form3" содержит поля для редактирования таблицы "students".
Основная форма имеет вид:

Для кнопки "Добавить группу" напишем код:
procedure TForm1.Button1Click(Sender: TObject);
begin
//в главную таблицу добавляем новую запись
ADOQuery1.Append;
//отображаем форму form2 для редактирования данных
form2.ShowModal;
//после окончания редактирования ставим курсор в дерево
fcDBTreeView1.SetFocus;
end;
Для кнопки "Добавить студента" напишем код:
procedure TForm1.Button2Click(Sender: TObject);
begin
//в подчиненную таблицу добавляем новую запись
ADOTable1.Append;
//отображаем форму form3 для редактирования данных
form3.ShowModal;
//после окончания редактирования ставим курсор в дерево
fcDBTreeView1.SetFocus;
//раскрываем родительский узел группы,
//чтобы увидеть, что студент был добавлен
fcDBTreeView1.Expand(fcDBTreeView1.ActiveNode);
end;
Для кнопки "Редактировать" напишем код:
procedure TForm1.Button3Click(Sender: TObject);
begin
//если активный узел в дереве принадлежит главной таблице
if fcDBTreeView1.ActiveNode.DataSet = ADOQuery1 then
begin
//переводим главную таблицу в режим редактирования
ADOQuery1.Edit;
//отображает форму form2 для редактирования данных
form2.ShowModal;
end
//иначе, если активный узел в дереве принадлежит подчиненной таблице
else if fcDBTreeView1.ActiveNode.DataSet=ADOTable1 then
begin
//переводим подчиненную таблицу в режим редактирования
ADOTable1.Edit;
//отображает форму form3 для редактирования данных
form3.ShowModal;
end
else
Application.MessageBox('Не выбран элемент для редактирования',
'Ошибка',MB_OK+MB_IconStop);
end;
Для кнопки "Удалить" напишем код:
procedure TForm1.Button4Click(Sender: TObject);
//переменная для запоминания родительского узла
var Node:TfcDBTreeNode;
begin
//если активный узел принадлежит главной таблице
if fcDBTreeView1.ActiveDataSet=ADOQuery1 then
begin
//выдаем запрос на удаление
if Application.MessageBox('Вы подтверждаете удаление группы?’,
'Подтвердите',MB_YesNo)=IdYes then
begin
//удаляем узел из главной таблицы
ADOQuery1.Delete;
//обновляем главную таблицу в дереве
ADOQuery1.Requery();
end;
end
//иначе, если узел в дереве принадлежит подчиненной таблицей
else if fcDBTreeView1.ActiveDataSet=ADOTable1 then
//выдаем запрос на удаление данных
if Application.MessageBox('Вы подтверждаете удаление студента?’,
'Подтвердите',MB_YesNo)=IdYes then
begin
//запоминаем родительский узел
Node:=fcDBTreeView1.ActiveNode.Parent;
//удаляем выбранный узел из подчиненной таблицы
ADOTable1.Delete;
//обновляем главную таблицу в дереве
ADOQuery1.Requery();
//раскрываем сохраненный родительский узел,
//чтобы увидеть, что студент удален из группы
fcDBTreeView1.Expand(Node);
end;
end;
Форма редактирования группы

Для кнопки "Сохранить" напишем код:
procedure TForm2.Button1Click(Sender: TObject);
//переменная для сохранения номера активной группы в дереве
var id:integer;
begin
//если номер группы не пустой
if form1.ADOQuery1.Fieldbyname('nom_grup').AsString<>'' then
begin
//сохраняем изменения в главной таблице
form1.ADOQuery1.Post;
//в переменную запоминаем номер активной группы
id:=form1.ADOQuery.FieldByName('nom_grup').AsInteger;
//обновляем главную таблицу в дереве
form1.ADOQuery.Requery();
//ставим указатель на запись с сохраненным номером группы
form1.ADOQuery.Locate('nom_grup',id,[]);
//закрываем форму
Close;
end;
end;
Для кнопки "Отмена" напишем код:
procedure TForm2.Button2Click(Sender: TObject);
begin
//отменяем изменения в главной таблице
form1.ADOQuery1.Cancel;
//закрываем форму
Close;
end;
Форма редактирования студента

Для кнопки "Сохранить" напишем код:
procedure TForm3.Button1Click(Sender: TObject);
//переменная для записи номера активной группы в дереве
var id:integer;
begin
//запоминаем номер активной группы
id:=form1.ADOQuery1.FieldByName('nom_grup').AsInteger;
//если номер студенческого не пустой
if form1.ADOTable1.FieldByName('nom_stud').AsString<>'' then
begin
//сохраняем изменения в подчиненной таблице
form1.ADOTable1.Post;
//обновляем главную таблицу в дереве
form1.ADOQuery1.Requery();
//ставим указатель на запись с сохраненным номером группы
form1.ADOQuery1.Locate('nom_grup',id,[]);
//закрываем форму
Close;
end;
end;
Для кнопки "Отмена" напишем код:
procedure TForm3.Button2Click(Sender: TObject);
begin
//отменяем изменения в подчиненной таблице
form1.ADOTable1.Cancel;
//закрываем форму
Close;
end;
Вопросы для самоконтроля
1. Какие преимущества имеет отображение таблиц БД в виде дерева?
2. Какие изменения надо сделать в главной таблице для корректного отображения наличия подчиненных записей в дереве?
3. Какой компонент позволяет отобразить данные БД в виде дерева? Его основные свойства.
4. В каком событии нужно писать код для настройки параметров дерева?
5. Как при настройке параметров дерева определить, к какой таблице принадлежит активный узел в дереве?
6. Как прочитать значение нужного поля активного узла в дереве?
7. Как возле активного узла дерева отобразить иконку?