Board index » delphi » TeamB: TTabSheet Templates...

TeamB: TTabSheet Templates...

   Last August (1999), Team B posted an helpfull message explaining how
to use memory streaming to clone objects at run-time (typically extra
TTabSheets):

 > I would like to create a predefinied TTabSheet (e.g. four edit boxes
on the
 > TTabsheet) at design time.  And at run time, I would like to create
the
 > predefined TTabSheet as many according to my own need.  Basically my
 > question here is how to create the TTabSheet object with four
editboxes on
 > it, and then call the tabsheet at run time.
 >
 You can do this using streaming. Here is a little example project. The
two key points are

 - the components on the template tabsheet need to be owned by the
tabsheet, not the form.
 The formCreate method takes care of this.
 - all control classes used on the tabsheet need to be registered. The
RegisterClasses call in the
 initialization section takes care of that.

<snip>

   It works fine, and one can even generalise the code by moving the
FormCreate and Initialization bits into the CloneButton event handler.

   However, WriteComponent seems not to write out the objects' event
handlers.  Which means one clones the TTabSheet but it is "dead", having
no event handlers hooked up.  How does one fix this?

       Daniel U. Thibault
a.k.a. Urhixidur
a.k.a. Sire Bohmond de Nice

Sent via Deja.com http://www.deja.com/
Before you buy.

 

Re:TeamB: TTabSheet Templates...


In article <86ibo1$v3...@nnrp1.deja.com>,
  Urhixidur <D.U.Thiba...@Bigfoot.com> wrote:

Quote
>    Last August (1999), Team B posted an helpfull message explaining
how
> to use memory streaming to clone objects at run-time (typically extra
> TTabSheets):

>  > I would like to create a predefinied TTabSheet (e.g. four edit
boxes
> on the
>  > TTabsheet) at design time.  And at run time, I would like to create
> the
>  > predefined TTabSheet as many according to my own need.  Basically
my
>  > question here is how to create the TTabSheet object with four
> editboxes on
>  > it, and then call the tabsheet at run time.

>  You can do this using streaming. Here is a little example project.
The
> two key points are

>  - the components on the template tabsheet need to be owned by the
> tabsheet, not the form.
>  The formCreate method takes care of this.
>  - all control classes used on the tabsheet need to be registered. The
> RegisterClasses call in the
>  initialization section takes care of that.

> <snip>

>    It works fine, and one can even generalise the code by moving the
> FormCreate and Initialization bits into the CloneButton event handler.

>    However, WriteComponent seems not to write out the objects' event
> handlers.  Which means one clones the TTabSheet but it is "dead",
having
> no event handlers hooked up.  How does one fix this?

   Meanwhile, here's a workaround:

uses
   TypInfo;

function CloneControl(AForm : TForm; AControl : TControl) : TControl;
var
   NProps,
   i, j     : Integer;
   c        : TControl;
   ms       : TMemoryStream;
   PropList : TPropList; // = array[0..16379] of PPropInfo;
begin
   //Make the template AControl the owner of all controls on it
   //(Normally, the Form Owns all its controls)
   //If this isn't done, the cloned TControl will be empty
   //Simultaneously register the classes with the streaming system
   RegisterClass(TPersistentClass(AControl.ClassType));
   //Only TWinControls may contain other controls
   if AControl is TWinControl then begin
      for i := 0 to ((AControl as TWinControl).ControlCount - 1) do
begin
         c := (AControl as TWinControl).Controls[i];
         RegisterClass(TPersistentClass(c.ClassType));
         if c.Owner <> AControl then begin
            AForm.RemoveComponent(c);
            AControl.InsertComponent(c);
         end; { then }
      end; { for }
   end; { then }

   ms := TMemoryStream.Create;
   try { ms }
      ms.WriteComponent(AControl);
      //Rewind
      ms.Position := 0;
      //Instantiate Sheet
      Result := ms.ReadComponent(Nil) as TControl; //AControl.ClassType;
   finally
      ms.Free;
   end; { finally }

   //Recover the event handlers from the template's objects
   NProps := GetPropList(Result.ClassInfo, [tkMethod], @PropList);
   for j := 0 to (NProps - 1) do begin
      SetMethodProp(Result, PropList[j], GetMethodProp(AControl,
PropList[j]));
   end; { for }
   if Result is TWinControl then begin
      for i := 0 to ((Result as TWinControl).ControlCount - 1) do begin
         c := (Result as TWinControl).Controls[i];
         NProps := GetPropList(c.ClassInfo, [tkMethod], @PropList);
         for j := 0 to (NProps - 1) do begin
            SetMethodProp(c, PropList[j], GetMethodProp((AControl as
TWinControl).Controls[i], PropList[j]));
         end; { for }
      end; { for }
   end; { then }
