In Part 4, we saw how TEdit and TMemo's ancestor: TCustomEdit uses observers to manage passive states like ReadOnly. However, TCustomCheckBox and hence, any descendants, demonstrates a much more aggressive use of the pattern: Active Interception.

The KeyPress Sentry

A TCustomCheckBox doesn't just wait for a value to change; it actively consults its observers during user input. In the KeyPress override, the TCustomCheckBox checks if an EditLinkID observer is present and if is currently read-only.

procedure TCustomCheckBox.KeyPress(var Key: Char);
begin
  if Observers.IsObserving(TObserverMapping.EditLinkID) then
  begin
    if TLinkObservers.EditLinkIsReadOnly(Observers) then
      Exit; // Block input entirely if the observer says so
      
    case Key of
      #8, ' ': // Backspace or Space
        if TLinkObservers.EditLinkEdit(Observers) then
          TLinkObservers.EditLinkModified(Observers);
    end;
  end;
  inherited KeyPress(Key);
  // ... post-inheritance logic
end;

Two-Way State Synchronization

The TCustomCheckBox also uses ObserverToggle to modify its own internal behavior based on data requirements. For instance, it can dynamically enable or disable the cbGrayed state based on whether the data field is "Required."

procedure TCustomCheckBox.ObserverToggle(const AObserver: IObserver; const Value: Boolean);
var
  LEditLinkObserver: IEditLinkObserver;
begin
  if Value and Supports(AObserver, IEditLinkObserver, LEditLinkObserver) then
    AllowGrayed := not LEditLinkObserver.IsRequired
  else
    AllowGrayed := False;
end;

Defensive Updates

The most critical aspect of the TCustomCheckBox implementation is its use of try...except blocks during the Toggle method. If an observer fails to update the underlying data (perhaps due to a database validation error), the TCustomCheckBox ensures the UI stays in sync by catching the exception and re-focusing the control.

try
  if Observers.IsObserving(TObserverMapping.ControlValueID) then
  begin
    TLinkObservers.ControlValueModified(Observers);
    TLinkObservers.ControlValueTrackUpdate(Observers);
  end;
except
  SetFocus;
  raise; // Re-raise to let the application handle the error
end;

This reveals the VCL Observer pattern as a sophisticated "gatekeeper" for data integrity. It isn't just about moving data; it's about validating the intent to change data before the UI reflects that change.


In Part 6: We move from VCL internals to our own code, implementing a reusable TBaseVclObserver framework to simplify observer management.