Delphi-PRAXiS
Seite 2 von 3     12 3      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   MVC + Observer Pattern Konzept / Was haltet Ihr davon (https://www.delphipraxis.net/183799-mvc-observer-pattern-konzept-haltet-ihr-davon.html)

Sir Rufo 6. Feb 2015 08:28

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
Zitat:

Zitat von FAM (Beitrag 1288952)
Habe das ganze mit dem MVVP Konzept implementiert, das Problem ist jetzt allerdings meine Delphi Version (XE), dort gibt es noch keine Live-Bindings. Kennt jemand ein Workaround um das "zu simulieren"?

Ja, ich habe einmal an jedem ViewModel einen PropertyChanged Event hängen und kann damit entweder den BindSourceAdapter refreshen oder eben manuell den Wert übergeben. Wenn manuell gebunden, dann brauche ich aber auch ein manuelles Rückschreiben in das ViewModel, was aber eigentlich idR mit einem Event geht, wo dann von allen die Werte zurückgeschrieben werden.

Für Listen habe ich eine Observable Collection mit einem CollectionChanged Event. Dieser teilt mir genau mit, was sich in der Liste geändert hat. Damit kann ich dann jede andere Liste (Listendarstellung) mit möglichst wenig Stress (für mich und die Darstellung) synchron halten. Der Event liefert mir die Art der Änderung, den jeweiligen Index, wo sich etwas geändert hat und die jeweiligen Items (jeweils für OldItems und NewItems).

FAM 6. Feb 2015 08:40

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von mkinzler (Beitrag 1288953)
http://mitov.com/products/openwirelivebindings#overview

danke für den Hinweis.

Einen anderen Gedanken den ich noch hätte wäre folgender (skizze siehe Screen)

Anhang 42527

Im prinzip erstelle ich mir ein ScopeModel und immer wenn sich das ViewModel verändert (propertyChanged)
rufe ich im Scope-Model die an diesen registrierten Methoden zur GUI (-Elemete) aktualisierung auf...

eine Testbarkeit des ViewModels ist somit immer noch komplett unabhängig vom zustand des Views möglich!

was haltet ihr davon?

FAM 6. Feb 2015 08:46

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
Zitat:

Zitat von Sir Rufo (Beitrag 1288956)
Ja, ich habe einmal an jedem ViewModel einen PropertyChanged Event hängen und kann damit entweder den BindSourceAdapter refreshen oder eben manuell den Wert übergeben.

das kommt dem schon nahe wie ich mir das denke.

Zitat:

Zitat von Sir Rufo (Beitrag 1288956)
Wenn manuell gebunden, dann brauche ich aber auch ein manuelles Rückschreiben in das ViewModel, was aber eigentlich idR mit einem Event geht, wo dann von allen die Werte zurückgeschrieben werden.

kannst du bitte mal einen source-snippet posten - wie du das genau implementierst (das rückschreiben)? Ich würde das (indirekt) über die write Methode in der jeweiligen Property vom eigentlichen Model machen, den aufruf dafür würde ich im view schreien

Sir Rufo 6. Feb 2015 08:53

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
Aber du solltest den Scope mit dem ViewModel verbinden und nicht mit dem Model. Das ViewModel kapselt ja genau dieses Model und übersetzt, transferiert oder reichert das um weitere Informationen an.

Eine andere Möglichkeit ist das erstellen von WrapperKomponenten für die jeweiligen Controls. Dann hast du z.B. einen EditWrapper (nenn ihn meinetwegen Presenter) und der bekommt das ViewModel, den Namen der Eigenschaft, sowie einen Accessor (zum Lesen) und Mutuator (zum Schreiben). Klatsch ein Edit daran und schon tauscht das fröhlich aus.

Genau sowas benutze ich um einen TreeView an ein Tree_ViewModel zu binden

FAM 6. Feb 2015 09:00

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
Zitat:

Zitat von Sir Rufo (Beitrag 1288964)
Aber du solltest den Scope mit dem ViewModel verbinden und nicht mit dem Model. Das ViewModel kapselt ja genau dieses Model und übersetzt, transferiert oder reichert das um weitere Informationen an.

Achso da ist in meiner Skizze ein fehler... der erste Kasten müsste das View enthalten, dann passt das denke ich

Zitat:

Zitat von Sir Rufo (Beitrag 1288964)
Eine andere Möglichkeit ist das erstellen von WrapperKomponenten für die jeweiligen Controls. Dann hast du z.B. einen EditWrapper (nenn ihn meinetwegen Presenter) und der bekommt das ViewModel, den Namen der Eigenschaft, sowie einen Accessor (zum Lesen) und Mutuator (zum Schreiben). Klatsch ein Edit daran und schon tauscht das fröhlich aus.

Genau sowas benutze ich um einen TreeView an ein Tree_ViewModel zu binden

da bin ich jetzt raus - ich wüsste jetzt nicht wie ich für eine Visuelle Komponente einen Wrapper erstellen könnte? Code-Snippet ?!


welches Konzept ist für dich besser?

Sir Rufo 6. Feb 2015 09:15

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
Liste der Anhänge anzeigen (Anzahl: 1)
Also hier erst mal das manuelle Binding in der View
Delphi-Quellcode:
unit View.MainView;

interface

uses
  de.itnets.Events,
  de.itnets.References,

  MVVM.ViewModel.ViewModelBase,

  ViewModel.MainViewModel,

  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
  MVVM.View.FMX.Form.Base, FMX.Controls.Presentation, FMX.Edit;

type
  TMainView = class( TViewBaseForm )
    BarEdit: TEdit; { OnChangeTracking -> ControlChanged }
    FooEdit: TEdit; { OnChangeTracking -> ControlChanged }
    CheckLabel: TLabel;
  private
    FMain: WeakRef<TMainViewModel>;
  protected
    procedure AttachToViewModel( AViewModel: TViewModelBase ); override;
    procedure DetachFromViewModel( AViewModel: TViewModelBase ); override;
    procedure ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs ); override;
  public

  published
    procedure ControlChanged( Sender: TObject );
  end;

