دلفي - الدعوة بشكل حيوي وظائف مختلفة

لدي treeview (VirtualTree) التي لديها عقد. عندما ينقر المستخدم على عقدة ، أحتاج إلى تشغيل وظيفة محددة ، اجتياز اسم النص للعقدة. هذه الوظيفة هي واحدة من سمات العقدة. على سبيل المثال ، افترض عقدتين.

العقدة 1 ، الاسم = MyHouse ، الوظيفة = BuildHouse
العقدة 2 ، الاسم = MyCar ، الوظيفة = RunCar

عندما انقر فوق "عقدة 1" ، أحتاج إلى استدعاء الدالة BuildHouse ('MyHouse')؛
عندما اضغط على العقدة 2 ، أحتاج إلى استدعاء RunCar ('MyCar') ؛

الوسيطات هي دائما سلاسل. تجدر الإشارة إلى أن هذه هي وظائف حقيقية ، وليس أعضاء في الصف.

هناك العديد من العقد التي تحتوي على نوع CASE أو IF/THEN من بنية التعليمات البرمجية. أحتاج إلى طريقة لاستدعاء الوظائف المختلفة ديناميكيًا ، على سبيل المثال دون الحاجة إلى ترميز للسلوك. كيف أقوم بهذا العمل؟ كيف يمكنني استدعاء وظيفة عندما أحتاج للبحث عن اسم الوظيفة في وقت التشغيل ، وليس وقت التحويل البرمجي؟

شكر، GS

9
آسف ل offtopic لكنني رأيت أن virtualtree تحظى بشعبية كبيرة أين يمكنني الحصول على هذا المكون؟
وأضاف المؤلف opc0de, مصدر
تعد الفئات الفرعية والطرق الافتراضية أفضل الطرق ، إذا كانت عملية. خلاف ذلك ، Pascal/Delphi Function Functioners بخير. لاري Lustig يعطي مثالا ممتازا أدناه.
وأضاف المؤلف paulsm4, مصدر
أنا أكره أن أكون استنساخ منصبي الخاص ... ولكن بديل آخر (اعتمادا على السيناريو) هو ببساطة لإعلان مؤشر الأسلوب الخاص بك AS مؤشر أسلوب. EXAMPLE: type TNodeFunction = procedure (AInput: String) للكائن ؛ . مزيد من التفاصيل هنا: docwiki.embarcadero.com/RADStudio/XE3/en/&hellip؛
وأضاف المؤلف paulsm4, مصدر
وأضاف المؤلف gabr, مصدر

3 إجابة

كتب لاري مثالاً لطيفاً حول كيفية استخدام مؤشرات الدالة ، ولكن لا تزال هناك مشكلة في تخزينها بهذه الطريقة التي يمكن لـ 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;

... وللاستدعاء الوظيفة المناسبة ، افعل نفس الشيء ولكن استدعوا الطريقة الافتراضية للعملية.

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;
19
وأضاف
+1 - جيد جدا!
وأضاف المؤلف paulsm4, مصدر
+1 ، إجابة ممتازة
وأضاف المؤلف TLama, مصدر

تسمح دلفي بإنشاء المتغيرات التي تشير إلى وظائف ثم استدعاء الدالة من خلال المتغير. لذلك ، يمكنك إنشاء وظائفك وتعيين وظيفة لسمة مكتوبة بشكل صحيح للعقدة (أو يمكنك تعيين وظائف ، على سبيل المثال ، الخاصية data </​​code> المفيدة للعديد من فئات عناصر المجموعة).

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;
13
وأضاف

أنا أبدا استخدام VirtualTree ولكن يمكنني أن أقول لك 2 طرق لذلك.

الطريق الأول:

إذا كنت تستخدم دلفي 2009 أو الإصدار العلوي ، حاول استخدام rtti لأسلوب الاستدعاء بطريقة ديناميكية

هذا مثال على rtti

uses rtti;

function TVLCVideo.Invoke(method: string; p: array of TValue): TValue;
var
  ctx     : TRttiContext;
  lType   : TRttiType;
  lMethod : TRttiMethod;

begin
  ctx := TRttiContext.Create;
  lType:=ctx.GetType(Self.ClassInfo);//where is the your functions list ? if TFunctions replace the Self with TFunctions class
  Result := nil;
  try
    if Assigned(lType) then
      begin
       lMethod:=lType.GetMethod(method);

       if Assigned(lMethod) then
        Result := lMethod.Invoke(Self, p); //and here is same replace with your functions class
      end;
  finally
    lMethod.Free;
    lType.Free;
    ctx.Free;
  end;
end;

الطريقة الثانية إذا كنت تعرف نوع المعلمات وعدد الدالة ، يمكنك وضع مؤشر لوظائفك في كل عقد!

But you have to define a procedure or function type like as Tproc = procedure (var p1: string; p2: integer) of object;

2
وأضاف
تأكد من أن TProc = procedure (var p1: string؛ p2: integer) ؛ كما user1009073 بشكل خاص يقولون أنها ليست أساليب لفئة.
وأضاف المؤلف Gerry Coll, مصدر