gpt4 book ai didi

Delphi - 动态调用不同的函数

转载 作者:行者123 更新时间:2023-12-03 14:42:23 28 4
gpt4 key购买 nike

我有一个有节点的 TreeView (VirtualTree)。当用户单击节点时,我需要运行特定的函数,并传递节点的文本名称。该功能是节点的属性之一。例如,假设有两个节点。

节点 1,名称 = MyHouse,功能 = BuildHouse
节点 2,名称 = MyCar,功能 = RunCar

当我点击节点1时,我需要调用函数BuildHouse('MyHouse');
当我点击节点2时,我需要调用RunCar('MyCar');

参数始终是字符串。应该注意的是,这些是真正的函数,而不是类的成员。

节点太多,无法形成 CASE 或 IF/THEN 类型的代码结构。我需要一种动态调用各种函数的方法,即无需对行为进行硬编码。我该怎么做呢?当我必须在运行时而不是编译时查找函数名称时,如何调用函数?

谢谢,GS

最佳答案

Larry 编写了一个关于如何使用函数指针的很好的示例,但是仍然存在以 VirtualTree 可以访问它们的方式存储它们的问题。您在这里至少可以使用两种方法。

1。将函数指针与数据一起存储

如果名称和函数在整个应用程序中属于一起,您通常希望将它们放在一个结构中。

type
TStringProc = procedure (const s: string);

TNodeData = record
Name: string;
Proc: TStringProc;
end;

var
FNodeData: array of TNodeData;

如果你有两个字符串函数...

procedure RunCar(const s: string);
begin
ShowMessage('RunCar: ' + s);
end;

procedure BuildHouse(const s: string);
begin
ShowMessage('BuildHouse: ' + s);
end;

...您可以使用以下代码将它们放入此结构中。

procedure InitNodeData;
begin
SetLength(FNodeData, 2);
FNodeData[0].Name := 'Car'; FNodeData[0].Proc := @RunCar;
FNodeData[1].Name := 'House'; FNodeData[1].Proc := @BuildHouse;
end;

VirtualTree 只需要在该数组中存储一个索引作为属于每个节点的附加数据。

InitNodeData;
vtTree.NodeDataSize := 4;
vtTree.AddChild(nil, pointer(0));
vtTree.AddChild(nil, pointer(1));

OnGetText 从节点数据中读取此整数,查看 FNodeData 并显示名称。

procedure vtTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: string);
begin
CellText := FNodeData[integer(vtTree.GetNodeData(Node)^)].Name;
end;

单击时(我在本例中使用了 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;

2。将函数指针直接存储到 VirtualTree

如果您的字符串函数仅在显示树时使用,则独立管理数据结构(节点名称)并将函数指针直接存储到节点数据中是有意义的。为此,您必须将 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;

树构建器(FNodeNames 存储各个节点的名称):

Assert(SizeOf(TStringProc) = 4);
FNodeNames := TStringList.Create;
vtTree.NodeDataSize := 8;
AddNode('Car', @RunCar);
AddNode('House', @BuildHouse);

辅助函数AddNode将节点名称存储到FNodeNames中,创建一个新节点,将节点索引设置到第一个用户数据“槽”中,并将字符串过程设置到第二个“槽”中。

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;

文本显示与之前的情况相同(除了我现在使用辅助函数来访问用户数据)。

procedure vtTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: string);
begin
CellText := FNodeNames[VTGetNodeDataInt(vtTree, node, 0)];
end;

OnFocusChanged 从第一个用户数据“槽”中获取名称索引,从第二个“槽”中获取函数指针并调用相应的函数。

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;

3。面向对象方法

还可以选择以面向对象的方式进行操作。 (我知道我一开始就说了“至少两种方法”。那是因为第三种方法并不完全符合您的定义(字符串函数作为纯函数,而不是方法)。)

为每个可能的字符串函数设置一个类层次结构。

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;

节点(类的实例)可以直接存储在 VirtualTree 中。

Assert(SizeOf(TNode) = 4);
vtTree.NodeDataSize := 4;
vtTree.AddChild(nil, TVehicle.Create('Car'));
vtTree.AddChild(nil, TBuilding.Create('House'));

要获取节点文本,只需将用户数据转换回 TNode 并访问 Name 属性...

procedure vtTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: string);
begin
CellText := TNode(VTGetNodeData(vtTree, node, 0)).Name;
end;

...并调用适当的函数,执行相同的操作,但调用 Process 虚拟方法。

procedure vtTreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex);
begin
TNode(VTGetNodeData(vtTree, node, 0)).Process;
end;

这种方法的问题在于,您必须在销毁 VirtualTree 之前手动销毁所有这些对象。最好的位置是在 OnFreeNode 事件中。

procedure vtTreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
begin
TNode(VTGetNodeData(vtTree, node, 0)).Free;
end;

关于Delphi - 动态调用不同的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8102255/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com