![]() |
AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
Zum Event-Parameter:
Ich habe mir einen generischen MultiCast-Event geschrieben und davon einen speziellen PropertyChanged-Event erzeugt. Dieser Event hängt an jedem ViewModel (im Model hat der nichts verloren ;)) Siehe die Methode
Delphi-Quellcode:
OnPropertyChanged
Delphi-Quellcode:
Da diese Methode
unit MVVM.ViewModel.ViewModelBase;
interface uses de.itnets.Events, de.itnets.References; type TViewModelBase = class private FPropertyChanged: PropertyChangedEvent; FDisplayName: string; function GetPropertyChanged: IPropertyChangedEvent; protected procedure SetDisplayName( const Value: string ); procedure OnPropertyChanged( const PropertyName: string = '' ); overload; procedure OnPropertyChanged( const PropertyNames: TArray<string> ); overload; public property DisplayName: string read FDisplayName; property PropertyChanged: IPropertyChangedEvent read GetPropertyChanged; end; TViewModelClass = class of TViewModelBase; implementation { TViewModel } function TViewModelBase.GetPropertyChanged: IPropertyChangedEvent; begin Result := FPropertyChanged; end; procedure TViewModelBase.OnPropertyChanged( const PropertyNames: TArray<string> ); var LPropertyName: string; begin for LPropertyName in PropertyNames do begin OnPropertyChanged( LPropertyName ); end; end; procedure TViewModelBase.OnPropertyChanged( const PropertyName: string ); begin // Hier wird jetzt der Event durch die Gegend geschickt FPropertyChanged.Invoke( Self, TPropertyChangedArgs.Create( PropertyName ) ); end; procedure TViewModelBase.SetDisplayName( const Value: string ); begin if FDisplayName <> Value then begin FDisplayName := Value; OnPropertyChanged( 'DisplayName' ); end; end; end.
Delphi-Quellcode:
ist, kann ich die von allen abgeleiteten ViewModels aufrufen.
protected
Warum kein Notify im Model? Wenn sich im Model etwas ändert, dann ist das durch das ViewModel passiert ... ja, äh dann soll bitte auch das ViewModel die Nachricht schicken. Bei mir sind alle Models immutable (unveränderlich). Wenn ich etwas ändere, dann erzeuge ich mir eine neue Model-Instanz mit den aktuellen Werten. Nach dem Speichern sende ich dann diese neue Instanz in die Runde und jeder der sich dafür interessiert, kann dann diese neue Instanz übernehmen. Exakt zu diesem Zweck gibt es die virtuelle Methode ![]()
Delphi-Quellcode:
Das kann ich aber auch überschreiben
function Object.Equals( Obj : TObject ) : Boolean;
begin Result := Self = Obj; end;
Delphi-Quellcode:
TPerson = class
private FID : Integer; FName : string; public constructor Create( const ID : Integer; const Name : string ); function SameEntityAs( Other : TPerson ): Boolean; function Equals( Obj : TObject ) : Boolean; override; property ID : Integer read FID; property Name : string read FName; end; function TPerson.Equals( Obj : TObject ) : Boolean; begin Result := ( Self = Obj ) or Assigned( Obj ) and ( Self.ClassType = Obj.ClassType ) and SameEntityAs( Obj as TPerson ); end; function TPerson.SameEntityAs( Other : TPerson ): Boolean; begin Result := (Self = Other ) or Assigned( Other ) and (Self.FID = Other.FID ); end;
Delphi-Quellcode:
Und noch etwas weitergedacht, kann ich sogar das ViewModel vergleichen und zwar mit einem anderen
procedure Test;
var LPerson1, LPerson2 : TPerson; begin LPerson1 := TPerson.Create( 1, 'Foo' ); LPerson2 := TPerson.Create( 2, 'Foo' ); if LPerson1.Equals( LPerson2 ) then WriteLn('Gleiche Person ist gemeint'); LPerson2 := TPerson.Create( 1, 'Bar' ); if LPerson1.Equals( LPerson2 ) then WriteLn('Gleiche Person ist gemeint'); end;
Delphi-Quellcode:
und einem
TPersonViewModel
Delphi-Quellcode:
:
TPerson
Delphi-Quellcode:
Das auch noch auf die View übertragen und ich kann quasi alles mit jedem vergleichen.
TPersonViewModel = class
private FPerson : TPerson; public function Equals( Obj : TObject ) : Boolean; end; function TPersonViewModel.Equals( Obj : TObject ) : Boolean; begin Result := (Self = Obj ) or Assigned( Obj ) and ( ( ( Self.ClassType = Obj.ClassType ) and ( Self.FPerson.Equals( ( Obj as TPersonViewModel ).FPerson ) ) ) or ( Self.FPerson.Equals( Obj ) ) ); end; |
AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
hmm irgendwie klappt das doch nicht so wie ich mir das dachte....
wenn ich aus der Main heraus das Model ändere (über die ModelView) dann wird
Delphi-Quellcode:
ausgeführt, wenn ich aber aus dem ModelView das Model ändere passiert nichts... eigentlich logisch und denn wieder auch nicht ;)
TForm1.ViewModelStockpilePropertyChanged
jemand eine idee? ich vermute ich muss noch ein Event vom ModelView in Richtung View dispatchen?! Main
Delphi-Quellcode:
...
// Create reference of ViewModelStockpile (Scope to Model.Stockpile) FViewModelStockpile := TViewModelStockpile.Create; // Register PropertyChanged-Methode if changing the // Datamodel in ViewModelStockpile FViewModelStockpile.registerOn(ViewModelStockpilePropertyChanged); ... procedure TForm1.ViewModelStockpilePropertyChanged; begin EditContentbarStartMarker.Text := FViewModelStockpile.Foo; ShowMessage('Model wurde geändert'); end; ... view.model.stockpile
Delphi-Quellcode:
unit view.model.stockpile;
interface uses model, model.stockpile; type { View Model for Stockpile } TViewModelStockpile = class(TModel) private FModel: TStockpileModel; data: TArray<Double>; cdsBioLife: String; FBar: String; FFoo: string; function GetBioLife: String; procedure SetBar(const Value: String); procedure SetFoo(const Value: string); public constructor Create; function GetData: TArray<Double>; procedure SetData(data: TArray<Double>); property BioLife: String read GetBioLife; destructor destroy; override; property Foo: string read FFoo write SetFoo; property Bar: String read FBar write SetBar; end; implementation { TViewModelStockpile } constructor TViewModelStockpile.Create; begin inherited Create; FModel := TStockpileModel.Create; Foo := 'test'; end; destructor TViewModelStockpile.destroy; begin FModel.Free; inherited; end; procedure TViewModelStockpile.SetBar(const Value: String); begin if FBar <> Value then begin FBar := Value; notify; end; end; procedure TViewModelStockpile.SetFoo(const Value: string); begin if FFoo <> Value then begin FFoo := Value; ShowMessage('notify'); notify; // OnPropertyChanged('Foo'); end; end; function TViewModelStockpile.GetBioLife: String; begin Result := cdsBioLife; end; function TViewModelStockpile.GetData: TArray<Double>; begin Result := self.data; end; procedure TViewModelStockpile.SetData(data: TArray<Double>); begin self.data := data; // alle Ereignis-Behandlungs-Routinen der Liste aufrufen // wurde mit registerOn an das Model regestriert notify; end; end. Model(Base)
Delphi-Quellcode:
unit model;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, jpeg, StdCtrls, pngimage, JvPanel, JvExExtCtrls, JvExtComponent, Series, TeeShape, TeEngine, TeeProcs, Chart, Math, TeeGDIPlus, ComCtrls, JvExComCtrls, JvComCtrls, dOPCIntf, dOPCComn, dOPCDA, dOPC; type TEvent = procedure of object; TModel = class protected // interne Liste OnChange: array of TEvent; // Aufruf aller Routinen der Liste procedure notify; destructor destroy; override; public // neuer 'Event-Handler' in Liste procedure registerOn(routine: TEvent); // 'Event-Handler' aus Liste entfernen procedure registerOff(routine: TEvent); end; implementation // registriert neue routinen an den controller procedure TModel.registerOn(routine: TEvent); var n: integer; begin n := Length(OnChange); SetLength(OnChange, n + 1); OnChange[n] := routine; end; // de-registriert routinen vom controller procedure TModel.registerOff(routine: TEvent); var i, j: integer; begin i := Low(OnChange); while i <= High(OnChange) do // High liefert -1 bei leerem Array begin if @OnChange[i] = @routine // mit '@' nur Adressen vergleichen then begin for j := i to High(OnChange) - 1 do OnChange[j] := OnChange[j + 1]; SetLength(OnChange, Length(OnChange) - 1); end else i := i + 1; end; end; // alle Ereignis-Behandlungs-Routinen der Liste aufrufen destructor TModel.destroy; begin // inherited; end; procedure TModel.notify; var i: integer; begin for i := Low(OnChange) to High(OnChange) do OnChange[i]; end; end. |
AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
fehler gefunden ...
Delphi-Quellcode:
Main
constructor TViewModelStockpile.Create(const EventHandler: TEventHandler);
begin inherited Create; FEventHandler := EventHandler; FModel := TStockpileModel.Create; //Foo := 'TViewModelStockpile.Create => Value1'; <<<<<<<<<<<< FEHLER! end;
Delphi-Quellcode:
läuft im Kreis ...
procedure TForm1.ViewModelStockpilePropertyChanged;
begin EditContentbarStartMarker.Text := FViewModelStockpile.Foo; ShowMessage('Model wurde geändert'); end; |
AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
Tja, was da nicht so richtig tut gute Frage.
Einen Fehler habe ich aber schon entdeckt:
Delphi-Quellcode:
Du vergleichst hier
procedure TEventHandler.setPropertyChanged(const Value: TEvent);
begin if @FPropertyChanged <> @Value then begin ShowMessage('setPropertyChanged'); FPropertyChanged := Value; end; end;
Delphi-Quellcode:
die Adresse der Methode. Das reicht aber nicht aus, denn bei unterschiedlichen Instanzen der gleichen Klasse ist diese Adresse gleich!
if @FPropertyChanged <> @Value then
Du musst das nach ![]()
Delphi-Quellcode:
und
TMethod.Code
Delphi-Quellcode:
vergleichen ;)
TMethod.Data
Meine Erfahrung mit MVVM haben mir allerdings auch gezeigt, dass die normalen Delphi-(SingleCast)-Events da nicht ausreichen und ein MultiCast-Event zwingend erforderlich ist. Ein und dasselbe ViewModel kann an mehreren Views hängen (Liste und Detail) und da wäre es doof, wenn nur eine View aktualisiert wird. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:48 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz