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
The following bulleted points have been converted from the powerpoint presentation.
Quite a bit of verbal explanation is, of course, missing.
Objectives
| 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? |
Brute Force Approach
| 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 |
Natural Streaming Approach
| 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 |
How to participate in streaming
3 techniques, all involve overriding:
| getChildren <<<< we will be using this technique |
| defineProperties |
| readData/writeData |
Basic Solution
| 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) |
Basic Treeview facts
| 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. |
Surprising facts about Ttreeview
| 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 |
Ownership & Memory Issues
| 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) |
getChildren() secrets
| 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; |
Lets go coding
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.
|