コルネの進捗や備忘録が記されたなにか

進捗や成果物や備忘録てきななにかを雑に更新していきます。

JPPGB #3 -ブロック崩しを作成する-

はじめに

この記事はJPPGB #3でのハンズオン資料です。

jppgb.connpass.com

ハンズオンではPower Appsでブロック崩しを作成します。

レコーディング動画

当日行われたハンズオンの模様はこちらにアップロードされています。

youtu.be

* 一部編集はしていますが、ほぼノーカットです。時間も長いので適宜倍速などで再生していただければ。

ブロック崩しゲームを作成する

初期化用ボタンを作成する

まず最初に変数やコレクションなんかの初期化を行うためのボタンを用意します。

画面内にボタンを追加して、"InitialBtn"と名前を変更してください。
わかりやすくボタンのテキストは「ini」とか「初期化」とかつけておくとわかりやすいですね。

この初期化用ボタンはScreen.OnVisibleで実行したいような処理を記載するためのもの。と思ってください。

このような初期化ボタンを作成するメリットとしては大きく2つあります。

  1. 開発中にScreen.OnVisibleを実行させるために一々画面遷移を行わずに済む
  2. 処理内で同様の初期化処理を行いたいときにSelect(InitialBtn)みたいな記載で済む

開発をちょっと楽にするテクニックみたいなものですね。

この"InitialBtn"はOnVisibleで実行させたいので、OnVisibleには以下の式を設定しておいてください。

Screen.OnVisible

Select(InitialBtn)

バーを作成する

こんな感じのプレイヤーが操作してボールを落とさないようにするためのバーをまずは作成します。

バーの要素は1つに纏めたいので、コンテナを用意してその中にスライダーと図形の四角形を追加してください。
コントロール名は画像のものと揃えておくと、後々記載する式をコピペで利用できるので揃えておくことをおすすめします。

バーの動作イメージとしては、プレイヤーはスライダーを操作することによってバーを動かせるような形にします。

バーの見た目はスライダーのレールみたいな丸いやつじゃなく、ちゃんと四角形のバーにしたいので、そのために図形の四角形を用います。

プレイヤーは(透明にする予定なので最終的にはみえなくなりますが)実際に触って操作することになるのはスライダーなので、必ずスライダーが四角形よりも上にくるように配置に気を付けてくださいね。

バーの位置を決める

バーをどこに配置するのかまずは設定します。

このバーはプレイ領域を画面いっぱいにするのであれば、
    可動域 = 画面幅
となりますね。

また、このバーは画面の下の方に設定することになるのでそれに合わせて、画面下のほうに配置されるよう式を設定します。
高さはバーに用いたい高さを適当に設定します。

上記を踏まえ式にすると以下のようになりますね。

BarCtn.X

0

BarCtn.Y

Parent.Height - Self.Height - 50

BarCtn.Width

Parent.Width

BarCtn.Height

20

コンテナ内に配置したコントロールたちもコンテナのサイズに合わせて調整してあげましょう。

Slider.X

0

Slider.Y

0

Slider.Width

Parent.Width

Slider.Height

Parent.Height

RectangleBar.X

0

RectangleBar.Y

Slider.Y

RectangleBar.Width

150

RectangleBar.Height

Slider.Height

四角形のX座標はあとで設定します。
幅は適当な幅を設定しておいてください。

スライダーの操作によってバーの位置が変化するように設定する

スライダーの値が、バー(四角形)のX座標の値となるように設定します。

スライダーの最大値は現在デフォルトの100に設定されているかと思いますが、画面幅の値を最大値にしたいと思います。
こうすることで
    スライダーの値 = スライダーのハンドルのX位置
となりますね。

ここでワンポイント。
スライダーの最大値は上記の文章通り、Parent.Widthでもいいのですが、これでは仕様変更によってスライダーの幅を変更したときに、修正箇所が増えてしまいます。
確かに、スライダーの最大値 = 画面幅に要件としてはしたいのですが、突き詰めるとスライダーの最大値はスライダーの右端のX座標としたいですし、最小値は左端のX座標となるように設定したいですよね。

これを考慮して式を設定してあげると、以下のようになりますね。

Slider.Max

Self.Width + Self.X

Slider.Min

Self.X

デフォルト値(既定)はゲーム開始時のバーの初期位置になります。
お好きな値を設定してあげてください。
例えば中心にしたいのであれば以下のようになりますね。

Slider.Default

(Self.Max - Self.Min) / 2

続いてスライダーの値を四角形のX座標に設定するのですが、このとき意識しておかなくてはならない点として、単純にスライダーの値をバーのX座標に設定してしまいますと、バーが中心からずれてしまうので、バーの中心がスライダーのハンドルの位置にくるように設定してあげる必要があります。

これを考慮してX座標を設定すると以下のようになるのですが、

RectangleBar.X

Slider.Value - Self.Width / 2

これだとこんな感じで画面端に来た時にバーが見切れちゃいます。

このずれを補正するために、スライダーの最大値や可動域を考慮して式を設定してあげる必要があります。

RectangleBar.X

Slider.Value / Slider.Max * (Parent.Width - Self.Width)

これによって、スライダーの値が最小もしくは最大にいくにしたがってX位置が補正されますね!

スライダーをみえなくする

スライダーはゲームをプレイする上でプレイヤーには表示させなくてもよいコントロールなので透明にします。
非表示ではなく透明ですね。非表示にしちゃうとスライダーが触れなくなっちゃいます。

透明にする箇所は以下です。

  • RailFill
  • ValueFill
  • HandleFill
  • BorderColor

透明にするには上記4か所の設定値を

RGBA(0, 0, 0, 0)

に変更します。

もしくはカラーパレットを開いて赤枠の箇所を選択します。

透明にするだけでなく、スライダーの値の表示も邪魔なのでこれも非表示にしちゃいましょう。

Slide.ShowValue

false

これでバーの完成です!

余談ですが、このバーを応用することで自作のスクロールバーを作成することもできちゃうのです。
興味があればまたみてみてください。

koruneko.hatenablog.com

ボールを表示させて画面端で跳ね返るようにする

続いてブロックを崩すためのボールの作成です。

ボールを用意する

ボールには図形の円を利用します。
名前はそのまま"Ball"にでもしておきます。

このボールは正円としたいので、幅と高さは揃えておきます。
サイズはお好きなサイズにしてください。

Ball.Width

Self.Height

Ball.Height

30

ボールのXやYの初期位置は初期化ボタンで宣言しましょう。

バーの右端あたりにくるように設定しようと思います。
1点注意するとすれば、バーはコンテナの中にあるので、コンテナの座標も加味しないと、画面上でのバーの位置は算出できないということですね。

InitialBtn.OnSelect

// ボールの座標の初期化
UpdateContext({ball_X: BarCtn.X + RectangleBar.X + RectangleBar.Width - Ball.Width});
UpdateContext({ball_Y: BarCtn.Y + RectangleBar.Y - Ball.Height});

ボールのX、Yにはこの初期化した変数を割り当てておきます。

Ball.X

ball_X

Ball.Y

ball_Y

ここまでの設定で今画面としては、こんな感じになっているかと思います。

ボールを動かす

