C++ Builder > TControl.Parent property > does the parent free the children? - c++builder

My environment:
C++ Builder 10.2 Tokyo on Windows 10 v1809
I have a question about TControl.Parent property.
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TPanel *pnlptr = new TPanel(/*AOwner=*/this);
TLabel *lblptr = new TLabel(/*AOwner=*/this);
lblptr->Parent = pnlptr;
// some processing
//delete lblptr;
delete pnlptr;
}
Does the above code free the lblptr?
In the document (for 2009 although)
TControl.Parent Property
Note: The Parent property declared in TControl is similar to the Owner property declared in TComponent, in that the Parent of a control frees the control just as the Owner of a component frees that Component.
With the note, I think when the pnlptr is freed, the lblptr is also freed without delete lblptr.
Is this right?

Does the above code free the lblptr?
Yes, it does. When the TPanel is freed, its base TWinControl destructor frees any components that are still in the Controls[] property (controls that have the TWinControl set as their Parent):
destructor TWinControl.Destroy;
var
I: Integer;
Instance: TControl;
begin
...
I := ControlCount;
while I <> 0 do
begin
Instance := Controls[I - 1];
Remove(Instance);
Instance.Destroy; // <-- FREED HERE
I := ControlCount;
end;
...
end;

Related

Custom GridPanel ControlItems Issue

I am subclassing TGridPanel to my control TMyGridPanel.
I do this because i want to add 4 default buttons in the GridPanel.
So i override the constructor and create the buttons like:
constructor TMyGridPanel.Create(AOwner: TComponent);
var
i: Integer;
btn: TButton;
begin
inherited Create(AOwner);
for i := 0 to 3 do
begin
btn := TButton.Create(Self);
btn.Parent := Self;
btn.Align := alClient;
btn.Caption := 'Hello World';
btn.Visible := True;
end;
end;
This is working fine.
The ControlCollection Items property shows 4 Buttons as CollectionItems .
Now i want to copy and paste (duplicate) my control because i want to have 2 of it.
However when i do it the buttons don't show up in the control.
The ControlCollection Items property shows 4 Collection Items but they don't have a name (empty).
When i close the form and reopen it the buttons appear.
I am trying to fix this problem for some days now but can't figure it out.
Problem:
When you copy your panel component to clipboard, all its published properties are streamed into text (paste it in notepad to see how it looks).
Pasting to the form reconstructs the component back from this text.
And as ControlCollection property is defined in Vcl.ExtCtrls.TGridPanel as published, buttons within it are included in this text. Here is an excerpt:
object MyGridPanel1: TMyGridPanel
Left = 64
...
ControlCollection = <
item
Column = 0
Control = Button9
Row = 0
end
item
Column = 1
Control = Button10
Row = 0
end
...
object Button9: TButton
Left = 1
...
end
object Button10: TButton
Left = 92
...
end
...
end
When pasting, the IDE designer first creates a new object of class TMyGridPanel. During this step the constructor of TMyGridPanel creates a new set of buttons.
After that all published properties get reconstructed from the text, including the ControlCollection and Buttons within it, and this is where problem lies.
Possible solution:
A possible solution in this situation is to change parent class of TMyGridPanel to TCustomGridPanel
TMyGridPanel2 = class(TCustomGridPanel)
...
TCustomGridPanel (similar to other TCustom... components) does not publish any of its properties, so they won't get streamed into clipboard.
Actually inheriting from TCustom... variants of controls, and not from the one registered in Component Pallet, is the right way to subclass components.
If we now copy this variant of TMyGridPanel2 to clipboard and paste it in notepad, we can see that there no additional properties:
object MyGridPanel21: TMyGridPanel2
Left = 184
Top = 200
Width = 185
Height = 41
end
Drawbacks:
This approach works, but have several cons that has to be noted:
You cannot access custom properties introduced by TGridPanel in Object Inspector (but you can access them at runtime).
A workaround to bring a property back in Object Inspector, is to add it in published section of your component:
TMyGridPanel2 = class(TCustomGridPanel)
public
...
published
property BorderStyle;
property ColumnCollection;
property RowCollection;
...
end;
You cannot change properties of the four buttons via Object Inspector, nor attach events to them. You have to do that in code.
Actually this is good behavior. When you create a composite component that has child controls, it is good practice to have all functionality contained within the component itself.
Full code sample:
unit MyGridPanel2;
interface
uses
Classes, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Controls;
type
TMyGridPanel2 = class(TCustomGridPanel)
private
public
constructor Create(AOwner: TComponent); override;
published
end;
procedure Register;
implementation
{ TMyGridPanel2 }
constructor TMyGridPanel2.Create(AOwner: TComponent);
var
i: Integer;
btn: TButton;
begin
inherited Create(AOwner);
for i := 0 to 3 do
begin
btn := TButton.Create(Self);
btn.Parent := Self;
btn.Align := alClient;
btn.Caption := 'Hello World';
btn.Visible := True;
end;
end;
procedure Register;
begin
RegisterComponents('Custom', [TMyGridPanel2]);
end;
end.
Try this in test project first, not in production.

