Also ich habe einen Lösungsansatz, den ich in Ausschnitten beschreibe.
Die View ist ein normales TForm. Beim Erzeugen wird das Binding erstellt.
Delphi-Quellcode:
constructor TfrmMain.Create(AOwner: TComponent);
begin
ViewModel := TViewModelKunde.create;
inherited;
// Binding einzeln erzeugen
FBindingList := TBindingList.Create;
FNameBindObject := TEditBinding.Create;
FNameBindObject.Control := edtNameTest;
FNameBindObject.ControlProp := 'Text';
FNameBindObject.BindObject := ViewModel.Kunde;
FNameBindObject.BindProp := 'Name';
// Und hinzufügen
FBindingList.Add(FNameBindObject);
// Oder direkt
FBindingList.Add(TEditBinding.Create(edtKdNrTest, ViewModel.Kunde, 'Text', 'Kdnr'));
// Hier wird alles initialisiert
FBindingList.Load;
end;
Im Viewmodel wird dann die UI-Relevante Logik implementiert, z.b. ob gespeichert werden darf.
unit Viewmodel.Kunde;
Delphi-Quellcode:
interface
uses Model.Kunde;
type
TViewModelKunde = class
strict private
FKunde: TKunde;
procedure SetKunde(const Value: TKunde);
function GetCanSave: boolean;
published
constructor create;
property Kunde : TKunde read FKunde write SetKunde;
procedure Save;
property CanSave : boolean read GetCanSave;
end;
// Kann aus dem View heraus benutzt werden um festzustellen ob z.B. Speicherung zulässig
function TViewModelKunde.GetCanSave: boolean;
begin
result := FKunde.IsValid;
end;
Und das Model stellet eben die Daten dar, in diesem Fall ganz schlicht.
Delphi-Quellcode:
unit Model.Kunde;
interface
uses
System.Classes;
type
MaxLen =
class(TCustomAttribute)
private
FMaxLen : integer;
public
constructor Create(
const ALength : integer);
overload;
// Per Konvention muss die Attribut-Property immer "Value" heißen
property Value : integer
read FMaxLen;
end;
TKunde =
class
strict private
FName:
string;
FKdNr: integer;
FStrasse:
string;
procedure SetKdNr(
const Value: integer);
procedure SetName(
const Value:
string);
procedure SetStrasse(
const Value:
string);
function GetIsValid: boolean;
public
property KdNr : integer
read FKdNr
write SetKdNr;
[MaxLen(20)]
property Name :
string read FName
write SetName;
property Strasse :
string read FStrasse
write SetStrasse;
property IsValid : boolean
read GetIsValid;
end;
implementation
{ TKunde }
function TKunde.GetIsValid: boolean;
begin
result := (FKdNr > 0)
and
(FName <> '
')
and
(FStrasse <> '
');
end;
procedure TKunde.SetKdNr(
const Value: integer);
begin
FKdNr := Value;
end;
procedure TKunde.SetName(
const Value:
string);
begin
FName := Value;
end;
procedure TKunde.SetStrasse(
const Value:
string);
begin
FStrasse := Value;
end;
{ MaxLen }
constructor MaxLen.Create(
const ALength: integer);
begin
FMaxLen := ALength;
end;
Das ganze wird verbunden durch das Binding. Wenn ein Binding erzeugt wird mit z.b.
Delphi-Quellcode:
TEditBinding.Create(edtKdNrTest, ViewModel.Kunde, 'Text', 'Kdnr')
TEditBinding.Load
wird eigentlich nur folgendes festgelegt:
1. edtKdnrTest.Text entspricht ViewModel.Kunde.Kdnr
2. Evtl. im Model angelegte Attribute werden ausgelesen und in die UI eingetragen (Hier MaxLen)
3. Der OnExit Event wird durch einen eigenen Event überschrieben, der Original-Event wird intern gemerkt
Immer wenn jetzt der OnExit des Edit aufgerufen wird, wird zunächst der Inhalt an das Model übertragen und dann ein evtl. vorher vorhandener Original-Event aufgerufen.
Delphi-Quellcode:
procedure TCustomBinding.DoNotify(Sender: TObject);
begin
Save;
if Assigned(FOldNotify) then
FOldNotify(Sender);
end;
procedure TCustomBinding.Save;
var
AValue : Variant;
begin
AValue := GetPropValue(FControl, FControlProp);
SetPropValue(FBindObject, FBindProp, AValue);
end;