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

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


スポンサードリンク

Power Automateで数値の切捨て / 切上げ / 四捨五入を行う

はじめに

Power AppsにはRound、RoundDown、RoundUp、および Trunc関数という切捨て、切上げ、四捨五入を行う関数があるのに、なんでPower Automateにはそれと同等の関数がないんですかね...?

docs.microsoft.com

不便じゃない?と思ったので切捨て、切上げ、四捨五入を行う式を作成してみました。

ものすごくだるかったですね。

インプット値

インプット値は、切捨て、切上げ、四捨五入をする数値と少数N桁まで表示させる。ということを表す数値です。

名前 内部名
triggerBody()['number']
小数点以下 triggerBody()['number_1']

内部名 = 数式内で扱うときの動的な値です。
数式をコピペする際に、適宜置き換えてください。

四捨五入

これが一番簡単ですね。
四捨五入というより正確にいうと丸めですが。

float(
    formatNumber(triggerBody()['number'], concat('F', triggerBody()['number_1']))
)

ただ、数値のフォーマットを行っているだけですね。

F[N]で少数N桁の数値に丸めます。

F3だと少数3桁の数値に丸められますね。

詳しくはHiroさんのブログを参照。

qiita.com

その他フォーマットはこちらを参照。

docs.microsoft.com

切捨て

切捨てを行うようなフォーマットはないので、ここからは自作していきます。
切捨ては、少数N桁まで。とした場合は「 . 」(小数点)のあるところからN桁までの数値を取得すればいいですね。

これを実現するためにslice関数を利用します。

ただ1点気を付けなくてはならないのが、
元となる数値の小数点以下の桁数 = 切捨てを行う小数点以下の桁数
となった場合、slice関数で取得できる値が空白になってしまうので、その場合の処理が必要です。

数式にすると、以下のようになります。

float(
    if(
        equals(
            length(string(triggerBody()['number'])), 
            add(
                indexOf(string(triggerBody()['number']), '.'),
                add(1, triggerBody()['number_1'])
            )
        ), 
        triggerBody()['number'],
        slice(
            string(triggerBody()['number']),
            0,
            mul(
                sub(
                    length(string(triggerBody()['number'])), 
                    add(
                        indexOf(string(triggerBody()['number']), '.'),
                        add(1, triggerBody()['number_1'])
                    )
                ), 
                -1
            )
        )
    )
)

切上げ

一番めんどくさいやつです。

まずは式だけ書いておきます。

float(
    if(
        equals(
            length(string(triggerBody()['number'])), 
            add(
                indexOf(string(triggerBody()['number']), '.'),
                add(1, triggerBody()['number_1'])
            )
        ), 
        triggerBody()['number'],
        if(
            empty(
                slice(
                    string(triggerBody()['number']),
                    mul(
                        sub(
                            length(string(triggerBody()['number'])), 
                            add(
                                indexOf(string(triggerBody()['number']), '.'),
                                sub(
                                    add(1, max(triggerBody()['number_1'], 1)),
                                    1
                                )
                            )
                        ), 
                        -1
                    ),
                    mul(
                        sub(
                            length(string(triggerBody()['number'])), 
                            add(
                                indexOf(string(triggerBody()['number']), '.'),
                                add(1, triggerBody()['number_1'])
                            )
                        ), 
                        -1
                    )
                )
            ),
            add(
                int(
                    slice(
                        string(triggerBody()['number']),
                        0,
                        mul(
                            sub(
                                length(string(triggerBody()['number'])), 
                                add(
                                    indexOf(string(triggerBody()['number']), '.'),
                                sub(
                                    add(1, triggerBody()['number_1']),
                                    1
                                )
                                )
                            ), 
                            -1
                        )
                    )
                ),
                1
            ),
            concat(
                slice(
                    string(triggerBody()['number']),
                    0,
                    mul(
                        sub(
                            length(string(triggerBody()['number'])), 
                            add(
                                indexOf(string(triggerBody()['number']), '.'),
                            sub(
                                add(1, triggerBody()['number_1']),
                                1
                            )
                            )
                        ), 
                        -1
                    )
                ),
                add(
                    int(
                        slice(
                            string(triggerBody()['number']),
                            mul(
                                sub(
                                    length(string(triggerBody()['number'])), 
                                    add(
                                        indexOf(string(triggerBody()['number']), '.'),
                                        sub(
                                            add(1, max(triggerBody()['number_1'], 1)),
                                            1
                                        )
                                    )
                                ), 
                                -1
                            ),
                            mul(
                                sub(
                                    length(string(triggerBody()['number'])), 
                                    add(
                                        indexOf(string(triggerBody()['number']), '.'),
                                        add(1, triggerBody()['number_1'])
                                    )
                                ), 
                                -1
                            )
                        )
                    ),
                    1
                )
            )
        )
    )
)

読み解く気もなくなるほど長いですねw

簡単に説明すると、まず最初のif関数の箇所は先ほどの切捨て同様
元となる数値の小数点以下の桁数 = 切捨てを行う小数点以下の桁数
となった場合の処理になります。

なので、1つ目にif関数の結果がfalseの場合の数式が切上げを行っている処理のメインになります。

切上げは以下のように実現しています。

少数N桁とした場合、少数N - 1桁までの値を取得してきます。
少数N - 1桁までの値は変わらないのでそのままslice関数で取得するのでいいですね。

切上げなので、少数N桁目の値を + 1してあげる必要があります。

よって、 「少数N - 1桁までの値」 と 「少数N桁目の値に + 1した値」を文字列として結合してあげて、それを数値に戻せばいいですね。
それをこの数式では行っています。

おわりに

なんか切上げの数式ごちゃごちゃやっちゃったけど、もうちょっと簡単にかけそうな気がしますね。

Power Automateでデータ型の判定を頑張って行ってみる

はじめに

わたるふ(@wataruf01)さんのこちらのツイートをみて面白そうだな。と思って頑張ってPower Automateでデータ型の判定を行ってみました。

フローの全体はこんな感じ。

一応サンプル値をいくつか実行してみて、多分動いているだろう。
ということで記事にしてみましたが、このフローが正しいかは正直保証しかねます。

ここおかしくないか?とかありましら、遠慮なく指摘してください。(指摘もらうために公開したまである。)

データ型の判定を行う方法

データ型の判定を行う方法として2種類のフローを考えてみました。

まず1つ目が、元の値とその値を型変換した値を比較して判断する方法
2つ目は、関数が成功したかで型判定する方法
です。

2つめはおいしみ(@ksgiksg )さんもリプで案を上げていましたね。

データ型の判定を行う

今回判定を行う型の一覧は以下です。

  • int型
  • float型
  • DateTime型
  • bool型
  • array型
  • JSON
  • XML
  • string型


2種のフローの共通部は以下になります。

  • 型判定対象の型はアレイ変数に追加する
  • 型判定対象の値は作成アクションにいれる

型判定の結果をarrayの変数にいれているのは、諸事情によるところも正直あるのですが、たとえば以下のような場合はJSON型かつArray型になりますね。

[
    {
        "name": "Bob",
        "age": 20
    },
    {
        "name": "Nancy",
        "age": 18
    }
]

なのでその値の型は配列変数に入れていることにしています。

判定する値を作成アクションに入れている理由は、変数やトリガーのインプットのように明示的にこちらで型宣言をしたくないからです。

これらの部分は2つとも同じなので説明とか省きますね。

元の値とその値を型変換した値を比較して判断する

この方法が私がぱっと思いついた方法ですね。

Tryスコープの中身はこのようになっています。

実行条件ですが、配列変数に追加するアクションが実行されるのはその型判定を行っている作成アクションが成功したときのみ実行しています。

型判定は直前の型判定、つまり配列変数への追加アクションが成功した場合か、スキップされた場合のみ実行しています。
配列変数への追加アクションがスキップされた場合 = 直前の型判定の結果、判定した型ではなかった場合
ですね。

処理をそのまま言葉にするとわかりにくいですが要約すると、型判定はint、string...それぞれ毎回行いますよ。ということです。
失敗したときやタイムアウトしたときに実行しないのは、その場合は不正な結果なのでエラーとして扱いたいからです。

そして型判定を行っている数式ですが、以下のようにしています。

equals(outputs('作成'), int(outputs('作成')))

単純ですね。
判定する値と、判定する値を判定する型に変換した値とで、値が同じかを判定しています。

なぜ同じか?を判定する必要があるかというと、たとえばbool関数って数値や'true'や'false'なんかの文字列を渡しても成功するんですね。

これは公式docsにも実行結果の例として記載されているので、気になる人はみてみてください。

この場合、型判定がエラーになっていないとはいえ、10などの数値をbool値として扱いたくないので、equals関数で値が同じか?を判定しています。

正直この型判定のところではequals関数なくてもいいんですけど、後続の配列変数に追加するアクションと数式を揃えたかったのでこのようにしています。

後からフローをみたときに処理をわかりやすくするためですね。

配列変数に追加するアクションの式は以下のようにしています。

if(
    equals(outputs('作成'), int(outputs('作成'))),
    'int',
    false
)

後の数式は、それぞれの型に変換して判定を行っているだけなので割愛します。
1点、JSONの判定だけが、json関数に渡す値はstringかxmlでないといけないので、元の値をstringに変換してからjson関数に入れています。

equals(outputs('作成'), json(string(outputs('作成'))))

このTryのスコープですが、必ずstringの判定を最後にしてください。

stringの型判定を行っている数式たちは、(多分)エラーにはならないはずです。

equals(outputs('作成'), string(outputs('作成')))
if(
    equals(outputs('作成'), string(outputs('作成'))),
    'string',
    false
)

string関数使ってのstring型への変換は多分ですがエラーにならないんじゃないかな?
もしここがエラーになってしまうと、スコープの最後の処理がエラーになってしまうことになるので、Tryスコープが(成功している処理があっても)エラー扱いになり、後のCatchスコープが問答無用で実行されちゃいます。

stringがエラーになる場合の回避策は次に紹介する方法で記載しています。

CatchスコープはTryスコープが成功しなかったときに実行したいので、以下としています。

中の処理としては単純で配列変数に'Error'を追加しているだけです。

FinallyスコープはCatchスコープが成功かスキップした場合のみ実行しています。

このスコープ内では、型がなんだったかを、作成アクションで表示するようにしています。

ただ、上で述べたように型判定する値が数値だった場合、bool型の判定が失敗せず、配列変数にfalseが追加されてしまっているので、余分なものを取り除いてから型の表示を行います。

これにはアレイのフィルター処理アクションが使えますね。

「項目」というのはitem関数を設定しているとこう表示されます。

数式でみてみるとこうなりますね。

@not(equals(item(), false))

この結果を用いて、作成アクションで値の型を表示させるわけですが、以下の場合はなにかしらエラーが起きているということなので、エラーを表す文言を表示させています。

  • 配列の最後がErrorの場合
  • 配列が空の場合
if(
    and(
        not(equals(last(body('アレイのフィルター処理')), 'Error')),
        not(empty(body('アレイのフィルター処理')))
    ),
    concat('データ型は: ', join(body('アレイのフィルター処理'), ', ')),
    concat(join(body('アレイのフィルター処理'), ''), ' 型判定に失敗しました。')
)

エラーの場合は別にjoin関数いらないんですけど、これでここだけみればCatchが実行されたのか飛ばされたのかが把握できるので入れています。
デバッグ用ですね。

上で共通部はここ!って書きましたが、CatchとFinallyはもう1つのフローでも同じ処理をしています。
上で書かなかった理由はいきなりここの部分だけ書かれてもわからないな。と思ったそれだけの理由です。

この方法のだめな点は、例えば 10 と入力すると、
「データ型は: int, float, json
と返ってくる点です。

正しくは
「データ型は: int」 と返ってきて欲しいですね。

また、DateTime型の判定もDateTimeの定義を年、月、日、時間が含まれている。と定義するならうまくいってないんですよね。

この判定では

yyyy-MM-ddTHH:mm:ss.fffffffK

というタイムフォーマットでないと、DateTime型として認められません。

関数が成功したかで型判定する

初めに書いておくと、この方法はあまりお勧めしません。(私のやり方がよくないだけかもですが)

実行条件の構成は先ほどと同じで、異なるのは設定している式だけです。

この方法では例えばint型の判定ですと、int型をさないと関数の結果としてエラーを返すような関数を用いて、判定を行う方法です。

まずint型の判定ですが、addDays関数を用いています。
もちろん、パラメータとしてint型の値を設定すべき他の関数を用いてもOKです。

int型の判定

addDays(utcNow(), outputs('作成'))

配列変数に追加アクションでは、
このアクションが実行される = 型判定の結果、判定した型であった
ということなのでそのままその型の値をいれています。

float型、json型、xml型はそれぞれの値のみを受け入れるパラメータをもった関数が見つからなかったので、今回対象外にしています。
(これもこの方法がダメな点ですね)
json関数はxmlをパラメータとして受け取りますが、string型も受け入れるので完全な型判定が行えないので、対象外にしています。

他型の判定式を以下列挙していきます。

DateTime型

ticks(outputs('作成'))

bool型

not(outputs('作成'))

array型

join(outputs('作成'), '')

string型

length(outputs('作成'))

このままではstring型でない関数がやってきたとき、stringの判定(最後の判定)でエラーになってしまい、Tryスコープ全体としてエラー扱いになるので、回避策を行う必要があります。

Tryの中でエラーと判定したいときは、配列変数になにも入っていないときですね。
なのでstring型判定が失敗したら、配列変数の中身を確認して、空であればなにもしない(エラーのまま)。値が入っていればなにかアクションを実行する。
ということにします。
(まぁこの方法配列に入っていたのがfalseだけだったらダメなんですけどね!!!)

条件アクションを追加して、実行条件の構成をstring型の配列変数に追加アクションがスキップされたときに設定します。

条件には、配列変数が空でなかった場合。となるようにします。

左辺が

empty(variables('DataType'))

で、右辺が

true

ですね。

はいの場合、つまり配列変数に値があった場合に実行するアクションはなんでもいいのですが、とりあえず失敗しなさそうな、現在の時刻アクションでも入れておきます。

さて、このやり方のダメなところはDateTimeの判定ですね。
DateTime型は(私の理解だと)

yyyy-MM-ddTHH:mm:ss

が基本フォーマットになるはずで、少なくとも

yyyy/MM/dd

はDateTime型ではないはずです。
これはTimeStampを含文字列、string型に分類されるかな。

ただ、Power Automateの関数はそもそもパラメータとしてDateTime型のみを受け入れるものはなく、すべてTimeStampを含む文字列を受け入れる仕様なので、このやり方ではDateTime型ではなくTimeStampを含む文字列の判定しか行えません。

Power Automate側がどう定義しているかちょっとわからないですが(そもそもDateTimeを型として取り扱ってないんじゃないかな)、時間ありをDateTime型、時間なしをDate型と簡単に定義するとしてもこの方法はだめですね。

また、このやり方のダメな点もう1つはjson型やarray型もstring型として判断されてしまう点ですね。

[1, 2, 3] とかを入れて試してみてください。

コピペ用

元の値とその値を型変換した値を比較して判断する

変数を初期化する(DataType)

作成

Try 型判定

Catch 型判定

Finally 型判定

関数が成功したかで型判定する

変数を初期化する(DataType)

作成

Try 型判定

Catch 型判定

Finally 型判定

コピペのやり方

以下記事の「なにこのコードどうやって使うの」でやり方紹介していますので、参考にしてください。

koruneko.hatenablog.com

完成形

両者のやり方にそれぞれ問題点があったので改善してみました。

DateTime型は以下と定義しました。
(ここらへんの定義はよく、DBとかで使われている理解で、それ製品やバージョンによって定義違ったはずだから、公式でこれだよ。っていってくれてないと正直わからんのですよね)

yyyy-MM-ddTHH:mm:ss

もしくはミリ秒があってもOKとします。

yyyy-MM-ddTHH:mm:ss.fffffffK

Date型は

yyyy-MM-dd

単なる日付フォーマットはTimeStampを含む文字列(便宜上TimeStamp型)にします。

新たに判定する型が増えたので、それぞれ判定するアクションを追加します。

まず今まで話してきた日付型たちからいきましょうか。

ちなみに実行条件の構成は変更なしです。

まずはDateTime型ですが、上記のフォーマットであるか?を判断できればよいので、日付部分と時間の部分で分けて判断します。
分けるために、間にある'T'を利用したいと思います。

式にすると以下のようになります。

and(
    equals(
        slice(
            outputs('作成'),
            0,
            mul(sub(length(outputs('作成')), indexOf(outputs('作成'), 'T')), -1)
        ),
        formatDateTime(outputs('作成'), 'yyyy-MM-dd')
    ),
    or(
        equals(
            slice(
                outputs('作成'),
                add(indexOf(outputs('作成'), 'T'), 1)
            ),
            formatDateTime(outputs('作成'), 'HH:mm:ss')
        ),
        equals(
            slice(
                outputs('作成'),
                add(indexOf(outputs('作成'), 'T'), 1)
            ),
            formatDateTime(outputs('作成'), 'HH:mm:ss.fffffffK')
        )    
    )
)

ちょい複雑ですが、上が日付部分の判定、下が時間の判定です。
時間ですが、ミリ秒含むか?も判断必要なので、or関数で秒数含む場合と含まない場合の判断を行っています。

配列変数に追加するアクションは以下のようにしています。

if(
    outputs('型判定(DateTime)'),
    'DateTime',
    false
)

作成アクションの型判定でtrue or falseの判断までしてるんだから、この判定方法でよかったですね。

Date型は以下方法で判断します。

equals(
    outputs('作成'),
    formatDateTime(outputs('作成'), 'yyyy-MM-dd')
)

これは簡単ですね。
もとの値と、フォーマット後の値が一緒か?を判断しているだけです。

配列変数の追加はDateTimeと同じなので割愛。

最後にTimeStamp型ですがこちらは、エラーで判定していたころのDateTime型の判定をTimeStamp型の判定と定義を変えただけです。

残りはfloat型の判定とjson型の判定ですね。

まずfloat型です。

float型は、

  • float関数を実行してもエラーにならない
  • 整数ではなく浮動少数点であればよい

という条件を満たしていればいいですね。

1つ目の条件はただ実行すればいいだけなのでいいとして、問題は2つ目の条件ですね。

値に少数を含むか?は
元の値 - 元の値から小数点以下切り捨てたもの > 0
であればよいです。(作成アクションで 10.00 のように入力しても 10 になるので .00 などは考えないです)

ただし、Power Automateには切り捨てを行う関数は残念ながら存在しないです。
丸めならあるんですけどね。

丸めのやり方はHiroさんがQiitaで纏めてくれている記事があったので、そちらを参照してください。

qiita.com

さて、では切り捨てですが、これはDateTime型判断のときに日付を切り出したとき同様、slice関数使って切り出してあげればいいです。

数式にするとこんな感じですね

greater(
    sub(
        float(outputs('作成')), 
        int(
            slice(
                string(outputs('作成')),
                0,
                mul(
                    sub(
                        length(string(outputs('作成'))), 
                        indexOf(string(outputs('作成')), '.')
                    ), 
                    -1
                )
            )
        )
    ),
    0
)

最後にjson型です。

こいつは、判定条件に最初が「 { 」か最後が「 [ 」であること。
という条件をプラスするだけでOKですね。

厳密な判定をいうと、最後も同じ閉じかっこである必要がありますが、そうでなかった場合はjson関数での変換で引っかかるので今回はそこまでやらなくてOKです。

and(
    or(
        equals(first(string(outputs('作成'))), '{'),
        equals(first(string(outputs('作成'))), '[')
    ),
    equals(outputs('作成'), json(string(outputs('作成'))))
)

これで(多分)型判定がちゃんとできるようになりました。

最後にこの完成後のフローも置いておきます。
変更したのはTryスコープだけなので、Tryスコープだけ記載します。

コピペ用

Try 型判定

さいごに

疲れました。

ブログに残しとこ。と思ったら予想以上に時間かかりました。。。

眠い + 書きながら修正 + 眠い
みたいな状態でかいたので日本語おかしかったり、前後の文おかしいとこあるかもです。。。

そこは指摘してもらえると助かります。

動かなかったりもっといい方法あれば教えてくださいー

Power Appsのコンポーネントでカスタムラベルコントロールを作成してみる

はじめに

Power Appsのコンポーネントを利用して縁取りや影を設定できるようなカスタムラベルコントロールを作成してみたので紹介します。

完成イメージはこんな感じです。

カスタムラベルコントロールを作成する

コンポーネントを作成する

コンポーネントの作成方法の概要については、以前私がM365VM2022で登壇したセッションをご覧ください。

今日から始めるPower Appsでのコンポーネント開発 コルネ - Korune - YouTube

www.docswell.com

デザインをカスタムできる文字列を作成する

今回デザインをカスタムするためにSVGを利用します。

SVGについてはこの記事ではあまり詳しくは触れませんが、利用する要素の参考ドキュメントだけ残しておきます。

Textの基本要素
developer.mozilla.org

Textの縦方向の要素
developer.mozilla.org

Textの影の要素
developer.mozilla.org

上記要素を利用して組み立てたSVGがこちらになります(唐突)

data:image/svg+xml,
<svg viewBox='0 0 640 640' xmlns='http://www.w3.org/2000/svg'>
    <defs>
        <filter id='shadow'>
            <feDropShadow 
                dx='10' 
                dy='5' 
                stdDeviation='2'
            />
        </filter>
    </defs>

    <text 
        x='50%' 
        y='50%' 
        text-anchor='middle' 
        dominant-baseline='middle' 
        font-family='Algerian'
        font-size='80'
        stroke-width='3' 
        stroke='#d42314' 
        fill='#18eee0ff'
        filter='url(#shadow)'
    >Sample Text</text>
</svg>

他にもSVG利用すればもっとリッチなテキストを作成することが出来はするのですが、めんどくさいキリがないので、このくらいにします。

こちらをコンポーネント利用時にカスタマイズできるようにパラメータを外だししていこうと思います。

カスタムプロパティを作成する

必要なパラメータは以下になりますね。

  • テキスト
  • フォントのサイズ
  • 文字のフォント
  • 横方向の位置
  • 縦方向の位置
  • 文字の外枠の色
  • 文字の外枠の太さ
  • 文字の色
  • ぼかしのX要素
  • ぼかしのY要素
  • ぼかしの標準偏差

これらのパラメータを作成してきます。
コンポーネント利用者がカスタムできるパラメータを作成したいので、プロパティの型はすべて入力になりますね。

設定値は以下になります。

表示名 名前 説明 プロパティの型 データ型
Text Text テキスト 入力 テキスト
FontSize FontSize フォントサイズ 入力 数値
Font Font フォント 入力 テキスト
Align Align テキストのアライメント 入力 テキスト
VirticalAlign VirticalAlign 垂直方向の配置 入力 テキスト
StrokeFill StrokeFill 縁取りの色(16進数) 入力 テキスト
StrokeWidth StrokeWidth 縁取りの太さ 入力 数値
FontFill FontFill 文字の塗りつぶし色(16進数) 入力 テキスト
ShadowX ShadowX ぼかしのX要素 入力 数値
ShadowY ShadowY ぼかしのY要素 入力 数値
ShadowstdDeviation ShadowstdDeviation ぼかしの標準偏差 入力 数値

「値が変更されたときに OnReset を実行する」はすべてチェックしています。

* カスタムパラメータの作成方法は冒頭に紹介した資料や動画をご確認ください。

次にこのカスタムパラメータを先ほど紹介したSVGに設定していきます。

SVGにカスタムパラメータを設定する

Power AppsでSVGを表示するためには画像コントロールが必要です。

画像コントロールImageSVGの定義を記載します。
ただし記載方法は

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 640 640' xmlns='http://www.w3.org/2000/svg'>
...
    </svg>"
)

のようになります。
EncodeUrl関数が必要な点が注意ですね。

また、配置した画像コントロールコンポーネントのサイズによって変わるように

Image.X

0

Image.Y

0

Image.Width

Parent.Width

Image.Height

Parent.Height

コンポーネントのサイズによって可変になるように設定しましょう。

次にSVGの描画領域(viewBox)も画像コントロールのサイズによって可変になるようにしましょう。

これは

<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'>

のように描画領域を画像のサイズにあわせてあげればよいです。
これはコンポーネント化するとき以外でもPower AppsでSVGを利用するときの基本テクニックなので覚えておきましょう!

さて、Power AppsでSVGを利用するための準備ができましたので、先ほどのSVGにカスタムプロパティを組み込んだ式を設定します。

完成形は以下です。

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'>
        <defs>
            <filter id='shadow'>
                <feDropShadow 
                    dx='"& Parent.ShadowX &"' 
                    dy='"& Parent.ShadowY &"' 
                    stdDeviation='"& Parent.ShadowstdDeviation &"'
                />
            </filter>
        </defs>

        <text 
            x='"&
                Switch(
                    Parent.Align,
                    Align.Left, "0%",
                    Align.Center, "50%",
                    Align.Right, "100%",
                    "50%"
                )
            &"' 
            y='"&
                Switch(
                    Parent.VerticalAlign,
                    VerticalAlign.Top, "0%",
                    VerticalAlign.Middle, "50%",
                    VerticalAlign.Bottom, "100%",
                    "0%"
                )
            &"' 
            text-anchor='"&
                Switch(
                    Parent.Align,
                    Align.Left, "start",
                    Align.Center, "middle",
                    Align.Right, "end",
                    "middle"
                )
            &"' 
            dominant-baseline='"&
                Switch(
                    Parent.VerticalAlign,
                    VerticalAlign.Top, "hanging",
                    VerticalAlign.Middle, "middle",
                    VerticalAlign.Bottom, "auto",
                    "middle"
                )
            &"' 
            font-family='"& Parent.Font &"'
            font-size='"& Parent.FontSize &"'
            stroke-width='"& Parent.StrokeWidth &"' 
            stroke='"& Parent.StrokeFill &"' 
            fill='"& Parent.FontFill &"'
            filter='url(#shadow)'
        >"&
            Parent.Text
        &"</text>
    </svg>"
)

垂直および水平要素はPower Appsで、定義済みパラメータがありますので、利用者はこちらをもとに設定してもらうことにします。(そのほうが混乱がないはず)

ちなみにパラメータは AlignVerticalAlign です。
ドキュメントはこちら

docs.microsoft.com

ただ、その値をSVGでそのまま利用することはできないので、Switch関数で適当な値に変換しています。

色についてもPower Appsの定義済みパラメータを利用したかったのですが、あれは16進数で返してくれないので。。。

キャンバスアプリで使ってみる

冒頭でも画像を載せましたが、Power Appsでこのコンポーネントを読み込むと、以下のような感じでパラメータを弄ることができます。

フォントはPower Appsの定義済みパラメータのプロパティを利用するのでもよいのですが、このように定義済み以外のフォントを設定することもできます。

これは元から存在しているラベルコントロールのフォントでも同じことができますので、知らなかった人は是非試してみてくださいね。

おわりに

だいぶ説明雑ですが以上でカスタムラベルコントロールの作成は以上で終了です。

SVGの説明どこまですればいいのかわからないや。

Power AppsのコンポーネントライブラリがGAされました!

はじめに

Power AppsでのコンポーネントライブラリがようやくGAされました!!!🎉

日本時間でいう2022/06/02 2:47に以下でアナウンスが行われました。

powerapps.microsoft.com

何が言いたいかというとくそ眠い。
でも(コンポーネントを布教?してきた私にとって)割と重要で嬉しいニュースなのですぐブログにしたかった。

検証とかできてないけどごめんね。

ちなみに06/02 3:30頃現在、私の環境ではまだプレビューの文字が外れていませんでした。

記事中のGIFでもここのプレビュー外れてないので、外れるのはまだ先ですかねー。

GAとともに発表された機能

ALMのサポート

以前M365VMにて私は、キャンバスアプリで読み込んだコンポーネントを他テナントに持っていった場合、コンポーネントとアプリ両方をいかなる方法で移行しても、アプリ側が移行先のコンポーネントの変更を認識してくれない。認識させるには再読み込み or コード書き換えしか方法がない。と、資料でいうとこのページあたりで記載していましたが、それがどうやらできるようになったらしいです。

やり方は、コンポーネントとアプリをソリューションとして移行すればいいようです。

手順てきにはとくに難しいことなくできそうですね。

上記のGIFのほか、私の資料でもソリューションでの移行方法自体は触れていますのでよかったらみてみてくださいね。

これにより、コンポーネント利用をより勧められるようになったかと思います!!

コンポーネントライブラリ内にメディアの追加ができるようになりました

コンポーネントライブラリに画像などマルチメディアを含ませることができるようになったそうです。
(今までできなかったっけ?覚えてない。。。)

試しにやってみたら、確かにできました。

記事内ではこんな感じで紹介されてますね。

このメディアも標準的なALMに含まれるようです。

カスタムページでの使用

作成したコンポーネントを、カスタムページを使用したモデル駆動型アプリで使用できるようになるそうです。

コンポーネントライブラリは、最新のFluent UIコントロールを含む、カスタムページがサポートするコントロールセットを利用して、コンポーネントライブラリを作成できるようです。
これらのコンポーネントは、カスタムページ及びアプリで使えるとのこと。

また、Dataverseを利用して一元的に更新 / パッケージ化 / 移動が可能なようです。

詳細は以下を参照とのこと。(記事の更新追いついているのかな????)

docs.microsoft.com

さいごに

コンポーネントはパブリックプレビューの時から、多くの組織で採用がみられすでに何十万ものアプリで利用されており、その結果からのフィードバックがGAに結び付いた。と感謝の言葉が綴られていました。

原文

Fundamentals investments – Performance, reliability and accessibility. Canvas components have seen huge adoption across large and small organizations since our public preview announcement. Components already are being utilized heavily by hundreds of thousands of apps. We have been constantly monitoring user feedback, feature usage, errors and have been shipping weekly updates to improve feature fundamentals. Thanks everyone for helping us take feature to GA.

Looking forward to next phase of low code extensibility.

Cheers,
Hemant Gaur

まだまだ、完璧な機能ではない or 私の環境にはまだGAされていないだけかもしれませんが、一部まだGAされていないような機能もあるようです。
例えば、「拡張コンポーネントのプロパティ」ですね。

この機能の詳細は私のセッション資料見てもらうとして、この機能も便利なものなので早くGAされてほしいですね。

ところで私がいくつか観測していた不具合?は直ったのでしょうか。
また見ておかねば。

最近の活動まとめ

はじめに

あまりこういう記事は書かないのですが、最近の活動をちょっと自分で振り返る & 宣伝も兼て棚卸してみようと思います。

Microsoft 365 Virtual Marathon 2022

Microsoft 365 Virtual Marathon 2022(#M365VM)にて日本トラックで登壇させていただきました!

www.m365virtualmarathon.com

世界各国でMicorosft系のすごい人たちが登壇するすごくすごいイベントです。(語彙力消失)

私は「今日から始めるPower Appsでのコンポーネント開発」というタイトルで、Power Appsのコンポーネントについてお話させていただきました。

セッション動画はこちらにて随時アップロードされていく予定です。

www.youtube.com

私のセッションの動画はまだ公開されていませんが、日本トラックのセッションが既にいくつか公開されていますので、順次公開されると思います。

日本トラックの資料はこちらにてアップされています。

connpass.com

どれも素晴らしい内容ですので、お時間のある時に動画と合わせてこちらの資料を確認すると非常に勉強になるかと思います!

ちなみに私の資料はこちらです。

www.docswell.com

当日できなかったDEMO動画もこちらにてアップしておりますので、良ければご視聴ください。

www.youtube.com

業務改善検討会

もう5月になっちゃいましたが、2022年初の業務改善検討会を開催しました。

今回は、「ビジネスコミュニケーション -チーム内コミュニケーション-」というテーマで、とても豪勢な4名の方々にお話しいただきました!

普段あまりこういったお話って聞く機会ないですし、私自身も非常にためになった勉強会でした。
業務を回していく上では、やはり技術だけ向上させるのではなく、コミュニケーション能力や上手な立ち回りを学んでいく必要がありますね。

また、今回から業務改善検討会のOPを作成しました!!

www.youtube.com

自作です!
まだまだ荒も多いですが、、、
とりあえず頑張って作ってみました!

あと最近しれっとトップ画像変えましたw

前のものと比べると少しは良くなりましたかね?

JPPGB

Japan Power Platform Game Builders 通称 JPPGB のグループを作成しました。(いまさら)

jppgb.connpass.com

多分しらない方の方が多いと思いますが、このグループのFacebookグループはそこそこ前からすでに作成されていたんですよね。。。

ただ作ったはいいもののまったく活動できていませんでした。申し訳ないです。

それを今回満を持してイベント開催しようと思い、グループ作成に踏み切ったわけです。

このグループの趣旨としては、「Power Platformでゲーム作成しようぜ!」という簡単なものになっています。

緩くやっていこうと思いますので、皆さまどうぞご参加くださいー

そして、第1回イベントですが6/4(土)に開催予定です!

jppgb.connpass.com

今回のイベントは、主催者 / 登壇者ともに私だけなので(録画した動画をアップし直すのもめんどくさいので)私のチャンネルでやります。

youtu.be

次回以降の開催は他の方にもご登壇いただきたいので、Teamsとかでの開催になりますかねー

YouTube

1億年と2000年ぶりに動画投稿再開しました!

最近アップしたのは「るぅの一口講座」という音声合成ソフト(VOICEVOX)を利用した、自作3Dモデル「夢乃るぅ」ちゃんによる解説動画シリーズです。

www.youtube.com

もしよかったら見てみてください!

また、これを機に私の3Dモデルも更新しました!

アイコンの見た目に近づけて作成しました!
可愛いですよね?

左が私、コルネ
右が夢乃るぅちゃんです

また頑張っていこうと思いますので、応援よろしくお願いしますー

www.youtube.com

【Power Apps】コンボボックスで選択されたユーザーのEmailをテキスト入力に設定する

はじめに

私のマシュマロにて以下のような質問をいただきました。

今回はこちらの回答をしていきます。

質問のようなアプリを作成してみる

リスト作成

SPOリストには[タスク]、[ユーザーとグループ]、[メールアドレス]・・・の項目があるようなので、わかっている[タスク]、[ユーザーとグループ]、[メールアドレス]の3つのフィールドを作成します。

文章内での説明から、

列名 列の種類
タスク 1行テキスト
ユーザーとグループ ユーザーまたはグループ
メールアドレス 1行テキスト

で作成していきます。
タスクはデフォルト列であるタイトル列を利用することにします。

アプリを作成する

アプリは作成したリストから自動生成しました。

メールアドレス欄にユーザーとグループで選択したメールアドレスを設定する

質問の回答です。

質問の記載内容より、「ユーザーとグループ」で選択したユーザー(グループ)のメールアドレスを「メールアドレス」に設定すればいいと思いますので、それを設定していきます。

質問者さんの利用されたUser()関数は現在のアプリ利用者のユーザー情報を取得するための関数ですので、今回の用途には適さないですね。

Office365Usersコネクタでも行いたいことはできますけど、ユーザーとグループ列をItemsに設定してあるコンボボックスで選択されたユーザーを参照することで、その選択されたユーザーの情報を取得できますのでそれで取得しましょう。

「メールアドレス」のテキスト入力欄を変更していくわけですが、まずはプロパティの変更がブロックされているため解除します。

「ユーザーとグループ」で選択したユーザーのメールアドレスを「メールアドレス」のテキスト入力のDefaultに設定します。

「ユーザーとグループ」はDataCardValue5という名前のコントロールなので、これを参照します。

Power Appsのコンボボックスのプロパティをみてみますと、コンボボックスで選択されたアイテムはSelectedItemsで取得できることがわかります。

ただ取得できるのはテーブルですので、以下のように記載してしまうとエラーになります。

テキスト入力の箇所にはテキスト型が設定されるべきなのに、テーブル型が設定されているというエラーですね。

これを回避するために

First(DataCardValue5.SelectedItems).Email

のように、テーブルの最初のレコードを取り出して、それのEmailをテキスト入力に設定する。
でも解決できますが、この方法ですと、例えばユーザーとグループで複数の選択がなされたときに最初の1ユーザー(グループ)の情報しか表示できなくなってしまいます。

多分質問者さんの内容てきにこれでも問題なさそうな気がしますが、上記問題から今後機能拡充なのでユーザー列が複数選択できるようになった場合に困ってしまうと思いますので別の解決策も出しときます。

テキスト入力欄にはテキスト型を設定すればいいわけですので、テーブルの特定のフィールドの値を結合させてテキスト型にしてあげようと思います。

これを実現するためにConcat関数を利用します。

Concat(DataCardValue5.SelectedItems, Email & ";") 

これにより、「ユーザーとグループ」で選択したユーザーのメールアドレスが「;」で結合されて表示されました。

もし、1ユーザーしか選択されていなかった場合や、複数のユーザーを選択した場合でも最後の「;」は表示したくないよ。という場合は以下のようにして最後の文字を消してあげます。

With(
    {txt:Concat(DataCardValue5.SelectedItems, Email & ";") },
    Left(txt, Len(txt) - 1)
)

これで、最後の一文字(今回でいうと区切り文字の「;」)を除去できます。

おまけ

ユーザーとグループでチェックをしたら自身が自動で設定されるようにする

こんな感じのチェックボックスを追加しておいて、このチェックボックスがチェックされたら自身をコンボボックスに設定するようにしたいと思います。

コンボボックスのデフォルト選択項目の設定箇所は、DefaultSelectedItemsなのでここに設定していきます。

次にSPOリストのユーザー列ですが、

  • Claims
  • Department
  • DisplayName
  • Email
  • JobTitle
  • Picture

の6つで構成されています。
省いてもいい項目もあるのですが、今回そこは説明しません。

チェックボックスが選択されていれば、現在のユーザーを設定し、そうでなければリストに設定されていたユーザーを表示するようにします。

If(
    !Checkbox1.Value,
    Parent.Default,
    {
        Claims: "i:0#.f|membership|" & Lower(User().Email),
        Department: Office365ユーザー.MyProfileV2().department,
        DisplayName: User().FullName,
        Email: User().Email,
        JobTitle: Office365ユーザー.MyProfileV2().jobTitle,
        Picture: User().Image
    }
)

これにより、一々自分自身を検索しなくてもよくなったのでユーザーの操作性があがりますね。

おわりに

細かいところまで詳しく説明しようとして、ちょっと無駄なとこ多々あったかもです。

簡単にまとめると、「コンボボックスの値参照すれば欲しい値とれるよ」になります。

記載内容で不明点などありましたら追加で質問お願いします!

特になにも考えずマシュマロで質問募集してますけど、他のエンジニア系の方々は質問箱使ってますよね。
そちらの方が、私のブログとか読んでくれている人向けには回答しやすいんでしょうか?
誰か教えてー

marshmallow-qa.com

Power Automateを利用してSharePointリストのテナント間移行を行う

はじめに

SharePointリストのテナント間移行を行いたい人なんて少数かもしれませんが、この記事では以下のやり方も纏めてあります。

  • Power Automateを用いて特定のSPOリストからフィールド情報を取得する
  • Power Automateを用いてリストを作成する
  • Power Automateを用いて特定のリストにフィールドを追加する
  • Power Automateのアクションをコピペする(おまけ)

PnPとか使ってスクリプト書く方法もありますが、テナントによっては管理者に許可されていない場合もありますからね。

ざっくり構成

こんな感じで

  1. Tenant A(移行元)でリストのフィールド情報をPower Automateを利用して取得
  2. Tenant B(移行先)のPower Automateに 1. で取得した情報を設定
  3. Tenant Bにリストを作成

を行います。

手順2は手動ですね。

フローを作成する

リストの情報を取得する

フローの全体イメージはこんな感じです。

こちらは移行元のテナント(Tenant A)で作成して実行します。

アクションを順にみていきましょう。

リストが存在するサイトのアドレスでは取得したいリストが存在するアドレスのリンクを設定します。

リスト名では取得したいリストの表示名を設定します。

SharePoint に HTTP 要求を送信しますで上記変数で設定した情報をもとにリストのフィールドを取得します。
今回の設定では、デフォルトフィールドは取得しないように省いています。
ユーザが作成したフィールドだけが欲しいですからね。

もしすべてのフィールドを取得したければ、filterを外してください。

このアクションの出力結果として、以下のようなbodyが取得できます。

欲しいのはd.resultsなので、結果を加工して作成に格納します。
この結果は配列で取得されるのでarray関数を利用しています。

array(body('SharePoint_に_HTTP_要求を送信します')?['d']?['results'])

これで対象リストのフィールド情報が取得できました。

「作成」で得られた出力はこの後使いますので控えておいてください。

リストを作成してフィールドを作成する

フローの全体イメージはこんな感じです。

こちらは移行先のテナント(Tenant B)で作成して実行します。

まずはリスト作成に必要な情報だけみていきましょう。

リストを作成するサイトのアドレスにはリストを作成したいサイトのアドレスを設定します。

リスト名には作成したいリストの内部名(表示名にも利用します)を設定します。

残り二つの変数は、フィールド作成の時に利用するものですので、後述します。

SharePoint に HTTP 要求を送信します(リストの作成)で先ほど設定した2つの変数をもとにリストを作成します。

説明を変えたい場合は、"Description"を変更してください。

リストを作成している箇所は以上です。

続いて先ほど作成したリストに先ほど取得したフィールド情報をもとにフィールドを作成していきます。

ListFieldObjに先ほど移行元で取得したフィールド情報をコピペします。

booleanは条件分岐のtrue or falseの判定に利用したいだけですので、trueとだけ設定しておきます。

続いてリストのフィールド作成を実行していくわけですが、フィールド作成は1フィールドずつしか行えません(多分。知らんけど。)

試しにArray情報を渡してみたらObject情報を渡せ。とのエラーで怒られました。

よって先ほど設定したArray変数からObject単位で情報を取得してアクションを実行していこうと思います。

これを実現するためにApply to eachを利用しています。

まず最初のJSON の解析ではApply to eachのitems()つまり、1フィールドの情報に対して解析を行っています。

ここは、フィールドの型などによってサンプルから生成したスキーマが異なり、そのまま利用するとエラーになってしまうので、地味にめんどくさかったですね。

続いて、JSON の解析(metadata)では__metadataの内容のうちtypeだけが欲しかったので、解析を行っています。
'body('JSON_の解析')?['__metadata']?['type']でもよかったかもですね。(試してはないです)

いよいよフィールドの作成を行っていくのですが、フィールドの型が選択肢か否か。でPOSTする際のBodyが変わってきます。

今ブログを書いているときに思いましたが、型がSP.FieldChoiceどうか?で判断すればよかったかな?とは思いましたが、今回作成したフローでは選択肢が設定されているかどうか?で判断しています。

empty(body('JSON_の解析')?['Choices'])選択肢が空であれば、Choicesを利用しません。

まずがChoicesを利用しない(Bodyに設定しない)パターンです。

SharePoint に HTTP 要求を送信します(フィールドの作成)では選択肢を作成しない場合のフィールドの作成を行っています。
リストは先ほど作成したリストを指定しています。

続いてChoicesを利用する(Bodyに設定する)パターンです。

Choiceで得られた結果は以下のようになっています。

"Choices": {
    "__metadata": {
        "type": "Collection(Edm.String)"
    },
    "results": [
        "選択肢1",
        "選択肢2",
        "選択肢3"
    ]
}

このうち欲しいのはresultsの結果ですので、JSON の解析(Choices)で抜け出せるようにしてあげます。

SharePoint に HTTP 要求を送信します(フィールドの作成(Choicesあり))では選択肢を作成する場合のフィールドの作成を行っています。
選択肢には先ほどの解析で得られた選択肢を設定します。

リストは先ほど作成したリストを指定しています。

以上でTenant A(移行元)からTenant B(移行先)へSPOリストを移行させる仕組みを作成することができました。

とはいえ色々な型やフィールドの設定などは未検証なため正しく動かない部分があるかもです。

もしそのような場合は、どのようなフィールド設定がエラーとなったか?エラー内容はどのようなものか?を教えていただけると助かります。

フローコピペ用コード

リスト情報を取得する

リストが存在するサイトのアドレス

リスト名

SharePoint に HTTP 要求を送信します

作成

リストを作成してフィールドを作成する

リストを作成するサイトのアドレス

リスト名

ListFieldObj

boolean

SharePoint に HTTP 要求を送信します(リストの作成)

Apply to each

なにこのコード?どうやって使うの?

いずれかのコードをクリップボードにコピーした状態でフローを開いて、

「自分のクリップボード > Ctrl + V」をすると、アクションが選択肢に追加されますので、それを選択すると、フロー内にアクションが追加されます。
便利

ここには自分のテナント内でコピーしたアクションであればCtrl + Vをしなくとも、自動でアクションが溜まっていきます。
もちろんログアウト行ったりするとリセットされます。

ただこの機能、プレビューなだけあって「条件」アクション内や「スイッチ」アクション内などにはこの方法でアクションを追加することはできない。などの制限があるようです。
残念

おわりに

短時間で作ったフローなのでちょっと作り雑だったり検証不足だったりする箇所があります。
お気づきの点があればご指摘いただければー

それでは


スポンサードリンク