Entwickler-Ecke

Programmierwerkzeuge - Eine kleine Vorstellung von LiveBindings™


jaenicke - Mo 29.08.11 02:00
Titel: Eine kleine Vorstellung von LiveBindings™
Hallo,

ich möchte euch hier ein paar Aspekte der mit Delphi XE 2 kommenden LiveBindings™ zeigen.

Vorweg: Die Vorstellung von Details zu den bereits veröffentlichten Features ist von Embarcadero autorisiert. Im Rahmen dessen bin ich auch gern bereit Fragen zu beantworten soweit möglich.

Nun zur Sache. ;-) Ich benutze im Beispiel einfach einmal eine der mitgelieferten Datenbanken. Diese beinhaltet eine Liste von Ereignissen mit unter anderem einem Namen und einem Foto. Diese möchte ich jetzt anzeigen sowie durch die Datensätze navigieren. Zudem möchte ich eine eigene Klasse ansteuern, die die Formularüberschrift mit der aktuellen Recordnummer füttert.

Ich belasse im Beispiel explizit die Standardbezeichner, um keine unnötige Verwirrung zu stiften. Ich habe eine neue FireMonkey Anwendung erstellt. Auf das Formular kommt links oben ein Label zur Anzeige des Namens des Events, die Recordnummer des aktuellen Eintrags in eines rechts oben, darunter das Bild des Events. Darunter dann noch eine Fortschrittsanzeige zur Visualisierung des Fortschritts beim Blättern durch die Datenbank sowie ein Navigator. Das Formular sieht im Designer fertig so aus:

[url=http://www.sj-berlin.de/service/df/xe2/XE2DataBindingProject.png]user defined image[/url]

Jetzt benötige ich zunächst die Datenbankverbindung, dafür benutze ich ein TClientDataSet verbunden zu einer TDataSource. Dem ClientDataSet gebe ich den FileName, in diesem Fall die Datei events.xml. Die TDataSource bekommt als DataSet noch das TClientDataSet und dieses wird auf Active gesetzt.

Nun kennen die LiveBindings™ automatisch die Felder der Datenbank. Ich möchte jetzt die Felder der Datenbank mit meinen Komponenten verknüpfen. Dafür klicke ich mit rechts auf das Label links oben um den Eventnamen damit zu verknüpfen:

user defined image

Es wird die Auswahl der verfügbaren Datenquellen und Datenbankfelder angezeigt. Im Hintergrund sieht man bereits die automatisch neu erstellte TBindingsList. Zu der komme ich gleich.

user defined image

Für die Verknüpfung mit den Datenbankfeldern wird auch automatisch eine TBindScopeDB Komponente erstellt. Auf die selbe Weise verknüpfe ich auch das Bild. Beim TBindNavigator genügt es die TBindScopeDB Komponente an BindScope zuzuweisen.

Nun verknüpfe ich noch den aktuellen Wert der ProgressBar sowie den maximalen Wert mit den Werten des ClientDataSets. Das geht nicht ohne ein wenig Handarbeit. Ich erstelle diesmal eine neue Datenbindung über eine TBindExpression, die ich manuell definieren kann:

user defined image
user defined image

Dort gibt es Control* und Source*, ersteres definiert das zu steuernde Control, letzteres das Control, von dem die Daten stammen. In diesem Fall wird die ProgressBar kontrolliert, und zwar dessen Wert Value bzw. im zweiten Fall Max. Die Daten kommen vom ClientDataSet. Nämlich aus RecNo bzw. RecordCount.

user defined image

Genauso binde ich auch noch Label2.Text an ClientDataSet1.RecNo. Soweit so gut. Wenn man dieses Programm ausführt, wird allerdings die ProgressBar nicht verändert. Warum? Nun, das liegt daran, dass die Datenbindung nicht weiß, dass die Eigenschaften RecNo und RecordCount einen neuen Wert bekommen haben. Daher werden die gebundenen Elemente nicht aktualisiert. Es reicht also mitzuteilen, dass diese Datenbindungen aktualisiert werden müssen. Dafür benutze ich das Event OnAfterScroll des ClientDataSets:

Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TForm114.ClientDataSet1AfterScroll(DataSet: TDataSet);
begin
  TBindings.Notify(ClientDataSet1, 'RecNo');
  TBindings.Notify(ClientDataSet1, 'RecordCount');
end;
Dafür wird die Unit System.Bindings.Helper benötigt. Das genügt bereits. Jetzt funktioniert die kleine Demo bereits. Und das mit zwei Zeilen Code. ;-)

