MoreBeerMorePower

Power Platform中心だけど、ノーコード/ローコード系を書いてます。

WinForms で Power Fxをはじめてみる

ついに Power Fxのソースコードが公開されました!

powerapps.microsoft.com

自前のサービスにPower Fxを使ったユーザー自身によるカスタマイズを用意できますね!

そんなたいそうなことはできないので、WinFormsで本当に簡素なアプリを作ってみました。

今回はその時の始め方を書いていきます。 作業は Visual Studio 2019を使う前提です。

まずはサンプルアプリのダウンロード

Power Fxが公開されたのに伴い、C#で書かれたサンプルのコンソールアプリが公開されています。

github.com

ダウンロードしたらあとはVisual Studioで "ConsoleREPL.sln" を開いてビルド!と押すだけです。

実行するとコンソールが開かれてあとは馴染みのPower Appsの関数が使えます。

f:id:mofumofu_dance:20211105103937p:plain

関数はPower Appsで使えるものすべてではなく、Power Apps特有のデータ構造などには依存しないような部分に限定されているように見えます。

サンプルの中身を見ると、処理の大部分は以下のコードで実現されていることが分かります。

 // variable assignment: Set( <ident>, <expr> )
 if ((match = Regex.Match(expr, @"^\s*Set\(\s*(?<ident>\w+)\s*,\s*(?<expr>.*)\)\s*$")).Success)
 {
     var r = engine.Eval(match.Groups["expr"].Value);
     Console.WriteLine(match.Groups["ident"].Value + ": " + PrintResult(r));
     engine.UpdateVariable(match.Groups["ident"].Value, r);
 }

 // formula definition: <ident> = <formula>
 else if ((match = Regex.Match(expr, @"^\s*(?<ident>\w+)\s*=(?<formula>.*)$")).Success)
     engine.SetFormula(match.Groups["ident"].Value, match.Groups["formula"].Value, OnUpdate);

 // eval and print everything else, unless empty lines and single line comment (which do nothing)
 else if (!Regex.IsMatch(expr, @"^\s*//") && Regex.IsMatch(expr, @"\w"))
 {
     var result = engine.Eval(expr);

     if (result is Microsoft.PowerFx.Core.Public.Values.ErrorValue errorValue)
         throw new Exception("Error: " + errorValue.Errors[0].Message);
     else
         Console.WriteLine(PrintResult(result));
 }

これを見ると大体どういう入力を受け付けるのか/弾くのか読み取れますね。

WinForms で使ってみる

Power Fxを使い始めるには自分のアプリにパッケージを追加してあげる必要があります。 現在はまだnuget.orgに登録されておらず、仮置き場のようです。この仮置き場をパッケージマネージャのソースに追加して、PowerFx.CorePowerFx.Interpreter を使えるようにします。

新しいプロジェクトを作成したら、 プロジェクト > Nugetパッケージの管理 と進み

f:id:mofumofu_dance:20211105104756p:plain

パッケージソースの追加で以下のURLを追加してあげます。

https://pkgs.dev.azure.com/ConversationalAI/BotFramework/_packaging/SDK%40Local/nuget/v3/index.json

f:id:mofumofu_dance:20211105104932p:plain

この状態で、追加したパッケージソースにて "PowerFx" を検索、追加してあげれば準備は完了です。

f:id:mofumofu_dance:20211105105139p:plain

あとは適当に画面を作って、処理のほとんどをサンプルから持ってくれば完成です。

f:id:mofumofu_dance:20211105105305p:plain

躓いたところ:

PowerFxのエンジンを初期化するところで、どうすればいいんだーとなりましたが、 engine = new RecalcEngine(); としておけばいいようです。

サンプルだと "varTest = 123" みたいな入力をしたときも結果を返してくれているんですが、単純にコピーしただけではフォームのラベルに結果を表示できませんでした。engine.SetFormula(...)している部分。

ここは、 SetFormula(...) → Eval(...) としてあげることで結果の表示ができました。

結局、フォームのボタンには以下のようなコードを書いています。

string input = textBox1.Text;
var expr = input;


    Match match;

// variable assignment: Set( <ident>, <expr> )
if ((match = Regex.Match(expr, @"^\s*Set\(\s*(?<ident>\w+)\s*,\s*(?<expr>.*)\)\s*$")).Success)
{
    var r = engine.Eval(match.Groups["expr"].Value);
    Console.WriteLine(match.Groups["ident"].Value + ": " + PrintResult(r));
    engine.UpdateVariable(match.Groups["ident"].Value, r);
    label1.Text = match.Groups["ident"].Value + ": " + PrintResult(r);
}
// formula definition: <ident> = <formula>
else if ((match = Regex.Match(expr, @"^\s*(?<ident>\w+)\s*=(?<formula>.*)$")).Success)
{
    engine.SetFormula(match.Groups["ident"].Value, match.Groups["formula"].Value, OnUpdate);
    label1.Text = PrintResult(engine.Eval(match.Groups["ident"].Value));
}
// eval and print everything else, unless empty lines and single line comment (which do nothing)
else if (!Regex.IsMatch(expr, @"^\s*//") && Regex.IsMatch(expr, @"\w"))
{
    var result = engine.Eval(expr);

    if (result is Microsoft.PowerFx.Core.Public.Values.ErrorValue errorValue)
        throw new Exception("Error: " + errorValue.Errors[0].Message);
    else
        Console.WriteLine(PrintResult(result));
    label1.Text = PrintResult(result);
}

ということで、ソースコードが公開されてお手軽に、C#で作るアプリにPower Fxを追加できるようになったよという紹介でした。