Delphi. Немного о TreeView.

27.03.2017

TreeView довольно удобный компонент для создания иерархических структур.
Это может быть абсолютно любой набор данных, в котором есть родительский и подчиненные узлы.
В этой статье будет рассмотрен процесс наполнения дерева данными из БД, таблицы которой связаны по внешнему ключу определяемому разработчиком.
В приведенном ниже примере рассмотрим ручное наполнение TreeView и наполение с помощью запросов к базе данных, визуальное оформление и формирование данных передаваемых узлом. В данном примере используются как первичный(primary key), так и вторичный или внешний (foreign key) ключ записи.
Для соединения с базой MySQL я использую компоненты из набора Zeos (ZConnection).
Определившись с постановкой задачи начинаем ваять проект.
Положим на форму компонент TreeView (в моем случае это sTreeView — не принципиально) и добавим компоненты для связи с БД и компонент ImageList, в котором будем хранить нужные для отображения в узлах дерева картинки.
Изображение с индексом =0 используется для всех узлов по умолчанию, поэтому при наполнении дерева необходимо узлам назначать изображения, если хотите видеть разные в различных пунктах. Сразу замечу, что назначив изображения пунктам дерева с помощью параметра node.ImageIndex, — вы не избавитесь от его автоматической замены на картинку с индексом=0 из ImageList, — для этого необходимо использовать для узла параметр node.SelectedIndex равный ImageIndex выбранный для узла.
В рассмотреном примере Вы увидите как выделить доступный узел среди всех остальных.  Для начала объявим переменные и типы которые будут использоваться:

 
var Form1: TForm1; 
Node: TTreeNode; 
pch_node,pd_node:ttreenode; 
cur_pch,cur_pd,user_pch,user_pd,i,j:integer; 

Здесь pch_node — узел верхнего уровня, pd_node — подчиненные узлы.
cur_pch — будет использовано для получения кода главного узла
cur_pd — будет использовано для получения кода подчиненного объекта
user_pch,user_pd — коды, в данном случае, предприятия и цеха определенные заранее для пользователя.

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

//Корневой узел
pch_node:= sTreeView1.Items.Add(nil,'Главный узел');
pch_node.Data:=Pointer(1);pch_node.ImageIndex:=2; pch_node.SelectedIndex:=2;
//Вложенные узлы
pd_node:= sTreeView1.Items.AddChild(pch_node,'Подчиненный-1');  pd_node.Data:=Pointer(11);
pd_node:= sTreeView1.Items.AddChild(pch_node,'Подчиненный-2');  pd_node.Data:=Pointer(12);  pd_node.ImageIndex:=1;
pd_node:= sTreeView1.Items.AddChild(pch_node,'Подчиненный-3');  pd_node.Data:=Pointer(13);

Обратите внимание на использование ImageIndex и SelectedIndex.
В первой строке им заданы значения и они равны — это даст для верхнего уровня иконку из ImageList с индексом 2 и указывает, что при выборе этого узла дерева иконка останется та же.
Во второй и четвертой строках эти параметры не заданы — следовательно они в любом случае будут отображатся с картинкой заданной индексом равным нулю.
Третья строка нам показывает, что для узла задана иконка с индексом равным 1 , но не задано значение SelectedIndex, а значит при выборе этого узла, — его иконка заменится на иконку с индексом = 0.
Если Вы внимательно следите за текстом, то наверняка заметили, что ничего не сказано про параметр .Data:=Pointer()
Этим кодом для объекта дерева присваеваем ему некое значение, в данном случае ключ, которое будет получено при его выборе.
Со структурой дерева и некоторыми его параметрами определились.
Теперь научимся связывать БД с TreeView.
Данные о предприятии (в этом примере) находятся в одной таблице базы данных, а данные по цехам предприятий — в другой. Связь между таблицами я организовал через использование первичного и вторичного ключей.
Так, в таблице предприятий есть поле FK, содержащее номер предприятия,а в таблице цехов предприятия поле pkpch — содержащее номер предприятия, их и слинковываю.
*
Конечно было бы правильнее связывать поле pkpch из таблицы цехов (таблица с именем pd) с первичным ключем таблицы предприятий (таблица с именем pch), но я допустил ошибку при заполнении таблицы цехов вписав в поле pkpch таблицы pd реальные номера предприятий не обратив внимания на первичные ключи pch, в связи с чем в таблице pch и добавил поле FK. Ну это не существенно…
*
Наполняем дерево объектами из базы:

 // дерево объектов
  zquery1.Close;
    zquery1.sql.Clear;
   with zquery1 do
    begin
    if user_pch<>0 then begin // условие ограничивающее пользователя доступом только по предприятию
    sql.Add('select * from pch where fk= :pP');
    Params[0].Value:=user_pch;
                            end;
    if user_pch=0 then begin  // условие разрещающее видеть все предприятия
    sql.Add('select * from pch ');
                             end;
    open;
     end;

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

