Search code examples
delphifiremonkey

How to get Delphi to serialize dynamically created controls along with events attached to them?


I have a container control which is TFramedScrollBox. This in turn contains TFlowLayout which in turn contains TPanel and TRectangle in them.

Again TPanel can contain other controls like Edit Box, Combo Box, etc.

So the hierarchy is like this:

TFramedScrollBox
   |->TFlowLayout 
         |-> TPanel 
         |     |-> EditBox
         |     |-> ComboBox
         |     |-> DateTime Picker
         |     |-> Label
         |     |-> CheckBox
         |     |-> Radio Buttons
         |     |-> ListBox
         |-> TRectangle 

Actually all the controls in TFramedScrollBox (except for TRectangle) are created dynamically at runtime. At time of creation I am attaching events to each control as necessary based on different criteria.

Here is the code that I use to attache events to controls are time of creation:

pnlNewRow.OnClick := RowClick;
pnlNewRow.OnMouseEnter := RowMouseEnter;
pnlNewRow.OnMouseLeave := RowMouseLeave;
pnlNewRow.OnDblClick   := RowDblClick;

I want to save all the controls in the container control will its sub components & sub-sub components along with events attached to each control in a field in SQLite. And in future load all the components form SQLite and create them on form.

I tried to serialize the container component but only the components are getting serialized and the events attached to it are not getting serialized.

Here is the code snippet that I am using to serialize components:

procedure TfrmMain.Button1Click(Sender: TObject);
var
  MS: TMemoryStream;
  TS: TStringStream;
begin
  MS := TMemoryStream.Create;
  MS.WriteComponent(sbMain);
  MS.Position := 0;
  TS := TStringStream.Create;
  ObjectBinaryToText(MS, TS);
  TS.Position := 0;

  ShowMessage(TS.DataString);
end;

Please guide as to how I can save all the events a component is linked to and also the custom properties (that I plan to add in future) that are attached to it.


Solution

  • Please compile and run this minimal FMX project

    type
      TForm1 = class(TForm)
        Memo1: TMemo;
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
      private
      protected
      public
      end;
    [...]
    function ComponentToString(AComponent : TComponent) : String;
    var
      SS : TStringStream;
      MS : TMemoryStream;
      Writer : TWriter;
    begin
      SS := TStringStream.Create('');
      MS := TMemoryStream.Create;
      Writer := TWriter.Create(MS, 4096);
    
      try
        Writer.Root := AComponent;
        Writer.WriteSignature;
        Writer.WriteComponent(AComponent);
        Writer.FlushBuffer;
        MS.Position := 0;
        ObjectBinaryToText(MS, SS);
        Result := SS.DataString;
      finally
        Writer.Free;
        SS.Free;
      end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      Memo1.Lines.Text := ComponentToString(Form1);
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      //
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      //
    end;
    
    end.
    

    Clicking Button1 writes this to Memo1:

    object Form1: TForm1
      Left = 224
      [...]
      OnCreate = FormCreate
      OnDestroy = FormDestroy
      DesignerMasterStyle = 0
      [...]
      object Button1: TButton
        IsPressed = True
        [...]
        OnClick = Button1Click
      end
      object Memo1: TMemo
        [...]
      end
    end
    

    which, as you can see, includes the two event handlers of Form1 and the single one of Button1 and is like what the .DFM would contain. Isn't that what you were asking for?

    And since you can store ComponentToString as a library routine, I'm not sure it is correct to say "to serialize events you need to manually write code", well, not form-specific code anyway.

    Btw, this works fine with dynamically created controls. Add a TButton, called Button, in the Private, Protected or Public section of the form and the following code to the FormCreate handler

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      Button :=  TButton.Create(Self);
      Button.Name := 'Button';
      Button.Parent := Self;
      Button.OnClick := Button1Click;
    end;
    

    Clicking either button produces a similar list as the one above, but with the addition of the extra, dynamically created, button.