end; { CloneControl }

procedure TForm1.CloneButtonClick(Sender: TObject);
var
   Sheet    : TTabSheet;
begin
   Sheet := CloneControl(Self, TabSheet1) as TTabSheet;
   Sheet.PageControl := PageControl1;
   Sheet.Caption := Format('TabSheet%d', [PageControl1.PageCount]);
   Sheet.Name := Sheet.Caption;
end; { CloneButtonClick }

       Daniel U. Thibault
a.k.a. Urhixidur
a.k.a. Sire Bohmond de Nice

Sent via Deja.com http://www.deja.com/
Before you buy.

Re:TeamB: TTabSheet Templates...


   Numerous Difficulties Remain!

In article <86ibo1$v3...@nnrp1.deja.com>,
  Urhixidur <D.U.Thiba...@Bigfoot.com> wrote:

Quote
>    Last August (1999), Team B posted an helpfull message explaining
how
> to use memory streaming to clone objects at run-time (typically extra
> TTabSheets):

>  > I would like to create a predefinied TTabSheet (e.g. four edit
boxes
> on the
>  > TTabsheet) at design time.  And at run time, I would like to create
> the
>  > predefined TTabSheet as many according to my own need.  Basically
my
>  > question here is how to create the TTabSheet object with four
> editboxes on
>  > it, and then call the tabsheet at run time.

>  You can do this using streaming. Here is a little example project.
The
> two key points are

>  - the components on the template tabsheet need to be owned by the
> tabsheet, not the form.
>  The formCreate method takes care of this.
>  - all control classes used on the tabsheet need to be registered. The
> RegisterClasses call in the
>  initialization section takes care of that.

> <snip>

>    It works fine, and one can even generalise the code by moving the
> FormCreate and Initialization bits into the CloneButton event handler.

   I've refined the code somewhat, but a rather large number of
exceptions remain --so many that my original need for the code remains
unfulfilled (I can't clone the TTabSheet template I designed).

   Quite a few controls object to the cloning method, always for the
same reason: the cloned control has no Parent yet.  This occurs during
the ms.ReadComponent.
   TMemo, TListBox and TComboBox children fail to retrieve .Items.List.
   A TRichEdit child fails to retrieve .Lines.Strings.
   A TTabControl child fails to retrieve .TabIndex.
   A THotKey child fails to retrieve .HotKey.
   A TDateTimePicker child fails to retrieve .Date.
   A TTreeView child fails to retrieve .Indent.
   A TListView child fails to retrieve .Items.Data (only if it contains
some Items).
   A TPageControl child fails without giving details, but only if it has
TTabSheet children.
   A TAnimate child fails without giving details.
   A TToolBar child fails without giving details, but only if it has
Images.
   TStringGrid and TDrawGrid do not fail, but they do not clone their
Cells contents (because they are not Published properties).

   Clearly, the problems are two fold:

   a) WriteComponent/ReadComponent ignores public properties; and
   b) some child controls fail to Read back in because their parent (the
newly cloned control) hasn't completed its own Read.

   The *order* in which the properties are set may also be part of the
problem.

############
procedure TransferOwnership(AComponent, BComponent : TComponent);
{
   Sets Ownership of all of AComponent's children (recursively) to
BComponent.
   Note that AComponent can only have children if it is a TWinControl.

Quote
}

var
   i     : Integer;
   Child : TControl;
begin
   //Only TWinControls may have children
   if not (AComponent is TWinControl) then Exit;
   for i := 0 to ((AComponent as TWinControl).ControlCount - 1) do begin
      Child := (AComponent as TWinControl).Controls[i];
      if Child.Owner <> BComponent then begin
         Child.Owner.RemoveComponent(Child);
         BComponent.InsertComponent(Child);
      end; { then }
      TransferOwnership(Child, BComponent);
   end; { for }
end; { TransferOwnership }

procedure RegClasses(AComponent : TComponent);
{
   Private function used by CloneControl.
   Registers AComponent's class as well as those
   of its children (recursively) with the streaming system.
   Note that AComponent can only have children if it is a TWinControl.

Quote
}

var
   i : Integer;
begin
   Classes.RegisterClass(TPersistentClass(AComponent.ClassType));
   //Only TWinControls may have children
   if not (AComponent is TWinControl) then Exit;
   for i := 0 to ((AComponent as TWinControl).ControlCount - 1) do begin
      RegClasses((AComponent as TWinControl).Controls[i]);
   end; { for }
end; { RegClasses }

procedure CloneEvents(AControl, BControl : TControl);
{
   Private function used by CloneControl.
   It recursively copies the event handlers of AControl
   and its children to its clone BControl.

Quote
}

var
   NProps,
   i        : Integer;
   PropList : TPropList; // = array[0..16379] of PPropInfo;
begin
   //Obtain the list of BControl's method properties
   NProps := GetPropList(BControl.ClassInfo, [tkMethod], @PropList);
   for i := 0 to (NProps - 1) do begin
      //Skip read-only properties
      if Assigned(PropList[i]^.SetProc) then
         SetMethodProp(BControl, PropList[i], GetMethodProp(AControl,
PropList[i]));
   end; { for }
   if not (BControl is TWinControl) then Exit;
   for i := 0 to ((BControl as TWinControl).ControlCount - 1) do begin
      CloneEvents((AControl as TWinControl).Controls[i], (BControl as
TWinControl).Controls[i]);
   end; { for }
end; { CloneEvents }

function CloneControl(AControl : TControl) : TControl;
{
   Creates a clone of an existing AControl, including all
   of its properties, events and children.
   The clone will be owned by the same TForm as AControl.
   To avoid Name conflicts, the clone's children are owned
   by the clone, not by the form.  Use TransferOwnership (q.v.)
   to restore the children's ownership once their Names are
deconflicted.
   Particularly useful when loading a TPageControl with TTabSheets.

   Some controls object to this cloning method, always for the same
reason:
   the cloned control has no Parent yet.  TMemo, TListBox and TComboBox
   children fail to retrieve .Items.List.  A TRichEdit child fails to
   retrieve .Lines.Strings.  A TTabControl child fails to retrieve
.TabIndex.
   A THotKey child fails to retrieve .HotKey.  A TDateTimePicker child
fails
   to retrieve .Date.  A TTreeView child fails to retrieve .Indent.
   A TListView child fails to retrieve .Items.Data (only if it contains
   some Items).
   A TPageControl child fails without giving details, but only if it has
   TTabSheet children.  A TAnimate child fails without giving details.
   A TToolBar child fails without giving details, but only if it has
   Images.
   TStringGrid and TDrawGrid do not fail, but they do not clone their
   Cells contents (because they are not Published properties).
   The following are "certified" as having no problem:
   [Standard]
   TPopupMenu, TLabel, TEdit, TButton, TCheckBox, TRadioButton,
TScrollBar,
   TGroupBox, TRadioGroup, TPanel
   [Additional]
   TBitBtn, TSpeedButton, TMaskEdit, TImage, TShape, TBevel, TScrollBox,
   TCheckListBox, TSplitter, TStaticText, TChart
   [Win32]
   TImageList, TTrackBar, TProgressBar, TUpDown, THeaderControl,
TStatusBar,
   TCoolBand

   Based on code by
   Peter Below (TeamB)  <100113.1...@compuserve.com>  12 August 1999

   Usage example:
   var Sheet : TTabSheet;
   Sheet := CloneControl(TabSheet1) as TTabSheet;
   Sheet.PageControl := PageControl1;
   Sheet.Caption := Format('TabSheet%d', [PageControl1.PageCount]);
   Sheet.Name := Sheet.Caption;

Quote
}

var
   ms : TMemoryStream;
begin
   //Make the template AControl the owner of all controls on it
   //(Normally, the Form Owns all its controls)
   //If this isn't done, the cloned TControl will be empty
   TransferOwnership(AControl, AControl);
   //Register the classes with the streaming system
   RegClasses(AControl);

   ms := TMemoryStream.Create;
   try { ms }
      ms.WriteComponent(AControl);
      //Rewind
      ms.Position := 0;
      //Instantiate clone
      Result := ms.ReadComponent(Nil) as TControl;
   finally
      ms.Free;
   end; { finally }

   //Restore ownership
   TransferOwnership(AControl, AControl.Owner);
// TransferOwnership(Result, AControl.Owner);
   //Recover the event handlers
   CloneEvents(AControl, Result);
end; { CloneControl }
############

       Daniel U. Thibault
a.k.a. Urhixidur
a.k.a. Sire Bohmond de Nice

Sent via Deja.com http://www.deja.com/
Before you buy.

Other Threads