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; |