Работа с деревьями состоит из двух этапов:
1. Сохранение дерева в таблицу.
2. Считывание дерева из таблицы.
В этом разделе разберем первый этап. Щелкните дважды по компоненту PopupMenu1, который "привязан" к дереву, и создайте в нем следующие разделы:
· Создать главный раздел
· Добавить подраздел к выделенному
· Переименовать выделенный
· Удалить выделенный
· -
· Свернуть дерево
· Развернуть дерево
Все эти команды относятся к работе с разделами дерева. Прежде всего, создадим обработчик для команды "Создать главный раздел". Листинг процедуры смотрите ниже:
{Создать главный раздел}procedure TfMain.N1Click(Sender: TObject);var s: String; //для получения имени раздела (подраздела) NewRazd: TTreeNode; //для создания нового узла дереваbegin //вначале очистим s s:= ''; //Получим в s имя нового раздела: if not InputQuery('Ввод имени раздела', 'Введите заголовок раздела:', s) then Exit; //снимаем возможное выделение у дерева: TreeView1.Selected:= nil; //создаем главный раздел (ветвь): NewRazd:= TreeView1.Items.Add(TreeView1.Selected, s); //Сразу же сохраняем его в базу: tRazdels.Append; //добавляем запись tRazdels['R_Parent']:= 0; //не имеет родителя //присваиваем значение созданного раздела: tRazdels['R_Name']:= NewRazd.Text; //сохраняем изменения в базе: tRazdels.Post;end;Разберем код. Переменная NewRazd имеет тип TTreeNode, к которому относятся все разделы и подразделы (узлы) дерева. В текстовую переменную s с помощью функции InputQuery() мы получаем имя нового главного узла. Функция имеет три строковых параметра:
1. Заголовок окна.
2. Пояснительная строка.
3. Переменная, куда будет записан введенный пользователем текст.
Если переменная, передаваемая в качестве третьего параметра, пуста, то поле ввода будет пустым. Если же в ней содержался текст - он будет выведен как текст "по умолчанию". Функция возвращает True, если пользователь ввел (или изменил) текст, и False в противном случае.
В результате работы функции для пользователя будет выведено простое окно с запросом:
Окно функции InputQuery()
Далее строкой
TreeView1.Selected:= nil;мы снимаем выделение, если какой либо раздел был выделен, ведь мы создаем главный раздел, не имеющий родителя.
Свойство Selected компонента TreeView указывает на выделенный узел и позволяет производить с ним различные действия, например, получить текст узла:
TreeView1.Selected.Text;А присваиваемое значение nil (ничто) снимает всякое выделение, если таковое было. Далее мы создаем сам узел:
NewRazd:= TreeView1.Items.Add(TreeView1.Selected, s);Разберем эту строку подробней. Переменная NewRazd - это новый узел дерева. Каждый узел - объект, обладающий своими свойствами и методами. Все узлы хранятся в списке - свойстве Items дерева TreeView, а метод Add() этого свойства позволяет добавить новый узел. У метода два параметра - выделенный узел (у нас он равен nil) и строка текста, которая будет присвоена новому узлу. Таким образом, в дереве появляется новый главный узел.
Затем мы сохраняем его в базу данных, предварительно добавив в таблицу новую запись:
tRazdels.Append; //добавляем запись tRazdels['R_Parent']:= 0; //не имеет родителя //присваиваем значение созданного раздела: tRazdels['R_Name']:= NewRazd.Text; //сохраняем изменения в базе: tRazdels.Post;Такие методы, как Append или Insert автоматически переводят таблицу в режим редактирования, поэтому вызывать метод Edit излишне.
Обратите внимание на то, что мы сохраняем ноль в поле "R_ Parent ", так как это - главный раздел, не имеющий родителя. Свойство Text нового узла NewRazd содержит название нового узла, которое мы присваиваем полю "R_Name".
Далее сгенерируем процедуру для команды меню "Добавить подраздел к выделенному":
{Добавить подраздел к выделенному разделу(подразделу)}procedure TfMain.N2Click(Sender: TObject);var s: String; //для получения имени раздела (подраздела) z: String; //для формирования заголовка окна NewRazd: TTreeNode; //для создания нового узла дереваbegin //Проверим - есть ли выделенный раздел? //Если нет - выходим: if TreeView1.Selected = nil then Exit; //вначале очистим s s:= ''; //сформируем заголовок окна запроса: z:= 'Раздел "' + TreeView1.Selected.Text + '"'; //Получим в s имя нового раздела: if not InputQuery(PChar(z), 'Введите заголовок подраздела:', s) then Exit; //создаем подраздел: NewRazd:= TreeView1.Items.AddChild(TreeView1.Selected, s); //перед сохранением подраздела в базу, прежде получим //номер его родителя: Q1.SQL.Clear; Q1.SQL.Add('select * from Razdels where R_Name='''+ NewRazd.Parent.Text+''''); Q1.Open; //Теперь сохраняем его в базу: tRazdels.Append; //добавляем запись //присваиваем № родителя: tRazdels['R_Parent']:= Q1['R_Num']; //присваиваем название узла: tRazdels['R_Name']:= NewRazd.Text; //сохраняем изменения в базе: tRazdels.Post;end;Код этой процедуры очень похож на код предыдущей, но есть и отличия. Прежде всего, мы проверяем имеется ли выделенный раздел, т.к. фокус ввода мог быть на сетке DBGrid, когда пользователь щелкнул правой кнопкой по дереву, и выбрал эту команду. В этом случае, если не делать проверки, мы получим ошибку, пытаясь добавить дочерний узел к пустоте.
Далее, мы ввели строковую переменную z, чтобы сформировать запрос. Ведь пользователю будет удобней, если в окне InputQuery() он сразу увидит, к какому именно разделу он добавляет подраздел.
Затем, при добавлении дочернего узла вместо метода Add() мы используем метод AddChild().
Ну и, наконец, при сохранении узла в таблицу мы записываем не только созданный узел, но и номер его родителя, получив его с помощью запроса
Q1.SQL.Add('select * from Razdels where R_Name='''+ NewRazd.Parent.Text+'''');Запрос формирует набор данных с единственной строкой - записью родителя добавляемого элемента. Поле Q1['R_Num'], как вы понимаете, хранит номер этого родителя в запросе.
Код процедуры переименования выделенного раздела выглядит так:
{Переименовать выделенный раздел (подраздел)}procedure TfMain.N3Click(Sender: TObject);var s: String; //для получения имени раздела (подраздела) z: String; //для формирования заголовка окнаbegin //Проверим - есть ли выделенный раздел? //Если нет - выходим: if TreeView1.Selected = nil then Exit; //получаем текущий текст: s:= TreeView1.Selected.Text; //формируем заголовок: z:= 'Редактирование "' + s + '"'; //если не изменили, выходим: if not InputQuery(PChar(z), 'Введите новый заголовок:', s) then Exit; //находим эту запись в таблице, учитывая, что ее по каким то //причинам может и не быть: if not tRazdels.Locate('R_Name', TreeView1.Selected.Text, []) then begin ShowMessage('Ошибка! Указанный раздел не существует в таблице.'); Exit; end; //if //если до сих пор не вышли из процедуры, значит запись найдена, //и является текущей. изменяем ее: tRazdels.Edit; tRazdels['R_Name']:= s; tRazdels.Post; //теперь меняем текст выделенного узла: TreeView1.Selected.Text:= s;end;Следует обратить внимание на то, что вначале исправляется запись в таблице, и только потом - в узле. Если бы мы сначала исправили текст узла, то для поиска старой записи в таблице пришлось бы вводить дополнительную переменную для хранения старого текста.
Удаление выделенного узла:
{Удалить выделенный раздел (подраздел)}procedure TfMain.N4Click(Sender: TObject);var s: String; //для строки запросаbegin //Проверим - есть ли выделенный раздел? //Если нет - выходим: if TreeView1.Selected = nil then Exit; //иначе формируем строку запроса: s:= 'Удалить "' + TreeView1.Selected.Text + '"?'; //запросим подтверждение у пользователя: if Application.MessageBox(PChar(s), 'Внимание!', MB_YESNOCANCEL+MB_ICONQUESTION) <> IDYES then Exit; //если не вышли - пользователь желает удалить раздел. //найдем и удалим его вначале из таблицы: if tRazdels.Locate('R_Name', TreeView1.Selected.Text, []) then tRazdels.Delete; //теперь удаляем раздел из дерева: TreeView1.Items.Delete(TreeView1.Selected);end;Далее осталось сгенерировать процедуры для сворачивания и разворачивания дерева:
{свернуть дерево}TreeView1.FullCollapse; {развернуть дерево}TreeView1.FullExpand;Итак, метод FullCollase дерева TreeView сворачивает его узлы, а метод FullExpand разворачивает.
Сохраните проект и скомпилируйте его. Попробуйте заполнить дерево разделами и подразделами, убедитесь, что параллельно данные сохраняются и в таблице