A few components that exhibit this behavior are
Note: This is different than components that are composed of several components. In one case, the sub-components are created by the main component and will always be there. In the other (the subject of this page), the designer adds several separate components to a form and then associates them using the Object Inspector.
The Basics
FreeNotification (placed in the property setter) notifies the remote control that this control has a pointer (ie, that its Notification method must be called when the remote control is destroyed).
property Component: TAnotherComponent read FComponent write SetComponent;
procedure TMyControl.SetComponent(Value: TAnotherComponent);
begin
FComponent := Value;
if Value <> nil then Value.FreeNotification(Self);
end;
|
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
procedure TMyControl.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = FComponent)
then FComponent := nil;
end;
|
If this is not done, then the Delphi IDE will crash.
This applies only to controls that are associated using properties and does not apply to controls that are created and destroyed by a master control and saved to an internal variable (which is typically done in a creator).
Delphi VCL Examples
Most controls have published properties for PopupMenu and Action. Typically, both of these can be set in the IDE at design time. If any of the associated components are deleted, then the controls that reference them must be told so that the related pointers can be set to nil. Note that both PopupMenus and Actions can be associated with multiple controls .. and that if a PopupMenu (or Action) is deleted, then all the associated controls need to be notified.
property PopupMenu: TPopupMenu read FPopupMenu write SetPopupMenu;
procedure TControl.SetPopupMenu(Value: TPopupMenu);
begin
FPopupMenu := Value;
if Value <> nil then
begin
Value.ParentBiDiModeChanged(Self);
Value.FreeNotification(Self);
end;
end;
|
procedure TControl.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if Operation = opRemove then
if AComponent = PopupMenu then PopupMenu := nil
else if AComponent = Action then Action := nil;
end;
|
These are some additional examples showing the typical code structure seen in the VCL (but with comments and additional spacing added by me). Notice that inherited is called so that the PopupMenu and Action properties are still handled.
// Common technique when there is only one control
procedure TPageScroller.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = Control) then Control := nil;
end;
// Shows testing for multiple components
procedure TCustomTreeView.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if Operation = opRemove then
begin
if AComponent = Images then Images := nil;
if AComponent = StateImages then StateImages := nil;
end;
end;
|
Exceptions
procedure TSpinButton.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = FFocusControl) then
FFocusControl := nil;
end;
|
According to the TComponent.FreeNotification help
| FreeNotification must be called for components in other forms that have references to the component. For components in the same form as the component, the Notification method is called automatically. |
The code in the next section shows that FreeNotification checks and ignores components with the same parent (which covers components placed on the same form). It also shows that all the components owned by a component will be automatically called by TComponent.Notification.
Note: TObject -> TPersistent -> TComponent -> TControl |
Basic Architecture
destructor TComponent.Destroy;
var
I: Integer;
begin
Destroying;
if FFreeNotifies <> nil then
begin
for I := FFreeNotifies.Count - 1 downto 0 do
begin
TComponent(FFreeNotifies[I]).Notification(Self, opRemove);
if FFreeNotifies = nil then Break;
end;
FFreeNotifies.Free;
FFreeNotifies := nil;
end;
DestroyComponents;
if FOwner <> nil then FOwner.RemoveComponent(Self);
inherited Destroy;
end;
procedure TComponent.FreeNotification(AComponent: TComponent);
begin
if (Owner = nil) or (AComponent.Owner <> Owner) then
begin
if not Assigned(FFreeNotifies) then FFreeNotifies := TList.Create;
if FFreeNotifies.IndexOf(AComponent) < 0 then
begin
FFreeNotifies.Add(AComponent);
AComponent.FreeNotification(Self);
end;
end;
Include(FComponentState, csFreeNotification);
end;
procedure TComponent.RemoveNotification(AComponent: TComponent);
begin
if FFreeNotifies <> nil then
begin
FFreeNotifies.Remove(AComponent);
if FFreeNotifies.Count = 0 then
begin
FFreeNotifies.Free;
FFreeNotifies := nil;
end;
end;
end;
procedure TComponent.RemoveFreeNotification(AComponent: TComponent);
begin
RemoveNotification(AComponent);
AComponent.RemoveNotification(Self);
end;
procedure TComponent.Notification(AComponent: TComponent;
Operation: TOperation);
var
I: Integer;
begin
if (Operation = opRemove) and (AComponent <> nil) then
RemoveFreeNotification(AComponent);
if FComponents <> nil then
for I := 0 to FComponents.Count - 1 do
TComponent(FComponents[I]).Notification(AComponent, Operation);
end;
|
Designer
procedure TCustomForm.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
// other code here
if FDesigner <> nil then
FDesigner.Notification(AComponent, Operation);
end;
// from classes.pas
procedure NotifyDesigner(Self, Item: TPersistent; Operation: TOperation);
var
Designer: IDesignerNotify;
begin
GetDesigner(Self, Designer);
if Designer <> nil then
Designer.Notification(Item, Operation);
end;
|