Delphi RTLには非常に強力な式エンジン(Expressions Engine)が搭載されており、これはLive Bindingsアーキテクチャの基盤の1つですが、式を処理するための独立したエンジンとして使用することもできます。このブログでは、このトピックについて簡単に紹介しています。
DelphiのRTLには、多くの隠れた名作のライブラリがあります。その一つが「Expressions Engine」です。最近、長年Delphiを使っている開発者と会談しました。この開発者は類似した機能を探していましたが、実は何年も前からDelphi RTLに搭載されていることを知りませんでした。私はデモやドキュメントを書いたことがあるのは知っていたので、少し時間をかけて探してみたところ、いくつかのデモを見つけることができました。現在、このトピックは非常に複雑であり、完全に網羅することはできませんが、単純なシナリオでは、式を解析して処理するために必要なコードはごく僅かです。
本題に入る前に、新しいVCLのNumberBoxコンポーネント(Delphi 10.4.2で追加されたもので、例えば、https://blogs.embarcadero.com/ja/blogs-embarcadero-com-two-new-vcl-controls-coming-in-rad-studio-10-4-2-ja/)で公開されたことにも触れておきたいと思います。
このコンポーネントを利用すると、使用者が式を入力して、その結果の値に置き換えることができます。当然のことながら、既に用意されているExpressions Engineを使用し、シンプルなクラスメソッドを呼び出します。
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; |
このコードスニペットでは、LTextは式を含む文字列であり、Valueは浮動小数点の結果です。TBindings.CreateExpressionクラスのメソッドは、コードを簡略化するためのショートカットですが、詳細については、もう少し詳しく説明したいと思います。
Table of Contents
バインディング式の重要なコンセプト
上記のコードでは、このエンジンのコアクラスであるTBindingExpressionオブジェクトを作成しています。その名が示すように、これは純粋な式の式エバリュエータにとどまらず、統合のためにRTTIを使用して、式を外部オブジェクトに「バインド」または関連付けることができます。
バインディング式のもう一つの重要なコンセプトは、入力を完全に動的に評価するのではなく、テキストを処理する解析操作(コンパイルと呼ばれる)と最終処理を行う評価操作を必要とすることです。もちろん、式のテキストを変更しないのであれば、一度コンパイルして、関連するオブジェクトの値を変えて式を複数回評価できます。
バインディングのデモ
それでは実際にデモを見ていきましょう。
最初のデモでは、2つのMemoコントロールを持つフォームを作成しました。1つは式を入力する目的で、もう1つは出力を表示する目的のため使用します。ここでの目的は、数値ではなく文字列を処理することです。唯一のボタンのコードでは、以下のようにバインド式オブジェクトを直接使用しています。
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; |
以下の例は、文字列の事前定義された操作は連結のみであるため、それほど便利ではありませんが、
1 |
"Hello " + " world" |
と入力すると、Hello worldという結果が得られます。入力に二重引用符が使用されていることに注目してください。
オブジェクトのバインディング
さて、ここからが本題ですが、式をオブジェクトにバインドする場合です。そのために、以下のような簡単なクラスを作りました(ここでは、privateフィールドやメソッドを省略しています)。
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; |
ここで、式にバインディングを追加するためにコードを変更し、Compileメソッドにオブジェクトとシンボリック名の関連付けを追加できます(式エンジンのオブジェクトの名前のように考えることができます)。
1 2 |
pers := TPerson.Create; BindExpr.Compile([TBindingAssociation.Create(pers, 'person')]); |
この拡張機能により、このオブジェクトを次のような式で使用できるようになりました。例えば、コードでpersオブジェクトのNameに’John’を代入した後、以下の式では、
1 |
person.name + " is happy" |
John is happyという結果になります。あるいは、こんな式も使うことができます。
1 |
person.name + " is " + person.age + " years old." |
この式では、Integerからstringへの型変換を行っています。
メソッドのバインディング
バインディング式はオブジェクトに限定されますが、プロパティへのアクセスの他に、メソッドを実行することもできます。そのため、式の中で使用する複数のメソッドを持つクラスを作成することができます。
1 2 3 4 5 6 |
type TMyFunct = class public function Double (I: Integer): Integer; function ToStr (I: Integer): string; end; |
これらのメソッドを呼び出すには、以下のようにオブジェクトをバインドする必要があります。
1 2 3 |
BindExpr.Compile([ TBindingAssociation.Create(pers, 'person'), TBindingAssociation.Create(myFunct, 'fn')]); |
これで、以下のような式が書けるようになりました。
1 |
"At double age " + person.name + " will be " + fn.ToStr(fn.Double(person.age)) + " years old" |
上記の式を実行するとAt double age John will be 66 years oldという結果になります。
UIデモの実行結果
派手さはありませんが、以下はデモの実行結果です。
次回は
今回のブログでは、Expressions Engineの簡単な紹介に留めていますが、他にもバインディング式は、バインディングコントロールを可能にし、通知の登録も可能(一種のコールバックメカニズムとして)で、これらのいくつかデモを見つけましたので、近々ブログで紹介したいと思います。
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition