Link to home
Start Free TrialLog in
Avatar of hrvica5
hrvica5Flag for Croatia

asked on

Delphi, TreeView

Hi i have one function for finding root and formshow procedure
i need much simpler Syntax

Dat.q(q)(q) is TadoDataset

function FindRootNode(ACaption: String; ATreeView: TTreeView): TTreeNode;
var LCount: Integer;
begin
  result := nil;
  LCount := 0;
  while (LCount < ATreeView.Items.Count) and (result = nil) do
  begin                                              // and (ATreeView.Items.Item[LCount].Parent = nil)
    if (ATreeView.Items.Item[LCount].Text = ACaption) then
      begin
         result := ATreeView.Items.Item[LCount];
      end;
    inc(LCount);
  end;
end;

procedure TDomainObjectsOperatorLocations.FormShow(Sender: TObject);
var i,j:Integer;
    LDestNode, ObjectNode, OperatorNode, LocationNode, DeviceNode: TTreeNode;
begin
  inherited;
  Dat.q.Close;
  Dat.q.CommandText:= ' Select * from domains';
  Dat.q.Open;
  TreeView1.Items.Clear;
  Dat.q.First;
  i:=0;
  while not Dat.q.Eof do
    begin                                                      // +' '+Dat.q.FieldByName('NAME').AsString
      TreeView1.Items.Add(nil,Dat.q.FieldByName('CODE').AsString); // +' '+Dat.q.FieldByName('NAME').AsString
      LDestNode:= FindRootNode(Dat.q.FieldByName('CODE').AsString, TreeView1);
      Dat.qq.Close;
      Dat.qq.CommandText:= 'Select objects.code, objects.name, objects.domaincode, domains.name as domainname';
      Dat.qq.CommandText:= Dat.qq.CommandText + ' from objects left join domains on objects.domaincode=domains.code';
      Dat.qq.CommandText:= Dat.qq.CommandText + ' where objects.domaincode='+QuotedStr(Dat.q.FieldByName('CODE').AsString);
      Dat.qq.Open;
      //objects
      if Dat.qq.RecordCount > 0 then
         begin
           TreeView1.Items.AddChild(LDestNode, 'Objects');
           Dat.qq.First;
           i:=0;
           while not Dat.qq.eof do
              begin
                    ObjectNode:=FindRootNode('Objects', TreeView1);                       //  +' '+Dat.qq.FieldByName('NAME').AsString
                    TreeView1.Items.AddChild(ObjectNode, Dat.qq.FieldByName('CODE').AsString);
                    //locations
                    Dat.qqq.Close;
                    Dat.qqq.CommandText:= 'Select * from vlocations where objectCode='+QuotedStr(Dat.qq.FieldByName('CODE').AsString);
                    Dat.qqq.Open;
                    if Dat.qqq.RecordCount > 0 then
                       begin                                                             // +' '+Dat.qq.FieldByName('NAME').AsString
                             ObjectNode:= FindRootNode(Dat.qq.FieldByName('CODE').AsString, TreeView1);
                             TreeView1.Items.AddChild(ObjectNode, 'Locations');
                             j:=0;
                             while not Dat.qqq.eof do
                               begin
                                 LocationNode:=  FindRootNode('Locations', TreeView1);                    // +' '+Dat.qqq.FieldByName('NAME').AsString
                                 TreeView1.Items.AddChild(LocationNode, Dat.qqq.FieldByName('CODE').AsString);
                                 j:=j+1;
                                 if j>3  then
                                   Break;
                                   Dat.qqq.Next;
                               end;
                       end;

                    //devices
                    Dat.qqq.Close;
                    Dat.qqq.CommandText:= 'Select * from vDevices where objectCode='+QuotedStr(Dat.qq.FieldByName('CODE').AsString);
                    Dat.qqq.Open;
                    if Dat.qqq.RecordCount > 0 then
                       begin                                                             // +' '+Dat.qq.FieldByName('NAME').AsString
                             DeviceNode:= FindRootNode(Dat.qq.FieldByName('CODE').AsString, TreeView1);
                             TreeView1.Items.AddChild(DeviceNode, 'Devices');
                             j:=0;
                             while not Dat.qqq.eof do
                               begin
                                 LocationNode:=  FindRootNode('Devices', TreeView1);
                                 TreeView1.Items.AddChild(LocationNode, Dat.qqq.FieldByName('CODE').AsString);
                                 j:=j+1;
                                 if j>3  then
                                   Break;
                                 Dat.qqq.Next;
                               end;
                       end;

                  i:=i+1;
                  if i > 3  then
                     Break;
                  Dat.qq.Next;
              end;
         end;
      //operators
      Dat.qq.Close;
      Dat.qq.CommandText:= 'Select * from vOperators where DomainCOde ='+QuotedStr(Dat.q.FieldByName('CODE').AsString);
      Dat.qq.Open;
      if Dat.qq.RecordCount > 0 then
         begin
           TreeView1.Items.AddChild(LDestNode, 'Operators');
           Dat.qq.First;
           i:=0;
           while not Dat.qq.eof do
              begin
                  OperatorNode:=FindRootNode('Operators', TreeView1);                     //  +' '+Dat.qq.FieldByName('NAME').AsString
                  TreeView1.Items.AddChild(OperatorNode, Dat.qq.FieldByName('CODE').AsString);
                  i:=i+1;
                  if i > 3  then
                     Break;
                  Dat.qq.Next;
              end;
         end;
      Dat.Q.Next;
    end;
