Streaming treeview
Home Up

 

ADUG Presentation
April 1999
Andy Bulka
abulka@netspace.net.au

I gave this talk to the Australian Delphi User group:

The main speaker of the evening was ADUG member Andy Bulka who gave a well received presentation on using Delphi's component-based streaming mechanisms, and in particular using the TreeView components as the basis for a hierarchical object persistent storage mechanism. Here are Andy's excellent presentation notes as well as a demo project.

Saving TreeViews to disk

     From the presentation given to the Australian Delphi User Group on 19th April 1999
     by Andy Bulka  abulka@netspace.net.au

wpe3.jpg (21323 bytes)

The following bulleted points have been converted from the powerpoint presentation.    Quite a bit of verbal explanation is, of course, missing.

Objectives

bulletTo save a treeview to disk
bulletInclude all the tree nodes
bulletYou already get this with SaveToFile & LoadFromFile
bulletAssociate a custom object with each tree node
bulletSave treeview + nodes + custom objects - how?

Brute Force Approach

bulletLoop through treenodes & write to file/stream yourself
bulletSimple & fast to implement
bulletFragile file format
changing custom info object requires changing read in & write out code
file version problems - ever more complex code

Natural Streaming Approach

bulletWhy natural?
Delphi does this sort of thing all the time
bulletFamiliar file format e.g.

wpe5.jpg (7267 bytes)

bullet“Robust” file format - can change the order of properties etc.
bulletEditable file format - load the .dfm into delphi's editor etc.
bulletEfficient - only changed properties streamed

How to participate in streaming

3 techniques, all involve overriding:
bulletgetChildren  <<<< we will be using this technique
bulletdefineProperties
bulletreadData/writeData

Basic Solution

bulletWriteComponentResFile( file, tree);
bulletReadComponentResFile( file, tree );

Plus we need to define our own treeview so we can override a few choice methods
bulletgetChildren  (writecomponent this, here we stream out our own custom objects)
bulletloaded   (after readcomponent, we need to do some fixups)

Basic Treeview facts

bullettreeview.Items[] are tree nodes (TtreeNode)
bullettreeview.selected is current node
bulletEach tree node has a .data pointer
  This is how I plan to associate my custom object with each node.

    dotdata.gif (7407 bytes)

Surprising facts about Ttreeview

bulletTreeView component does not define any children components.
bulletTreeView contains a single TTreeNodes object which houses many TtreeNode objects.
bulletNeither TTreeNodes or TtreeNode is a component
bulletTTreeNodes are streamed out using defineProperties()
bulletEach TtreeNode streamed out using writeData

Ownership & Memory Issues

bulletForm owns all components (usually)
bulletTreeview is a component owned by form
bulletcan own other components, but actually doesn’t

So why not:
bulletEnsure our custom objects are TComponents
bulletTrick treeview into owning our custom objects & treating them as children (for the purposes of ownership & streaming)

getChildren() secrets

bulletYour treeview component’s overriden getChildren method is automatically called by the streaming process
bulletYou loop & Proc( obj ) out each custom object
bulletprocedure GetChildren (Proc: TGetChildProc; Root: TComponent); override;

Lets go coding

wpe4.jpg (19652 bytes)

We are going to build the application, pictured above.  Clicking on each tree node will display data in the edit fields (see blue line).  Full source code below.    For the unit code + form dfm + executable download this.

ANDYSTREAMTREE.PAS

unit andyStreamTree;

{
     From the presentation given to AGUG on 19th April 1999
     by Andy Bulka  abulka@netspace.net.au

     This single unit / form illustrates the principles of
     persistence through component streaming, by showing you
     how to save a treeview to disk.
}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, comctrls;

type
  TForm4 = class(TForm)
    Button1CREATETREE: TButton;
    Button1FREETREE: TButton;
    Button1POPULATE: TButton;
    Button1STREAMOUT: TButton;
    Button2STREAMIN: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Button1SAVERECORD: TButton;
    procedure Button1CREATETREEClick(Sender: TObject);
    procedure Button1FREETREEClick(Sender: TObject);
    procedure Button1POPULATEClick(Sender: TObject);
    procedure Button1STREAMOUTClick(Sender: TObject);
    procedure Button2STREAMINClick(Sender: TObject);
    procedure Button1SAVERECORDClick(Sender: TObject);
  private
    procedure clickFest(sender: TObject);
    procedure canDrop(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure draggy(Sender, Source: TObject; X, Y: Integer);
    { Private declarations }
  public
    { Public declarations }
  end;

  Ttree = class(TTreeview)
  private
    Ftest1 : string;
    procedure clear;
  protected
    procedure getchildren(proc: TGetChildProc; root: Tcomponent); override;
    procedure loaded; override;
  published
    property test1: string read Ftest1 write Ftest1;
  end;

  Tinfo = class(Tcomponent)
  private
    Fs1 : string;
    Fs2 : string;
  published
    property address: string read Fs1 write Fs1;
    property phone: string read Fs2 write Fs2;
  end;

var
  Form4: TForm4;
  tree: Ttree;

implementation

{$R *.DFM}

procedure TForm4.Button1CREATETREEClick(Sender: TObject);
begin
     tree := Ttree.create(self);
     tree.parent := self;
     tree.height := tree.height * 3;
     tree.width := tree.width * 2;

     tree.OnClick := clickFest;
     tree.DragMode := dmAutomatic;
     tree.OnDragDrop := draggy;
     tree.OnDragOver := canDrop;
end;

procedure TForm4.Button1FREETREEClick(Sender: TObject);
begin
     tree.free;
     tree := nil;
end;

procedure TForm4.Button1POPULATEClick(Sender: TObject);
var
     obj: Tinfo;
begin
     obj := Tinfo.create(tree);
     obj.address := '123 street';
     obj.phone := '999335533';
     tree.items.AddChildObject(nil, 'hello',obj);

     obj := Tinfo.create(tree);
     tree.items.AddChildObject(nil, 'world',obj);

     tree.test1 := 'laughing is not clowing';
end;

procedure TForm4.Button1STREAMOUTClick(Sender: TObject);
begin
     WriteComponentResFile('c:\windows\desktop\andyout.dfm', tree);
end;

procedure TForm4.Button2STREAMINClick(Sender: TObject);
begin
     tree.clear;
     RegisterClasses([Ttree,Tinfo]);
     ReadComponentResFile('c:\windows\desktop\andyout.dfm', tree);
end;

procedure TForm4.clickFest(sender: TObject);
begin
     if (tree.selected <> nil) and (tree.selected.data <> nil) then
     begin
       edit1.text := Tinfo(tree.selected.data).address;
       edit2.text := Tinfo(tree.selected.data).phone;
     end;
end;

procedure Ttree.getchildren(proc: TGetChildProc; root: Tcomponent);
var
     i: integer;
begin
     for i := 0 to items.count-1 do
        Tinfo(items[i].data).name := '';

     for i := 0 to items.count-1 do
     begin
        Tinfo(items[i].data).name := 'ID'+inttostr(i);
        proc( items[i].data );
     end
end;

procedure Ttree.loaded;
var
     i: integer;
begin
     for i := 0 to items.count-1 do
        items[i].data := findcomponent('ID'+inttostr(i));
end;

procedure Ttree.clear;
var
  i: integer;
begin
  for i := ComponentCount - 1 downto 0 do
    if components[i].ClassName = 'Tinfo' then
         RemoveComponent(components[i]);
  for i := items.count-1 downto 0 do
     items[i].Delete;
end;

procedure TForm4.Button1SAVERECORDClick(Sender: TObject);
var
    info : Tinfo;
begin
  if tree.selected is TTreeNode then begin
    info := tree.selected.data;
    info.address := edit1.text;
    info.phone := edit2.text;
  end
  else beep;

end;

procedure TForm4.canDrop;
begin
    Accept := True;
end;

procedure TForm4.draggy;
var
  TargetNode, SourceNode: TTreeNode;
begin
  TargetNode := tree.GetNodeAt (X, Y);
  if TargetNode <> nil then
  begin
    SourceNode := tree.Selected;
    SourceNode.MoveTo (TargetNode, naAddChildFirst);
    tree.Selected := TargetNode;
  end;
end;


end.