Delphi RTL enthält eine sehr leistungsfähige Ausdrucks-Engine, die eine der Grundlagen der Live-Bindungs-Architektur ist, aber auch als separate Engine für die Verarbeitung von Ausdrücken verwendet werden kann. Dieser Blogbeitrag bietet einen schnellen Einstieg in das Thema.
In Delphi RTL gibt es viele versteckte Schätze. Eine davon ist die Expression-Engine. Vor kurzem hatte ich einige Gespräche mit einem langjährigen Delphi-Entwickler, der nach einem ähnlichen Feature suchte, ohne zu wissen, dass es seit vielen Jahren im Produkt enthalten ist. Ich wusste, dass ich einige Demos und Dokumentationen geschrieben hatte, also verbrachte ich ein wenig Zeit damit, sie zu suchen und fand ein paar. Nun, dieses Thema ist sehr komplex und ich kann es nicht vollständig behandeln, aber für einfache Szenarien benötigen Sie wirklich sehr wenig Code, um einen Ausdruck zu analysieren und zu verarbeiten.
Bevor ich mit dem eigentlichen Thema beginne, wollte ich auch erwähnen, dass wir diese Funktion kürzlich in der neuen VCL NumberBox-Komponente aufgetaucht haben (hinzugefügt in Delphi 10.4.2, siehe zum Beispiel https://blog.marcocantu.com/blog/2021-february -new-vcl-controls-1042.html ). Mit dieser Komponente können Endbenutzer einen Ausdruck eingeben und durch den resultierenden Wert ersetzen. Es überrascht nicht, dass die vorhandene Ausdrucksmaschine verwendet wurde, und zwar durch den Aufruf einer vereinfachten Klassenmethode von
1 2 3 4 5 6 7 8 9 |
var LExpression: TBindingExpression; begin LExpression := TBindings.CreateExpression([], LText); try Value := LExpression.Evaluate.GetValue.AsExtended; finally LExpression.Free; end; |
In diesem Code-Snippet ist LText ein String mit dem Ausdruck und Value ist das Gleitkomma-Ergebnis. Die Klassenmethode TBindings.CreateExpression ist eine Abkürzung, die den Code vereinfachen kann, aber ich möchte Sie lieber zu einigen weiteren Details führen.
Table of Contents
Schlüsselkonzepte von Bindungsausdrücken
Der obige Code erstellt ein TBindingExpression-Objekt, die Kernklasse dieser Engine. Wie der Name schon sagt, geht dies über einen reinen Ausdrucksauswerter hinaus, sondern kann den Ausdruck mithilfe von RTTI für die Integration an externe Objekte „binden“ oder assoziieren.
Ein weiteres Schlüsselkonzept von Bindungsausdrücken besteht darin, dass sie die Eingabe nicht vollständig dynamisch auswerten, sondern vielmehr eine Analyseoperation (genannt Compile) erfordern, die den Text verarbeitet, und eine Auswertungsoperation, die die endgültige Verarbeitung durchführt. Wenn Sie den Ausdruckstext nicht ändern, können Sie den Ausdruck natürlich einmal kompilieren und mehrmals mit unterschiedlichen Werten für die zugehörigen Objekte auswerten.
Kommen wir zu einer Demo
Für meine erste Demo habe ich ein Formular mit zwei Memo-Steuerelementen erstellt, eines zum Eingeben des Ausdrucks und das zweite zum Anzeigen der Ausgabe. Das Ziel hier ist es, Zeichenfolgen zu verarbeiten, keine numerischen Werte. Der Code für die einzige Schaltfläche verwendet die Bindungsausdrucksobjekte direkt wie folgt:
1 2 3 4 5 6 7 8 9 10 |
procedure TForm1.btnEvalClick(Sender: TObject); var bindExpr: TBindingExpression; begin bindExpr := TBindingExpressionDefault.Create; bindExpr.Source := MemoExpr.Lines.Text; BindExpr.Compile(); MemoOut.Lines.Add (BindExpr.Evaluate.GetValue.ToString); bindExpr.Free; end; |
Not that its much useful, as the only predefined operations for strings is concatenation, so you can type in the input:
1 |
"Hello " + " world" |
und als Ergebnis Hello world erhalten . Beachten Sie die doppelten Anführungszeichen in der Eingabe!
Binden eines Objekts
Interessant wird es, wenn Sie den Ausdruck an ein Objekt binden. Dazu habe ich eine einfache Klasse wie die folgende erstellt (hier habe ich die privaten Felder und Methoden weggelassen):
1 2 3 4 5 6 |
type TPerson = class public property Name: string read FName write SetName; property Age: Integer read FAge write SetAge; end; |
Jetzt kann ich den Code ändern, um dem Ausdruck eine Bindung hinzuzufügen, indem ich der Compile-Methode die Assoziation eines Objekts mit einem symbolischen Namen hinzufüge (Sie können ihn wie den Namen des Objekts für die Ausdrucks-Engine betrachten):
1 2 |
pers := TPerson.Create; BindExpr.Compile([TBindingAssociation.Create(pers, 'person')]); |
Mit dieser Erweiterung kann ich dieses Objekt nun in einem Ausdruck wie verwenden. Nachdem Sie beispielsweise dem Namen des pers- Objekts im Code ‚John‘ zugewiesen haben, lautet der Ausdruck:
1 |
person.name + " is happy" |
würde dazu führen, dass John glücklich ist . Ich kann aber auch den Ausdruck verwenden:
1 |
person.name + " is " + person.age + " years old." |
was die Typumwandlung von Integer in String durchführt.
Bindungsfunktionen
Bindungsausdrücke sind auf Objekte beschränkt, können aber neben dem Zugriff auf Eigenschaften auch Methoden ausführen. Daher können Sie eine Klasse mit mehreren Methoden erstellen, die in einem Ausdruck verwendet werden:
1 2 3 4 5 6 |
type TMyFunct = class public function Double (I: Integer): Integer; function ToStr (I: Integer): string; end; |
Um diese Methoden aufzurufen, müssen Sie ein Objekt dieses Typs binden:
1 2 3 |
BindExpr.Compile([ TBindingAssociation.Create(pers, 'person'), TBindingAssociation.Create(myFunct, 'fn')]); |
Jetzt können Sie Ausdrücke schreiben wie:
1 |
"At double age " + person.name + " will be " + fn.ToStr(fn.Double(person.age)) + " years old" |
Ergebnis: Im doppelten Alter wird John 66 Jahre alt
Die Demo-Benutzeroberfläche
Nicht so schick, aber das ist die Demo in Aktion:
Nur die Oberfläche berühren
Diese Einführung berührt nur die Oberfläche, da Bindungsausdrücke Bindungskontrollen ermöglichen und auch die Registrierung für Benachrichtigungen (eine Art Rückrufmechanismus) ermöglichen. Ich habe noch ein paar Demos gefunden, über die ich bald versuchen werde, darüber zu bloggen.
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition
Ich hatte gehofft, hier einen „ExpressionEvaluator“ zu finden.