|
|
|
ADUG Presentation 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.
From the presentation given to the Australian Delphi User Group on 19th April 1999 |
| To save a treeview to disk | |
| Include all the tree nodes | |
| You already get this with SaveToFile & LoadFromFile | |
| Associate a custom object with each tree node | |
| Save treeview + nodes + custom objects - how? |
| Loop through treenodes & write to file/stream yourself | |
| Simple & fast to implement | |
| Fragile file format changing custom info object requires changing read in & write out code file version problems - ever more complex code |
| Why natural? Delphi does this sort of thing all the time | |
| Familiar file format e.g. |
| Robust file format - can change the order of properties etc. | |
| Editable file format - load the .dfm into delphi's editor etc. | |
| Efficient - only changed properties streamed |
3 techniques, all involve overriding:
| getChildren <<<< we will be using this technique | |
| defineProperties | |
| readData/writeData |
| WriteComponentResFile( file, tree); | |
| ReadComponentResFile( file, tree ); |
Plus we need to define our own treeview so we can override a few choice methods
| getChildren (writecomponent this, here we stream out our own custom objects) | |
| loaded (after readcomponent, we need to do some fixups) |
| treeview.Items[] are tree nodes (TtreeNode) | |
| treeview.selected is current node | |
| Each tree node has a .data pointer This is how I plan to associate my custom object with each node. |

| TreeView component does not define any children components. | |
| TreeView contains a single TTreeNodes object which houses many TtreeNode objects. | |
| Neither TTreeNodes or TtreeNode is a component | |
| TTreeNodes are streamed out using defineProperties() | |
| Each TtreeNode streamed out using writeData |
| Form owns all components (usually) | |
| Treeview is a component owned by form | |
| can own other components, but actually doesnt |
So why not:
| Ensure our custom objects are TComponents | |
| Trick treeview into owning our custom objects & treating them as children (for the purposes of ownership & streaming) |
| Your treeview components overriden getChildren method is automatically called by the streaming process | |
| You loop & Proc( obj ) out each custom object | |
| procedure GetChildren (Proc: TGetChildProc; Root: TComponent); override; |

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.
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.
![]()