var
  MainView: TMainView;

implementation

{$R *.fmx}
{ TMainView }

procedure TMainView.AttachToViewModel( AViewModel: TViewModelBase );
begin
  FMain := AViewModel as TMainViewModel;
  inherited;

end;

procedure TMainView.ControlChanged( Sender: TObject );
begin
  if FMain.IsAssigned
  then
    begin

      FMain.Reference.Bar := BarEdit.Text;
      FMain.Reference.Foo := FooEdit.Text;

    end;
end;

procedure TMainView.DetachFromViewModel( AViewModel: TViewModelBase );
begin

  inherited;
  FMain := nil;
end;

procedure TMainView.ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs );
begin
  inherited;
  if FMain.IsAssigned
  then
    begin

      if e.Match( 'Bar' )
      then
        BarEdit.Text := FMain.Reference.Bar;

      if e.Match( 'Foo' )
      then
        FooEdit.Text := FMain.Reference.Foo;

      if e.Matches( ['Bar', 'Foo'] )
      then
        CheckLabel.Text := Format( 'Bar: "%s", Foo: "%s"', [FMain.Reference.Bar, FMain.Reference.Foo] );

    end;
end;

end.
Delphi-Quellcode:
CheckLabel
ist nur für die visuelle Rückmeldung, dass es tatsächlich im ViewModel angekommen ist.
Im Anhang der Source (immer noch ohne mein Basis-Framework) und eine Exe zum Ausprobieren

Sir Rufo 6. Feb 2015 09:26

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
So ein Edit-Wrapper könnte ungefähr so aussehen
Delphi-Quellcode:
TEditView = class(Component)
public
  procedure SetViewModel( AViewModel : TViewModelBae; const PropertyName : string; const AAccessor : TFunc<string>; const AMutuaotr : TProc<string> );
  propety Edit : TEdit;
