3

I have this application that reuses a sort of idiom in a number of places. There's a TPanel, and on it are some labels and buttons. The purpose is to allow the user to select a date range.

Here's an example of one such panel at runtime

The "&Dates" caption is one label, and the "All Dates" part is a second one. When the user clicks the "Choose" button a form pops up presenting the user with a pair of Date/Time controls and OK/Cancel buttons. If the user hits OK after selecting some dates, the 2nd label changes to "From mm/dd/yyyy To mm/dd/yyyy".

Is it plausible to create a component that packages up these controls? I have been looking at various resources for component writers and they don't seem to be pointed at the problems I am thinking about, such as handling the onclick events for the buttons. If this is a reasonable thing to attempt, I'd also appreciate pointers to descriptions of how to make such a "composite control."

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
wades
  • 927
  • 9
  • 24
  • 7
    If you don't want to write a full-blown component you can use [frames](http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Forms_TFrame.html) for this. – Uli Gerhardt Jun 05 '12 at 18:33
  • +1. This is the modern alternative. (You might want to expand it some, though; technically it doesn't qualify as an answer here because it's only a link that goes off-site, and should be flagged as "not an answer".) You can also multi-select the components, though, and use `Component->Create Component Template...` from the IDE's menu. (It's the old-school way, and has been there since Delphi 1 or 2.) – Ken White Jun 05 '12 at 21:48
  • Actually I found the TFrame alternative when I started searching for the phrase "delphi composite controls" and that does look as though it could do the trick. There is some lack of design-time support for the properties I'd like the thing to have but it will probably do the trick. – wades Jun 05 '12 at 21:59
  • Yes, frames will also do the trick. But, they can be a bit unwieldly to work with at design time. For smaller components, like the one in your example, I usually create components. But, for more complex composite arrangements, resembling forms, I usually use frames. – bjaastad_e Jun 05 '12 at 22:09
  • If you are comfortable writing custom controls which perform both drawing and mouse events, then this is a rather simple control to be built. But for newbies to custom controls, it would be a fine project to get to learn how to write controls. This would mean there is no `TBitBtn` or `TLabel` but just the one `TCustomControl` descendant and its properties. Buttons and text would get drawn dynamically using the overridden `Paint` procedure of the control. – Jerry Dodge Jun 05 '12 at 23:07

4 Answers4

6

It's reasonable, yes.

To create such a component, just derive a new class from for instance TCustomPanel, and add the sub-components as fields within the class.

Like this:

TMyDatePicker = class(TCustomPanel)
protected
  FChooseButton: TButton;
  FClearButton: TButton;
public
  constructor Create(Owner: TComponent); override; 
end;

constructor TMyDatePicker.Create(Owner: TComponent)
begin
  // Inherited
  Inherited;

  // Create Choose Button
  FChooseButton := TButton.Create(Self);
  FChooseButton.Parent := Self;
  FChooseButton.Align := alRight;
  FChooseButton.Caption := 'Choose';

  // Create Clear Button
  FClearButton := TButton.Create(Self);
  FClearButton.Parent := Self;
  FClearButton.Align := alRight;
  FClearButton.Caption := 'Clear';
end;

To add event handers, just add new protected procedures to your class.

For instance:

procedure TMyDatePicker.HandleChooseButtonClick(Sender: TObject)
begin
  // Do whatever you want to do when the choose button is clicked
end;

Then connect the event handler to the OnClick event of the choose button (this should be done within the Create method of the class):

FChooseButton.OnClick := HandleChooseButtonClick;

There's a bit more to it than this, of course, such as fine tuning the alignments of the buttons and adding icons. Also you'll need to create your own events, such as OnDateSelected or OnDateModified.

But, apart from that, I think the above example should at least get you going. :)

bjaastad_e
  • 691
  • 6
  • 10
  • Some people recommend against setting Parent when you dynamically create a control. – Gabriel Sep 12 '12 at 13:54
  • @Altar I think maybe you're confusing it with warnings against setting Parent within the constructor of the control itself. That should be avoided, since it breaks the Parent assignment logic of the Delphi DFM-streaming system. The way I'm doing it here is the correct way, I think, letting the owner of the control set it's Parent property. – bjaastad_e Sep 13 '12 at 07:07
5

Yes, absolutely it is wise to build components like that because It saves a huge amount of coding.

Here is a guide to creating them semi-visually: How to Build Aggregate/Composite Components in Delphi

Essentially, the process outlined in this document is:

  1. Design the layout of your components inside a form in Delphi, placing all the components inside a TPanel (or a descendant thereof).
  2. Select and copy the panel and paste it into a text file.
  3. Replace all instances of " = " with " := ", and add a semi-colon to the end of each line.
  4. Convert all DFM "object" declaration lines to appropriate object constructor code, setting the parent of all visual controls to the container panel.
  5. Clean up any remaining code. Bitmaps will need to be placed in resource files.
  6. Place this new pascal code inside a create constructor for your component. Within the constructor , group object sections under the appropriate sub-component creator.

Where the document I think is wrong is that, for example, the example component is descended from a TPanel whereas to me it makes more sense to use TCustomPanel and expose only the methods you want to.

But it also explains how to add OnClick handlers etc.

the advantage of this method is that you get the layout of the components within the Panel done visually.

RobS
  • 3,807
  • 27
  • 34
2

A very pragmatic way to develop composite controls is to use TFrame as a base for it.

That way, you can visually design your control, and either use events or inheritance

There are a couple of things you need to watch but all in all it is a much easier process than coding everything by hand (like some of the other answers suggest).

Things to watch for (not a complete list, but close):

  • don't forget the sprig
  • at design time (both in the TFrame and when putting the composite control on a design surface), sub-controls marked Visible=True are still visible. Two ways to solve this: Destroy those controls, or move them to an invisible region (Top/Left to minus values or to values bigger than Width/Height of the parent)
  • registering the TFrame descendent as a component where the TFrame descendent is also part of your project sometimes confues the IDE. Easy solution: call the TFrame descendent "TMyCustomControl", derive "TMyControl" from it, and register "TMyControl" as a component.

As a bonus, you don't have to remove the bevel/border and caption from the TPanel.

Jeroen Wiert Pluimers
  • 23,965
  • 9
  • 74
  • 154
2

Yet another way is to make the group of components a component template.

iamjoosy
  • 3,299
  • 20
  • 30
  • 1
    @Ken pointed this out in the 1st answer, and I did look at it. I don't like it... basically this looks like a form of copy-and-paste programming: it clones all of the assorted event handlers for all of the controls, for example, so that if you use it n times and something turns out to be wrong, you have to fix it in n places. – wades Jun 06 '12 at 15:24