use*_*073 9 delphi function dynamic call
我有一个树视图(VirtualTree),它有节点.当用户点击某个节点时,我需要运行一个特定的函数,传递该节点的文本名称.此函数是节点的属性之一.例如,假设有两个节点.
节点1,Name = MyHouse,Function = BuildHouse
节点2,Name = MyCar,function = RunCar
当我点击节点1时,我需要调用BuildHouse函数('MyHouse');
当我点击Node 2时,我需要调用RunCar('MyCar');
参数总是字符串.应该注意的是,这些都是真正的功能,而不是一个类的成员.
有太多的节点要有CASE或IF/THEN类型的代码结构.我需要一种动态调用各种函数的方法,即不需要对代码进行硬编码.我该怎么做呢?当我必须在运行时查找函数的名称而不是编译时,如何调用函数?
谢谢,GS
gab*_*abr 19
Larry已经编写了一个关于如何使用函数指针的很好的例子,但是仍然存在以VirtualTree可以访问它们的方式存储它们的问题.您可以在这里使用至少两种方法.
如果名称和功能在整个应用程序中属于一起,您通常希望将它们组合到一个结构中.
type
TStringProc = procedure (const s: string);
TNodeData = record
Name: string;
Proc: TStringProc;
end;
var
FNodeData: array of TNodeData;
Run Code Online (Sandbox Code Playgroud)
如果你有两个字符串函数......
procedure RunCar(const s: string);
begin
ShowMessage('RunCar: ' + s);
end;
procedure BuildHouse(const s: string);
begin
ShowMessage('BuildHouse: ' + s);
end;
Run Code Online (Sandbox Code Playgroud)
...您可以使用以下代码将它们放入此结构中.
procedure InitNodeData;
begin
SetLength(FNodeData, 2);
FNodeData[0].Name := 'Car'; FNodeData[0].Proc := @RunCar;
FNodeData[1].Name := 'House'; FNodeData[1].Proc := @BuildHouse;
end;
Run Code Online (Sandbox Code Playgroud)
然后,VirtualTree只需要将索引存储到此数组中,作为属于每个节点的附加数据.
InitNodeData;
vtTree.NodeDataSize := 4;
vtTree.AddChild(nil, pointer(0));
vtTree.AddChild(nil, pointer(1));
Run Code Online (Sandbox Code Playgroud)
OnGetText从节点数据中读取此整数,查看FNodeData并显示名称.
procedure vtTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: string);
begin
CellText := FNodeData[integer(vtTree.GetNodeData(Node)^)].Name;
end;
Run Code Online (Sandbox Code Playgroud)
单击(我在本例中使用了OnFocusChanged),您将再次从节点数据中获取索引并调用相应的函数.
procedure vtTreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex);
var
nodeIndex: integer;
begin
if assigned(Node) then begin
nodeIndex := integer(vtTree.GetNodeData(Node)^);
FNodeData[nodeIndex].Proc(FNodeData[nodeIndex].Name);
end;
end;
Run Code Online (Sandbox Code Playgroud)
如果仅在显示树时使用字符串函数,则独立管理数据结构(节点名称)并将函数指针直接存储到节点数据中是有意义的.要做到这一点,你必须将NodeDataSize扩展为8(4个字节用于指针到名称结构,4个字节用于函数指针).
由于VirtualTree没有提供任何处理用户数据的好方法,我喜欢使用以下帮助程序来访问用户数据中单个指针大小的"插槽".(想象一下,用户数据是第一个索引为0的数组 - 这些函数访问这个伪数组.)
function VTGetNodeData(vt: TBaseVirtualTree; node: PVirtualNode; ptrOffset: integer): pointer;
begin
Result := nil;
if not assigned(node) then
node := vt.FocusedNode;
if assigned(node) then
Result := pointer(pointer(int64(vt.GetNodeData(node)) + ptrOffset * SizeOf(pointer))^);
end;
function VTGetNodeDataInt(vt: TBaseVirtualTree; node: PVirtualNode; ptrOffset: integer): integer;
begin
Result := integer(VTGetNodeData(vt, node, ptrOffset));
end;
procedure VTSetNodeData(vt: TBaseVirtualTree; value: pointer; node: PVirtualNode;
ptrOffset: integer);
begin
if not assigned(node) then
node := vt.FocusedNode;
pointer(pointer(int64(vt.GetNodeData(node)) + ptrOffset * SizeOf(pointer))^) := value;
end;
procedure VTSetNodeDataInt(vt: TBaseVirtualTree; value: integer; node: PVirtualNode;
ptrOffset: integer);
begin
VTSetNodeData(vt, pointer(value), node, ptrOffset);
end;
Run Code Online (Sandbox Code Playgroud)
树构建器(FNodeNames存储各个节点的名称):
Assert(SizeOf(TStringProc) = 4);
FNodeNames := TStringList.Create;
vtTree.NodeDataSize := 8;
AddNode('Car', @RunCar);
AddNode('House', @BuildHouse);
Run Code Online (Sandbox Code Playgroud)
辅助函数AddNode将节点名称存储到FNodeNames中,创建新节点,将节点索引设置为第一个用户数据"slot",将字符串过程设置为第二个"slot".
procedure AddNode(const name: string; proc: TStringProc);
var
node: PVirtualNode;
begin
FNodeNames.Add(name);
node := vtTree.AddChild(nil);
VTSetNodeDataInt(vtTree, FNodeNames.Count - 1, node, 0);
VTSetNodeData(vtTree, pointer(@proc), node, 1);
end;
Run Code Online (Sandbox Code Playgroud)
文本显示与前一种情况相同(除了我现在使用辅助函数来访问用户数据).
procedure vtTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: string);
begin
CellText := FNodeNames[VTGetNodeDataInt(vtTree, node, 0)];
end;
Run Code Online (Sandbox Code Playgroud)
OnFocusChanged从第一个用户数据"slot"获取名称索引,从第二个"slot"获取函数指针并调用相应的函数.
procedure vtTreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex);
var
nameIndex: integer;
proc: TStringProc;
begin
if assigned(Node) then begin
nameIndex := VTGetNodeDataInt(vtTree, node, 0);
proc := TStringProc(VTGetNodeData(vtTree, node, 1));
proc(FNodeNames[nameIndex]);
end;
end;
Run Code Online (Sandbox Code Playgroud)
还可以选择以面向对象的方式进行.(我知道我在开头说"至少有两种方法".这是因为第三种方法并不完全符合你的定义(字符串函数是纯函数,而不是方法).)
为每个可能的字符串函数设置一个类层次结构.
type
TNode = class
strict private
FName: string;
public
constructor Create(const name: string);
procedure Process; virtual; abstract;
property Name: string read FName;
end;
TVehicle = class(TNode)
public
procedure Process; override;
end;
TBuilding = class(TNode)
public
procedure Process; override;
end;
{ TNode }
constructor TNode.Create(const name: string);
begin
inherited Create;
FName := name;
end;
{ TVehicle }
procedure TVehicle.Process;
begin
ShowMessage('Run: ' + Name);
end;
{ TBuilding }
procedure TBuilding.Process;
begin
ShowMessage('Build: ' + Name);
end;
Run Code Online (Sandbox Code Playgroud)
节点(类的实例)可以直接存储在VirtualTree中.
Assert(SizeOf(TNode) = 4);
vtTree.NodeDataSize := 4;
vtTree.AddChild(nil, TVehicle.Create('Car'));
vtTree.AddChild(nil, TBuilding.Create('House'));
Run Code Online (Sandbox Code Playgroud)
要获取节点文本,只需将用户数据转换回TNode并访问Name属性...
procedure vtTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: string);
begin
CellText := TNode(VTGetNodeData(vtTree, node, 0)).Name;
end;
Run Code Online (Sandbox Code Playgroud)
...并调用相应的函数,执行相同的操作但调用Process虚方法.
procedure vtTreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex);
begin
TNode(VTGetNodeData(vtTree, node, 0)).Process;
end;
Run Code Online (Sandbox Code Playgroud)
此方法的问题是您必须在VirtualTree销毁之前手动销毁所有这些对象.最好的地方是在OnFreeNode事件中.
procedure vtTreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
begin
TNode(VTGetNodeData(vtTree, node, 0)).Free;
end;
Run Code Online (Sandbox Code Playgroud)
Lar*_*tig 14
Delphi允许创建指向函数的变量,然后通过变量调用函数.因此,您可以创建函数并将函数分配给节点的正确类型属性(或者您可以将函数分配给,例如,data许多集合项类的便捷属性).
interface
type
TNodeFunction = function(AInput: String): String;
implementation
function Func1(AInput: String): String;
begin
result := AInput;
end;
function Func2(AInput: String): String;
begin
result := 'Fooled You';
end;
function Func3(AInput: String): String;
begin
result := UpperCase(AInput);
end;
procedure Demonstration;
var
SomeFunc, SomeOtherFunc: TNodeFunction;
begin
SomeOtherFunc = Func3;
SomeFunc := Func1;
SomeFunc('Hello'); // returns 'Hello'
SomeFunc := Func2;
SomeFunc('Hello'); // returns 'Fooled You'
SomeOtherFunc('lower case'); // returns 'LOWER CASE'
end;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
17594 次 |
| 最近记录: |