user defined image

Wenn ihr die Möglichkeit habt, schaut doch auf der RAD Studio XE 2 World Tour vorbei um weitere spannende Details zu sehen. ;-)
http://www.embarcadero.com/world-tour


jaenicke - Mo 29.08.11 02:01

Jetzt zeige ich noch kurz, dass man auf diese Weise natürlich auch eigene Klassen ansteuern kann. Hier ein kleines Beispiel, das die aktuelle Recordnummer auch in die Formularüberschrift schreibt:

// EDIT:
Da das wohl noch nicht klar genug formuliert war:
Es geht hier nicht darum die Überschrift zu aktualisieren, das ist nur ein Beispiel. Mir ist halt als erstes das für ein abgeschlossenes kleines Beispiel eingefallen.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
type
  TUpdateFormCaption = class(TComponent)
  private
    FFormCaption: string;
    FForm: TForm;
    procedure SetFormCaption(const Value: string);
  public
    constructor Create(AForm: TForm);
    property FormCaption: string read FFormCaption write SetFormCaption;
  end;

  TForm114 = class(TForm)
  // ...
  public
    FTest: TUpdateFormCaption;
    FTestBindExpression1: TBindExpression;
  end;

var
  Form114: TForm114;

implementation

{$R *.fmx}

procedure TForm114.ClientDataSet1AfterScroll(DataSet: TDataSet);
begin
  TBindings.Notify(ClientDataSet1, 'RecNo');
  TBindings.Notify(ClientDataSet1, 'RecordCount');
end;

{ TMyLittleClass }

procedure TForm114.FormCreate(Sender: TObject);
begin
  FTest := TUpdateFormCaption.Create(Self);
  FTestBindExpression1 := TBindExpression.Create(Self);
  FTestBindExpression1.ControlComponent := FTest;
  FTestBindExpression1.ControlExpression := 'FormCaption';
  FTestBindExpression1.SourceComponent := ClientDataSet1;
  FTestBindExpression1.SourceExpression := 'RecNo';
  FTestBindExpression1.Active := True;
end;

{ TUpdateFormCaption }

constructor TUpdateFormCaption.Create(AForm: TForm);
begin
  inherited Create(AForm);
  FForm := AForm;
end;

procedure TUpdateFormCaption.SetFormCaption(const Value: string);
begin
  FFormCaption := Value;
  FForm.Caption := Value;
end;
Hier weise ich die Eigenschaften einfach nur manuell zu. Das ist also alles kein Hexenwerk. ;-)

Da die Benachrichtigung für die Bindings bereits existiert, muss da auch nichts weiter passieren. Die beiden Zeilen in OnAfterScroll genügen um die ProgressBar, das Label und dieses eigene Objekt zu aktualisieren.


jaenicke - Mo 29.08.11 02:12

Als letztes zeige ich noch wie die erzeugten Datenbindungen in der Übersicht aussehen:

[url=http://www.sj-berlin.de/service/df/xe2/XE2BindingsOverview.png]user defined image[/url]

Hier seht ihr auch wofür die Kategorien bei den einzelnen Bindings sind. Durch diese Bezeichnungen können die Bindings sortiert werden, damit man bei umfangreicheren GUIs die Übersicht behält.

Natürlich funktioniert das unter 64-Bit genauso:

user defined image

Es gibt auch die Möglichkeit in die Verarbeitung manuell einzugreifen. Dafür gibt es z.B. ein Event, wenn ein neuer Wert einer Expression zugewiesen wird. In dem Event ist z.B. auch die Änderung des Wertes möglich. Das habe ich mir aber selbst noch nicht genauer angeschaut.


Lemmy - Mo 29.08.11 06:41

Hi Sebastian,

vielen herzlichen Dank!!

Lemmy


mkinzler - Mo 29.08.11 18:29

Das funktioniert natürlich nicht nur bei FireMonkey-Anwendungen, sondern wurde auch in die VCL eingeführt.


Dude566 - Di 30.08.11 18:32

Sind die Fenster eigentlich nur bei Firemonkey Projekten schwarz oder welchen Hintergrund hat das? :lupe:


mkinzler - Di 30.08.11 18:36

Nein. sie sehen nur im Formulardesigner so aus, möglicherweise um sofort zu sehen, ob es sich um eine VCL oder FMX Anwendung handelt.


Dude566 - Di 30.08.11 18:39

Ja ich meinte ja auch nur im Designer, aber eben um Firemonkey und VCL zu unterscheiden.


Stevie - Di 30.08.11 21:53

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Das genügt bereits. Jetzt funktioniert die kleine Demo bereits. Und das mit zwei Zeilen Code. ;-)

Wo ist da jetzt der Fortschritt? Ging schon seit (seit wann gibt es die DB Controls und TClientDataset in Delphi?)...

Die 2 Zeilen Code bis Delphi XE sähen übrigens so aus:

Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TForm114.ClientDataSet1AfterScroll(DataSet: TDataSet);
begin
  ProgressBar1.Value := ClientDataSet1.RecNo;
  ProgressBar1.Max := ClientDataSet1.RecordCount;
end;


Übrigens mit dem Vorteil, dass mich der Compiler anmeckert, falls ich mich verschreibe.

Sieht für mich irgendwie wie ein Ersatz für die DB Controls in FMX aus.


jaenicke - Di 30.08.11 22:48

user profile iconStevie hat folgendes geschrieben Zum zitierten Posting springen:
Sieht für mich irgendwie wie ein Ersatz für die DB Controls in FMX aus.
Das ist es dort auch, ja. Die gibt es dort nicht.

Die Anbindung anderer Objekte als TDBxxx (z.B. eigene Controls) sehe ich aber in XE nicht ohne Quelltext. :gruebel:
(Auch wenn ich noch nicht weiß, ob ich die Datenanbindung tatsächlich dafür nutzen werde, das tue ich z.B. in C# auch nicht.)


Martok - Mi 31.08.11 00:12

So wie ich das sehe, ist das die offensichtliche Verwendung von RTTI, die ja jetzt ordentlich funktioniert.

Die Umsetzung des Ganzen in der IDE ist ziemlich gut gemacht; allerdings finde ich den Code dazu etwas sehr länglich. Da extra ein Objekt für zu konfigurieren finde ich etwas seltsam, aber die Helper-Funktion dazu kann man sich ja selber schreiben.


Stevie - Mi 31.08.11 00:22

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Die Anbindung anderer Objekte als TDBxxx (z.B. eigene Controls) sehe ich aber in XE nicht ohne Quelltext. :gruebel:

Hab ich auch nicht gesagt. Da es aber nunmal in der VCL die DB Controls gibt, seh ich keine Veranlassung, dort diese LiveBinding Geschichte zu benutzen (zumal ich irgendwo gelesen habe, dass es diese "Mit DB-Feld verknüpfen..." Sache nur unter FMX gibt, aber da kannst du sicher was zu sagen)

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
So wie ich das sehe, ist das die offensichtliche Verwendung von RTTI, die ja jetzt ordentlich funktioniert.

Öh, ja, trotzdem ist die RTTI nach wie vor string basiert, was das Finden eines Bezeichners angeht und wird erst, wie der Name schon sagt, erst zur Laufzeit ausgewertet. Was das an Fehlerpotenzial enthält, brauch ich wohl nicht zu erläutern. Funktioniert das eigentlich noch, wenn ich die RTTI ausschalte? Ich hätt Besseres erwartet.

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Die Umsetzung des Ganzen in der IDE ist ziemlich gut gemacht; allerdings finde ich den Code dazu etwas sehr länglich. Da extra ein Objekt für zu konfigurieren finde ich etwas seltsam, aber die Helper-Funktion dazu kann man sich ja selber schreiben.

Ich dachte mir nur: "WTF, wat nen Haufen Code, nur um ne Caption zu befüllen?!"


jaenicke - Mi 31.08.11 04:43

user profile iconStevie hat folgendes geschrieben Zum zitierten Posting springen:
Geschichte zu benutzen (zumal ich irgendwo gelesen habe, dass es diese "Mit DB-Feld verknüpfen..." Sache nur unter FMX gibt, aber da kannst du sicher was zu sagen)
Richtig, das ist FireMonkey only. Genau aus dem Grund, dass es in der VCL die DB-Komponenten gibt. Das ist dort auch nicht als Ersatz gedacht.

Ich schreibe wohl besser auch noch etwas was die Bindings in der VCL angeht.

user profile iconStevie hat folgendes geschrieben Zum zitierten Posting springen:
Ich dachte mir nur: "WTF, wat nen Haufen Code, nur um ne Caption zu befüllen?!"
Oh, sorry, ich dachte das wäre klar genug geschrieben, dass das nur ein Beispiel sein sollte um zu zeigen, dass auch eigene Objekte so ansteuerbar sind. :shock: Ich habe das noch einmal explizit dazu geschrieben.


Sinspin - Mi 31.08.11 13:12

Diese Technik ist aber nur für Komponenten gedacht die sonst nichts mit Datenbanken am Hut haben? Denn, wäre es anders wäre es ein Schritt in die Steinzeit.
Man muss sich ja nur mal den Irrsinn vorstellen wenn man eine komplexe Eingabemaske hat und für jedes Edit so einem Murks machen müsste.
Eins verstehe nicht, wenn eh bekannt ist das ein bestimmtes Property einem Tabellenfeld zugeordnet ist warum man dann noch manuell benachrichtigen muss das es geändert werden muss. Denn jedes DataSet und jede DataSource ist in der Laage alle interessenten über Änderungen zu informieren. Sogar jedes einzelne Tabellenfeld feuert Ereignisse wenn es sich ändert.

Wie es scheint gibt es immernoch keinen ordentlichen Aufbewahrungsort für nichtvisuelle Komponenten. Was bedeutet das die mir immernoch meine Formulare zumüllen. Gerade wenn ich mir überlege das fürs Livebinding jedesmal eine weitere nicht visuelle Komponente erstellt wird, wird das ganz schön schnell unübersichtlich.


jaenicke - Mi 31.08.11 13:55

user profile iconSinspin hat folgendes geschrieben Zum zitierten Posting springen:
Eins verstehe nicht, wenn eh bekannt ist das ein bestimmtes Property einem Tabellenfeld zugeordnet ist warum man dann noch manuell benachrichtigen muss das es geändert werden muss.
Das ist nicht nötig, ich wollte RecNo und RecordCount auslesen, nur da musste ich manuell benachrichtigen. ;-)


Stevie - Mi 31.08.11 13:57

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconSinspin hat folgendes geschrieben Zum zitierten Posting springen:
Eins verstehe nicht, wenn eh bekannt ist das ein bestimmtes Property einem Tabellenfeld zugeordnet ist warum man dann noch manuell benachrichtigen muss das es geändert werden muss.
Das ist nicht nötig, ich wollte RecNo und RecordCount auslesen, nur da musste ich manuell benachrichtigen. ;-)

Und genau das ist eins der Hauptprobleme mit der aktuellen Implementierung. Die Benachrichtigungen über Property Änderungen gehen nicht von dem Objekt selber aus. Somit ist immer extra Code in entsprechenden Events nötig, um dies abzugreifen.