Delphi. Немного о TreeView.
27.03.2017TreeView довольно удобный компонент для создания иерархических структур.
Это может быть абсолютно любой набор данных, в котором есть родительский и подчиненные узлы.
В этой статье будет рассмотрен процесс наполнения дерева данными из БД, таблицы которой связаны по внешнему ключу определяемому разработчиком.
В приведенном ниже примере рассмотрим ручное наполнение 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 делающий иконку цвета неактиного элемента(серым), но мне захотелось сделать другой иконкой.
У Вас может быть совсем другая реализация. Это не эталон.
Возможно будут комментарии благодаря которым код станет лучше 🙂