Search code examples
delphispring4d

Why is Spring4D's IList<T> OnChanged event not fired when the object changes (while Add and Remove fire the event)?


I modified @Stefan Glienkes example from Notify the TObjectList when Object changed to use IList, since I am using interfaced objects in my list. In the event handler, I can handle caAdded and caRemoved events, but caChanged is not signaled.

Is this by design or am I making a mistake somewhere?

This example shows the behavior:

program Project61;

{$APPTYPE CONSOLE}


uses
  Spring,
  Spring.Collections,
  SysUtils;

type
  TNotifyPropertyChangedBase = class(TInterfaceBase, INotifyPropertyChanged)
  private
    fOnPropertyChanged: Event<TPropertyChangedEvent>;
    function GetOnPropertyChanged: IPropertyChangedEvent;
  protected
    procedure PropertyChanged(const propertyName: string);
  end;

  IMyInterface = interface(IInterface)
    ['{D5966D7D-1F4D-4EA8-B196-CB9B39AF446E}']
    function GetName: String;
    procedure SetName(const Value: String);
    property Name: String read GetName write SetName;
  end;

  TMyObject = class(TNotifyPropertyChangedBase, IMyInterface)
  private
    FName: string;
    function GetName: string;
    procedure SetName(const Value: string);
  public
    property Name: string read GetName write SetName;
  end;

  TMain = class
    procedure ListChanged(Sender: TObject; const item: IMyInterface;
      action: TCollectionChangedAction);
  end;

  { TNotifyPropertyChangedBase }

function TNotifyPropertyChangedBase.GetOnPropertyChanged: IPropertyChangedEvent;
begin
  Result := fOnPropertyChanged;
end;

procedure TNotifyPropertyChangedBase.PropertyChanged(
  const propertyName: string);
begin
  fOnPropertyChanged.Invoke(Self,
    TPropertyChangedEventArgs.Create(propertyName) as IPropertyChangedEventArgs);
end;

{ TMyObject }

procedure TMyObject.SetName(const Value: string);
begin
  FName := Value;
  PropertyChanged('Name');
end;

function TMyObject.GetName: string;
begin
  Result := FName;
end;

{ TMain }

procedure TMain.ListChanged(Sender: TObject; const item: IMyInterface;
  action: TCollectionChangedAction);
begin
  case action of
    caAdded:
      Writeln('item added ', item.Name);
    caRemoved, caExtracted:
      Writeln('item removed ', item.Name);
    caChanged:
      Writeln('item changed ', item.Name);
  end;
end;

var
  main: TMain;
  list: IList<IMyInterface>;
  o   : IMyInterface;

begin
  list := TCollections.CreateList<IMyInterface>;
  list.OnChanged.Add(main.ListChanged);
  o := TMyObject.Create;
  o.Name := 'o1';
  list.Add(o);          // triggering caAdded
  o := TMyObject.Create;
  o.Name := 'o2';
  list.Add(o);          // triggering caAdded
  list[1].Name := 'o3'; // not triggering caChanged
  list.Remove(o);       // triggering caRemoved
  Readln;

end.

Solution

  • The lists created by TCollections.CreateList, TCollections.CreateObjectList or TCollections.CreateInterfaceList don't support INotifyPropertyChanged.

    You see that TCollections.CreateObservableList which I used in my example is contraint to only hold objects as these are typically candidates for implementing property change notification as PODOs are imo usually bad candidates to be used as interfaces.

    You can probably still code your own version of that list that accepts interfaces and queries them for INotifyPropertyChanged.