Virtual Treeview step by step[求翻译此文] (200分)

  • 主题发起人 主题发起人 sdcx
  • 开始时间 开始时间
S

sdcx

Unregistered / Unconfirmed
GUEST, unregistred user!
Virtual Treeview step by step
1 Preparations
Before we start some preparations are necessary:
Place a Virtual Treeview component on a form.
Change the properties as you like.
A record for node data must be defined.
In order to store the own node data some musing is important. How shall the record look like?
a) All nodes in the tree are equal
In this case a simple record defines the necessary data structure, e.g.:
type
rTreeData = record
Text: WideString;
URL: string[255];
CRC: LongInt;
isOpened: Boolean;
ImageIndex: Integer;
end;
b) There are different nodes in the tree (e.g. folders that can have sub nodes)
I will follow this case because my tree will hold folders, which can in turn get own nodes.
Since I intent to store created trees in a file in order to restore them later further deliberations are necessary:
Suppose a folder node has only a name and a leaf node has a name and a text info field. Potentially I also want to store a second kind of leaf node, which will for instance have a number instead of the text field.
The problem in the context of reading data form a stream is that I must know which data is stored in which order in the stream, because I have to read it in exactly the same order again. Hence I have to determine from the very first information in the stream which information will follow. For instance there is a node name, but then
? Is there nothing more or another text information (string) or even an integer value? I think the point is clear. The first data, which I read, has to carry this information.
These deliberations have leaded me to the following solution: I save now in the stream [label]à[name]à[following data]
0 à 'Folder'
1 à 'Info node' à 'Blabla'
2 à 'Number node' à 123
I know from the stream I always read an integer value first. Depending whether this is 1, 2 or 3 I have to read - now known - following values. Now let us consider the record.
type
rTreeData = record
Typ: Integer;
Name: string[255];
pNodeData: Pointer;
end;
Hey, there is suddenly a pointer in the record. Well, here are some additional comments:
1) Typ is an integer value, from which I can determine what kind of node this is, in my example 1, 2 or 3.
2) Name is the name of the node. This will be needed relatively often because it is also seen as part of the tree and I want to access this information easily (man, I am lazy).
3) The pointer allows (similar to the data property of the tree) a record or even better a class instance to connect.
Now I still have the freedom to define a base class of node. It contains all properties and methods, which all classes will share. And from this I can derive proper sub classes (e.g. text nodes, value nodes etc.). An additional advantage of this record is its fixed size. Hence you can always return the same size in case the tree asks for it (see also property NodeDataSize), but more about that later.
Just one remark:
If youdo
n’t want to use classes you can also simply define 3 records, which define as first element, a type and which react differently depending on this type.
Alternative solution:
Okay, I admit it. It would of course also be possible to write the type into the stream and read it from the stream separately without saving it as part of the record. The type of the node class is indirectly known because you can ask a class which class name it has (see e.g. class function ClassName) and the class knows it too, respectively.
So I shall store a node, okay. I pass on the stream to the Node.SaveToFile(Stream) method, which writes, depending on which node class we actually have, automatically the value 1, 2 or 3 into the stream.
During load from stream I read first the value 1, 2 or 3 and decide what class is meant. then
I create an instance of this class and call its LoadFromFile method.
Well, this solution is my most preferred and before another one enters my brain I will implement it (Note: in step 5 I will change something).
So Ido
following:
As you can see from the declaration of the internal node of Virtual Tree
TVirtualNode = packed record
Index, // index of node with regard to its parent
ChildCount: Cardinal;
// number of child nodes
...
...
LastChild: PVirtualNode;
// link to the node's last child...
Data: record end;
// this is a placeholder, each node gets extra
// data determined by NodeDataSize
end;

there is another record at the end of the record structure. Which exact structure this is will be determined indirectly.
type
rTreeData = record
Name: string[255];
// the identifier of the node
ImageIndex: Integer;
// the image index of the node
pNodeData: Pointer;
end;
Let the above record be the structure. The Virtual Treeviewdo
es not really know this structure, but it knows how much space must be reserved. We tell it by
myVirtualTree.NodeDataSize := SizeOf(rTreeData);
Note: even if you want to store only one value, e.g. a pointer as node data, simply return the size, which should be reserved.
2 Implementation
2.1 An empty tree

I begin
with an empty tree (no top level nodes are created at design time):

Either an existing tree is read from a file or
A top-level node is created.

Before a node can be created you have to determine the size of the actual node data. According to thedo
cs there are three opportunities:
In the object inspector
In the OnGetNodeDataSize - event or
During creation of the form

I decide to use the last variant and will nowdo
the following during form creation:

procedure TMyForm.FormCreate(Sender: TObject);
var
Node: PVirtualNode;
begin
...
// create tree
MyTree.NodeDataSize := SizeOf(TTreeData);
if MyForm.filename = '' then
begin
// if there is no tree to load
// create tree with top level node
Node := BookmarkForm.BookmarkTree.AddChild(nil);
// adds a top level node
end
else

begin
// load tree
....
end;
....

end;


2.2 Data for the node

After the call of AddChild data can be assigned. For this a pointer to the self-defined record will be declared and via the function GetNodeData connected with the correct address. By using this pointer we can now access the elements of the record and assign them values.

var
...
NodeData: ^rTreeData;
begin
...
// determine data for node
NodeData := BookmarkForm.BookmarkTree.GetNodeData(Node);
NodeData.Name := 'new project';
NodeData.ImageIndex := 0;
...

2.3 Show the node name

The name of the node shall now appear as node identification in the tree. All data about the node as well as the name are unknown to the treeview and it has to query for them.

Every time the identification of the node is needed an event OnGetText will be triggered. In the event handler we return the name of the node in the variable Text. Nothing more is needed.

procedure TBookmarkForm.BookmarkTreeGetText(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Column: Integer;
TextType: TVSTTextType;
var Text: WideString);
var
NodeData: ^rTreeData;
begin
NodeData := Sender.GetNodeData(Node);

// return identifier of the node
Text := NodeData.Name;
end;

2.4 The icon for the node

Because I like it colorful I want also to provide an icon for the top-level node. Following steps are necessary to accomplish that:
A TImageList must be placed onto the form and filled with images
The property Images of the VirtualTreeview gets assigned this image list
Implement an OnGetImageIndex event handler.

In the event OnGetImageIndex you can the index be determine which determines in turn which image form the list must be shown.

Because the method is also called for the state icons but Ido
not want yet to state icons (but I already have assigned and image list to the property StateImages) the value for this case (Kind à ikState) is -1.

procedure TBookmarkForm.BookmarkTreeGetImageIndex(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Kind: TVTImageKind;
Column: Integer;
var Index: Integer);
var
NodeData: ^rTreeData;
begin
NodeData := Sender.GetNodeData(Node);

case Kind of
ikState: // for the case the state icon has been requested
Index := -1;
ikNormal, ikSelected: // normal or the selected icon is required
Index := NodeData.ImageIndex;
end;

end;

Depending on whether a node is selected or not, different icons shall be shown (see step 6).

2.5 Only one node class in the record

Since I want to avoid mixing data in the record and later then
data in the node class I decided to change this record

type
TTreeData = record
Name: string[255];
// the identifier of the node
ImageIndex: Integer;
// the image index of the node
pNodeData: Pointer;
end;

into a record which contains only one pointer to a node class. I declare therefore first a node class

TBasicNodeData = class
...
end;

and then
a structure of the form:

rTreeData = record
BasicND: TBasicNodeData;
end;

This record always needs 4 bytes for the pointer to the class.

Particular attention is to direct to the event OnGetText. This event will already be called during creation of the node with Tree.AddChild(nil) in order to determine the space the new node’s caption will need (but only if no columns were created). At this point however the node class could not yet be initialised (no constructor call yet). Hence for this case

if NodeD.BasicND = nil then

Text := ''

must be returned or you wrap the entire initialization into a begin
Update/EndUpdate block and initialized the nodes before EndUpdate is called (e.g. by ValidateNode(Node)).

Without this provision an access violation would be the result.

2.5.1 Example class declaration

unit TreeData;

interface

//===========================================

type
// declare common node class
TBasicNodeData = class
protected
cName: ShortString;
cImageIndex: Integer;
public
constructor Create;
overload;
constructor Create(vName: ShortString;
vIIndex: Integer = 0);
overload;

property Name: ShortString read cName write cName;
property ImageIndex: Integer read cImageIndex write cImageIndex;
end;

// declare new structure for node data
rTreeData = record
BasicND: TBasicNodeData;
end;

implementation