while not ZQuery1.Eof do begin
// отсюда берем имя ПЧ для главного узла
pch_node:= sTreeView1.Items.Add(nil,zquery1.FieldValues['pch']);  pch_node.Data:=Pointer(integer(zquery1.FieldValues['fk']));  pch_node.ImageIndex:=2;  pch_node.SelectedIndex:=2;

Заполняется имя предприятия (имя ПЧ), устанавливаются параметры отображения иконки и присваивается код для узла.
Узел сформирован. Создаем список цехов для предприятия.

// выбираем ПД для текущего узла
zquery2.Close;
zquery2.sql.Clear;
with zquery2 do
begin
sql.Add('select * from pd where pkpch= :pPch and rwcode= :pR');
Params[0].Value:=zquery1.FieldValues['fk'];
Params[1].Value:=rw;
open;
end;
while not ZQuery2.Eof do  begin
// отсюда берем имя ПД для  узла
pd_node:= sTreeView1.Items.AddChild(pch_node,zquery2.FieldValues['pd']);  pd_node.Data:=Pointer(integer(zquery2.FieldValues['pk']));
// настройка дерева по условию  разграничения доступа пользователей
if integer(pd_node.Data)=user_pd then begin   // вход в свое ПД
                      pd_node.ImageIndex:=0;   pd_node.SelectedIndex:=0;
                      pch_node.ImageIndex:=2;  pch_node.SelectedIndex:=2;
                      end else begin
                      if user_pd<>0 then begin
                      // вход в чужое ПД
                      pd_node.ImageIndex:=1;  pd_node.SelectedIndex:=1; pd_node.Data:=Pointer(0);
                      pch_node.ImageIndex:=2; pch_node.SelectedIndex:=2;
                      end else begin
                      // вход с уровнем ПЧ или П
                      pd_node.ImageIndex:=0;  pd_node.SelectedIndex:=0;
                      pch_node.ImageIndex:=2; pch_node.SelectedIndex:=2;
                                end;
                                       end;
ZQuery2.Next;
end;

На приведенном скрине  показано как выглядит полное дерево.
Один из узлов раскрыт для наглядности.
Ниже приведен цикл заполнения цехов вместе с распределением прав доступа к данным связанным с узлом.
Для данных которые недоступны текущему пользователю устанавливаю node.Data:=Pointer(0) и при обращении пользователя — не возвращается никаких данных если ключ равен нулю..
И завершаем начатый цикл перебора предприятий:

ZQuery1.Next;
                  end;

Осталось только обработать событие смены объекта дерева пользователем:

rocedure TForm1.sTreeView1Change(Sender: TObject; Node: TTreeNode);
begin
   if sTreeView1.Selected.Parent<>nil  then  begin
     cur_pch:= integer(sTreeView1.Selected.Parent.Data); сur_pd:= integer(sTreeView1.Selected.Data);
     end else begin cur_pch:= integer(sTreeView1.Selected.Data); end;
end;

С этим можете разобраться самостоятельно.
Здесь происходит получение значений используемых в запросах, в зависимости от размещения объекта в дереве.
А вот так  будет выглядеть TreeView, когда пользователю доступен для обработки связанных данных только один из дочерних узлов, при этом не отображаются предприятия к которым пользователь не имеет отношения.
Конечно, для недоступных узлов можно было использовать параметр Cut делающий иконку цвета неактиного элемента(серым), но мне захотелось сделать другой иконкой.

У Вас может быть совсем другая реализация. Это не эталон.
Возможно будут комментарии благодаря которым код станет лучше 🙂

Оставить комментарий