Icon View Thread

The following is the text of the current message along with any replies.
Messages 1 to 10 of 15 total
Thread Populating a treeview
Wed, Feb 9 2011 7:54 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

For years I've been populating treeviews from self referencing tables (eg ID & LinkToID) using simple recursive functions - essentially populating a branch at a time. Since generally the tables involved were small (think email mailboxes) it worked fast enough.

I now have a case where I have c10k nodes to populate and using the recursive approach it was taking c12 seconds which was not acceptable. My new approach takes less than 1/2 second. It can only work where the table has a numeric ID.

I'm "lucky" in that my table starts at 1 otherwise I'd have to adjust the code so not to many nodes were created initially.


procedure TForm1.Button1Click(Sender: TObject);
var
Cntr: integer;
MaxItems: integer;
Node: TELTreeItem;
begin
ToC.Items.BeginUpdate;
Manual.Open;
MaxItems := Manual.RecordCount - 1;
for Cntr := 0 to MaxItems do ToC.Items.Add(nil, IntToStr(Cntr));
while not Manual.Eof do begin
 Node := ToC.Items[Manual_ID.AsInteger - 1];
 Node.Text := Manual_Title.AsString;
 if not Manual_Link.IsNull then Node.Parent := ToC.Items[Manual_Link.AsInteger - 1];
 Node.Tag := Manual_ID.AsInteger;
 Manual.Next;
end;
for Cntr := MaxItems downto 0 do begin
 if ToC.Items[Cntr].Text = '' then ToC.Items.Delete(ToC.Items[Cntr]);
end;
ToC.Items.EndUpdate;
end;

The initial creating the nodes takes c70 ms, setting the data takes c400 ms.

As always any suggestions on how to improve it would be welcome.

Opening the table exclusive did help (overall time down to c300ms) but its not something I can do in a multi user environment.

Roy Lambert
Mon, Feb 14 2011 6:16 AMPermanent Link

John Hay

Roy

> For years I've been populating treeviews from self referencing tables (eg ID & LinkToID) using simple recursive
functions - essentially populating a branch at a time. Since generally the tables involved were small (think email
mailboxes) it worked fast enough.
>
> I now have a case where I have c10k nodes to populate and using the recursive approach it was taking c12 seconds which
was not acceptable. My new approach takes less than 1/2 second. It can only work where the table has a numeric ID.
>

12 seconds down to <  0.5, you are just being greedy!   Seriously though how many of the items have a parent (ie need to
be moved) ?.  If it's a high percentage then the TELtree (?) treeview seems very quick at moving nodes.

Am I right in thinking that if you ever delete records the procedure could blow up with range errors?

John


Mon, Feb 14 2011 8:15 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

John


ElTreeLite (and I presume heavy Smiley work in a different way to a standard TTreeView. Exactly how different I don't know but to give an example with a standard treeview I can't set the parent, I have to add children.

How many items have a parent? All but the top four! That makes it c9,500 Smiley

Without spending time trying to understand someone else's code I'm guessing that the nodes aren't actually moved and that its a paint thing. The parent is simple a TElTreeItem and the item list remains the same, in the same order.

<<Am I right in thinking that if you ever delete records the procedure could blow up with range errors?>>

I suddenly thought about that. Well really after building for the 8th time and realising it was getting longer and there were a lot of blank nodes I switched to a query.......

procedure TnlhOLMVForm.InitialBuildTree(const CatDir, ShowWhichBooks: string);
var
Cntr: integer;
FirstID: integer;
LastID: integer;
MaxItems: integer;
Node: TELTreeItem;
Qry: TEDBQuery;
sl: TStringList;
begin
olmSession.LocalConfigPath := CatDir;
ToC.Items.Clear;
Qry := TEDBQuery.Create(nil);
Qry.SessionName := 'olm';
Qry.DatabaseName := 'olm';
Qry.SQL.Text := 'SELECT MIN(_ID), MAX(_ID) FROM Contents';
Qry.ExecSQL;
FirstID := Qry.Fields[0].AsInteger;
LastID := Qry.Fields[1].AsInteger;
MaxItems := (LastID - FirstID) + 10;
Qry.Close;
Qry.SQL.Text := 'SELECT _ID, (_ID-' + IntToStr(FirstID) + ') AS ID, (_Link-' + IntToSTr(FirstID) + ') AS LINK, _Title FROM Contents';
if ShowWhichBooks <> '' then begin
 if 0 = Pos(',', ShowWhichBooks) then begin
  Qry.SQL.Text := Qry.SQL.Text + ' WHERE _Book = ' + QuotedStr(Trim(ShowWhichBooks));
 end else begin
  sl := TStringList.Create;
  sl.CommaText := ShowWhichBooks;
  Qry.SQL.Text := Qry.SQL.Text + ' WHERE ';
  for Cntr := 0 to sl.Count - 1 do begin
   Qry.SQL.Text := Qry.SQL.Text + '_Book = ' + QuotedStr(Trim(sl[Cntr]));
   if Cntr <> sl.Count - 1 then Qry.SQL.Text := Qry.SQL.Text + ' OR ';
  end;
  sl.Free;
 end;
end;
Qry.ExecSQL;
ToC.Items.BeginUpdate;
for Cntr := 0 to MaxItems do ToC.Items.Add(nil, '');
while not Qry.Eof do begin
 Node := ToC.Items[Qry.Fields[1].AsInteger];
 Node.Text := Qry.Fields[3].AsString;
 if not Qry.Fields[2].IsNull then Node.Parent := ToC.Items[Qry.Fields[2].AsInteger];
 Node.Tag := Qry.Fields[0].AsInteger;
 Qry.Next;
end;
for Cntr := MaxItems downto 0 do begin
 if ToC.Items[Cntr].Text = '' then ToC.Items.Delete(ToC.Items[Cntr]);
end;
ToC.Items.EndUpdate;
Qry.Close;
Qry.Free;
end;

Roy Lambert
Tue, Feb 15 2011 8:44 AMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Roy,

<< For years I've been populating treeviews from self referencing tables (eg
ID & LinkToID) using simple recursive functions - essentially populating a
branch at a time. Since generally the tables involved were small (think
email mailboxes) it worked fast enough.

I now have a case where I have c10k nodes to populate and using the
recursive approach it was taking c12 seconds which was not acceptable. My
new approach takes less than 1/2 second. It can only work where the table
has a numeric ID. >>

Are you trying to populate the entire treeview in one shot ?  I know you're
saying "populating a branch at a time", but it also seems like you're doing
the whole thing, based upon your code.

If you are trying to populate the entire tree, then I wouldn't do so.
Instead, populate only the current level that is visible, and use the Expand
events of the treeview to populate the children on-demand.

--
Tim Young
Elevate Software
www.elevatesoft.com
Tue, Feb 15 2011 10:27 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Tim

>If you are trying to populate the entire tree, then I wouldn't do so.
>Instead, populate only the current level that is visible, and use the Expand
>events of the treeview to populate the children on-demand.

That was my first thought. Its for my on-line user editable manual. So I thought figure out which component the mouse was over, look that up in the database and display back up the tree. I discarded it because only if a node has children attached does the expand/collapse button appear. This means that you have to click on a node to find out if children exist. Until you do there's no indication and I think a lot of people (apart from the click anything and everything brigade) will think that's the end of it.

Currently I'm thinking of moving to a different approach using TAdvExplorerTreeView (if I can figure out how to - TMS documentation must have improved since my licence lapsed)

Roy Lambert
Wed, Feb 16 2011 4:36 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Roy,

<< That was my first thought. Its for my on-line user editable manual. So I
thought figure out which component the mouse was over, look that up in the
database and display back up the tree. I discarded it because only if a node
has children attached does the expand/collapse button appear. This means
that you have to click on a node to find out if children exist. Until you do
there's no indication and I think a lot of people (apart from the click
anything and everything brigade) will think that's the end of it. >>

You can set the TTreeNode.HasChildren property to indicate whether there are
any children or not without actually populating them, and you can get the
count of the children very fast for the current node level.  None of this
involves actually populating the nodes, which is the time-consuming part.

--
Tim Young
Elevate Software
www.elevatesoft.com
Thu, Feb 17 2011 4:08 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Tim

>You can set the TTreeNode.HasChildren property to indicate whether there are
>any children or not without actually populating them, and you can get the
>count of the children very fast for the current node level. None of this
>involves actually populating the nodes, which is the time-consuming part.

That's roughly what I was doing (TElTreeLite is a bit different).

Going down the tree is great - just display the various "books" (4 of them) roughly 47ms.

Going UP the tree is a fraction more difficult Smiley I decided that from a given start point I wanted to display the selected nodes siblings, its parents and all books. All parents and books get an expand/collapse button unconditionally. Siblings and selected node would be tested for children. I'm still trying to figure out an efficient way to build the tree going upwards. All the functionality seems to be AddChild and none AddParent Smiley

Roy Lambert
Thu, Feb 17 2011 9:23 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Tim


Building a tree upwards is bad enough but it pales into insignificance with the extra coding involved to handle a second press of F1 in the app without the on-line manual having been closed and freed. So I decided I could just wipe and rebuild each time its fast enough at 40ms.

Now the fun really starts. I need a three state button - expand, collapse, show the bits that haven't been loaded yet Smiley

I think I'll stick with populating the whole tree (depending on speed trials over my wireless connection to my LAN)


Roy Lambert
Tue, Feb 22 2011 9:13 AMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Roy,

<< Going UP the tree is a fraction more difficult Smiley I decided that from a
given start point I wanted to display the selected nodes siblings, its
parents and all books. All parents and books get an expand/collapse button
unconditionally. Siblings and selected node would be tested for children.
I'm still trying to figure out an efficient way to build the tree going
upwards. All the functionality seems to be AddChild and none AddParent Smiley
>>

Why would you need to go "up" ?  Isn't the only way to get where you are
currently is to go down through all of the levels ?

--
Tim Young
Elevate Software
www.elevatesoft.com
Tue, Feb 22 2011 10:00 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Tim

>Why would you need to go "up" ? Isn't the only way to get where you are
>currently is to go down through all of the levels ?

Only if you start at the top Smiley

This is for my on-line manual. So visualise some user pressing F1. I'm trapping that key and work out the component below the mouse cursor (subject to some restrictions eg it has to be a TWinControl descendant) and pass the component, form and app names over to the OLM sub system.

I'm still thinking about it but currently I'm going to allow help to be stored for container controls (eg panels, pagecontrols) rather than the ultimate control so I can have a tree which would go app-form-container..container-control.

So I start at the bottom and work my way up. I could locate the app node, then the form node but then if I want to go any further building downwards I'd have to build all branches until I hit the control I actually want.

I'm still working on how I want it to look but I'm leaning towards a crossover of hierarchical table of contents type and the current google type that MS seems to be using in their office products.

Roy Lambert
Page 1 of 2Next Page »
Jump to Page:  1 2
Image