constructor TBasicNodeData.Create;
begin
{ not necessary
cName := '';
cImageIndex := 0;
}
end;

constructor TBasicNodeData.Create(vName: ShortString;
vIIndex: Integer = 0);
begin
cName := vName;
cImageIndex := vIIndex;
end;

end.

2.5.2 Example creation of the tree

// Tree will be created when the form is created.
procedure TMyForm.FormCreate(Sender: TObject);
var
Node: PVirtualNode;
NodeD: ^rTreeData;
begin
....
// create tree
MyTree.NodeDataSize := SizeOf(rTreeData);
if MainControlForm.filename = '' then

begin
// create tree with top level node
Node := MyTree.AddChild(nil);
// adds a node to the root of the tree
// assign data for this node
NodeD := MyTree.GetNodeData(Node);
NodeD.BasicND := TBasicNodeData.Create('new project');
end
else

begin
// load tree
end;
...
end;


{______________________________MyTree_________________________________}


// returns the text (the identification) of the node
procedure TMyForm.MyTreeGetText(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Column: Integer;
TextType: TVSTTextType;
var Text: WideString);
var
NodeD: ^rTreeData;
begin
NodeD := Sender.GetNodeData(Node);

// return the identifier of the node
if NodeD.BasicND = nil then

Text:=''
else

Text := NodeD.BasicND.Name;
end;

// returns the index for image display
procedure TMyForm.MyTreeGetImageIndex(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Kind: TVTImageKind;
Column: Integer;
var Index: Integer);
var
NodeD: ^rTreeData;
begin
NodeD := Sender.GetNodeData(Node);

case Kind of
ikState: // for the case the state index has been requested
Index := -1;
ikNormal, ikSelected: // normal icon case
Index := NodeD.BasicND.ImageIndex;
end;

end;

2.6 Icons for selected nodes

If a node is selected a different symbol shall be shown:
Therefore I implement a new method

function GetImageIndex(focus: Boolean): Integer;
virtual;

which gets the normal image index or the index for focused nodes depending on whether the node has the focus or not.

Call:
Index := NodeD.BasicND.GetImageIndex(Node = Sender.FocusedNode);

Implementation of the method:

function TBasicNodeData.GetImageIndex(focus: Boolean): Integer;
begin
if focus then

Result := cImageIndexFocus
else

Result := cImageIndex;
end;

where cImageIndex has always the normal index and cImageIndex Focus the index for focused nodes.
I assume in this case that the selected index is always one more than the normal index. To ensure this, the constructor is changed this way:

constructor TBasicNodeData.Create(vName: ShortString;
vIIndex: Integer = 0);
begin
cName := vName;
cImageIndex := vIIndex;
cImageIndexFocus := vIIndex + 1;
end;

2.7 Adding and deleting nodes

In order to implement and test more functions I want finally an opportunity to create the tree. By using a context menu is shall be possible to add and remove nodes.

Hence I define a popup menu with two entries: [Add] and [Remove]. To have the clicked node getting the focus the option voRightClickSelect must be set to True.

So if Add has been chosen a child node will be created for the focused node:

procedure TMyForm.addClick (Sender: TObject);
var
Node: PVirtualNode;
NodeD: ^rTreeData;
begin
// Ok, a node must be added.
Node := MyTree.AddChild(MyTree.FocusedNode);
// adds a node as the last child
// determine data of node
NodeD := MyTree.GetNodeData(Node);
NodeD.BasicND := TBasicNodeData.Create('Child');
end;

Caution: What must bedo
ne if no node has the focus?
à e.g. insert the new node as child of a top level nodes.

if BookmarkTree.FocusedNode = nil then

begin
// insert as child of the first top level node
Node := BookmarkTree.AddChild(BookmarkTree.RootNode.FirstChild);
// determine data for node
NodeD := BookmarkTree.GetNodeData(Node);
NodeD.BasicND := TFolderNodeData.Create('new folder');
end
else

begin
// Ok, a new node must be added.
Node := BookmarkTree.AddChild(BookmarkTree.FocusedNode);
// determine data of the node
NodeD := BookmarkTree.GetNodeData(Node);
NodeD.BasicND := TFolderNodeData.Create('new folder');
end;


If the node with the focus must be deleted the following happens:

