Правильная ссылка на эту страницу
http://az-design.ru/Support/DataBase/DBTree2/2600.shtml

Отображение древовидных структур

Архангельский Андрей

       Конечно, для отображения древовидных структур есть специальные компоненты, например, IB_TreeView в библиотеке IBObjects. С одной стороны, делать практически ничего не нужно:
       — разместить компонент IB_TreeView на форме,
       — разместить на форме компоненты IB_Query и IB_Datasource. В компоненте IB_Query заполнить свойство SQL следующим запросом:

  Select * from People where PID<>0;

       — в свойстве IB_TreeView.Dataset указать соответствующий Dataset,
       — в свойстве IB_TreeView.Field_Data указать имя поля, которое будет отображатся как строка дерева,
       — в свойстве IB_TreeView.Field_Key указать имя поля, являющееся уникальным идентификатором. В данном случае PID.
       — в свойстве IB_TreeView.Field_Master указать имя поля, указывающего на родителя. В данном случае Parent.
       — и при создании формы загрузить древовидную структуру в дерево:

procedure TForm1.FormCreate(Sender: TObject); {Example00IBO}
Var
   db : String;
   ps : Integer;
begin
   If IB_Connection.Connected then IB_Connection.Disconnect else Begin
      db := ExtractFilePath(Application.ExeName);
      db := ReverseString(db);  Delete(db,1,1);
      ps := Pos('\',db);  Delete(db,1,ps);
      db := ReverseString(db)+'\DB_Tree.fb';
      IB_Connection.Database := db;
      IB_Connection.Username := 'SYSDBA';
      IB_Connection.Password := 'masterkey';
      IB_Connection.Connect;
   End;
   qrTreePeople.Close;
   qrTreePeople.SQL.Clear;
   qrTreePeople.SQL.Add('Select * from People where PID<>0');
   qrTreePeople.Active := True;
   IB_TreeView.LoadData;
End;

       И вот что их этого получилось:


Рис.1-4 Отображение дерева с помощью компонента TIB_TreeView

       У этого способа одно достоинство — все очень просто — и два недостатка — маленький и большой;
       Маленький недостаток заключается в том, что отображается ТОЛЬКО одно поле из таблицы, а также то что невозможно сортировать значения внутри узла
       Большой недостаток заключается в том, что данный компонент больше ни на что не годится.
       Поэтому будем строить дерево самостоятельно, с учетом различных возможностей, которые требуются при работе с деревом. Для примера будем использовать штатный компонент TreeView из закладки Win32.
       Отображение древовидных структур состоит из двух этапов:
       1) Отображение корневых узлов структуры
       2) Раскрытие выбранного узла дерева
       Таким образом, даже для очень большого дерева получается максимальное быстродействие — дерево никогда не раскрывается полностью, расскрываются только выбранные узлы, всегда отображается текущее состояние таблицы в БД.
       Несколько замечаний по реализации.
       В статье Д.Кузьменко говориться, что в узле необходимо хранить "некоторую структуру данных", в которой содержаться идентификатор записи, идентификатор родительской записи и количество дочерних записей. Это не совсем так. Конечно есть случаи, когда вместе с узлом нужно хранить некоторую, связанную с ним информацию. Но чаще можно обойтись без дополнительной структуры в поле Data.
       Во-первых, количество детей узла хранить необязательно, так как TTreeNode имеет свойство Count, которое и хранит количество детей.
       Во-вторых, хранить идентификатор родительского узла тоже необязательно, так как TTreeNode имеет свойство Parent, которое как раз и указывает на родительский узел.
       Таким образом единственный параметр, которых обязательно хранить в узле — это идентификатор текущей записи (PID). И здесь можно пойти на хитрость.
       Было замечено, что на больших деревьях использование картинок для отображений при узлах резко замедляет отображение дерева. (Это исправлено в последних версиях Delphi. По крайней мере, Raize Components v.4.0 скорость отображения не зависит от картинок.) Причем размер картинок не имеет значение, замедление начинается уже при включении режима отображения с картинками. Таким образом, если отказаться от картинок, то можно использовать два свойства TTreeNode для хранения своей информации, не прибегая к хранению структуры в поле Data, за распределением памяти для которой к тому же необходимо следить самостоятельно.
       Итак, свойство TTreeNode.ImageIndex можно использовать для хранения идентификатора текущей записи (PID) если он имеет тип Integer.
       Свойство TTreeNode.OverlayIndex можно использовать для хранения каких-нибудь признаков имеющих тип Integer, которые можно использовать, например, для раскраски элементов дерева.
       И, наконец, Д.Кузменко в своей статье приводит вариант процедуры, которая отображает дерево в обоих случаях — и корневые узлы, и раскрытие текущего узла. Это возможно для простого дерева, но запутывает текст программы для сложных деревьев. Поэтому, для сохранения ясности текста для отображения дерева используется две процедуры.
       Первая процедура PeopleDatasetOpen() полностью обновляет состояние дерева от корня. Она не привязана к какому-то событию и может вызываться как из события CreateForm, так и из других частей кода, когда нужно выполнить "полный рестарт" дерева.

procedure TForm1.PeopleDatasetOpen(); {Example01}
Var
   RowChild   : Integer;
   sFld,sCod  : String;
   ChildNode,NewNode : TTreeNode;
begin
  inherited;
  If qrTVPeople.Database.Connected then
     begin
