|
Antwort |
Registriert seit: 22. Dez 2014 47 Beiträge Delphi XE Enterprise |
#1
Delphi-Version: XE
Hallo Zusammen,
ich habe mir mal zum Thema MVC + Observer-Pattern einen konzeptionellen Entwurf überlegt ... Grundüberlegung war folgende: Im View werden unterschiedliche Events ausgelöst, über den Controller werden diese dann ausgewertet und die entsprechende Funktion werden dafür aufgerufen. Die Funktionen werden zuvor am jeweiligen Controller registriert. Verwendungsmöglichkeit: einfaches EventDispatching (Komponentenunabhängig), z.B. GUI-Update Unabhänig davon sollen Datenmodelle mit "integrierten Observer Pattern" implenetiert werden können. Verwendungsmöglichkeit: Bei Datenänderung im Model werden entsprechende EventHandlers ausgelöst. Ich finde das charmante daran ist die Event-Trennung (Kapselung) - Controller gesteuerte Events (MVC) + auto. Model-gesteuerte Events (ObserverPattern) Was haltet Ihr davon? - möchte nur der "Betriebsblindheit" mal vorbeugen VIEW
Delphi-Quellcode:
CONTROLLER
unit main;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, event.fam.types, model.stockpile, controller, StdCtrls, ExtCtrls; type TForm1 = class(TForm) LabelEventHandlerDebug: TLabel; RadioGroup1: TRadioGroup; RadioButton1: TRadioButton; RadioButton2: TRadioButton; Button1: TButton; procedure FormCreate(Sender: TObject); procedure SetApplicationBackground; procedure Button1Click(Sender: TObject); procedure RadioButton1Click(Sender: TObject); procedure EventHandlerViewLabelUpdate; procedure EventHandlerModelStockpileUpdate; procedure RadioButton2Click(Sender: TObject); private { Private-Deklarationen } controller: TController; FAMEvents: TEvents; StockpileModel: TStockpileModel; // Stockpile model public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var data: TArray<Double>; begin StockpileModel.SetData(data); end; procedure TForm1.FormCreate(Sender: TObject); begin { Create eventtype } FAMEvents := TEvents.Create(); { Create controller instances } controller := TController.Create(); { Register Event-Handler } controller.OnUpdateUI := EventHandlerViewLabelUpdate; controller.OnApplicationBackground := SetApplicationBackground; { Create object instances } StockpileModel := TStockpileModel.Create(); { Register Event-Handler for StockpileModel } StockpileModel.registerOn(EventHandlerModelStockpileUpdate); end; procedure TForm1.RadioButton1Click(Sender: TObject); begin controller.DispatchEvent(FAMEvents.Name.OnUpdateUI); end; procedure TForm1.RadioButton2Click(Sender: TObject); begin controller.DispatchEvent(FAMEvents.Name.OnApplicationBackground); end; procedure TForm1.SetApplicationBackground; begin self.Color := clBlue; end; procedure TForm1.EventHandlerModelStockpileUpdate; begin ShowMessage('EventHandlerModelStockpileUpdate'); end; procedure TForm1.EventHandlerViewLabelUpdate; begin LabelEventHandlerDebug.caption := ('TListener has been OnUpdateUI.'); end; end.
Delphi-Quellcode:
Model
unit controller;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, event.fam.types; type { Define a procedural type } TFAMEvent = procedure of object; TController = class private FUpdateUI: TFAMEvent; FApplicationBackground: TFAMEvent; public FAMEvents: TEvents; Constructor Create; procedure dispatchEvent(const FAMEventName: String); property OnUpdateUI: TFAMEvent read FUpdateUI write FUpdateUI; property OnApplicationBackground: TFAMEvent read FApplicationBackground write FApplicationBackground; end; implementation { TController } constructor TController.Create; begin { Create eventtype } FAMEvents := TEvents.Create(); end; procedure TController.dispatchEvent(const FAMEventName: String); begin if ((FAMEventName = FAMEvents.Name.OnUpdateUI) and Assigned(FUpdateUI)) then FUpdateUI(); if ((FAMEventName = FAMEvents.Name.OnApplicationBackground) and Assigned(OnApplicationBackground)) then OnApplicationBackground(); end; end.
Delphi-Quellcode:
MODEL (Eltern - Klasse)
unit model.stockpile;
interface uses model; type { Stockpile Model } TStockpileModel = class(TModel) private data: TArray<Double>; public function GetData: TArray<Double>; procedure SetData(data: TArray<Double>); end; implementation { TStockpileModel } 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.
Delphi-Quellcode:
unit model;
interface type TEvent = procedure of object; TModel = class(tObject) protected // interne Liste OnChange: array of TEvent; // Aufruf aller Routinen der Liste procedure notify; 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 procedure TModel.notify; var i: integer; begin for i := Low(OnChange) to High(OnChange) do OnChange[i]; end; end. Events-Types
Delphi-Quellcode:
unit event.fam.types;
interface type Events = record onUpdateUI: string; onApplicationBackground: string; end; TEvents = class private FEvents: Events; public Constructor Create; property Name: Events read FEvents; end; implementation { EventTypes } constructor TEvents.Create; var _FEvents: Events; begin _FEvents.onUpdateUI := 'onUpdateUI'; _FEvents.onUpdateUI := 'onApplicationBackground'; FEvents := _FEvents; Finalize(_FEvents); FillChar(_FEvents, SizeOf(_FEvents), 0); end; end. Geändert von FAM ( 5. Feb 2015 um 12:35 Uhr) |
Zitat |
Registriert seit: 5. Jan 2005 Ort: Stadthagen 9.454 Beiträge Delphi 10 Seattle Enterprise |
#2
Das ist irgendwie kein MVC, sondern irgendwas ... Nur weil man da etwas Model-View-Controller benennt, wird es noch kein MVC.
Schau dir mal an wie bei apple mit MVC gearbeitet wird, dann bekommt man eine ungefähre Vorstellung. Da hat der Controller jedes Control auf der View und auch da wird das erst benannt.
Delphi-Quellcode:
Problematisch ist und bleibt aber das vernünftige Umsetzen. Um es richtig zu machen müsste der Controller die Controls erzeugen und die View müsste sich nur noch merken an welcher Stelle diese Controls dargestellt werden sollen. Wenn das gesamte Framework dafür vorbereitet ist, dann ist alles ganz einfach. Wenn nicht, dann fängt man quasi bei Adam und Eva an.
TViewController = class
public property Firstname : TEdit; property Lastname : TEdit; end; TView = class( TForm ) Edit1 : TEdit; // -> ViewController.Firstname Edit2 : TEdit; // -> ViewController.Lastname end; Da ist das MVVM schon "wesentlich einfacher" umzusetzen und kommt deinem Entwurf auch wesentlich näher. Grundlegend bei den Entwürfen ist aber, dass die Views nicht die ViewModels/Controller erzeugen, denn sonst geht der gesamte Vorteil der Testbarkeit sofort flöten und die Views übernehmen auf einmal wieder die Kontrolle. Ich kann dir mal ein kleines Beispiel zeigen, wie ich das mit MVVM mache:
Genauso wie das ActivityViewModel der ActivityView zugewiesen wird. Im Anhang habe ich den gesamten restlichen Source (exclusive den Basis-Units) und die ausführbare Exe angehängt Zum besseren Verständnis hier einmal die BaseView
Delphi-Quellcode:
und natürlich die ViewModelBase
unit MVVM.View.FMX.Form.Base;
interface uses de.itnets.Events, de.itnets.References, MVVM.Core, MVVM.ViewModel.ViewModelBase, System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs; type TViewBaseForm = class( TForm, IView ) private FViewModel: WeakRef<TViewModelBase>; function GetViewModel: TViewModelBase; protected procedure ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs ); virtual; procedure AttachToViewModel( AViewModel: TViewModelBase ); virtual; procedure DetachFromViewModel( AViewModel: TViewModelBase ); virtual; public procedure SetViewModel( AViewModel: TViewModelBase ); virtual; procedure BeforeDestruction; override; function Equals( Obj: TObject ): Boolean; override; end; var ViewBaseForm: TViewBaseForm; implementation {$R *.fmx} { TForm1 } procedure TViewBaseForm.AttachToViewModel( AViewModel: TViewModelBase ); begin AViewModel.PropertyChanged.Add( Self.ViewModelPropertyChanged ); end; procedure TViewBaseForm.BeforeDestruction; begin SetViewModel( nil ); inherited; end; procedure TViewBaseForm.DetachFromViewModel( AViewModel: TViewModelBase ); begin AViewModel.PropertyChanged.Remove( Self.ViewModelPropertyChanged ); end; function TViewBaseForm.Equals( Obj: TObject ): Boolean; begin Result := ( Self = Obj ) or Assigned( Obj ) and {} ( ( Obj is TViewBaseForm ) and ( Self.FViewModel = ( Obj as TViewBaseForm ).FViewModel ) ) {} or {} ( Self.FViewModel.IsAssigned and Self.FViewModel.Reference.Equals( Obj ) ); end; function TViewBaseForm.GetViewModel: TViewModelBase; begin Result := FViewModel; end; procedure TViewBaseForm.SetViewModel( AViewModel: TViewModelBase ); begin if FViewModel <> AViewModel then begin if FViewModel.IsAssigned then DetachFromViewModel( FViewModel ); FViewModel := AViewModel; if FViewModel.IsAssigned and not( csDestroying in Self.ComponentState ) then begin AttachToViewModel( FViewModel ); PropertyChangedEvent.Call( ViewModelPropertyChanged, FViewModel, TPropertyChangedArgs.Create( ) ); end; end; end; procedure TViewBaseForm.ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs ); begin // Nothing to do here end; end.
Delphi-Quellcode:
PS Ein für mich sehr wichtiger Punkt ist die Unterstützung von allen Plattformen mit so wenig Anpassungen wie möglich. Dieser Code läuft ohne Änderungen exakt gleich auf Windows, OSX und Android (iOS nicht getestet, aber da befürchte ich eigentlich keine großen Überraschungen)
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 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.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60) Geändert von Sir Rufo ( 5. Feb 2015 um 13:43 Uhr) |
Zitat |
Registriert seit: 22. Dez 2014 47 Beiträge Delphi XE Enterprise |
#3
Grundlegend bei den Entwürfen ist aber, dass die Views nicht die ViewModels/Controller erzeugen, denn sonst geht der gesamte Vorteil der Testbarkeit sofort flöten und die Views übernehmen auf einmal wieder die Kontrolle.
danke für deine Antwort. Was die Testbarkeit angeht, verstehe ich dich nicht ganz. Warum sollte diese flöten gehen...? ich habe doch mit diesem Konzept die BusinessLogik sauber von der GUI (View) getrennt. oder nicht? |
Zitat |
Registriert seit: 5. Jan 2005 Ort: Stadthagen 9.454 Beiträge Delphi 10 Seattle Enterprise |
#4
Grundlegend bei den Entwürfen ist aber, dass die Views nicht die ViewModels/Controller erzeugen, denn sonst geht der gesamte Vorteil der Testbarkeit sofort flöten und die Views übernehmen auf einmal wieder die Kontrolle.
danke für deine Antwort. Was die Testbarkeit angeht, verstehe ich dich nicht ganz. Warum sollte diese flöten gehen...? ich habe doch mit diesem Konzept die BusinessLogik sauber von der GUI (View) getrennt. oder nicht? Mein MainViewModel kann ich testen ohne irgendeine View im Spiel zu haben.
Delphi-Quellcode:
Ich kann auch die gesamte Anwendung durchlaufen lassen ohne ein einziges View zu erzeugen, denn die View wird erzeugt, wenn es ein ViewModel gibt und nicht umgekehrt, das ViewModel wird erzeugt, wenn es eine View gibt.
procedure Test;
var LVM : TMainViewModel; begin LVM := TMainViewModel.Create; try Assert( LVM.SomeAction.CanExecute ); Assert( not Assigned( LVM.Activity ) ); LVM.SomeAction.Execute; Assert( not LVM.SomeAction.CanExecute ); Assert( Assigned( LVM.Activity ) ); while not LVM.CanClose do Sleep(10); Assert( LVM.SomeAction.CanExecute ); Assert( not Assigned( LVM.Activity ) ); finally LVM.Free; end; end; Du willst aber eine Aktion per Event an die View geben, die dann eine neue View mit Controller erstellt und dann soll dieser Controller irgendwie eingebunden werden. Schwups ist die Abhängigkeit von der View wieder da und wir haben nichts gewonnen, nur mehr Schreibarbeit.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60) Geändert von Sir Rufo ( 5. Feb 2015 um 14:11 Uhr) |
Zitat |
Registriert seit: 5. Jan 2005 Ort: Stadthagen 9.454 Beiträge Delphi 10 Seattle Enterprise |
#5
Richtig spannend wird es, wenn man das erweitern möchte:
z.B. benötigen wir auch eine Aktivitätsanzeige mit einem Fortschritt. Nichts leichter als das
Delphi-Quellcode:
Schon haben wir eine Aktivität mit Fortschritt.
unit ViewModel.ActivityViewModel;
interface uses MVVM.ViewModel.ViewModelBase; type TActivityViewModel = class( TViewModelBase ) private FInfo: string; procedure SetInfo( const Value: string ); public property Info: string read FInfo write SetInfo; end; TProgressActivityViewModel = class( TActivityViewModel ) private FProgress: Single; procedure SetProgress( const Value: Single ); public property Progress: Single read FProgress write SetProgress; end; implementation { TActivityViewModel } procedure TActivityViewModel.SetInfo( const Value: string ); begin if FInfo <> Value then begin FInfo := Value; OnPropertyChanged( 'Info' ); end; end; { TProgressActivityViewModel } procedure TProgressActivityViewModel.SetProgress( const Value: Single ); begin if FProgress <> Value then begin FProgress := Value; OnPropertyChanged( 'Progress' ); end; end; end. Dann mal in das MainViewModel und die Aktivitäten eingebaut (nur die Änderungen)
Delphi-Quellcode:
Da wir TProgressActivityViewModel
von TActivityViewModel
abgleitet haben brauchen wir die View nicht ändern um lauffähig zu bleiben. Nur der Progress wird eben nicht angezeigt, aber die Funktionalität bleibt gewahrt.
unit ViewModel.MainViewModel;
interface uses System.SysUtils, System.Classes, System.Threading, de.itnets.Commands, de.itnets.References, MVVM.ViewModel.ViewModelBase, ViewModel.WorkspaceViewModel, ViewModel.ActivityViewModel; type TMainViewModel = class( TWorkspaceViewModel ) private FSomeProgressActionCommand: ICommand; FSomeRandomActionCommand: ICommand; function GetSomeProgressActionCommand: ICommand; function GetSomeRandomActionCommand: ICommand; public property SomeProgressActionCommand: ICommand read GetSomeProgressActionCommand; property SomeRandomActionCommand: ICommand read GetSomeRandomActionCommand; end; implementation { TMainViewModel } function TMainViewModel.GetSomeProgressActionCommand: ICommand; begin if not Assigned( FSomeProgressActionCommand ) then FSomeProgressActionCommand := TRelayCommand.Create( procedure var LActivity: AutoRef<TProgressActivityViewModel>; begin SetCanClose( False ); // Aktivitätsanzeige setzen LActivity := TProgressActivityViewModel.Create; LActivity.Reference.Info := 'Performing SomeProgressAction'; SetActivity( LActivity ); // Task starten TTask.Run( procedure var LIdx: Integer; begin LActivity.Reference.Progress := 0; for LIdx := 1 to 10 do begin Sleep( 200 ); LActivity.Reference.Progress := LIdx * 10; end; // Aktivitätsanzeige ausschalten TThread.Synchronize( nil, procedure begin SetActivity( nil ); SetCanClose( True ); end ); end ); end, function: Boolean begin Result := not FActivity.IsAssigned; end ); Result := FSomeProgressActionCommand; end; function TMainViewModel.GetSomeRandomActionCommand: ICommand; begin if not Assigned( FSomeRandomActionCommand ) then FSomeRandomActionCommand := TRelayCommand.Create( procedure var LActivity: AutoRef<TActivityViewModel>; LProgressActivity: AutoRef<TProgressActivityViewModel>; begin SetCanClose( False ); // Aktivitätsanzeige setzen LActivity := TActivityViewModel.Create; LProgressActivity := TProgressActivityViewModel.Create; SetActivity( LActivity ); // Task starten TTask.Run( procedure var LIdx: Integer; begin TThread.Queue( nil, procedure begin LActivity.Reference.Info := 'Init data...'; SetActivity( LActivity ); end ); Sleep( 1000 ); TThread.Queue( nil, procedure begin LProgressActivity.Reference.Info := 'Reading data...'; LProgressActivity.Reference.Progress := 0; SetActivity( LProgressActivity ); end ); for LIdx := 1 to 20 do begin Sleep( 150 ); TThread.Queue( nil, procedure begin LProgressActivity.Reference.Progress := LIdx * 5; end ); end; TThread.Queue( nil, procedure begin LActivity.Reference.Info := 'Cleanup system...'; SetActivity( LActivity ); end ); Sleep( 1000 ); // Aktivitätsanzeige ausschalten TThread.Synchronize( nil, procedure begin SetActivity( nil ); SetCanClose( True ); end ); end ); end, function: Boolean begin Result := not FActivity.IsAssigned; end ); Result := FSomeRandomActionCommand; end; end. Ok, dann bauen wir uns eine entsprechende View (Frame mit einer ProgressBar und einem TextFeld) und bringen das auf die MainView. Damit das dann auch genutzt wird benötigen wir diese Änderungen an der View (nur die geänderten Teile)
Delphi-Quellcode:
Im Anhang nur die Exe
unit View.Form.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, View.Form.WorkspaceView, MVVM.View.FMX.Frame.Base, FMX.Objects, System.Actions, FMX.ActnList, FMX.Layouts, View.Frame.AcitivityView, View.Frame.ProgressAcitivityView; type TMainView = class( TWorkspaceView ) ActivityCurtain: TRectangle; ActivityView1: TActivityView; ToolBar1: TToolBar; SpeedButton1: TSpeedButton; ActionList1: TActionList; SomeActionAction: TAction; CloseAction: TAction; SpeedButton2: TSpeedButton; SomeProgressActionAction: TAction; SpeedButton3: TSpeedButton; ProgressActivityView1: TProgressActivityView; SomeRandomActionAction: TAction; SpeedButton4: TSpeedButton; procedure SomeActionActionExecute( Sender: TObject ); procedure SomeActionActionUpdate( Sender: TObject ); procedure CloseActionExecute( Sender: TObject ); procedure CloseActionUpdate( Sender: TObject ); procedure SomeProgressActionActionExecute( Sender: TObject ); procedure SomeProgressActionActionUpdate( Sender: TObject ); procedure SomeRandomActionActionExecute( Sender: TObject ); procedure SomeRandomActionActionUpdate( Sender: TObject ); private FMain: WeakRef<TMainViewModel>; protected procedure AttachToViewModel( AViewModel: TViewModelBase ); override; procedure DetachFromViewModel( AViewModel: TViewModelBase ); override; procedure ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs ); override; public end; var MainView: TMainView; implementation {$R *.fmx} uses System.StrUtils, ViewModel.ActivityViewModel; { TMainView } procedure TMainView.SomeProgressActionActionExecute( Sender: TObject ); begin inherited; FMain.Reference.SomeProgressActionCommand.Execute; end; procedure TMainView.SomeProgressActionActionUpdate( Sender: TObject ); begin inherited; TAction( Sender ).Enabled := FMain.IsAssigned and FMain.Reference.SomeProgressActionCommand.CanExecute; end; procedure TMainView.SomeRandomActionActionExecute( Sender: TObject ); begin inherited; FMain.Reference.SomeRandomActionCommand.Execute; end; procedure TMainView.SomeRandomActionActionUpdate( Sender: TObject ); begin inherited; TAction( Sender ).Enabled := FMain.IsAssigned and FMain.Reference.SomeRandomActionCommand.CanExecute; end; procedure TMainView.ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs ); begin inherited; if FMain.IsAssigned then begin if e.Matches( ['Active', 'DisplayName'] ) then begin Caption := FMain.Reference.DisplayName + ' (' + IfThen( FMain.Reference.Active, 'Active', 'Inactive' ) + ')'; end; if e.Match( 'Activity' ) then begin ActivityCurtain.BringToFront; ActivityCurtain.Visible := Assigned( FMain.Reference.Activity ); if FMain.Reference.Activity is TProgressActivityViewModel then begin ActivityView1.Visible := False; ActivityView1.SetViewModel( nil ); ProgressActivityView1.Visible := True; ProgressActivityView1.BringToFront; ProgressActivityView1.SetViewModel( FMain.Reference.Activity ); end else if FMain.Reference.Activity is TActivityViewModel then begin ProgressActivityView1.Visible := False; ProgressActivityView1.SetViewModel( nil ); ActivityView1.Visible := Assigned( FMain.Reference.Activity ); ActivityView1.BringToFront; ActivityView1.SetViewModel( FMain.Reference.Activity ); end else begin ActivityView1.Visible := False; ActivityView1.SetViewModel( nil ); ProgressActivityView1.Visible := False; ProgressActivityView1.SetViewModel( nil ); end; end; end end; end.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60) |
Zitat |
Registriert seit: 16. Apr 2006 71 Beiträge Delphi XE8 Professional |
#6
@Sir Rufo,
Ich habe das SimpleForm Beispiel versucht mit XE7 zu öffnen, doch dabei beschwert sich die IDE, das sie nicht alle Dateien finden kann, MVVM.View.FMX.Form.Base.pas, MVVM.View.FMX.Frame.Base.pas ausserdem kann meine Die folgende Units nicht auflösen de.itnets.Events, de.itnets.References, MVVM.Core, MVVM.ViewModel.ViewModelBase, de.itnets.Commands, … Könntest du die restlichen Dateien die zum Kompilieren notwendig sind ebenfalls veröffentlichen? |
Zitat |
Registriert seit: 5. Jan 2005 Ort: Stadthagen 9.454 Beiträge Delphi 10 Seattle Enterprise |
#7
@Thomas_K
Das habe ich auch geschrieben
Zitat von Sir Rufo:
Im Anhang habe ich den gesamten restlichen Source (exclusive den Basis-Units) und die ausführbare Exe angehängt
Das Beispiel-Projekt habe ich auch nur veröffentlicht um einen direkten Vergleich zwischen dem vorgestellten Konzept und MVVM zu ermöglichen, zu zeigen, dass MVVM mit Delphi durchaus zuverlässig funktioniert und wie MVVM grundsätzlich funktioniert.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60) |
Zitat |
Registriert seit: 25. Sep 2004 79 Beiträge Delphi XE7 Professional |
#8
MVVM ist in DSharp meiner Erfahrung nach am besten implementiert.
Ich hoffe, dass Stefan dieses Jahr etwas mehr Zeit findet, den MVVM-Branch (Caliburn Micro) in DSharp weiterzuentwickeln. https://bitbucket.org/sglienke/dsharp |
Zitat |
Registriert seit: 22. Dez 2014 47 Beiträge Delphi XE Enterprise |
#9
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"?
|
Zitat |
(Moderator)
Registriert seit: 9. Dez 2005 Ort: Heilbronn 39.858 Beiträge Delphi 11 Alexandria |
#10
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"?
Markus Kinzler
|
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |