Though all the following work, and all of them show handles when selected, I prefer those that also display handles when several components are selected.
Note: Controls and Components are similar. Components can be displayed on forms at design time, but never when the program is running. Controls, derived from components, appear on forms at both design time and runtime.
TObject -> TPersistent -> TComponent -> TControl -> TWinControl
Note: This page describes components that are composed of several components. This is different from the case where the designer adds several separate components to a form and then associates them using the Object Inspector.
In general, if you click and drag with the mouse, a box will be displayed. When the mouse is released, all the components inside the box will be "selected". (Shift-click also works.) Delphi provides visual feedback by placing 4 grey handles (4 small grey boxes) on each of the corners of the selected components.
The problem is that the control's contents are rendered on top of the handles. To be clear, the handles are displayed on top of the parent control, but under the child controls. As a result, if a child control is placed in a corner of its parent, then that handle will not be displayed. Since these handles are 5x5 pixels, leaving a margin of 2 or 3 pixels between the subcomponents and the corners of the parent "fixes" the problem. Note that both panels and frames have this problem when their internal components are too close to the corners.
I only know one composite VCL control shipped to Delphi 5 - Samples/TSpinEdit - and it has this problem. However, since only a part of the right 2 handles is covered, they are still visible.
Unfortunately, since the handles appear to be rendered by the Delphi IDE, I am not sure how to fix this (or if it is even possible).
Note that the 4 gray multiselect handles are covered if the components are at the edge of the frame. The solution is to leave a margin of 2 or 3 pixels between the subcomponents and the corners of the frame.
TWinControl / TPanel
Disadvantages with respect to a TFrame
To correctly draw the subcomponents, particularly at design time, you will need to override Create, SetBounds, Resize, and AdjustSize.
|Create||I call AdjustSize from here after the sub-components are created. This may not be required .. I added this as part of the IDE debug before overriding SetBounds and Resize. I have not verified that it is necessary. (It does not hurt.)|
|SetBounds||This is called every time Top, Left, Height, or Width is changed .. except when placing a component on a form at design time.|
|Resize||This is called when a component is added from the toolbar to a form. (Called at design time.)|
|AdjustSize||This is where I move (and resize) the sub-components.
The most important line of this method is the one that sets the Parent property. Without this, the sub-components will not be displayed.
When the program loads, SetBounds is called many times, before Parent has a value. AdjustSize is called again when the window is created (after Loaded is called and after Parent has a value).
This method is also called from the constructor (via inherited) before the constructor has created any of the sub-components. The exit stops the related errors.
It is likely that this should be a new method and not the existing virtual method. .. mainly because one of the inherited methods (TControl.AdjustSize) calls SetBounds with the current values (seems weird somehow). However, TWinControl.AdjustSize does not call its inherited method.
|Loaded||This is the obvious place to put some of this code. However, while this method is called when running a program and when reopening a form in the IDE, it is not called when placing a component on a form. In addition, the Parent properties are not yet assigned. As a result, code placed here is of little value.|
TmcLegendElement = class(TCheckBox) protected procedure AdjustSize ; override;// defined in TWinControl procedure Resize ; override;// defined in TControl public constructor Create(AOwner: TComponent); override; procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override; end; constructor TmcLegendElement.Create(AOwner: TComponent); begin inherited; FSeries_Color_UIPanel:= TPanel.Create(self); // must not be AOwner FSeries_Color_UIPanel.Parent := Parent; // This does not work - *Parent* = nil FSeries_Color_UIPanel.Width := 17; FSeries_Color_UIPanel.Height := 17; FSeries_Color_UIPanel.OnClick := Series_Color_UIPanelClick ; AdjustSize; // Needed when placing components on a form end; // Called when the window is created procedure TmcLegendElement.AdjustSize; begin inherited; if FSeries_Color_UIPanel = nil then exit; // Still in constructor FSeries_Color_UIPanel.Parent := Parent; // *Parent* <> nil here FSeries_Color_UIPanel.SetBounds (Left + Width + 3, Top, FSeries_Color_UIPanel.Width, FSeries_Color_UIPanel.Height); end; // This fixes the problem when placing a component at design time procedure TmcLegendElement.Resize; begin AdjustSize; // fixes another sizing problem, but still needs other 2 calls inherited; // calls OnResize event end; procedure TmcLegendElement.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); begin inherited; AdjustSize; end;
function GetParentForm(Control: TControl): TCustomForm;
Since I have decided to place sub-components outside the borders of the parent component, using just this method is probably inappropriate.