delphi accessing protected property of TControl [duplicate]

This question already has an answer here:
Accessing protected event of TWinControl
(1 answer)
Closed 8 years ago.
I'm working on delphi components.
I've been trying to access a customized component's designated parent control's onClick event. By designate, users can designate the component's parent control by using object inspector as a property. parent controls can be any of control components on the same form. However, because all parent controls I've made are subclasses of TControl and onClick event of TControl is protected, I can not access parent control's onclick event. practically, a customized component is like a sub-component positioned right next to a parent control, so whenever, a user clicks a customized component, I wanted parent control's click event will occur, if click event exists.
when I run this code, typecasting exception occurs.
procedure TSubCom.SetParentControl(const Value : TControl);
var
parentContLeft : Integer; //parent control's left + width
parentContTop : Integer; //parent control's top
begin
FParentControl := Value;
parentContLeft := FParentControl.Left + FParentControl.Width;
parentContTop := FParentControl.Top;
Left := parentContLeft - (Width div 2);
Top := parentContTop - (Height div 2);
Repaint;
end;
//TSubCom's onClick event is linked with its parent control's onClick event
procedure TSubCom.Click;
var
Parent: wrapClass;
begin
inherited;
if(FParentControl <> nil) then
begin
ShowMessage(FPArentControl.Name);
Parent := FParentControl as wrapClass;
ShowMessage('1');
if Assigned(Parent.OnClick) then
begin
Parent.OnClick(Self);
end;
// FParentControl as FParentControl.ClassType;
// if(FParentControl.OnClick <> nil) then
// FParentControl.OnClick;
end;
end;
Declare a class for accessing protected members,
typecast the Parent to this class, and do not use the OnClick event, instead use Click.
type
TControlAccess = class(TControl);
procedure TSubCom.Click;
begin
inherited Click;
if ParentControl <> nil then
TControlAccess(ParentControl).Click;
end;

How to change the default value for the Margins property?

Using the Delphi XE2 wizard to create a component, I have choose the TPanel to inherit from, and change defaults of some properties to fit my application.
My problem is to change the default of Margins:
TControl = class(TComponent)
...
property Margins: TMargins read FMargins write SetMargins;
Margins is a TMargin class declared with 4 properties that I need to redefine the defaults:
TMargins = class(TPersistent)
published
property Left: TMarginSize index 0 read FLeft write SetMargin default 3;
property Top: TMarginSize index 1 read FTop write SetMargin default 3;
property Right: TMarginSize index 2 read FRight write SetMargin default 3;
property Bottom: TMarginSize index 3 read FBottom write SetMargin default 3;
I can/will be setting on code the margins when the constructor of the component is called, however I have no idea how to redefine these defaults above in order to show up on the property editor.
You can declare your TMargins descendant with your own defaults to use in your panel
type
TMyMargins = class(TMargins)
protected
class procedure InitDefaults(Margins: TMargins); override;
published
property Left default 10;
property Top default 10;
property Right default 10;
property Bottom default 10;
end;
class procedure TMyMargins.InitDefaults(Margins: TMargins);
begin
with Margins do begin
Left := 10;
Right := 10;
Top := 10;
Bottom := 10;
end;
end;
then when you create your panel, dismiss the existing one and use yours
TMyPanel = class(TPanel)
private
procedure DoMarginChange(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
end;
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Margins.Free;
Margins := TMyMargins.Create(Self);
Margins.OnChange := DoMarginChange;
end;
procedure TMyPanel.DoMarginChange(Sender: TObject);
begin
// same as in TControl which is private
RequestAlign;
end;
The margins will be stored in the dfm only when they differ from the default.
Although I don't know why the above works... The problem with the above code is, Margins property have a setter which only assigns to the margins (left, right..). The code never writes to the backing field, but it still works. The line
Margins := TMyMargins.Create(Self);
is effectively same as
TMyMargins.Create(Self);
which also works.
By 'works' I mean, it works. The margins, f.i., gets properly destroyed, not because of ownership etc.. (margins iş a TPersistent, not a component) but right when the ascendant TControl calls FMargins.Free.
Anyway, since I don't understand how it works, as a safer although hacky approach, I'd use this:
constructor TMyPanel.Create(AOwner: TComponent);
var
Addr: NativeUInt;
begin
inherited Create(AOwner);
Addr := NativeUInt(#Margins);
Margins.Free;
PUINT_PTR(Addr)^ := NativeUInt(TMyMargins.Create(Self));
Margins.OnChange := DoMarginChange;
end;

Passing object reference as an Interface

I'm passing created object to a constructor of another object which need an Interface which that object implements.
ISomeInterface = interface
['{840D46BA-B9FB-4273-BF56-AD0BE40AA3F9}']
end;
TSomeObject = class(TInterfacedObject, ISomeinterface)
end;
TSomeObject2 = class
private
FSomeInterface: ISomeinterface;
public
constructor Create(SomeObject: ISomeInterface);
end;
var
Form1: TForm1; // main form
SomeObject: TSomeObject;
constructor TSomeObject2.Create(SomeObject: ISomeInterface);
begin
FSomeInterface := SomeObject;
end;
// main form creating
procedure TForm1.FormCreate(Sender: TObject);
var SomeObject2: TSomeObject2;
begin
SomeObject := TSomeObject.Create;
// SomeObject2 := TSomeObject2.Create(nil); // ok
SomeObject2 := TSomeObject2.Create(SomeObject); // not ok
try
// do some things
finally
SomeObject2.Free;
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SomeObject.Free; // if passed to a SomeObject2 Constructor - freeing it causing av
end;
After I close main form it gives me an AV and a memory leak - whole main form has leaked.
If I'm passing nil to a TSomeObject constructor everything is well. Is compilator freeing FSomeInterface by reference counting and I'm shouldn't try to free SomeObject in mainForm destructor? How can I avoid it?
TSomeObject inherited from TInterfacedObject and thus is reference counted. Your instance of TSomeObject is not reference counted and should be removed or replaced by an interface variable.
If you need the instance of TSomeObject created in FormCreate, you should assign it to a variable of type ISomeInterface, so that the reference counting will work for that, too.
Another approach is to inherit from TInterfacedPersistant instead of TInterfacedObject to avoid the reference counting.
To explain what is happening in your code:
procedure TForm1.FormCreate(Sender: TObject);
var SomeObject2: TSomeObject2;
begin
{ Here you create the instance and assign it to a variable holding the instance.
After this line the reference count of the instance is 0 }
SomeObject := TSomeObject.Create;
// SomeObject2 := TSomeObject2.Create(nil); // ok
{ Using the instance as a parameter will increase the reference count to 1 }
SomeObject2 := TSomeObject2.Create(SomeObject); // not ok
try
// do some things
finally
{ Freeing SomeObject2 also destroys the interface reference FSomeInterface is
pointing to (which is SomeObject), decreasing the reference count to 0, which
in turn frees the instance of TSomeObject. }
SomeObject2.Free;
end;
{ Now, after SomeObject is freed, the variable points to invalid memory causing the
AV in FormDestroy. }
end;

Setting multiple labels to transparent across 1.000 forms?

I skinned my software with Devexpress and I found that the labels were non-transparent causing them to have grey background.
There's just endless forms, so I was wondering whether there was a way to do this task (of setting labels to transparent) automatically.
I did a similar thing earlier, the Devexpress controls on the form had LookAndFeel.NativeStyle = True, I used Grep Search to replace it to False on all dfm forms. In the label's case however, the transparent property is not present.
Thank you.
The global Screen variable keeps track of all forms:
procedure MakeLabelsTransparent(AParent: TWinControl);
var
I: Integer;
begin
with AParent do
for I := 0 to ControlCount - 1 do
if Controls[I] is TLabel then
TLabel(Controls[I]).Transparent := True
else if Controls[I] is TWinControl then
MakeLabelsTransparent(TWinControl(Controls[I]));
end;
procedure TMainForm.ActiveFormChange(Sender: TObject);
begin
with Screen do
if (ActiveCustomForm <> nil) and (ActiveCustomForm.Tag = 0) then
begin
MakeLabelsTransparent(ActiveCustomForm);
ActiveCustomForm.Tag := 1;
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
Screen.OnActiveFormChange := ActiveFormChange;
end;
And if you have to use the Tag property for a particular form, then omit this check: it wouldn't really get that much slower.
For this type of task, GExperts contains the Set Component Properties tool:
This tool waits in the background
until you compile a project. It then
scans the current project's forms to
check for components with certain
properties and changes those
properties to a defined value. This
tool is useful to deactivate datasets
or database connections before you
compile your applications, but it can
be used for any similar situations as
well. To activate the scanning,
enable the checkbox next to this
expert in the GExperts Configuration
screen.
It can be used to set a property which is not yet in the DFM as well, and only requires one additional entry in the GExpert configuration, and a recompile.
I have just tested it and it works as expected.
At design time, you can just parse all .dfm then add the
Transparent = True
line just after any
object MyLabel : TLabel
line.
At runtime, you may override the TCustomForm.DoCreate and TCustomFrame.Create methods, as such:
type
THookedForm = class(TCustomForm)
procedure HookedDoCreate;
end;
THookedFrame = class(TCustomFrame)
constructor Create(AOwner: TComponent); override;
end;
var
PatchForm, OriginalForm: TPatchEvent;
PatchPositionForm: PPatchEvent = nil;
PatchFrame, OriginalFrame: TPatchEvent;
PatchPositionFrame: PPatchEvent = nil;
procedure PatchCreate;
var ov: cardinal;
begin
// hook TForm:
PatchPositionForm := PPatchEvent(#THookedForm.DoCreate);
OriginalForm := PatchPositionForm^;
PatchForm.Jump := $E9; // Jmp opcode
PatchForm.Offset := PtrInt(#THookedForm.HookedDoCreate)-PtrInt(PatchPositionForm)-5;
if not VirtualProtect(PatchPositionForm, 5, PAGE_EXECUTE_READWRITE, #ov) then
RaiseLastOSError;
PatchPositionForm^ := PatchForm; // enable Hook
// hook TFrame:
PatchPositionFrame := PPatchEvent(#TCustomFrame.Create);
OriginalFrame := PatchPositionFrame^;
PatchFrame.Jump := $E9; // Jmp opcode
PatchFrame.Offset := PtrInt(#THookedFrame.Create)-PtrInt(PatchPositionFrame)-5;
if not VirtualProtect(PatchPositionFrame, 5, PAGE_EXECUTE_READWRITE, #ov) then
RaiseLastOSError;
PatchPositionFrame^ := PatchFrame; // enable Hook
end;
{ THookedForm }
procedure THookedForm.HookedDoCreate;
var i: integer;
begin
// enumerate all labels, then set Transparent := true
for i := 0 to Components.Count-1 do
if Components[i] is TLabel then
TLabel(Components[i]).Transparent := true;
DoCreate; // call initial code
end;
{ THookedFrame }
constructor THookedFrame.Create(AOwner: TComponent);
var i: integer;
begin
// enumerate all labels, then set Transparent := true
for i := 0 to Components.Count-1 do
if Components[i] is TLabel then
TLabel(Components[i]).Transparent := true;
inherited Create(AOwner); // call normal constructor
end;
....
initialization
PatchCreate;
A related tip (I always forget to make use of this handy feature):
Configure the label the way you want to have it;
Select it on the form;
Go to Component/Create component template;
You can then a name for the template:
From then on, the template appears as a new component type in your tool palette, with the settings that you prefer.
(Yeah, I know this doesn't change current labels)
You can set the BackColor property to Color.Transparent.
The following should work: the transparent-property is present in the DFM-file only if the value is not the default. So you can us a Grep-Search to insert the "Transparent=TRUE" just in the next line after the "=TLabel". I have not tried this myself, but it is easy to try...

Resources