The TVirtualTreeview component by Mike Lischke is a custom-Delphi-control master piece.
If you ever needed a tree like user interface in your Delphi applications I would strongly suggest that you try the Virtual TreeView. Delphi IDE uses it. Have I mentioned that it is free.
Anyway, the rest of this article is not (only) about the Virtual TreeView but about something that can be named: "preserve the value of a value type pointer parameter in Delphi callbacks for simple types". What a stupid title :)
Before I continue I need to freshen up your memory on some fundamental Delphi topics:
On Value ParametersThe Understanding and Using Functions and Procedures along with The Many Faces of Delphi Routines: Functions and Procedures describe what functions and procedures in Delphi programming are, and provide a material for beginners to understand various parameter passing options.
Let's consider the next function declaration:
Note the different declaration between the X and Y parameters. They are both integers but X is a variable parameter while y is a value parameter.
function DoIt(var x : integer; y : integer) ;
A value parameter acts like a local variable that gets initialized to the value passed in the procedure or function call. If you pass a variable as a value parameter, the procedure or function creates a copy of it; changes made to the copy have no effect on the original variable and are lost when function exits.
On Procedural PointersConsider the next function declaration:
"Data" is a value parameter (a Pointer type). TCallBack is a procedural type defined as:
procedure IterateSubtree(Callback: TCallback; Data: Pointer) ;
In order to call the IterateSubtree procedure you also need a procedure pointer to the TCallBack procedural type.
type TCallback = procedure(Data: Pointer; var Abort: Boolean) ;
In the above scenario, the IterateSubtree calls Callback and passes the Data parameter to it until "Abort" is set to true.
Preserve The Value of a Value Type Pointer Parameter in Delphi Callbacks for Simple TypesThe above procedure declarations are simplified versions of same-named methods found in the Virtual TreeView. The IterateSubtree will walk through all the nodes in the tree and will call the Callback procedure for each node.
And finally here comes the question: what if your Data parameter is an integer variable - you want to count how many nodes fulfill some criteria.
Real Life: Counting "Interesting" NodesIn one of my applications, using TVirtualTreeView, I need to count how many nodes are "candidates" (never mind for what).
The TVirtualTreeView's IterateSubtree should be used.
Here's the stripped down version of the code I'm using:
Now, what's so special about the above code. Pointers and pointer "arithmetics". Here's what:
var candidateCount : integer; begin candidateCount := 0; IterateSubtree(GetCandidates, @candidateCount) ; end; .... procedure GetCandidates(Data: Pointer; var Abort: Boolean) ; begin if isCandidate then //some condition begin Integer(Data^) := 1 + Integer(Data^) ; end; end;
- GetCandidates should count something - the ideal type for this is an integer type variable: "candidateCount".
- IterateSubtree expects a value parameter for the Data parameter which is a Pointer.
- @candidateCount (in IterateSubtree function call) is used to pass the address of the candidateCount variable.
- In the GetCandidates I have to dereferences the pointer to get value stored at the memory address held by the pointer. Since I know this is an integer type value I cast it to integer.
- Once the Data pointer is dereferenced to an integer value I can simply increase the value using:
Integer(Data^) := 1 + Integer(Data^)
And that's it. Nothing smart just another trick (If I may call it like that) possible through the use of pointers:
Data is a value parameter, therefore the "value" of Data is lost when function exists. Since the IterateSubtree calls the GetCandidates several times I needed a way to preserve the value of the Data parameter. This was possible using Pointer manipulation.
Note: if the Data parameter was not a simple type (integer) no pointers are required. Just type cast the pointer back to the original passed type.