end;
Denkbar ist allerdings auch ein Wrapper nach dieser Art
Delphi-Quellcode:
TEditView = class( TComponent )
public
  property Edit : TEdit;
  property PropertyName : string;
  property ViewModel : TViewModelBase;
end;
Jetzt müsstest du allerdings per RTTI von dem PropertyNamen auf die Property zugreifen, was aber auch machbar ist. Das zusammen mit einem ValueConverter, der dafür sorgt, dass du auch von Boolean zu String und wieder zurück wechseln kannst macht die Sache richtig rund. Das geht dann schon in die Richtung LiveBinding

FAM 6. Feb 2015 09:32

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
Zitat:

Zitat von Sir Rufo (Beitrag 1288970)
Also hier erst mal das manuelle Binding in der View
Delphi-Quellcode:
procedure TMainView.ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs );
begin
  inherited;
  if FMain.IsAssigned
  then
    begin

      if e.Match( 'Bar' )
      then
        BarEdit.Text := FMain.Reference.Bar;

      if e.Match( 'Foo' )
      then
        FooEdit.Text := FMain.Reference.Foo;

      if e.Matches( ['Bar', 'Foo'] )
      then
        CheckLabel.Text := Format( 'Bar: "%s", Foo: "%s"', [FMain.Reference.Bar, FMain.Reference.Foo] );

    end;
end;

end.

ok cool, eine frage hätte ich da noch, sorgt dein Framework für den Aufruf der
Delphi-Quellcode:
ViewModelPropertyChanged
oder übersehe ich da irgendwo was?

Sir Rufo 6. Feb 2015 09:42

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
Zitat:

Zitat von FAM (Beitrag 1288975)
ok cool, eine frage hätte ich da noch, sorgt dein Framework für den Aufruf der
Delphi-Quellcode:
ViewModelPropertyChanged
oder übersehe ich da irgendwo was?

Ja, und da das bei jeder View erfolgen muss und ich jede View von einer Basis-View ableite, erfolgt das Anhängen an den PropertyChanged Event auch eben in dieser Basis View -> weniger Arbeit für mich ;)

In Beitrag #2 habe ich am Ende die
Delphi-Quellcode:
TViewModelBase
und die
Delphi-Quellcode:
TViewBaseForm
noch gezeigt, wo man das sehen kann.

Bei der Verwendung dieser Pattern (egal ob MVC, MVP, MVVM, MVVP, ...) ist das A&O OOP und Vererben, bis der Arzt kommt. Dann wird vieles einfacher und schneller und die speziellen Teile sind nicht überfrachtet, weil ja der Basisteil schon im Vorfahr erledigt wird.

Oder ich kann einfach erweitern, schon benutzen und trotzdem läuft noch alles (siehe die Erweiterung des Activity-ViewModels auf ProgressActivity-ViewModel).

FAM 6. Feb 2015 10:01

AW: MVC + Observer Pattern Konzept / Was haltet Ihr davon
 
Zitat:

Zitat von Sir Rufo (Beitrag 1288978)
..., erfolgt das Anhängen an den PropertyChanged Event auch eben in dieser Basis View -> weniger Arbeit für mich ;)

da ich jetzt nicht den luxus eines Frameworks habe mache ich das jetzt "per Hand"

@Sir: eine Frage noch :)
wie kann ich es erreichen das ich bei notify einen parameter mit übergeben kann?
nach dem Motto
Delphi-Quellcode:
notify(para1);
?


Delphi-Quellcode:
procedure TStockpileModel.SetData(data: TArray<Double>);
begin
  self.data := data;
  // alle Ereignis-Behandlungs-Routinen der Liste aufrufen
  // wurde mit registerOn an das Model regestriert
  notify;
end;

BasisModel

