tags:

views:

89

answers:

1

I'll start by saying my previous experience with component design was limited to fixing minor bugs in a pretty standard collection of visual controls.

I am currently working on a project which uses a tree of TPersistent descendants to define, store and retrieve data. The top of this tree is a descendant of TComponent. It has a published property which is a descendant of TCollection. Here's a simplification of the structure:

TMyComponent = class(TComponent)
private
  FMyCollection: TMyCollection;
published
  property Items: TMyCollection read FMyCollection write FMyCollection;
end;

TMyCollection = class(TCollection)
protected
  procedure SetItem(index: integer; val: TCompositeItem);
  function GetItem(index: integer): TCompositeItem;
public
  property Items[index: integer]: TCompositeItem read GetItem write SetItem; default;
end;

TCompositeItem = class(TCollectionItem)
private
  FMyCollection: TMyCollection;
published
  property Items: TMyCollection read FMyCollection write FMyCollection;
end;

There are at least a dozen descendants of TCompositeItem. When you want to add a new item to the collection of one of the nodes in the tree you bring up the context menu and click "Add Item". This creates a new item which you must then further define as one one of the numerous descendants of TCompositeItem using the Object Inspector.

This works relatively well. The problem is that changes to the tree in the designer are not reflected in the associated source file. To get around this my predecessor added some synchronization code that recreates declarations in the .pas file from the tree in the .dfm. To properly synchronize everything you have to save your changes in the dfm, then double-click the component on the form (which triggers the synchronization code), then save again. Additionally you can only do one synchronization per editing session otherwise you get a prompt telling you to close the form, reopen it, then do the synchronization. If you forget to follow this procedure all kinds of strange behaviors occur at run time. Its kind of awkward but it works.

What I would like to do is have changes in the designer automatically updated in the cached source file then saved as normal when the developer clicks save, eliminating the need for the awkward three step synchronization.

This is how normal controls work so I'm not really sure why it doesn't work with this one. I have a gut feeling that the reason may be related to the ultimate class for each item in a collection not being determined until after it is created in the designer. Any suggestions?

Update

Upon further investigation it looks like the synchronization code does two things:

  1. Edits the class definition of the form, adding published fields for each node in the tree.
  2. Edits a procedure called InitializeComponents, which assigns each node to one of the published fields.
A: 

The DFM streaming system is not designed to handle multiple TCollectionItem descendant classes being stored in a single TCollection. You would have to disable the native streaming for your TCollection and then stream all of the items manually so that you can store each item's ClassName into the DFM, and then read it back and instantiate the correct TCollectionItem class manually during loading time.

As for making updates to the source code, you need to handle that in a separate design-time component editor, not in the component itself. Normally, you would register a TComponentEditor class that adds a menu item to the main component's right-click context menu at design-time. When invoked, that menu item can prompt the user for a class type as needed, and then instantiate it and add a reference to it to the component's parent Form by calling the IDesigner.CreateComponent() method. However, this means the collection items need to be TComponent descendants and not TCollectionItem descendants anymore.

The system you are trying to implement is very simiilar to how the TFields collection works. But if you look closer at it, it is not based on TCollection and TCollectionItem at all. TField derives directly from TComponent, and TFields derives directly from TObject.

Remy Lebeau - TeamB