ボールを動かすために先ほど初期化したball_Xball_Yの値をループ処理によって変化させます。
ループ処理はタイマーを使ってもいいのですが、ループ間隔をより短くしたい(ループを早くしたい)のでスライダーを利用してのループ処理を行います。

スライダーによるループ処理はこちらの記事を参考にしてください。

qiita.com

ループを行うためのスライダーと、ループ開始用のボタン、ループの設定をミスしたときに緊急停止するようのボタンを追加します。

次にスライダーでのループ処理を行うにあたり必要な変数を用意します。
初期化用ボタンに以下を追加します。

InitialBtn.OnSelect

// ループ設定の初期化
UpdateContext({loopVal: 0});
UpdateContext({isLoop: false});

スライダーを使ってのループは簡単に説明すると、スライダーのOnChangeにてスライダーの値に設定した変数を更新することで、スライダーの値が変更されるので、またスライダーのOnChangeが呼ばれて・・・といった感じで行うループ処理です。

ただこのままだと無限ループに陥ってしまうので、ループを止めるようの処理が必ず必要になってくるので気を付けましょう。
忘れるとアプリ落ちちゃいます。

上記を踏まえたうえで式を設定していくと、各プロパティは以下のようになります。

LoopControlBtn.OnSelect

UpdateContext({isLoop: !isLoop});
If(
    isLoop,
    Reset(LoopSlider)
)

LoopControlBtn.Text (* これは任意)

If(
    isLoop,
    "Stop",
    "Start"
)

GameStartBtn.OnSelect

UpdateContext({isLoop: true});
Reset(LoopSlider);

LoopSlider.Default

loopVal

LoopSlider.OnChange

If(
    isLoop,
    /*
        ここに処理を記載!
    */

    // ループ継続
    UpdateContext({loopVal: loopVal + 1});
)

ここまでの設定が完了しましたら、アプリの保存を行ってからループ開始用ボタンを押してループを開始してみてください。
上手く設定できていれば、スライダーの値がだんだん増えていくはずです。

ループを止める際は、ループ制御用のボタンを押してループを止めてあげます。
これでループが止まらなかった場合は、設定どこかミスしているので設定を見直してください。
* ループが止まらず、アプリが重くなってしまった場合はスライダーを消すかアプリを開きなおして下さい。

さて、これでループを行うための準備ができましたので早速ボールを動かしていきます。

といってもここまで設定できていれば難しいことはなく、ループ内でボールの座標に設定している変数を加算/減算していくだけです。

ここでどれだけ加算/減算するかによって、ボールのスピードは決まってきます。
初期化用ボタンに以下式を追加して、ボールの速度用変数を宣言しましょう。

InitialBtn.OnSelect

// ボールの速度の初期化
UpdateContext({ball_X_Speed: 3, ball_Y_Speed: 3});

ループ処理内でボールの速度に従って、ボールの座標を変化させます。

LoopSlider.OnChange

If(
    isLoop,
    // ボールを動かす
    UpdateContext({ball_X: ball_X + ball_X_Speed});
    UpdateContext({ball_Y: ball_Y - ball_Y_Speed});

    // ループ継続
    UpdateContext({loopVal: loopVal + 1});
)

これで、ボールを動かすことができました!

画面端にいったらボールが跳ね返るようにする

ボールを動かせるようにはできましたが、このままだとボールは画面を超えてもただひたすら右上方向に動いて行ってしまいます。

ボールは壁(画面端)にぶつかったときに跳ね返るようにしたいので、その処理をループ内で追加します。

反射の処理は簡単に実装します。
原理としては、左右の壁にぶつかった際はボールのX方向の速度の正負を反転させて、上の壁にぶつかった際はボールのY方向の速度の正負を反転させます。
下の壁にあたっときはゲームオーバーなので、ループを止める(looValの加算をしない)処理が必要ですね。

これを式にすると以下のようになりますね。

LoopSlider.OnChange

If(
    ball_Y < Parent.Height && isLoop,
    // ボールを動かす
    UpdateContext({ball_X: ball_X + ball_X_Speed});
    UpdateContext({ball_Y: ball_Y - ball_Y_Speed});

    // 壁に当たった際の処理
    If(
        ball_X + Ball.Width >= Parent.Width,
        UpdateContext({ball_X: Parent.Width - Ball.Width});
        UpdateContext({ball_X_Speed: ball_X_Speed * -1})
    );
    If(
        ball_X <= 0,
        UpdateContext({ball_X_Speed: ball_X_Speed * -1})
    );
    If(
        ball_Y <= 0,
        UpdateContext({ball_Y_Speed: ball_Y_Speed * -1})
    );

    // ループ継続
    UpdateContext({loopVal: loopVal + 1});
)

これでボールが壁にぶつかったら反射できるようになりましたー

ゲーム開始前にバーを動かすとボールもそれに追従して動いてくれるようにする

今の処理のままですと、ゲーム開始前にバーを動かすとボールが置き去りになってしまいます。

これをちゃんとバーを動かすことでボールが追従されるように設定していきます。

バーを動かすことでボールの座標を変えたいので、バー用のスライダーのOnChangeにボールの座標用変数を更新する処理を追加します。

ただここで素直に式を追加してしまうと、ループ中にボールを動かしている処理にも影響を及ぼしてしまいます。

なのでここでボールの座標を更新するのは、ループ処理が行われていないときだけに限定しましょう。

Slider.OnChange

If(
    !isLoop,
    UpdateContext({ball_X: BarCtn.X + RectangleBar.X + RectangleBar.Width - Ball.Width});
    UpdateContext({ball_Y: BarCtn.Y + RectangleBar.Y - Ball.Height});
)

さて、ここまで設定してもらいましたがこの処理のままでは動きが若干不自然化と思います。
バーを動かし終わったらボールの座標が変わるようになっていますね。

これはスライダーのOnChangeは、ユーザがスライダーを離したときに実行されるからですね。

よって、ボールの座標はループ前であればバーの座標を、ループ中であればボールの座標を設定した変数をそれぞれ利用するようにします。

Ball.X

If(isLoop, ball_X, BarCtn.X + RectangleBar.X + RectangleBar.Width - Ball.Width)

Ball.Y

If(isLoop, ball_Y, BarCtn.Y + RectangleBar.Y - Ball.Height)

これでボールの初期位置をバーを動かすことによって決めることができるようになりましたね。

ボールがバーにぶつかったら跳ね返るようにする

ボールとバーがぶつかったら跳ね返るようにするには、当然ではありますがボールとバーがぶつかったことを判定する必要があります。

ということで当たり判定を実装する必要があるのですが、これは私が以前作成して公開している当たり判定を行うカスタム関数(コンポーネント)を利用してください!

github.com

上記GitHubリポジトリに"solution"内にあるmsappファイルを自身の環境で開いて保存してください。
利用方法はREADME.mdで記載していますが、難しいことはありません。

当たり判定を行いたいそれぞれのオブジェクトの座標やサイズを関数に渡すだけです。
これで接触していればtrue接触していなければfalseを返します。

このカスタム関数では、

  • 四角形 × 四角形
  • 円 × 円
  • 四角形 × 円

の当たり判定をサポートしています。

こちらのカスタム関数の中身を知りたい方は、こちらで解説を行っていますので暇なときにみてみてください。

koruneko.hatenablog.com

コンポーネントを環境内に追加したら、アプリにコンポーネントを追加しましょう。

コンポーネントをインポートすると、ライブラリ コンポーネントという項目が追加されますので、ここから画面にコンポーネントを追加します。

このコンポーネントはユーザには見えなくていいので非表示にしておきましょう。

さて、この関数を利用するとボールとバーが当たったときにtrueを返すようにできるので、ループ内の条件処理に追加してもいいのですが、当たった瞬間に動作させたいとなるともっと適切なコントロールがあります。

そう!トグル(切り替え)ですね。

トグルのDefaultに関数にて当たり判定の関数を設定し、OnCheckにて当たったときの処理を記載します。
当たり判定の関数はトグルとセットで使うことが主になってくると思います。

トグル(切り替え)を追加して、名前を"BallBarCollision"に変更してください。

プロパティでは以下を設定します。

BallBarCollision.Default

CollisionUtils_1.SquareCircle(
    BarCtn.X + RectangleBar.X,
    BarCtn.Y + RectangleBar.Y,
    RectangleBar.Width,
    RectangleBar.Height,
    ball_X + Ball.Width / 2,
    ball_Y + Ball.Height / 2,
    Ball.Width / 2
)

BallBarCollision.OnCheck

UpdateContext({ball_Y: BarCtn.Y + RectangleBar.Y - Ball.Height});
UpdateContext({ball_Y_Speed: ball_Y_Speed * -1});

BallBarCollision.Visible

false

これで、ボールとバーがぶつかったときにボールが跳ね返るようになりました!

小休止

ここまで開発していく中で、再生(F5)でアプリの動作を確認してみたらボールのスピードが遅いのでスピードの設定値を変えよう。と思われるかもしれませんが、ちょっと待ってください。

一度アプリを公開して公開されたアプリでも動作を試してみてください。

恐らく、アプリ編集中の再生でアプリの動作を確認していたときとでボールの速度が違ってくると思います。

編集中のアプリの再生での動作と、公開されたアプリでの動作って微妙に内部処理がちがってくるんですかねー?

正確な情報はMSの開発部門に潜入でもしないとわからなさそうですね。

ブロックを用意する

ブロックの情報の定義

まずはブロックたちの座標やサイズが定義されたコレクションを用意しましょう。

初期化用ボタンにブロックの情報の初期化処理を追加します。

InitialBtn.OnSelect

// ブロックの生成
ClearCollect(
    block,
    With(
        {
            n: 10, m: 5
        },
        ForAll(
            Sequence(n * m),
            {
                x: (Parent.Width / 2 - (n / 2) * 110) + Mod(ThisRecord.Value, n) * 110,
                y: 100 + RoundDown((ThisRecord.Value - 1) / n, 0) * 30,
                width: 100,
                height: 20
            }
        )
    )
)

今回はブロックの情報を1つ1つ定義していくのがめんどくさかったので、n * mのブロックを等間隔で並べるようにしています。

nが横方向でmが縦方向ですね。

横方向はブロックを並べたときに画面の中心にくるようにし、縦方向は高さ100から順に下方向に対して並べています。

ブロックの描画

続いてブロックの描画ですが、これにはSVGを利用しようと思います。

ギャラリーを利用するのでもいいのですが、ギャラリーですと描画するオブジェクト(ブロック)の数が増えてくるとどうしてもアプリが重くなってしまいます。

なのでこういったオブジェクトを描画させたい際はSVGを利用することを私は推奨します。

SVGですとコントロールとしては画像コントロール1つで済みますしね。

SVGを描画するための画像コントロールを追加して、"BlockImg"とします。

この画像コントロールは画面いっぱいに表示させてあげてください。
ただそうしちゃうと、バーやボタンが操作できなくなっちゃうので、画像コントロールは最背面に配置しておいてください。

この画像コントロールSVGを描画していくわけですが、Power AppsでのSVGについて簡単に触れておきます。

Power AppsでSVGを描画する際は画像コントロールのImageプロパティにSVGをData URI形式で記載してあげます。

例えば四角形をSVGで描画したい場合は以下になりますね。

Image.Image

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'>
        <rect 
            x='100'
            y='100'
            width='100' 
            height='100'
        />
    </svg>"
)

詳しく知りたい方はこちらのドキュメントを読んでみるといいかもです。

developer.mozilla.org

ただし、Power Appsでは取り扱えない表現もあるのでその点はご注意ください。(多分これがダメとか明確に記載されたドキュメントはないはず)

さて、上記のSVGを用いて先ほど定義したコレクションの情報を用いて四角形を描画していこうと思います。
コレクションでは座標やサイズを定義していたので、これをもとに四角形描画の要素を作成していきます。

四角形の描画は以下の要素で行われています。

<rect 
    x='100'
    y='100'
    width='100' 
    height='100'
/>

これを組み立ててやればいいわけですね。

Power Appsでコレクション(テーブル)の情報を1レコードずつ処理する関数といえば、みんな大好きForAll関数があります。
ForAll関数だけではテキストを返すことができない(テーブル型が返ってくる)ので、Concat関数を使って、テーブルの値を文字列に変換してあげます。

BlockImg.Image

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'>" &
        Concat(
            ForAll(
                block,
                "<rect 
                    x='" & ThisRecord.x & "'
                    y='" & ThisRecord.y & "'
                    width='" & ThisRecord.width & "' 
                    height='" & ThisRecord.height & "'
                />"
            ),
            Value
        )
    & "</svg>"
)

これでブロックの描画ができました!
あとはブロックとボールの当たり判定の処理です。あと一息!

ブロックとボールの当たり判定と処理を実装する

当たり判定

ブロックとボールの当たり判定だけでいうと、バーとボールの当たり判定のやり方の応用ですね。

トグルを用意して、BallBlockCollisionにリネームしてください。

今回判定を行いたいブロックは複数あるので、それぞれに対して判定を行う必要があります。
そこで活躍するのがまたまたForAll関数です。

ForAll関数を用いて、各レコードに対して当たり判定を行います。
ボールがいずれかのブロックに当たったのならば、ForAllの結果のどれかがtrueになっているはずなので、CountIf関数でtrueがあるかどうかの判断をしてあげます。

BallBlockCollision.Default

CountIf(
    ForAll(
        block,
        CollisionUtils_1.SquareCircle(
            ThisRecord.x,
            ThisRecord.y,
            ThisRecord.width,
            ThisRecord.height,
            ball_X + Ball.Width / 2,
            ball_Y + Ball.Height / 2,
            Ball.Width / 2
        )
    ),
    Value = true
) > 0

これでボールとブロックのいずれかが当たったかの判定が行えます。

ボールとぶつかったブロックを消す

ボールとぶつかったブロックをどのブロックがぶつかったのか特定したうえでブロックの削除を行います。

ForAll関数では、ThisRecord演算子を利用することで対象のレコードを返すので、当たり判定の結果そのレコード内のブロックがtrue(ボールとブロックが当たっている)であれば対象のレコードを返すようにします。

これとRemove関数を組み合わせることで、ブロックの削除が行えますね。