Als erstes erstes erstelle ich mir mal eine Basis Model, mit PropertyChanged-Handling
Delphi-Quellcode:
TModel.notify
davon abgeleitete Klasse müssen bei Setter-Methoden jetzt die Funktion
Delphi-Quellcode:
notify
aufrufen

Delphi-Quellcode:
unit model;

interface

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.
Eigentliches Model

Delphi-Quellcode:
TViewModel.SetBar(const Value: String);
hier könnte die
Delphi-Quellcode:
  notify;
Methoden-Aufruf weggelasen wird

Delphi-Quellcode:
unit model.stockpile;

interface

uses
  model;

type

  { Stockpile Model }
  TStockpileModel = class(TModel)
  private
    data: TArray<Double>;
    cdsBioLife: String;

    function GetBioLife: String;

  public
    function GetData: TArray<Double>;
    procedure SetData(data: TArray<Double>);
    property BioLife: String read GetBioLife;
    destructor destroy; override;

  end;

implementation

{ TStockpileModel }

destructor TStockpileModel.destroy;
begin
  inherited;
end;

function TStockpileModel.GetBioLife: String;
begin
  Result := cdsBioLife;
end;

function TStockpileModel.GetData: TArray<Double>;
begin
  Result := self.data;
end;

procedure TStockpileModel.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.
Dann das ViewModel

Delphi-Quellcode:
unit ViewModel;

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, model, model.stockpile,
  controller, dOPCIntf, dOPCComn, dOPCDA, dOPC;

implementation

type

  TViewModel = class(TModel)

  private
    FModel: TStockpileModel;
    FBar: String;
    FFoo: string;
    function GetBioLifeCDS: String;
    function GetFishPictures: TBitmap;

    procedure SetBar(const Value: String);
    procedure SetFoo(const Value: string);
  public
    constructor Create;
    destructor Destroy; override;

    property BioLifeCDS: String read GetBioLifeCDS;
    property FishPictures: TBitmap read GetFishPictures;

    property Foo: string read FFoo write SetFoo;
    property Bar: String read FBar write SetBar;

  end;

  { TViewModel }

constructor TViewModel.Create;
begin
  inherited Create;
  FModel := TStockpileModel.Create;
end;

destructor TViewModel.Destroy;
begin
  FModel.Free;
  inherited;
end;

procedure TViewModel.SetBar(const Value: String);
begin
  if FBar <> Value then
  begin
    FBar := Value;
    notify;
  end;
end;

procedure TViewModel.SetFoo(const Value: string);
begin
  if FFoo <> Value then
  begin
    FFoo := Value;
    notify;
    // OnPropertyChanged('Foo');
  end;
end;

function TViewModel.GetBioLifeCDS: String;
begin
  Result := FModel.BioLife;
end;

function TViewModel.GetFishPictures: TBitmap;
begin
  try
    Result := TBitmap.Create;
    Result.LoadFromFile
      (ExpandFileName(IncludeTrailingPathDelimiter(ExtractFileDir(ParamStr(0)) +
      '') + '../../Assets/Images/sidebar-icon-error.jpg'));

  finally
  end;

end;

end.
Main

Delphi-Quellcode:
....

// Model
    FViewModel: TStockpileModel;

    // Model wurde geändert
    procedure ViewModelPropertyChanged(Sender: TObject);



implementation


FViewModel := TViewModel.Create;

// Wenn model geändert wird -> ViewModelPropertyChanged ausführen
FViewModel.registerOn(ViewModelPropertyChanged);


// Daten aus dem Model holen und GUI updaten
procedure TForm1.ViewModelPropertyChanged(Sender: TObject);
begin
  EditContentbarStartMarker.Text := FViewModel.Foo;
end;

// Daten ins Model zurückschreiben
procedure TForm1.EditContentbarEndMarkerChange(Sender: TObject);
begin
  FViewModel.Foo := TEdit(Sender).Text;
end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 16:52 Uhr.
Seite 2 von 3     12 3      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz