Wer schon mit .Net zu tun hatte, wird sie kennen und schätzen gelernt haben: Data Bindings.
Man kann dort nahezu alles aneinander binden. Sei es, um Listen von Objekten in Trees, Lists oder Grids darzustellen oder Eigenschaften von Objekten in Text-, Check- oder Comboboxen editierbar zu machen.
Oder sogar die Sichtbarkeit oder das Aussehen einzelner Controls zu beinflussen.
Wie das genau funktioniert, kann man
in der MSDN nachlesen.
Ja und? In Delphi gibts DataSet, DataSource und diverse datensensitive Controls, um das zu bewerkstelligen!
Richtig, die Sache hat nur einen Haken: Man kann nicht einfach ein 08/15 Objekt daran hängen. Sicher, es gibt
TSnapObjectDataset oder spezielle Controls, mit denen man das machen kann. Hat aber den entscheidenden Nachteil, dass man immer entweder besondere Controls oder Komponenten haben muss oder seine bindbaren Objekte von einer Basisklasse ableiten muss.
Ich hab mir mal einige Dinge von der .Net Implementierung abgeschaut und das ganze in Delphi mit folgenden Zielsetzung nachgebaut:
- Standard Komponenten und Controls müssen unterstützt werden (z.B. TEdit)
- wenig bis keine extra Implementierung für bindbare Objekte
- minimaler Sourcecode, um Objekte aneinander zu binden
Ok, fangen wir an. Was brauchen wir?
Ein Binding braucht immer eine Source und ein Target (beides ein Objekt) und die Namen der zu bindenden Eigenschaften.
Für unser erstes Beispiel bauen wir uns eine einfache Klasse, die 2 Eigenschaften hat:
Delphi-Quellcode:
type
TSettings = class
private
FCaption: string;
FColor: TColor;
procedure SetCaption(const Value: string);
procedure SetColor(const Value: TColor);
public
property Caption: string read FCaption write SetCaption;
property Color: TColor read FColor write SetColor;
end;
implementation
procedure TSettings.SetCaption(const Value: string);
begin
FCaption := Value;
end;
procedure TSettings.SetColor(const Value: TColor);
begin
FColor := Value;
end;
Wir haben hier schonmal explizite Setter Methoden - die brauchen wir später noch. Aber für das erste Beispiel würde auch in direkter Zugriff auf die Feldvariable genügen.
So, was machen wir nun mit unserem TSettings Objekt? Wir erzeugen 2 Bindings, die auf den Titel und die Farbe unserer MainForm gehen.
Delphi-Quellcode:
procedure TMainForm.FormCreate(Sender: TObject);
begin
FSettings := TSettings.Create;
FSettings.Caption := 'Binding demo';
FSettings.Color := clGray;
TBinding.Create(FSettings, 'Caption', MainForm, 'Caption');
TBinding.Create(FSettings, 'Color', MainForm, 'Color');
end;
Dazu genügt es, die
Unit System.Bindings einzubinden. Auch um das Freigeben der Binding Objekte braucht man sich nicht kümmern solange mindestens eins der beiden verbundenen Objekte von TComponent abgeleitet ist (in unserem Fall ist es das MainForm).
Starten wir die Anwendung und sehen, was passiert. Titel und Farbe des MainForms ist entsprechend der Werte aus unserem TSettings Objekts gesetzt worden. Toll, oder?
Naja, da hätts auch das explizite Zuweisen der Werte getan... Klar, aber der eigentliche Clou der Bindings kommt erst noch!
Packen wir mal einen Button auf die Form und setzen dort die Eigenschaft unseres Settings Objekts:
Delphi-Quellcode:
procedure TMainForm.Button1Click(Sender: TObject);
begin
FSettings.Caption := 'Current time: ' + DateTimeToStr(Now);
end;
Da tut sich erstmal nix, aber wir sind auch noch nicht fertig. Gehen wir zurück zu unserer TSettings Klasse und passen sie etwas an.
Wir leiten sie mal von einem Basis Objekt ab, welches ein Interface implementiert - und zwar INotifyPropertyChanged. Auch das ist wieder brav von .Net abgekupfert.
Aha, nun brauchen wir also unsere Setter Methoden für die Eigenschaften.
So sieht unserer modifizierte Klasse nun aus:
Delphi-Quellcode:
type
TSettings = class(TPropertyChangedBase)
private
FCaption: string;
FColor: TColor;
procedure SetCaption(const Value: string);
procedure SetColor(const Value: TColor);
public
property Caption: string read FCaption write SetCaption;
property Color: TColor read FColor write SetColor;
end;
implementation
procedure TSettings.SetCaption(const Value: string);
begin
FCaption := Value;
DoPropertyChanged('Caption');
end;
procedure TSettings.SetColor(const Value: TColor);
begin
FColor := Value;
DoPropertyChanged('Color');
end;
Tadaa, und auf Knopfdruck wird nun auch der Titel unserer MainForm geändert. Naja, ganz putzig, aber so richtig rocken tut's noch nicht, oder?
Na gut, werfen wir mal ein TEdit auf die Form und binden es ebenfalls im Konstruktor des MainForms an die Caption Eigenschaft unseres TSettings Objekts:
Delphi-Quellcode:
...
TBinding.Create(FSettings, 'Caption', Edit3, 'Text');
Toll, nun steht der Titel des Forms auch im Edit drin... und wenn ich dort drin änder passiert nix!
Stimmt, es fehlt noch was. Als letzte
Unit (genauer gesagt nach den Units, in denen die Controls drin sind, an die ich binden möchte) im uses des Interface Teils des Forms muss die
Unit System.Bindings.Controls.
Dort befinden sich einige Ableitungen von Standard
VCL Controls mit dem gleichen Namen, welche das INotifyPropertyChanged Interface implementieren. Und dadurch, dass sie als letzte (oder zumindest nach den entsprechenden Units steht) werden beim Erstellen des Forms Objekte von den abgeleiteten Klassen erstellt. Das heißt im Klartext, es müssen keine speziellen Controls oder Komponenten auf der Form verbaut werden, damit man das Data Binding nutzen kann.
Starten wir mal die Anwendung und tippern ein wenig im Edit rum. Wow, der Titel des Forms ändert sich!
Um es nochmal zu betonen:
- keine speziellen Controls nötig (nur das Subclassing von Controls, die man Binding fähig machen möchte)
- keine speziellen Events nötig
- als read-once Source können direkte Ableitungen von TObject benutzt werden (lediglich für die Benachrichtigung von Eigenschaftsänderungen ist das Interface nötig)
- minimaler Sourcecode, um Objekte, Komponenten und Controls zu verbinden.
Im angehangenden Beispiel sind noch einige weitere Bindings und Controls auf der Form, mit welchen man einige Eigenschaften verändern kann.
Das ganze wurde unter Delphi XE entwickelt. Delphi 2010 könnte auch funktionieren, aber da waren die Generics noch ein wenig verbuggt. Alle Versionen darunter sind leider nicht unterstützt. Das liegt am regen Gebrauch von Generics und neuer
RTTI.
Außerdem findet ihr in den benötigten Units noch weitere Dinge wie zum Beispiel Multicast Events oder den NotificationHandler, der dafür zuständig ist, dass die TBinding Objekte ohne Zutun freigegeben werden.
In Planung und teilweise schon realisiert sind Bindings für Listen in dementsprechenden Controls (z.B. ListBox)
Anregungen, Kritik und Fragen sind herzlich willkommen und sehr erwünscht.
P.S.:
Crosspost bei Delphi-Forum
Update 18.06.2011:
- Source der Library aktualisiert (enthält den kompletten Code meiner DSharp Library)
- aktuelle Samples für data binding hinzugefügt
Update 10.04.2012:
- veraltete Archive entfernt (aktuelle und supportete Version im svn Repository)