BallBlockCollision.OnCheck

Remove(
    block,
    ForAll(
        block,
        If(
            CollisionUtils_1.SquareCircle(
                ThisRecord.x,
                ThisRecord.y,
                ThisRecord.width,
                ThisRecord.height,
                ball_X + Ball.Width / 2,
                ball_Y + Ball.Height / 2,
                Ball.Width / 2
            ),
            ThisRecord
        )
    )
)

ボールとブロックがぶつかったときに跳ね返るようにする

今のままでは、ボールとブロックがぶつかってもそのままボールが進み続けてしまうので、ボールとブロックがぶつかったときにボールが跳ね返るようにしたいと思います。

このとき注意したいのは、ボールがブロックの上下左右どこに当たったか?を意識する必要があるということです。

例えばボールがブロックの上に当たったときと、ブロックの左に当たったときとではボールが跳ね返る方向は異なりますよね?

なので、ボールがブロックの上下左右どこに当たったかでそれぞれ処理を分ける必要があるわけです。

上下左右どこにあたったかは?はブロックの上下左右に薄い四角形がそれぞれ張り付いていて、そいつとあたったかどうかで判断することが可能です。

気を付ける点としては、右と下の四角形は元の四角形のXまたはYがずれているのでその補正が必要という点ですかね。

BallBlockCollision.OnCheck

// Top
If(
    CountIf(
        ForAll(
            block,
            CollisionUtils_1.SquareCircle(
                ThisRecord.x,
                ThisRecord.y,
                ThisRecord.width,
                1,
                ball_X + Ball.Width / 2,
                ball_Y + Ball.Height / 2,
                Ball.Width / 2
            )
        ),
        Value = true
    ) > 0,
    UpdateContext({ball_Y_Speed: ball_Y_Speed * -1});
);

// Bottom
If(
    CountIf(
        ForAll(
            block,
            CollisionUtils_1.SquareCircle(
                ThisRecord.x,
                ThisRecord.y + ThisRecord.height - 1,
                ThisRecord.width,
                1,
                ball_X + Ball.Width / 2,
                ball_Y + Ball.Height / 2,
                Ball.Width / 2
            )
        ),
        Value = true
     ) > 0,
    UpdateContext({ball_Y_Speed: ball_Y_Speed * -1});
);

// Left
If(
    CountIf(
        ForAll(
            block,
            CollisionUtils_1.SquareCircle(
                ThisRecord.x,
                ThisRecord.y,
                1,
                ThisRecord.height,
                ball_X + Ball.Width / 2,
                ball_Y + Ball.Height / 2,
                Ball.Width / 2
            )
        ),
        Value = true
    ) > 0,
    UpdateContext({ball_X_Speed: ball_X_Speed * -1});
);

// Right
If(
    CountIf(
        ForAll(
            block,
            CollisionUtils_1.SquareCircle(
                ThisRecord.x + ThisRecord.width - 1,
                ThisRecord.y,
                1,
                ThisRecord.height,
                ball_X + Ball.Width / 2,
                ball_Y + Ball.Height / 2,
                Ball.Width / 2
            )
        ),
        Value = true
    ) > 0,
    UpdateContext({ball_X_Speed: ball_X_Speed * -1});
);

これでブロック崩しの完成です!
お疲れさまでした!!!

おわりに

これでブロック崩しの原型が作成できましたね。

これにさらにスコア表示や残基、ステージやアイテムの実装なんかするとよりゲーム性が増すと思います。

このハンズオン/ブログをきっかけにゲーム作成された方いらっしゃいましたら、是非SNSやブログ等で報告いただけると、嬉しいなーと思います。

【Microsoft Ignite 2022】Power Appsカードを試してみる

# はじめに Igniteで発表されましたPower Appsカードを皆さんはご存知でしょうか?

これはIgniteで発表されるちょっと前から画面上に表示されており、カードの作成も行うことができました。

カードの概要

前試したときは、Teamsに送ってもリンクを手動で送り付けて、ユーザはそれを開くだけ。とかいう存在意義がよくわからなかったものだったのですが、(多分)最近か今回の発表に合わせてTeams内のPower Appsアプリが更新されたので、カードをTeamsで表示できるようになりました。

Power Appsカードを作成してみる

「カードを作成する」からカードの名前を設定すると、カード編集画面が表示されます。

すると、キャンバスアプリの編集画面っぽい画面が表示されます。

現在利用できるコントロールは以下があるようですね。

データソースを追加できるけど、Dataverseしか利用できないっぽい(?)

初めからテキストラベルが2つ設定されていますが、邪魔なので消しちゃいましょう。
削除はAdaptive Cards Designerでのようなやり方で削除できるほか、Deleteボタンやツリービューのコントロール一覧から削除することができます。

コントロールは配置したいコントロールを選択するだけでカード内に順に配置されていきますね。

コントロールの順番を変えたいときは、Adaptive Cards Designerと同様にドラッグ & ドロップで変更が可能です。
また、こちらからも変更可能ですね。

配置したコントロールの設定は左側のプロパティペインから変更できる他、上の関数バーから変更することが可能です。
キャンバスアプリと同じですね。

赤枠のラベルには変数の値を設定したいと思います。

変数の利用方法はキャンバスアプリとちょっと異なるようです。
カードで変数を利用する際はまず、変数を作成してあげる必要があります。

numという変数を作成し、初期値0にしました。

ラベルに表示させたいのでラベルのTextに設定します。

設定後カードではこのように表示されます。

キャンバスのようにnumの数値が表示されてくれるわけじゃないようですね。

このnumの値をユーザ操作によって変えられるようにします。

ボタンを追加して、ボタンにはキャンバスアプリ同様OnSelectがあるので、ここにPower Fx式を記載します。

UpdateContextは利用できないようなのでSetを利用しました。

これでボタンが押されるたびにnumの値が1ずつ増えていってくれるはずですね。
「再生」を押して動作を確認してみましょう。

保存して再生を行います。

こんな感じの画面が表示されるので、動作確認します。

デバッグ情報をみると入力項目に入力された値や変数の値を確認することができます。

ただ、こちらボタンが押されたとき(というかアクション項目が実施されたとき?)に更新されるようです。
つまり、テキスト入力になにか入力したら即時デバッグ情報に表示されるわけではないということです。

動作に問題がなければ、「送信」を選択して表示されたURLをコピーします。

作成したカードをTeamsで送る

Microsoft Teams でカードを共有する (プレビュー)

作成したカードをTeamsで表示させるにはTeamsにPower Appsアプリが必要なようです。

画像のように検索してPower Appsを選択します。

チャットに送りたい場合は「チャットに追加」、チャネルに送りたい場合は「チームに追加」 でそれぞれ送信したいチャットやチャネルを選択します。

追加しましたら、追加先のチャットやチャネルで先ほど作成したカードのリンクを送ります。

ただ注意が必要な点として、メッセージ送信前の状態で以下のようにカードが表示されていない状態で送ってしまうと、送信後もカードが表示されませんのでご注意ください。

送られたメッセージはこんな感じで、カードの操作もできます。

ただ、ドキュメントのサンプルのようにリンクなしではメッセージ送れないんですよねーー
わかんない。。。

おわりに