procedure TMyForm.delClick (Sender: TObject);
begin
// The focused node should be removed. The top level must not be deleted however.
if MyTree.FocusedNode = nil then
MessageDlg('There was no node selected.', mtInformation, [mbOk], 0)
else
// Note: RootNode is the internal (hidden) root node and parent of all top
// level nodes. To determine whether a node is a top level node you also use
// GetNodeLevel which returns 0 for top level nodes.
if MyTree.FocusedNode.Parent = MyTree.RootNode then
MessageDlg('The project node must not be deleted.', mtInformation, [mbOk], 0)
else

MyTree.DeleteNode(MyTree.FocusedNode);
end;

I want to prevent, however, that the top-level node gets deleted. Hence I check with the comparison MyTree.FocusedNode.Parent = MyTree.RootNode whether the focused node is not a top-level node. Here you have to consider that the property RootNode returns the (hidden) internal root node, which is the common parent of all top-level nodes.

While we are at deleting nodes:
Every data of the record is automatically free as soon as this is required. In this case it is not enough, however, to free the memory, which holds the pointer to the class (object instance), but it is also necessary to free the memory, which is allocated by the class itself. This happens by calling the destructor of the class in the OnFreeNode event:

procedure TMyForm.MyTreeFreeNode(Sender: TBaseVirtualTree;
Node: PVirtualNode);
begin
// Free here the node data (Note: type PtreeData = ^rTreeData).
PTreeData(Sender.GetNodeData(Node)).BasicND.Free;
end;

2.8 Adding folder and leafs

Now I am ready to add folders to the tree as well as final nodes, whichdo
not have children. For this I derive two new node classes from the base class.

TFolderNodeData = class(TBasicNodeData)
TItemNodeData = class(TBasicNodeData)

Depending on which kind of node the user wants to create using the context menu I store a particular class in the node record.

NodeD.BasicND := TFolderNodeData.Create('new folder');
NodeD.BasicND := TItemNodeData.Create('new node');

These classes contain a new property ChildrenAllowed. Based on this property you can now distinct whether the node with the focus may get children (folder) or not (items).

2.9 Storing the tree

Now I can finally implement storing the tree. I have already thought a lot about this step. Let us see if this was worthwhile.

Again a quote from Preparations:
I want to store a node, okay. I hand over the stream to the MyNodeClass.SaveToFile method and this method writes depending upon which node class it actually is automatically the value 1, 2 or 3 as a kind of class ID into the stream (alternatively you can use an enumeration type).

During load I read first the value 1, 2 or 3 from the stream and decide based on it which class we deal with. then
I create an instance of this class and call its method LoadFromFile.

Hint:
It would also be possible to store the class name instead of the ID for the class. During read and creation of the class one could use class references and virtual constructors and save so the case-statement as I did in the OnLoadNode event, to decide which class instance must be created (example see Delphi 5, written by Elmar Warken, Addison-Wesley, chapter 4.3.3, page 439).

Before you can read something it must be written first. Hence I will first implement the necessary procedures to store the tree. Since we care ourselves that the identification of the node gets saved the option toSaveCaption can be removed from StringOptions. This way data is not stored twice.

For saving the tree the procedure

procedure TBaseVirtualTree.SaveToFile(const FileName: TFileName);

is called. Thereby the structure of the tree is automatically stored. In order to save our additional data there is an event OnSaveNode where we can simply store our data into the provided stream.

property OnSaveNode: TVTSaveNodeEvent read FOnSaveNode write FOnSaveNode;

If OnSaveNode is triggered then
the method SaveNode of the particular node class will be called:

procedure TMyForm.MyTreeSaveNode(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Stream: TStream);
begin
PTreeData(Sender.GetNodeData(Node)).BasicND.SaveToFile(Stream);
end;

In the SaveNode method of the class fields like node name, image index etc. are stored in the tree:

procedure TBasicNodeData.SaveNode(Stream: TStream);
var
size: Integer;
begin
// save type of the node
Stream.Write(Art, SizeOf(Art));

// store cName
Size := Length(cName) + 1;
// include terminating #0
Stream.Write(Size, SizeOf(Size));
// store length of the string
Stream.Write(PChar(cName)^, Size);
// now the string itself

// store cImageIndex
Stream.Write(cImageIndex, SizeOf(cImageIndex));

// store cImageIndexFocus
Stream.Write(cImageIndexFocus, SizeOf(cImageIndexFocus));

// store cChildrenAllowed
Stream.Write(cChildrenAllowed, SizeOf(cChildrenAllowed));
end;

Now we can the tree we save also load again. This process could look like:

try
// load tree
MyTree.LoadFromFile(MainControlForm.Filename);
except
on E: Exceptiondo

begin
Application.MessageBox(PChar(E.Message), PChar('Error while loading.'), MB_OK);
MainControlForm.Filename := '';

// create tree with top level node (since loading failed)
Node := MyTree.AddChild(nil);
NodeD := MyTree.GetNodeData(Node);
NodeD.BasicND := TBasicNodeData.Create('new project');
end;
end;

By the call of LoadFromFile the event OnLoadNode will be triggered and consequently the method LoadNode:

procedure TBasicNodeData.LoadNode(Stream: TStream);
var
Size: Integer;
StrBuffer: PChar;
begin
// load cName
Stream.Read(Size, SizeOf(Size));
// length of the string

StrBuffer := AllocMem(Size);
// get temporary memory
Stream.Read(StrBuffer^, Size);
// read the string
cName := StrBuffer;
FreeMem(StrBuffer);
// Alternatively you can simply use:
// SetLength(cName, Size);
// Stream.Read(PChar(cName)^, Size);

// load cImageIndex
Stream.Read(cImageIndex, SizeOf(cImageIndex));

// load cImageIndexFocus
Stream.Read(cImageIndexFocus, SizeOf(cImageIndexFocus));

// load cChildrenAllowed
Stream.Read(cChildrenAllowed, SizeOf(cChildrenAllowed));
end;

2.10 Two columns in the treeview

Now I want to show two columns in the treeview. Therefore I set the new properties of the tree in the object inspector.

By using Header.Columns you can create the desired columns. After that, you only have to set Header.Options.hoVisible to True and the columns will appear in the treeview.

After you have set all necessary options you can give now the text and the icon for the particular column, respectively. This happens in the already existing event handlers OnGetText and OnGetImageIndex where now also the given column index must be taken into account.

procedure TMyForm.MyTreeGetText(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Column: Integer;
TextType: TVSTTextType;
var Text: WideString);
var
NodeD: ^rTreeData;
begin
NodeD := Sender.GetNodeData(Node);

// return the the identifier of the node
if NodeD.BasicND = nil then

Text := ''
else

begin
case Column of
-1,
0: // main column, -1 if columns are hidden, 0 if they are shown
Text := NodeD.BasicND.Name;
1:
Text := 'This text appears in column 2.';
end;
end;
end;

procedure TMyForm.MyTreeGetImageIndex(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Kind: TVTImageKind;
Column: Integer;
var Index: Integer);
var
NodeD: ^rTreeData;
begin
NodeD := Sender.GetNodeData(Node);

if Column = 0 then
// icons only in the first column
case Kind of
ikState:
Index := -1;
ikNormal, ikSelected:
Index := NodeD.BasicND.GetImageIndex(Node = Sender.FocusedNode);
ikOverlay: // e.g. to mark a node whose content changed,
// Note:do
n’t forget to call ImageList.Overlay for the image.
if NodeD.BasicND.ImageIndex = 4 then

Index := 6;
end;
end;

2.11 Accessing the columns

I want to demonstrate the access to the columns of a TVirtualStringTrees based on an example. In order to store global options, as in Point 2.12 I want to know the width of a column. This information is updated every time an OnColumnResize event is triggered:

procedure TBookmarkForm.BookmarkTreeColumnResize(Sender: TBaseVirtualTree;
Column: Integer);
var
NodeD: PTreeData;
begin
NodeD := Sender.GetNodeData(Sender.RootNode.FirstChild);

// Keep the new size of the column in the project node.
TProjectNodeData(NodeD.BasicND).SetHColumnsWidth(
TVirtualStringTree(Sender).Header.Columns.Items[Column].Width,Column);
end;

The exciting part is the type casting of the sender object. In TBaseVirtualTree the header property is protected and only after conversion (casting) to TVirtualTree it becomes accessible.

2.12 Global tree options

Global options like the sizes of the columns, which are adjusted in the project, will be stored as properties of the top-level node. It contains so all project related options.

In order to avoid that all derived classes inherit these fields the top-level node class will be build from a new project node class, which will be derived from the base node class.

The new hierarchy looks now so:
»
Base node class... unites the properties of all nodes
»
Project node class... enriches the base with management of project related options
»
Folder node classes... enriches the base with default properties for all leaf nodes
»
Leaf node class... the actual node class (special properties)

Since this involves already very application specific program details I want only make some notes.

The base node class has the ability to store node data. These methods must be declared as virtual and will be overridden in the project node class to allow saving the project data.

Well, now I am ready to work with VirtualTreeview. It will become interesting later again when I will try to drag data from other applications to the tree. But this is a different story...
 
不明所以
 
看它的示例就明白了。
 
这么长?
 
结束问题
 
Virtual Treeview step by step
1 Preparations
Before we start some preparations are necessary:
Place a Virtual Treeview component on a form.
Change the properties as you like.
A record for node data must be defined.
In order to store the own node data some musing is important. How shall the record look like?
a) All nodes in the tree are equal
In this case a simple record defines the necessary data structure, e.g.:
type
rTreeData = record
Text: WideString;
URL: string[255];
CRC: LongInt;
isOpened: Boolean;
ImageIndex: Integer;
end;
b) There are different nodes in the tree (e.g. folders that can have sub nodes)
I will follow this case because my tree will hold folders, which can in turn get own nodes.
Since I intent to store created trees in a file in order to restore them later further deliberations are necessary:
Suppose a folder node has only a name and a leaf node has a name and a text info field. Potentially I also want to store a second kind of leaf node, which will for instance have a number instead of the text field.
The problem in the context of reading data form a stream is that I must know which data is stored in which order in the stream, because I have to read it in exactly the same order again. Hence I have to determine from the very first information in the stream which information will follow. For instance there is a node name, but then
? Is there nothing more or another text information (string) or even an integer value? I think the point is clear. The first data, which I read, has to carry this information.

我的翻译:
渐进学习Virtual Treeview
1,准备
在我们开始之前,作些相应的准备是必须的;
在form中放置一个Virtual Treeview组件
设置此组件的属性,但注意node数据的记录必须定义,在保存你的node数据前,先仔细考虑,你生成的node数据记录是什么样子;
a), 树中所有的node都是相同的;
在这种条件下,一条简单的记录定义了node所需的数据结构,例如;
type
rTreeData = record
Text: WideString;
URL: string[255];
CRC: LongInt;
isOpened: Boolean;
ImageIndex: Integer;
end;

b),在树中有不同的node(如文件夹下可能有子node),我将按此种情况继续因为我建的树中有文件夹,并且文件夹都有它自己的node
(待续!)
 
渐进学习虚拟 Treeview
1,准备工作
开始之前须作些准备工作:
在窗体上放置一个Virtual Treeview组件
按需要修改此组件的属性
必须为节点数据定义一个记录
仔细考虑如何保存下属节点的数据
节点数据记录应设计成什么样子呢?
a) 树中所有节点都同样处理
据此情形,可用一条简单的记录来定义所需的数据结构,例如:
type
rTreeData = record
Text: WideString;
URL: string[255];
CRC: LongInt;
isOpened: Boolean;
ImageIndex: Integer;
end;

b) 树中有不同的节点(如文件夹下可能有子节点)。
我将应对不同的节点,因为我所建的树要添加文件夹,而文件夹则可能又有其自身节点。
为了将所建树保存于文件中以便以后使用,要进一步考虑处理方法:
设一文件夹节点只有一个名称,一个叶节点有一名称和一文本人信息字段。还可能需要保存另外一种叶节点,该叶节点可能保存数字类型的数据而不是文本字段。
从流中读取数据时存在一个问题:必须知道流中的数据存储的顺序,因为要按相同的顺序将来对其再次读取。因此,从一开始,就应该确定流中信息存储的顺序。例如,设定了一个节点名称,可是然后怎么办呢?它会不会没有别的信息,还是又有一文本信息(字串),甚至还有没有一整型数值呢?我想这一点是清楚的。所以,首先读取的数据应包含这样的信息。
有了这样的考虑,可得出如下答案:在流中存入[标记码]|[名称]|[跟随数据)]
0 表示 '文件夹'
1 表示 '信息节点' 如 'Blabla'
2 表示 '数值节点' 如 123
首先从流中读取整型标记码,视其取值(应为0,1,2)读取后继数据值。
现在研究一下该记录。
type
rTreeData = record
Typ: Integer;
Name: string[255];
pNodeData: Pointer;
end;
怎么记录中突然多出了一个指针呢?简要解释如下:
1) Typ为一整型,用来决定节点的类型,本例中取值范为是1,2或3。
2) Name为节点名称。该字段作为相关引用,常被视为树的一部分,便于信息读取(原谅我的简洁)。
3) 指针准许提供对记录或者对类实例的连接(这一点类似于树中的data属性)。
现在仍可对基类节点进行定义。它包含所有的属性和方法,供所有类共用。由此可以派生出相应的子类(如,文本节点,数值节点,等等)。它的另外一个优点在于大小是固定的。所以,总可以应树的需求返回相同规模大小的值(又见NodeDataSize属性),后面将进一步对此进行解释。
再提醒一点:
如果不想使用类,也可简单地定义三个记录类型,对应不同的情型视类型不同进行处理。
(未完)
 