end;

Thx, I need simpler solution for this
Avatar of Geert G
Geert G
Flag of Belgium image

wouldn't it to be easier to let the database do all the work ?
if you have 1 query with the level, the parent id, the caption and optional info

it's just a straight forward running through the query and adding nodes or child nodes
this is a sample with an oracle table
create table test (id number, parent_id number, caption varchar2(200), info varchar2(200));

insert into test (id, parent_id, caption, info) values (1, null, 'Node 1', '');
insert into test (id, parent_id, caption, info) values (2, null, 'Node 2', '');
insert into test (id, parent_id, caption, info) values (3, null, 'Node 3', '');
insert into test (id, parent_id, caption, info) values (4, null, 'Node 4', '');
insert into test (id, parent_id, caption, info) values (5, null, 'Node 5', '');
insert into test (id, parent_id, caption, info) values (6, 1, 'Node 1.1', '');
insert into test (id, parent_id, caption, info) values (7, 1, 'Node 1.2', '');
insert into test (id, parent_id, caption, info) values (8, 2, 'Node 2.1', '');
insert into test (id, parent_id, caption, info) values (9, 4, 'Node 4.1', '');
insert into test (id, parent_id, caption, info) values (10, 5, 'Node 5.1', '');
insert into test (id, parent_id, caption, info) values (11, 8, 'Node 2.1.1', '');
insert into test (id, parent_id, caption, info) values (12, 8, 'Node 2.1.2', '');
insert into test (id, parent_id, caption, info) values (13, 11, 'Node 2.1.1.1', '');
insert into test (id, parent_id, caption, info) values (14, 9, 'Node 4.1.1', '');
insert into test (id, parent_id, caption, info) values (15, 9, 'Node 4.1.2', '');
insert into test (id, parent_id, caption, info) values (16, 10, 'Node 5.1.1', '');
insert into test (id, parent_id, caption, info) values (17, 10, 'Node 5.1.2', '');
insert into test (id, parent_id, caption, info) values (18, 16, 'Node 5.1.1.1', '');
 

select level, id, parent_id, caption, info
from test
start with parent_id is null
connect by prior id = parent_id 

Open in new window


the output looks like this, oracle database sorts this automatically
LEVEL,ID,PARENT_ID,CAPTION,INFO
1,1,,Node 1,
2,6,1,Node 1.1,
2,7,1,Node 1.2,
1,2,,Node 2,
2,8,2,Node 2.1,
3,11,8,Node 2.1.1,
4,13,11,Node 2.1.1.1,
3,12,8,Node 2.1.2,
1,3,,Node 3,
1,4,,Node 4,
2,9,4,Node 4.1,
3,14,9,Node 4.1.1,
3,15,9,Node 4.1.2,
1,5,,Node 5,
2,10,5,Node 5.1,
3,16,10,Node 5.1.1,
4,18,16,Node 5.1.1.1,
3,17,10,Node 5.1.2,

Open in new window


why not use a stack to push/pop parentnodes while you build the tree ?
i'll build a test with a tree view
SOLUTION
Avatar of Geert G
Geert G
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of hrvica5

ASKER

some exception pop upped


Thx
exception.jpg
is your data sorted correctly ?
Avatar of hrvica5

ASKER

I used yours, for example
exc.jpg
that data isn't sorted correctly
i guess you're not using oracle ?

no point in using my sample with a stack if the data isn't sorted
Avatar of hrvica5

ASKER

i'm using mssql.

i'll try to sort correctly.

thx
always best to add mssql when you need a query made for that environment

i don't have an mssql at my fingertips for testing
Avatar of hrvica5

ASKER

Hi i sorted,


Thank you very much you helped me like always.
but for my needs i need to make some procedure / view and sort that way my tables

Thx, Hrvica
sort.jpg
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of hrvica5

ASKER

Sory I tried now with more datas and i get wrong values
I put in attach how my tabel is sorted
and jpeg how it looks in treview

thx
sort.xls
TreeView.jpg
No need to use parentid anymore. I modified example to use your string parentNode notation. Then oder of sql is going:

select code, node, parentNode form table order by parentNode, node;

procedure FindRootNode(RootNode: TTreeNode; FindIdStr: AnsiString; var node: TTreeNode);
var
  i: Integer;
  sNodeStr: AnsiString;
begin
  if Assigned(RootNode) then
  begin
    sNodeStr := String(PAnsiChar(RootNode.Data));
    if sNodeStr = FindIdStr then
    begin
      node := RootNode;
      Exit;
    end;

    for i := 1 to RootNode.Count do
    begin
      sNodeStr := String(PAnsiChar(RootNode.Item[i-1].Data));
      if sNodeStr = FindIdStr then
      begin
        node := RootNode.Item[i-1];
        Break;
      end
      else
        FindRootNode(RootNode.Item[i-1], FindIdStr, node);

      if Assigned(node) then Break;
    end;
  end;
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  Node, rootNode: TTreeNode;
  parentId, nodeId: AnsiString;
  ptr: PAnsiChar;
begin
  data.First; //sort by parentid, id

  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Clear;

    while not data.Eof do
    begin
      parentId := data.FieldByName('parentNode').AsString;
      nodeId := data.FieldByName('node').AsString;

      rootNode := nil;
      FindRootNode(TreeView1.Items.GetFirstNode, parentId, rootNode);

      ptr := GetMemory(Length(nodeId)+1); //null termnated string
      ZeroMemory(ptr, Length(nodeId)+1);
      StrPCopy(ptr, nodeId);

      if Assigned(rootNode) then
        Node := TreeView1.Items.AddChildObject(rootNode, data.FieldByName('code').AsString, ptr)
      else
        Node := TreeView1.Items.AddObject(rootNode, data.FieldByName('code').AsString, ptr);

      data.Next;
    end;
  finally
    TreeView1.Items.EndUpdate;
  end;
end;

Open in new window


... on creating node I allocate memory for null terminated string and this memory should be released on delete  node. So add OnDeletation event to TreeView:

procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode);
var
  ptr: Pointer;
begin
  ptr := Node.Data;
  if Assigned(ptr) then
  begin
    Node.Data := nil;
    FreeMemory(ptr);
    ptr := nil;
  end;
end;

Open in new window

Avatar of hrvica5

ASKER

Thank you very much for soultions but I have 2 problems

TUC is ok,
but under first node BRE, it is not OK (treeview.jpg)

2. If I have 1000 rows it slow is slow (duration.jpg)


Thank you very much and sory
duration.jpg
TreeView.jpg
Ahhh. It is slow because of bad finding root node algorithm. Made some changes. Now I extract part of finding root to get top most root, then go deeper and find next root same way.

function GetLevelId(FindIdStr: AnsiString; iLevel: Integer): String;
var
  i, j: Integer;
begin
  Result := '';

  j := 0;
  for i:=1 to Length(FindIdStr) do
  begin
    if j<=iLevel then
    begin
      if FindIdStr[i] = '.' then
      begin
        Inc(j); //count points
      end
      else
        Result := FindIdStr[i] + Result;
    end
    else
      Break;
  end;
end;

procedure FindRootNode(RootNode: TTreeNodes; FindIdStr: AnsiString; iLevel: Integer;
  var node: TTreeNode);
var
  i: Integer;
  sNodeStr, FindIdRoot: AnsiString;
  bGoDeeper: Boolean;
begin
  if Assigned(RootNode) then
  begin
    //don't compare all nodes but use find first root logic
    FindIdRoot := GetLevelId(FindIdStr, iLevel); //get pre-root id
    bGoDeeper := Length(FindIdRoot) < Length(FindIdStr); //need to go deeper in tree

    //find first id for root and go deeper using this line
    for i := 1 to RootNode.Count do
    begin
      sNodeStr := String(PAnsiChar(RootNode.Item[i-1].Data));
      if sNodeStr = FindIdRoot then //compare to root
      begin
        if bGoDeeper then
        begin
          FindRootNode(TTreeNodes(RootNode.Item[i-1]), FindIdStr, iLevel+1, node); //go deeper to level+1
        end
        else
        begin
          node := RootNode.Item[i-1];
          Break;
        end;
      end;

      if Assigned(node) then Break;
    end;
  end;
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  Node, rootNode: TTreeNode;
  parentId, nodeId: AnsiString;
  ptr: PAnsiChar;
begin
  data.First; //sort by parentid, id

  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Clear;

    while not data.Eof do
    begin
      parentId := data.FieldByName('parentNode').AsString;
      nodeId := data.FieldByName('node').AsString;

      rootNode := nil;
      FindRootNode(TreeView1.Items, parentId, 0, rootNode); //always go from level 0

      ptr := GetMemory(Length(nodeId)+1); //null termnated string
      ZeroMemory(ptr, Length(nodeId)+1);
      StrPCopy(ptr, nodeId);

      if Assigned(rootNode) then
        Node := TreeView1.Items.AddChildObject(rootNode, data.FieldByName('code').AsString, ptr)
      else
        Node := TreeView1.Items.AddObject(rootNode, data.FieldByName('code').AsString, ptr);

      data.Next;
    end;
  finally
    TreeView1.Items.EndUpdate;
  end;
end;

procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode);
var
  ptr: Pointer;
begin
  ptr := Node.Data;
  if Assigned(ptr) then
  begin
    Node.Data := nil;
    FreeMemory(ptr);
    ptr := nil;
  end;
end;

Open in new window


fixed finding right node too.
fwiw,
the whole point of sorting and the stack is that it's not necessary to look for any parent/sub/child node
there is no point in sorting (or using a stack) when you do the lookups and move around in the tree

so if lookups are done, don't sort the data
> sorting the data slows down retreiving the query

use either the sorting+stack
or the lookups

don't use both !
Avatar of hrvica5

ASKER

Thank you very much