こちらだとAdaptive Cardと違い、Power Fxが利用できる他、Dataverseとの接続もできるようなのでAdaptive Cardとは差別化できそうですね。

ただ、Adaptive Card程アクションやトリガーがないのが現時点での難点ですね。。。

【Microsoft Ignite 2022】Power Automateの新機能で遊んでみた

はじめに

日本時間2022/10/12(木)の25:00頃にMicrosoft Ignite 2022が開催されました。

その際に発表された、

  • 文章からのフローの作成
  • サンプルからの書式設定を行う関数の提案

が面白そうだったのでちょっと試してみました。

Power Platform関連でいうと、よしだたいきさんのブログの方が読みごたえがあるかと。

memo.tyoshida.me

発表情報

Power Automate

文章からフローを作成する

[参考文献]
Create a cloud flow from a description (preview)
Create a flow from a description

作成したいフローの文章を入力するだけで、AIがそのフローを構築してくれる機能が公開(プレビュー)されました。

ドキュメントをみるに、文章を入力すると最大3つまでを候補としてフローのサンプルを作成してくれるようなので、そこから自分が欲しいフローを選んで適宜修正する形っぽいですね。

多分Power AppsのIdeasを思い浮かべてもらえれば想像しやすいんじゃないかな?と思っています。

もちろん制限事項もあり、

  • サポートしている記載元の文章は英語のみ
  • 作成できるのはクラウドフローのみ
  • 説明文にパラメータを記載しても、AIが自動で入力してくれない場合がある
  • 文章をもとにフローを作成するAIは以下のアプリケーションの主な動作を自動化するように最適化されている

なお現時点では、日本テナントは対応していないようで遊べなかった。試せなかった。

なので、米国(プレビュー)環境作って試しました。

英語で文章を入力してあげると、こんな感じで提案してくれました。

フローの表示を選択して、提案されたフローの中身をみてみましょう。

これで問題ない感じしますけど、動きみてみたいので「これは希望するものとは異なります」を選んでみます。

なんかだめっぽい。
Copilotくんごめんね。

先ほど提案されたもので問題なさそうなので、提案に戻り、画面下にある「次へ」を選択します。

接続の確認が為されるので、問題がなければ「次へ」

フロー内のトリガーやアクションに利用するパラメータが聞かれますので、設定したいものを選んで「フローの作成」を選択します。

できたーーーーー
SUGEEEEEEEEEEEE

ただ、Teamsで投稿するメッセージも入力したんですけどこれ作成されてないですね。。。

これはおそらく制限事項のここに引っかかってる感じですかね。

In the current version, the AI might not fill in some parameters automatically, even if you provide them in the description.

これ説明文って私訳しましたけど、メッセージが正しいんですかね?

ともあれ、こんな感じで簡単にフロー作成できるのはいいですねー
複雑なものはどの程度できるのか、今後試したりアップデートを待ったりしたいですね。

さて私の考えになりますが、こちら利用すればある程度作りりたいものが決まっている初学者にはテンプレートから探すよりこちらを利用するようにした方が良いかもしれないですね。(ちゃんとした精度はまだわからないですが)

数が多いのでメンテできていないのは仕方ないですが、現在公開されているテンプレートってバージョンが古く非推奨になっているものを利用していたりするんですよね。

サンプルからデータのフォーマットを行う関数を生成する

[参考文献]
Format data by examples (preview)

元データのサンプルと、そのデータを変換したい形式のデータを記載してあげることで、その書式に変換するための関数を提案してくれる機能のようです。

これは日本テナントでも使えた!

変換後のデータも提案してくれるんですねー

試しに以下にフォーマットするための式を提案してもらいました。

結果以下のような式が提案されました!

formatDateTime(parseDateTime(split(body('現在の時刻'), '.')[0], 'ja-JP', 'yyyy''-''MM''-''dd''T''HH'':''mm'':''ss'), 'MMMM d, yyyy h:mm tt', 'en-US')

こんな複雑な式いるかな?(眠いので考えるのが嫌だ)

こちら、提案された式に間違いがないか?をテストできるようですが、ちょっと私のところではこの機能は使えなかった。残念。

ともあれ、提案された式で問題なければ「適用」を選択すれば関数が適用されます。

書式設定って案外フローの作成で詰まるところだと思いますので、この機能は結構ありがたいって人も多いんじゃないでしょうか?

こういった変換に慣れている人もこの機能使って生成された関数と自分で考えた関数を比較することで勉強になりそうですね。

おわりに

より市民開発者になるためのハードルが下がった感がありますねー

他にもPower Automateに関して発表が行われています。
New ways to innovate with AI and Microsoft Power Automate

もちろん他サービスも様々な発表がされています。
BOOK OF NEWS

たくさん発表されてますねーーー

他気になった点としてPower Pagesが一般公開されましたね。

Microsoft Power Pages is a low-code development and hosting platform ideal for building business-centric websites, both for low-code makers and professional developers. Power Pages, announced as a stand-alone product Microsoft Build 2022, is now generally available. Updates for Power Pages include:

価格も公開されていました。

Pricing - Power Apps

ドキュメントからもプレビュー外れていました。

learn.microsoft.com

情報追わないとー

そして眠いよー仕事がーーーー

Power Appsの置換機能で作業を少し楽にする

はじめに

Power Appsでアプリを作成しているとき、例えばヘッダーやメニューバーなど、異なる画面でも同じコントロールたちを利用したい場合ってありますよね。
そのような場合は、皆さん、コントロールたちを選択して各画面にコピペを行うと思います。

その際、Power Appsは異なる画面間でも同じ名前でのコントロール命名は許可していないので、コントロール名の末尾に「_[数字]」が付与されます。

このコピペ作業の後、各命名規則に従ってコントロール名を変更していくことになるわけですが、コントロール数が多いとそんな作業もめんどくさいですよね。。。

そんな時役立つのが置換機能です!

置換機能を利用する

さて早速本題です。

こんな感じの画面の個々の部分

このヘッダーとサブタイトルの箇所を他の画面にもコピペしようと思います。
これらは以下のコントロールたちで構成されています。

今回命名規則は「[画面名の略称][機能名][コントロール種別]」みたいな感じの命名規則にしています。

この画面は「HomeScreen」って名前にしているので「HS」が頭についている感じですね。

さてこれらの要素を別の画面(ListScreen)にコピペしてあげます。

するとこんな感じで、コントトール名の後に「_1」が付与されますね。

これをちまちま名称変更していってもいいのですが、めんどうなので置換機能で纏めて置換しちゃいます。

置換はここから行うことができます。

この置換機能ですが、正規表現がサポートされいていますのでオンにしてあげましょう。

今回でいうとコピペされたコントロール名たちから、

  1. 画面名の略称を変更
  2. _1の取り除き

を行いたいです。

置換対象のコントロールたちに一致している条件は「HS[各コントロールによって可変]_1」なので、これを正規表現で表すと、

HS.*_1

になります。

するとこのように置換対象のコントロールの一覧が表示されました。

ただこのままでは置換後の文字列で「[各コントロールによって可変]」の部分の設定に困ってしまいます。

そこで検索文字を

HS(.*)_1

に変更してあげます。

()で囲ってあげることで、その箇所を$1などとすることによって置換後の文字列にもそのまま利用することができます。

これはPower Appsの機能ではなく、正規表現で置換を行う際にサポートされているような機能ですので覚えておくと他のときにも役立てることが可能です。

さて、肝心の置き換え先の文字列は以下のように設定します。

LS$1

ここまでくるとあとは「すべて置換」を選択するだけのように思えますが、一つ落とし穴があります。

実際に「すべて置換」をして確認してみましょう。

コントロールは置換できていますが。。。

なんだか関数のとこでエラーが発生しているようです。

みてみると、関数はうまく置換できていないようですね。

どうやら、$1などでの置換機能はコントロールに対してのみ有効で、関数式への置換は現時点では行えないようです。

これは不具合なのかそういう仕様なのか。。。
ちょっと残念。

ただ今回の事例でいうと別に詰んだわけではないです。

Power Appsはコントロール名を変更すると、それを用いている式の箇所も自動で置き換わってくれますよね?
それがこの置換機能でも有効です。

よってこのようなことを行う場合は、コントロールに対してのみ行うようにしましょう。

一度行った置換は「Ctrl + Z」(戻る機能)で一括ですべて戻せます。

置換対象はここのチェックを付けたり外したりすることで制御可能です。

関数の箇所は置換対象から外したいので、チェックを外しましょう。

まぁ、検索する式を

HS(.*)_1$

に変更することで今回は対処可能なんですけどね。

そんな余談は置いておいて置換を行うと...

無事コントロール名が置換されて、そのコントロールを参照している式も置き換わってくれました!

おわりに

こんな感じで置換機能を活用することでPower Appsでのアプリ作成作業での命名も、もう少し楽に作業ができるようになります。

個人的にこういうちまちました作業は嫌いなので、こんな感じの機能どんどん追加してほしいですw

最後に、このアプリなんだ?と思われた方もいるかもしれませんが、これは #JPPC2022 で登壇するように作成しているアプリです。

powerplatformconf.connpass.com

私は、「Power Apps でテストをしてみる」というテーマで今回登壇させていただきます。

テストに関してなのでこのアプリはただテストを実演するために作っているアプリですw

もしかするとめんどくさくなってMSが公開しているサンプルのアプリで説明を行う方向にシフトするかもしれませんが、そのときは察してください。

当日はテストとモニター機能についてお話したいなー。と思っていますので、興味があり、お時間の都合つく方は是非ご参加くださいー

平日ですが、私のセッションはお昼時なので、お昼食べながらでも!

セッションの時間てきにデモをお見せできる時間あるかなー?どうかなー?というのが心配ですね。
時間なかったら後でYouTubeにでも補足的な感じで投稿するかもです。

他の方のセッションも非常に面白そうなものが多いのでお時間つく方は是非ご参加くださいね!

Switch Botのカスタムコネクタを作成する①

はじめに

調べてみたら私のブログでカスタムコネクタに関しての記事書いていなかったので備忘録も兼て纏めてみました。

今回作成するもの

今回作成するカスタムコネクタはSwitch Botのコネクタです。

Switch Bot便利ですよね。

私はエアコン、電気、テレビのオンオフをSwirch BotとAlexaを連動させて音声での操作をしています。
電源ぐらい自分でつければ?と最初は思われるかもしれませんが、使ってみると何かしながら声で操作をできるので結構便利ですよ。

またこの時期ですと、外からもうすぐ家につくな?というタイミングで冷房をつけたりと結構重宝しています。

さて、このSwitch Botですが連携可能な製品が色々出ていまして例えばこいつ。

これを使えば温度と湿度も取れます。
おうちの状態これで色々管理・分析できるようになりそうですね。
時代はIoTですよ!w

そしてこのSwitch BotですがAPIが公開されています!

github.com

英語ですが、そこは頑張って読み解くことにしましょう。

事前準備

Switch Botや各製品を購入したばかりで、まだセットアップを行っていない。
という場合はまずはセットアップから初めて、通常の操作で想定の動作が行えていることを確認してください。

先ほど紹介したSwitch BotGitHubの「Getting Started」が事前準備にあたります。

  1. Switch Botのアプリをスマートフォンにダウンロード
    SwitchBot - Google Play のアプリ
    ‎「SwitchBot」をApp Storeで
  2. Switch Botのアカウントを登録し、ログイン
  3. アプリ内でトークンを生成
    1. プロフィール > 設定
    2. アプリバージョンを10回タップして開発者向けオプションを表示
    3. 開発者向けオプションをタップ
    4. トークンを取得をタップ

ここで取得したTokenを用いてAPIを叩きます。

カスタムコネクタを作成する

APIのテストを行う

作成方法はいくつかあるのですが、この記事ではPostmanから作成する方法を紹介します。

理由はいきなりカスタムコネクタの作成から入ってしまうと、APIの投げ方が誤っているのか、それともカスタムコネクタの作成方法に誤りがあるのか?が判別できないからですね。

Postman持ってないよーって方はこちらからダウンロードしてください。
無料です。

www.postman.com

Postman自体の詳しい使い方はこの記事では省きます。
有名なソフトなので調べればたくさん紹介している記事がでてきますよー

バイスの一覧を取得する

We will stop adding support for new products on v1.0 as we release v1.1.

Hence, we strongly recommend all SwitchBot users to migrate to the new API version because we have improved the authentication method. This will make the communication between your server and the SwitchBot server more secure.

と記載があり、新しいAPIであるv1.1の利用を強く勧められているのでv1.1を今回は利用することにします。

なおv1.1を利用するにはそれなりの知識が必要になります。
結構苦戦しました(1敗)

ただv1.1で行った方が通信がセキュアに行われますし、v1.0では行えなかったSwitchBotのロック制御ができるようになるらしいので、できればv1.1を使った方がいいと思いますよ。

さて、v1.0とv1.1の認証方法の違いですが、v1.0はTokenだけで認証を行っていましたが、v1.1ではTokenとSecret Keyで認証を行うようになりました。

先ほど開発者モードでTokenを取得した際、2つのTokenが表示されていたかと思います。(されてなかった人はアプリのバージョンが古いのでアップデートをおすすめします。)

これによりセキュアな認証が行えるようになったのですが、ここが躓くポイントですかね。

ドキュメントにはご丁寧にサンプルが記載されています。

import time
import hashlib
import hmac
import base64

# open token
token = '' # copy and paste from the SwitchBot app V6.14 or later
# secret key
secret = '' # copy and paste from the SwitchBot app V6.14 or later
nonce = ''
t = int(round(time.time() * 1000))
string_to_sign = '{}{}{}'.format(token, t, nonce)

string_to_sign = bytes(string_to_sign, 'utf-8')
secret = bytes(secret, 'utf-8')

sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest())
print ('Authorization: {}'.format(token))
print ('t: {}'.format(t))
print ('sign: {}'.format(str(sign, 'utf-8')))
print ('nonce: {}'.format(nonce))

上記はPython 3系です。
Python 2系もサンプルがありますので、必要な方はドキュメントをご参照ください。

コードわからない方向けに解説すると、13桁のタイムスタンプを生成し、TokenとSecret Key、先ほどの13桁のタイムスタンプ、さらに改ざんチェック用の文字列をもとに署名を生成しています。