还是看原文吧,以前翻译过的东西只有自己才能明白啥意思。后来在朋友劝说下放弃了。
 
另类解决方案:
应当指出,还可将该类型数据写入流中,并从流中单独读取,而不将它保存为记录的一部分。这是对节点类的非直接引用,因为可以访问一个类的类名称(例如使用函数ClassName),相应地,类也能对它进行识别。
这样,将流数据使用Node.SaveToFile(Stream)方法进行处理,根据实际的节点类型,在流中自动存中相应的值(1,2或3)。
流在加载时,先读取第一个数值,视其情况决定其类含义。然后创建该类的实例,并调用LoadFromFile方法。
我最愿采用的就是这种方法,至少目前还没想出别的方法。(注:在第五步中将有所改变)
于是进行如下操作:
虚拟树的内部节点声明如下:
TVirtualNode = packed record
Index, // 节点相对于父节点的指示值
ChildCount: Cardinal;
// 子节点数量
...
...
LastChild: PVirtualNode;
// 连接至最后子节点...
Data: record end;
// 作为存储域,每一节点都有
// 数据由NodeDataSize决定
end;

记录结构末尾还有另一记录,用间接决定结构的具体情形。
type
rTreeData = record
Name: string[255];
// 节点标识码
ImageIndex: Integer;
// 节点图示码
pNodeData: Pointer;
end;
树结构的记录就是这些。虚拟树无需知道这种结构,但应知道应为其预留的空间大小。这可由myVirtualTree.NodeDataSize := SizeOf(rTreeData)来得知;
注: 即便只保存一个值,如一个指针作为节点数据,只需返回大小并保存即可。
(未完)
 
2.4 给节点指定图标
为了好看,要给顶级节点加个图标。需要以下步骤:
在窗体上放置一个TImageList并添加图像。通过OnGetImageIndex事件的实现,虚拟树的Images属性被赋予该图像。
在OnGetImageIndex事件中指定要显示的图像索引值(index)。
状态图标也使用该方法实现,但我尚不需要状态图标(虽然我已通过StateImages属性这格做过了),这样将Kind à ikState)置为-1。

procedure TBookmarkForm.BookmarkTreeGetImageIndex(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Kind: TVTImageKind;
Column: Integer;
var Index: Integer);
var
NodeData: ^rTreeData;
begin
NodeData := Sender.GetNodeData(Node);

case Kind of
ikState: // 如果要指定状态图标
Index := -1;
ikNormal, ikSelected: // 需要正常或选择状态的图标
Index := NodeData.ImageIndex;
end;

end;

图标的标示视节点是否被选择而定(见第6步).
(未完)

 
真是好人啊,我当初刚学的时候由于e问不好,看的累死了.
我觉得翻译绝对很有价值,这个控件应该说delphi中很经典而且很常用的一个.
 
2.5 记录中唯一的节点类
为了避免记录中的数据与节点中的数据相混淆,我决定改变一下该记录。

type
TTreeData = record
Name: string[255];
// 节点标识
ImageIndex: Integer;
// 节点的图示索引
pNodeData: Pointer;
end;

将它改为只包含一个指向节点类的指针。因而先声明一个节点类:

TBasicNodeData = class
...
end;

然后声明窗体的结构:

rTreeData = record
BasicND: TBasicNodeData;
end;

该记录需要4个字节来作为指向类的指针。
特别注意的是事件。在使用Tree.AddChild(nil)创建节点期间总要调用这一事件,以便确定新节点的标题串(caption)所占的空间(仅在没有创建多列显示时)。 到此,还不能对节点类进行初始化(没有constructor调动)。为此应当

if NodeD.BasicND = nil then

Text := ''

或者将整个初始化封装于一个begin
Update/EndUpdate程序块中,从而在EndUpdate被调用(如通过ValidateNode(Node))之前对节点进行初始化。
不进行这样的处理就会导致访问冲突。
2.5.1 类声明示例

unit TreeData;

interface

//===========================================

type
// 声明通用的节点类
TBasicNodeData = class
protected
cName: ShortString;
cImageIndex: Integer;
public
constructor Create;
overload;
constructor Create(vName: ShortString;
vIIndex: Integer = 0);
overload;

property Name: ShortString read cName write cName;
property ImageIndex: Integer read cImageIndex write cImageIndex;
end;

// 声明节点数据的新结构
rTreeData = record
BasicND: TBasicNodeData;
end;

implementation

constructor TBasicNodeData.Create;
begin
{ 不必
cName := '';
cImageIndex := 0;
}
end;

constructor TBasicNodeData.Create(vName: ShortString;
vIIndex: Integer = 0);
begin
cName := vName;
cImageIndex := vIIndex;
end;

end.

2.5.2 创建树的示例

// 创建窗体时创建树
procedure TMyForm.FormCreate(Sender: TObject);
var
Node: PVirtualNode;
NodeD: ^rTreeData;
begin
....
// 创建树
MyTree.NodeDataSize := SizeOf(rTreeData);
if MainControlForm.filename = '' then

begin
// 创建顶级节点
Node := MyTree.AddChild(nil);
// 向树的根添加节点
// 给该节点赋值
NodeD := MyTree.GetNodeData(Node);
NodeD.BasicND := TBasicNodeData.Create('new project');
end
else

begin
// 加载树
end;
...
end;


{______________________________MyTree_________________________________}


// 返回节点的text(文字标识)
procedure TMyForm.MyTreeGetText(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Column: Integer;
TextType: TVSTTextType;
var Text: WideString);
var
NodeD: ^rTreeData;
begin
NodeD := Sender.GetNodeData(Node);

// 返回节点的标识
if NodeD.BasicND = nil then

Text:=''
else

Text := NodeD.BasicND.Name;
end;

// 返回所显图像的索引
procedure TMyForm.MyTreeGetImageIndex(Sender: TBaseVirtualTree;
Node: PVirtualNode;
Kind: TVTImageKind;
Column: Integer;
var Index: Integer);
var
NodeD: ^rTreeData;
begin
NodeD := Sender.GetNodeData(Node);

case Kind of
ikState: // 用作所要求的状态索引码
requested
Index := -1;
ikNormal, ikSelected: // 正常情况下的图标
Index := NodeD.BasicND.ImageIndex;
end;

end;
(未完)
 
2.6 为所选节点指定图标
如果一个节点被选择,应让它显示为别的符号:
我采用如下实现方法

function GetImageIndex(focus: Boolean): Integer;
virtual;
该方法根据节点是否被选择获得正常状态或者处于焦点状态时的图示索引码。

调用:
Index := NodeD.BasicND.GetImageIndex(Node = Sender.FocusedNode);

来实现该方法:

function TBasicNodeData.GetImageIndex(focus: Boolean): Integer;
begin
if focus then

Result := cImageIndexFocus
else

Result := cImageIndex;
end;

在这里,cImageIndex总使用正常状态的索引码,而cImageIndexFocus为处于焦点状态节点的索引码。
这里始终假设所选节点的索引比正常索引值大一,为确保这一点,要对constructor进行如下改动:

constructor TBasicNodeData.Create(vName: ShortString;
vIIndex: Integer = 0);
begin
cName := vName;
cImageIndex := vIIndex;
cImageIndexFocus := vIIndex + 1;
end;
(未完)
 
加油加油,我又重新学习了一边
 
呵呵,加分鼓励
地址如下:
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2522403
星期三早上揭帖。
 
多人接受答案了。
 

Similar threads

A
回复
0
查看
992
Andreas Hausladen
A
A
回复
0
查看
943
Andreas Hausladen
A
A
回复
0
查看
819
Andreas Hausladen
A
A
回复
0
查看
973
Andreas Hausladen
A
后退
顶部