2

I have a design and run-time component that contains a large number of event handlers. I'll call it TNewComp for now. I create an instance of TNewComp on a TForm and fill in the event stubs with specific code via the property editor at design time, and realize I would like to be able to create new instances of TNewcomp that use the current set of event handler code.

To do this now, I call TNewComp's constructor and then "manually" assign each of the new instance's event handlers the corresponding event stub code resident on the form that contains the TNewComp instance created at design time. So if I have an instance of TNewComp assigned to a variable named FNewComp on a form called TNewForm, for each event handler I would do:

FNewComp.onSomething = TNewform.onSomething
(... repeat for each event handler belonging to TNewComp ...)

This works fine, but it is cumbersome and worse, if I add a new event handler to TNewComp, I have to remember to update my "newTComp()" function to make the event handler assignment. Rinse and repeat this process for every unique component type that I create new instances of dynamically.

Is there way to automate this process, perhaps using property inspection or some other Delphi 6 introspection technique?

-- roschler

Robert Oschler
  • 14,153
  • 18
  • 94
  • 227
  • 2
    I think I'd just use Alt+F12 and do a quick copy/paste in the .dfm file. I'm not sure this merits bespoke tooling beyond that. – David Heffernan Jul 06 '11 at 17:13
  • @David Hefferman - I'm not sure I understand how your solution works when creating a new instances at runtime, potentially a lot of them, when the number of instances is unknown until runtime and they are all created via TNewComp.Create at runtime. – Robert Oschler Jul 06 '11 at 17:41
  • I thought your problems were at design time. I don't see why it's so hard to do what you do at present. What you are thinking of requires some rules to associate events and handlers. What do you have in mind? Name based? – David Heffernan Jul 06 '11 at 17:55

3 Answers3

4

I used the following code. Be careful with the Dest owner when creating, the safest way is to pass Nil and free the component by yourself later.

implementation uses typinfo;

procedure CopyComponent(Source, Dest: TComponent);
var
  Stream: TMemoryStream;
  TypeData : PTypeData;
  PropList: PPropList;
  i, APropCount: integer;
begin
  Stream:=TMemoryStream.Create;
  try
    Stream.WriteComponent(Source);
    Stream.Position:=0;
    Stream.ReadComponent(Dest);
  finally
    Stream.Free;
  end;

  TypeData := GetTypeData(Source.ClassInfo);
  if (TypeData <> nil) then
  begin
    GetMem(PropList, SizeOf(PPropInfo)*TypeData^.PropCount);
    try
      APropCount:=GetPropList(Source.ClassInfo, [tkMethod], PropList);
      for i:=0 to APropCount-1 do
        SetMethodProp(Dest, PropList[i], GetMethodProp(Source, PropList[i]))
    finally
      FreeMem(PropList);
    end;
  end;
end;
Maksee
  • 2,311
  • 2
  • 24
  • 34
  • thanks, I'll give that code a try. I almost never pass a parent to dynamically created components and choose to manage their lifetime myself. Otherwise it's an almost guaranteed session with FastMM later on to hunt down a doubly-freed or prematurely freed object as you point out. – Robert Oschler Jul 06 '11 at 21:16
2

One option would be to save "the properly set up component" into stream and then load that strem into new, dynamically created component as if it is done by Delphi IDE/runtime.

Another option is to use RTTI, the TypInfo unit. There you have function GetPropList witch will enable you to query for available events (TypeKind tkMethod) and then you can use GetMethodProp and SetMethodProp to copy eventhandlers from one component to other.

ain
  • 22,394
  • 3
  • 54
  • 74
1

I tweaked Maksee's solution to the following:

function CopyComponent(Source: TComponent; Owner: TComponent = nil): TComponent;
var
    Stream: TMemoryStream;
    TypeData : PTypeData;
    PropList: PPropList;
    i, APropCount: integer;
begin
    if not Assigned(Source) then
        raise Exception.Create('(CopyComponent) The Source component is not assigned.');

    Result := TComponent.Create(Owner);

    Stream := TMemoryStream.Create;

    try
        Stream.WriteComponent(Source);
        Stream.Position := 0;
        Stream.ReadComponent(Result);
    finally
        Stream.Free;
    end; // try()

    // Get the type data for the Source component.
    TypeData := GetTypeData(Source.ClassInfo);

    if (TypeData <> nil) then
    begin
        // Get the property information for the source component.
        GetMem(PropList, SizeOf(PPropInfo) * TypeData^.PropCount);

        try
            // Get the properties count.
            APropCount := GetPropList(Source.ClassInfo, [tkMethod], PropList);

            // Assign the source property methods to the destination.
            for i := 0 to APropCount - 1 do
                SetMethodProp(Result, PropList[i], GetMethodProp(Source, PropList[i]))

        finally
            // Free the property information object.
            FreeMem(PropList);
        end; // try()
    end; // if (TypeData <> nil) then
end;

So that a new component is returned by the function rather than passing in an existing component reference (the Dest parameter in Maksee's version). If anyone can see a flaw or problem that will result from this variant please comment.

Robert Oschler
  • 14,153
  • 18
  • 94
  • 227