ここで生成された13桁のタイムスタンプと署名がv1.1では必須になってきます。

ここですね。

signtがv1.1だと必須だと書かれているかと思います。

書いてないけど、nonceも必要なんじゃないかな?

ということで、Headerに必要なのは以下5つの要素ですね。

以下コピペ用

Content-Type:application/json; charset=utf8
Authorization:{{Token}}
sign:{{sign}}  
t:{{t}}  
nonce:{{nonce}}  

Bulk Editで編集モードを変えてこれ貼り付ければOKです。

私の場合、Host DomainやらToken、Secret Keyは環境編集に入れています。

ここから新規環境を適当な名前で作成して、以下を入れてください。

signtは適当でいいですよ。
スクリプトで書き換えるので。

バイス一覧を取得するには、

GET /v1.1/devices

で行うらしいので、Host Domainの

https://api.switch-bot.com

と組み合わせて

GET {{url}}/v1.1/devices

と設定します。

さて、tsignを生成するためのスクリプトを書いていきます。

Postmanではスクリプトは「Pre-request Script」に書いていきます。

ただ、ここJavaScriptで書かないといけないので、Pythonのコードをちょっと改修しないといけません。

...

と思って一生懸命書いたのにさ!記事書いている今更新したらREADME更新されてJSのコードも載ってるじゃん!!時間返して!

まぁ悲しいので備忘録がてらここに私の作成した無様なJSも載せておきます。

String.prototype.format = function(){
    let formatted = this;
    for(let arg in arguments){
        formatted = formatted.replace("{" + arg + "}", arguments[arg]);
    }
    return formatted;
};

var token = pm.environment.get("Token");
var secret = pm.environment.get("SecretKey");
var nonce = pm.environment.get("nonce");
var t = Date.now();
var string_to_sign = '{0}{1}{2}'.format(token, t, nonce);

string_to_sign = encodeURIComponent(string_to_sign);

var sign = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(string_to_sign, secret));

pm.environment.set("t", t);
pm.environment.set("sign", sign)

サンプルがこちら

const token = "yourToken";
const secret = "yourSecret";
const t = Date.now();
const nonce = "requestID";
const data = token + t + nonce;
const signTerm = crypto.createHmac('sha256', secret)
    .update(Buffer.from(data, 'utf-8'))
    .digest();
const sign = signTerm.toString("base64");
console.log(sign);

const body = JSON.stringify({
    "command": "turnOn",
    "parameter": "default",
    "commandType": "command"
});
const deviceId = "MAC";
const options = {
    hostname: 'api.switch-bot.com',
    port: 443,
    path: `/v1.1/devices/${deviceId}/commands`,
    method: 'POST',
    headers: {
        "Authorization": token,
        "sign": sign,
        "nonce": nonce,
        "t": t,
        'Content-Type': 'application/json',
        'Content-Length': body.length,
    },
};

const req = https.request(options, res => {
    console.log(`statusCode: ${res.statusCode}`);
    res.on('data', d => {
        process.stdout.write(d);
    });
});

req.on('error', error => {
    console.error(error);
});

req.write(body);
req.end();

ただこれ動くのかな?
適当に書き換えてみたけど、Postmanでは動かなかったですね。

そこまで検証しようとは思えないので、追走するどなたかお任せします。

これを実行すると以下のような結果が得られるはずです。

{
    "statusCode": 100,
    "body": {
        "deviceList": [
            {
                "deviceId": "500291B269BE",
                "deviceName": "Living Room Humidifier",
                "deviceType": "Humidifier",
                "enableCloudService": true,
                "hubDeviceId": "000000000000"
            }
        ],
        "infraredRemoteList": [
            {
                "deviceId": "02-202008110034-13",
                "deviceName": "Living Room TV",
                "remoteType": "TV",
                "hubDeviceId": "FA7310762361"
            }
        ]
    },
    "message": "success"
}

上の結果はドキュメントにあったサンプルなので私のではないです。

ここで結果の違った方は、どこか設定間違えている可能性があるので見直してみてください。

バイスを操作する

バイスを操作するには以下APIを投げるようです。

POST /v1.1/devices/{deviceId}/commands

GETではなくPOSTなのでお待ちがいなく。

deviceIdには先ほどデバイスの一覧で取得したデバイスのIDをいれます。

操作するコマンドはBodyに記載します。

例えば電気を消すコマンドは

{
    "command": "turnOff",
    "parameter": "default",
    "commandType": "command"
}

こんな感じ。

コマンドは製品ごとに異なってくるので、詳しいコマンドはドキュメント確認してください。

JSONファイルのエクスポート

PostmanでのAPIのテストが一通り済んだら、作成したAPIたちをコレクションにいれ、エクスポートしてあげます。

これを用いてカスタムコネクタを作成します。

Postmanからカスタムコネクタを作成する

Dataverse > カスタムコネクタ より、カスタムコネクタの新規作成 > Postmanコレクションからインポートします
を選択します。

コネクタの名前はわかりやすい名前を設定し、インポートするファイルは先ほどエクスポートしたファイルを用います。

ここから色々設定していけばできるはずですが、コード書きなおさないといけないのでいったんここまで。

続きは別で。

おわりに

PostmanのScriptそのままカスタムコネクタにもってこれたら楽なんですけどね。

あと私事ですが、この記事を書いているときに大型の台風がきていまして非常に体が怠い。
低気圧に敗北しました。

なので、一旦ここまでで区切ることにしました。すみません。。。

これだとカスタムコネクタの説明じゃなくってSwitch Botのv1.1のAPIをPostmanで使ってみた説明ですね。。。

Power AppsのFormulasプロパティがもうすぐ一般公開されます!

はじめに

Power AppsでもうすぐFormulasプロパティが提供されますね!

docs.microsoft.com

一般提供が2022年9月となっているのでもうすぐだと思っています。
違ったらごめんなさい。

ちなみに調べてみたらいち早くHiroさんが記事にされていました。

mofumofupower.hatenablog.com

はやい!

英語でかかれているようなので私は日本語で纏めますね。

今(2022/09/12)利用するには?

この機能を利用するには、バージョンを最新の3.22091.11に変更する必要があります。

一度最新バージョンに更新してアプリ再読み込みしてもなぜかバージョン変わってくれないときがほとんどなので、読み込み後もう一度バージョン更新してあげてください。

最新バージョンに変更すると、設定 > 近日公開の機能 > 実験段階の「名前付き計算式」がオンになっているかと思います。

Formulasプロパティを利用する

FormulasプロパティはAppオブジェクトの中にあります。

まだ公式のdocsには記載されていないですね。

docs.microsoft.com

設定内で記載されている表記みるに、SetやUpdateContextなんかの変数やCollectやClearCollectなんかのコレクション変数ではなく、「名前付き計算式」という特別な表記を利用するっぽいですね。

で、Formulasプロパティ内での名前付き計算式の記載方法ですが、以下のように記載するっぽいですね。

[変数名] = [計算式];

プログラムでの変数の記載方法に似ていますねー

実際の記載はこんな感じ。

num = 10 + 20;

もちろん数値以外も指定可能です。