01      tvPeople.Items.Clear;
02      tvPeople.Items.BeginUpdate;
03      trTVPeople.Active := True;
04      qrTVPeople.Close;   qrTVPeople.SQL.Clear;
05      qrTVPeople.SQL.Add('Select * from People');
06      qrTVPeople.SQL.Add(' where Parent=0 and PID<>0');
07      qrTVPeople.SQL.Add(' order by POrder');
08      qrTVPeople.Open;     qrTVPeople.First;
09      While not qrTVPeople.EOF do begin
10           sCod := '';
11           If not VarIsNull(qrTVPeople.FieldValues['PStatus']) then begin
12              sCod := qrTVPeople.FieldValues['PStatus'];
13              sCod := sCod + ' - ';
14              end;
15           sFld := qrTVPeople.FieldValues['PSurName'];
16           NewNode := tvPeople.Items.Add(tvPeople.TopItem,sCod+sFld);
17           NewNode.ImageIndex := qrTVPeople.FieldValues['PID'];
18           RowChild := qrTVPeople.FieldValues['PCount'];
19           If RowChild>0 then 
20            ChildNode := tvPeople.Items.AddChild(NewNode,IntToStr(RowChild));
21           qrTVPeople.Next;
22        end; // While not qrTVPeople.EOF do
23        tvPeople.Items.EndUpdate;
24        tvPeople.Update;
25        qrTVPeople.Close;      trTVPeople.Active := False;
    end; // qrTVPeople.Database.Connected
end;

       Строка 01 полностью очищает TreeView.
       Строки 04-08 подготавливают и открывают DataSet. Здесь вы должны написать тот запрос, который отображает корень дерева.
       В строках 09-22 просматривается весь набор и для каждой строки набора рисуется веточка дерева.
       Строки 11-16 показывают, что ветка дерева может не соответствовать какому-либо полю. Дело в том что при создании узла на TreeView в узел передается текстовая переменная, которая отображается как ветка дерева. А раз это так, то можно сформировать эту строку так как хочется, в том числе и как комбинацию полей из набора, и зависящую от конкретных полей и сформированную по любым правилам.
       И, наконец, строка 16 добавляет новый узел в корень (tvPeople.TopItem) дерева. А строка 17 записывает в свойство ImageIndex идентификатор записи (PersonID).
       Строки 18-20 определяют из набора есть ли у этой строки дети, и если количество детей больше нуля, то создается дочерний узел, у которого в качестве текста записано число детей. Нет необходимости сразу создавать всех детей. Один узел уже устанавливает свойство NewNode.HasChildren в True. Однако, как вариант можно не создавать узел, а установить это свойство самостоятельно. Кому как нравится. Д.Кузьменко предлагает устанавливать свойство. Но, при обновлении узла потребуется удалить существующих детей и построить их заново. В случае, если свойство HasChildren будет установлено в True, а реальных детей не будет, то будет возникать ошибка. Поэтому лучше добавлять узел-болванку в качестве детей, чем устанавливать свойство NewNode.HasChildren в True.
       Строка 21 переходит к следующей строке набора и все повторяется.
       Вторая процедура записывается как обработчик события TreeView.onExpanding:

procedure TForm1.tvPeopleExpanding(Sender: TObject; Node: TTreeNode; 
                                   var AllowExpansion: Boolean); {Example01}
Var
   RowChild : Integer;
   sFld,sCod : String;
   ChildNode,NewNode : TTreeNode;
begin
  inherited;
01   If Node.HasChildren then begin
02      Node.DeleteChildren;
03      trTVPeople.Active := True;
04      qrTVPeople.Close;  qrTVPeople.SQL.Clear;
05      qrTVPeople.SQL.Add('Select * from People');
06      qrTVPeople.SQL.Add(' where Parent='+IntToStr(Node.ImageIndex));
07      qrTVPeople.SQL.Add(' order by POrder');
08      qrTVPeople.Open;  qrTVPeople.First;
09      tvPeople.Items.BeginUpdate;
10      While not qrTVPeople.EOF do Begin
11         sCod := '';
12         If not VarIsNull(qrTVPeople.FieldValues['PStatus']) then begin
13            sCod := qrTVPeople.FieldValues['PStatus'] + ' - ';
14            end;
15         sFld := qrTVPeople.FieldValues['PSurName'];
16         newNode := tvPeople.Items.AddChild(Node,sCod+sFld);
17         newNode.ImageIndex := qrTVPeople.FieldValues['PID'];
18         RowChild := qrTVPeople.FieldValues['PCount'];
19         If RowChild>0 then 
20            ChildNode := tvPeople.Items.AddChild(NewNode,IntToStr(RowChild));
21         qrTVPeople.Next;
22      end; // While not qrTVPeople.EOF do
23         tvPeople.Items.EndUpdate;
24         trTVPeople.Active := False;
25   end;// Node.HasChildren
26   tvPeople.Update;
end;

       Строка 01 определяет есть ли у узла дети и в положительном случае начинает раскрывать узел
       Строка 02 удаляет удаляет детей если они есть.
       Взяв из Node.ImageIndex идентификатор элемента, строки 04-08 строят запрос и открывают набор данных.
       В остальном все так же как в предыдущей процедуре, только новые дети добавляются не в корень дерева, в текущий узел (строка 16).

       Следует отметить, что при использовании компонента TQuery из библиотеки IBX возникает проблема. Свойство TQuery.RecordCount на самом деле показывает не число строк в результирующем наборе, а номер текущей строки. В этом смысле оно равносильно свойству TQuery.RecNo. Поэтому для перебора строк приходится использовать свойство TQuery.EOF. При использовании библиотеки IBO таких проблем нет и свойство TIB_Query.RecordCount работает правильно.

       То, что из этого получилось, показано на рисунке ниже:


Рис.1-5 Отображение структуры организации на дереве

© 01.08.2009, Архангельский А.Г.

<<Пред. Оглавление
Начало раздела
След.>>




Дата последнего изменения:
Thursday, 21-Aug-2014 09:10:44 MSK


Постоянный адрес статьи:
http://az-design.ru/Support/DataBase/DBTree2/2600.shtml