アプリ内で利用するときはこんな感じ。
変数の利用と同じような感じですね。

注意点

App.Formulasで宣言した変数をSet関数などで更新することはできません。

ただし、もちろんApp.Formulasで宣言した変数をグローバル変数などに格納することは可能です。

また、ここで宣言した変数は変数ビューからは確認できないようです。

App.Formulasで宣言した変数は更新できないっぽいので、ここで確認するまでもなく、App.Formulas内で確認しろってことかな?

おわりに

多分ですけど、ここのプロパティはApp.OnStartプロパティとApp.StartScreenプロパティの変更に関連して追加されたプロパティですかね。

docs.microsoft.com

StartScreenプロパティ内にあるこの記載に関連してかな?と

StartScreenに関しては、前に纏めたのでよかったらみてね

koruneko.hatenablog.com

App.Formulasプロパティは他から更新できないっぽい?のでプログラムでいうconstのように定数のような感覚で扱うのがよさそうですねー

Power AppsでJSONがパースできるようになりました!(Preview)

はじめに

Hiroさん(@mofumofu_dance)のこちらのツイートをきっかけにPower AppsでJSONのパースができるようになったことを知りました。

待っていました!!な機能です!!!!

個人的にもとても興味があった機能ですので早速試したのでそのときのメモをこのブログで纏めます。

前提条件

この機能はまだPreview機能なので2022/09/07(水)現在、日本の環境では利用できない機能でした。
もしこの機能をいち早く試したい場合は、「プレビュー(米国)」の環境を作成して、そこでお試しください。

勘違いしていました! Hiroさんからご指摘いただきました。

わざわざ新しい環境作成しなくともよいようです。

設定 > サポート > 作成バージョン

より、現在選択できる最新バージョンの「3.22091.8」を選択してください。

これで、アプリを読み込みなおせばPerseJSONが利用できます。

バージョン変更の詳細はこちら。

docs.microsoft.com

ParseJSONを試してみる

ParseJSON function and untyped objectsの有効化

PerseJSONを試す前にこの設定をオンにしてください。

設定 > 近日公開の機能 > 実験段階
から設定可能です。

ここはまだ日本語化されていませんね。

アプリで動作を試してみる

答え合わせがしやすいように

  1. Collection作成
  2. 1で作成されたCollectionをもとにJSONを作成
  3. 2.で作成されたJSONをGalleryに表示

という感じで試しました。

コレクションの作成はこんな感じ。

ClearCollect(
    col,
    {Name: "Bob", Age: 26, Gender: ""},
    {Name: "Lily", Age: 24, Gender: ""},
    {Name: "John", Age: 32, Gender: ""}
)

JSONの作成は以下。

Set(colJson, JSON(col, JSONFormat.IndentFour))

このJSONをラベルに表示させたのが、画面左下のJSONですね。

JSONはこんな感じ。

[
    {
        "Age": 26,
        "Gender": "",
        "Name": "Bob"
    },
    {
        "Age": 24,
        "Gender": "",
        "Name": "Lily"
    },
    {
        "Age": 32,
        "Gender": "",
        "Name": "John"
    }
]

さて、肝心のGalleryの設定です。

ParseJSON関数では返された結果のデータ型はUntypedObjectになるらしく自身で明示的に型指定する必要があるようです。

ParseJSON(colJson)で行った結果、このJSONですと、Table型となるはずなので、Table型に指定します。

Gallery.Items

Table(ParseJSON(colJson))

Gallery内にJSONの値である「Age」「Gender」「Name」を表示させていきたいと思います。

これまでのGallery内にItemsに設定したオブジェクトの値を表示させるやり方の感覚からいくと、ThisItem.Nameとなるような気がしますが、これは違います。

Table(ParseJSON(colJson))

の中身をみてみると、以下のようになっています。

Valueにそれぞれ値が含まれているっぽいですね。
また、これらの値も型をこちらで明示してあげる必要があるようです。

よって「Name」を表示したい場合は以下のようになります。

Text(ThisItem.Value.Name)

「Name」はテキスト型ですのでText関数を用います。

次に「Age」ですが、これは数値型なので

Value(ThisItem.Value.Age)

とします。

Text(ThisItem.Value.Age)

ですと、型が異なりエラーになるので気を付けてください。

「Gender」は「Name」と同様テキスト型なので

Text(ThisItem.Value.Gender)

となりますね。

この設定をする際の注意点ですが、ThisItem.Value.と入力してもインテリジェンス(入力候補)が効かないので自身で表示させたいJSONのキー値を入力する必要があります。

これはプレビュー外れたりしたらできるようになってくれないですかね?
できるようになると嬉しいですね。

JSONの中に配列があるパターン

さて先ほどまではJSONの基本形。

今度はJSONの中に配列があり、またデータによっては存在しないスキーマがある場合で試してみます。

検証するJSONはこんな感じ。

[
    {
        "Age": 26,
        "Gender": "",
        "Name": "Bob",
        "ShipTo": {
            "Name": "Keito",
            "address": "hogehoge",
            "Cost": 1000
        }
    },
    {
        "Age": 24,
        "Gender": "",
        "Name": "Lily"
    },
    {
        "Age": 32,
        "Gender": "",
        "Name": "John"
    }
]

ShipToというキーを1つ目のデータにだけ追加してあります。

これをGaleryの中にGalleryを配置して表示させます。

ShipToはオブジェクトではなく配列です。

なので先ほどGalleryのItemsに指定したようにTable関数で型指定するのではなく、レコード型だと明示します。

レコード型だと明示する関数はないので[]で囲ってあげます。

Gallery.Items

[ThisItem.Value.ShipTo]

あとはこの配列内の値を表示させるだけですが、ここは先ほどと同じやり方ですね。

「Name」ですと

Text(ThisItem.Value.Name)

みたいな感じで、型に合わせて型を明示的に宣言してあげます。

実際に設定した画面がこんな感じ。

Galleryの中のGalleryは見やすいように枠を付けています。

また、「Name: ~」みたいに表示されているのは、わかりやすくテキストを表示するように設定したからですね。

$"Name: {Text(ThisItem.Value.Name)}"

おわりに

待ちに待ったPower AppsでJSONをパースできる機能がキタ―――(゚∀゚)―――― !!

これでPower Apps内でJSONを扱えるようになりましたね。

もうこれでMatchAll関数とか使って文字を分割したり、Power Automateに一度データを渡してカンマ区切りとかにして無理やりJSONの値を取得するみたいなことをしなくてよくなります。

具体的な利用用途ですが、例えば現在だとPower AutomateからPower Appsにデータを渡すときにCollection形式でデータを渡すことはできないです。
しかし、JSON形式でデータを渡してあげることはできますので、Power Automate側でごにょごごにょしたデータをJSON形式でPower Appsに渡し、Power Apps側でパースして取り扱う。
みたいなことができるようになります。

他にもデータソース内にJSON形式のデータを入れる。というデータ設計の選択肢がなしではなくなる。
というのもでかいですね。

私が以前紹介したPower AppsでRedo & Undo機能を実装するやり方もこれ使えばもう少し簡単になりそうなので、改良版また書きたいですねー

koruneko.hatenablog.com


スポンサードリンク