1. 記事一覧 >
  2. ブログ記事
Power Apps
category logo

Power Appsキャンバスアプリ 【続】最初から知っていたら嬉しかった15のこと

(更新) (公開)

はじめに

人柱的に社内で誰もやったことがない Power Apps のキャンバスアプリ開発をしていて、「あれ?なんで?うーん...あっ!そうか!」ってなったことが何回かありました。
そうなったとき、思ったのが、「事前に先輩とかに教えてもらっていて、知っていたら、嬉しかったなぁ。」です。
そう思った事柄について、15個手短に列挙していきます。
...というのは、前回記事、「Power Apps キャンバスアプリ 最初から知っていたら嬉しかった15のこと」の書き出しだったのですが、
今回は、前回記事に追加で取り上げたいこと15個になります。

今回取り上げた事柄は、完全に独断と偏見で選抜しています。あるあるランキングでもなんでもないです。単に初心者向けと高を括って勉強不足のまま始めたという側面もあります。

2023 年 4 月現在の Power Apps の話です。状況が異なる可能性があります。

全て Windows の Chrome 前提です。


目次
1Concatenate 関数
2データソース反映タイミング
3xxx=Blank() と IsBlank(xxx) では意味が違う
4自動フォーマット
5ActiveDirectory から取れる情報
6部品を画面外に配置
7チェックボックス 押したときの枠を消す
8コレクション 先頭に挿入
9変数に部品やコレクションそのものを代入
10ラベルのマウスカーソル
11非同期実行 Concurrent
12関数が作れる
13ドロップダウン選択肢直指定
14ギャラリーの中の部品をリセット
15コレクション → フォームのフィールド

Concatenate 関数

エクセルで同名関数がありますが、& しか知らなかったため、何て読むんだ??から始まり、関わりたくない関数に見えました。
なんてことなく、Concatenate(コンカチネート)関数は文字列を連結する関数でした。& 演算子と同じ意味ですが、Concatenate 関数は、複数の文字を繋げられます。
Concatenate("Hello", " ", "world!") // 結果: "Hello world!"
"Hello" & " world!" // 結果: "Hello world!"


& がたくさん必要なときは、Concatenate 関数を使うと & だらけにならないで済みます。


& だらけにならないで済む方法として、前回記事で書いた $"..." 形式でも良いです。

$"..." 形式は、「文字列補間」と言うようです。他の言語で言う テンプレートリテラル、Template literal、テンプレート文字列、文字列展開、式展開 です。

参考:https://learn.microsoft.com/ja-jp/power-platform/power-fx/data-types

Concatenate関数で文字連結


アンパサンドで文字連結


文字列補間


結局、文字列を連結する方法として、Concatenate、&、$"..." の3パターンあるということになります。


データソース反映タイミング

データソースの変更は、編集中に反映されません。
再生ボタンを押しても反映されません。
ボタンの OnSelect で
Reset(Gallery1)
のようにしても反映されません。
「最新の状態に更新」でデータソースの再読み込みが必要です。

データソースの変更前

↓

データソースを変更

↓

データソース変更前のまま

↓

最新の状態に更新

↓

データソース変更が反映される


再読み込みが必要なデータソースがたくさんある時は、ブラウザの再読み込み(F5 キー)を行うと全てのデータソースについて反映されるようです。(注意:保存するのを忘れないでください。)


xxx=Blank() と IsBlank(xxx) では意味が違う

「xxx が空の文字列("")かどうか?」を If 文に書くとき、
If( xxx=Blank(), ...
ではなく、IsBlank を使わないといけないです。


If( xxx=Blank(), ...
の xxx=Blank() 部分は、xxx が、Blank()(空白値) かどうかを検査しています。
Set(xxx, Blank())
で xxx の値がセットされているときは、true です。
Set(xxx, "")
で xxx の値がセットされているときは、false です。


If( IsBlank(xxx), ...
の IsBlank(xxx) 部分は、xxx が、空の文字列("")または、Blank()(空白値) かどうかを検査しています。
Set(xxx, Blank())
で xxx の値がセットされているときは、true です。
Set(xxx, "")
で xxx の値がセットされているときも true です。


なんとなく、空白の意味になりそうな
Set(xxx, false)
や
Set(xxx, 0)
で xxx の値がセットされているとき、IsBlank(xxx) は、 false です。(true かなと思いましたが、普通に false でした。)

参考:https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-isblank-isempty


自動フォーマット

Power Apps Studio(編集画面)には、自動フォーマット(整形/Beautifier)機能があります。


式の編集中に テキストの書式設定 をクリックすると、きれいになります。

Set(グローバル変数,1);
ClearCollect(col_Innu,[{ID:1,Namae:"ポチ"},{ID:2,Namae:"むぎ"}]);
Set(VehicleType,{Car:1,Bus:2,Train:3});

↓

Set(
    グローバル変数,
    1
);
ClearCollect(
    col_Innu,
    [
        {
            ID: 1,
            Namae: "ポチ"
        },
        {
            ID: 2,
            Namae: "むぎ"
        }
    ]
);
Set(
    VehicleType,
    {
        Car: 1,
        Bus: 2,
        Train: 3
    }
);

テキストの書式設定をクリック


フォーマットの解除 をクリックすると、元に戻ります。

フォーマットの解除をクリック

と思ったら、元の形とは違う形になりました。フォーマットと逆のロジックが働いているようです。(元々の形が重要だった場合、戻せなくなる可能性があります。)


ActiveDirectory から取れる情報

Office365ユーザー.MyProfileV2() で取り出せる情報を調べてみました。


調べ方は、単純で、
Set(userV2,Office365ユーザー.MyProfileV2());
を実行させて、グローバル変数 userV2 の中身を目視しました。


「Azure AD の項目」とは、Azure ポータル → Azure Active Directory → ユーザー に表示される設定項目のことです。


●Office365 ユーザー.MyProfile()
戻り値の型:レコード型

項目Azure AD の項目/意味値の例
AccountEnabled有効なアカウントTrue
BusineessPhones勤務先の電話番号080-0000-0000(レコード)
City市区町村豊田市 ○○ 町
CompanyName会社名株式会社 ○○
Country国または地域日本
Department部署経営企画部
DisplayName表示名山田 太郎
GivenName名太郎
Idオブジェクト ID00000000-abcd-1111-ef12-222222222222
JobTitle役職室長
Mailメールyamada.taro@contoso.com
MailNickNameメール ニックネームyamada.taro
OfficeLocation勤務先所在地豊田市 ○○ 町
PostalCode郵便番号000-0000
Surname姓山田
TelephoneNumber携帯電話番号090-0000-0000
UserPrincipalNameユーザー プリンシパル名yamada.taro@contoso.onmicrosoft.com
mobilePhone携帯電話番号090-0000-0000

●Office365 ユーザー.MyProfileV2()
戻り値の型:レコード型

項目Azure AD の項目/意味値の例
abountMe自己紹介文※よろしくお願いします。
accountEnabled有効なアカウントTrue
birthday誕生日※1/01/01 17:18
busineessPhones勤務先の電話番号080-0000-0000(レコード)
city市区町村豊田市 ○○ 町
companyName会社名株式会社 ○○
country国または地域日本
department部署経営企画部
displayName表示名山田 太郎
givenName名太郎
hireDate雇用された日※1/01/01 17:18
idオブジェクト ID00000000-abcd-1111-ef12-222222222222
interests関心事一覧※映画(レコード)
jobTitle役職室長
mailメールyamada.taro@contoso.com
mailNickNameメール ニックネームyamada.taro
mobilePhone携帯電話番号090-0000-0000
myStie個人用サイトの URL※https://xxxxx-my.sharepoint.com/personal/...
officeLocation勤務先所在地豊田市 ○○ 町
pastProjects過去のプロジェクトビッグプロジェクト(レコード)
postalCode郵便番号000-0000
preferredLanguage言語ja-JP
preferredName呼ばれたい名前※やまだ
responsibilities責任の範囲※経営計画(レコード)
schools在籍した学校※スタンフォード(レコード)
skillsスキル※FP(レコード)
state都道府県愛知県
streetAddress番地1-11-1
surname姓山田
userPrincipalNameユーザー プリンシパル名yamada.taro@contoso.onmicrosoft.com
userTypeユーザーの種類Member

(レコード) のところは、レコード型で、複数値を登録できます。

※ の項目は、Azure ポータル → Azure Active Directory → ユーザー に表示されませんでした。Microsoft Graph API で書き換える必要があるかもしれません。

myStie は、SharePoint の「自分のファイル」の URL が入っていました。

preferredLanguage は、ja-JP が入っていました。


「従業員 ID」
「従業員の種類」
は、Azure AD にあるのですが、Office365 ユーザー.MyProfileV2() で参照できない情報のようです。

Azure AD 従業員 IDと従業員の種類


下記参考サイトは、V2 ではなく、V1 の情報のようです。

参考:https://learn.microsoft.com/ja-jp/power-apps/maker/canvas-apps/connections/connection-office365-users


部品を画面外に配置

フォームの OnSuccess、OnFailure、SubmitForm を利用したくて、フォームを導入したけど、画面には表示したくないときがありました。(SubmitForm の データ内容は、フォーム内の DataCard の Default、Update プロパティを直接操作して対応しました。)
Visible を false に切り替えれば良いですが、たまに フォーム内の DataCard を直接クリックしたくなるとします。


その場合、座標に数式を書くと、外に追いやることができます。
数値だけの場合、中に留まる座標になります。(自動的に中に留まる座標に変換されます。)

数値だけの場合、中に留まる座標になる 動画

外に追いやって、追いやった部分がキャンバス周囲の灰色の部分よりもさらに外の場合、クリックできなくなります。スクロールできないため、クリックするためには、縮小する必要がありました。

参考:https://qiita.com/h-nagao/items/6870610d23a18305b9d9


チェックボックス 押したときの枠を消す

チェックボックスにチェックを入れると、枠線が表示されます。
チェックを外したときでもフォーカスが当たると枠線が表示されます。
HTML の場合、チェックボックスは、そうなっていないのですが、Power Apps ではこのような初期動作になっています。


●HTML の場合 HTMLのチェックボックス 動画


●Power Apps の場合 Power Appsのチェックボックス 動画

BorderColor
Color.Transparent

を設定して、自分で枠線が表示されないようにしないといけないです。

Power Appsのチェックボックス 枠線が表示されないようにした後 動画


参考:https://anko7793.com/2022/02/524/


コレクション 先頭に挿入

レコードをコレクションの末尾ではなく、先頭に入れたいとします。


既存レコードを colIceCream とします。

味量
チョコレート100
バニラ200

入れたいレコードは、{味: "ピスタチオ", 量: 50} (1レコード)とします。

味量
ピスタチオ50

まず、末尾の場合、当然ですが、Collect 関数一発です。


Collect(colIceCream, {味: "ピスタチオ", 量: 50})


結果、以下のようになります。

味量
チョコレート100
バニラ200
ピスタチオ50

先頭の場合、少しやっかいで、以下の手順です。

ClearCollect(colTemp, {味: "ピスタチオ", 量: 50});//一旦テンポラリコレクションに入れたいレコードを格納
Collect(colTemp, colIceCream);//既存コレクションをテンポラリコレクションに追加
Clear(colIceCream);//既存コレクションをクリア
Collect(colIceCream, colTemp);//テンポラリコレクションを既存コレクション(空)に追加
Clear(colTemp);//テンポラリコレクションをクリア

結果、以下のようになります。

味量
ピスタチオ50
チョコレート100
バニラ200

変数に部品やコレクションそのものを代入

変数に部品やコレクションそのものを入れると、参照の意味になります。
極端な例の場合、以下のようにすると、意図したことにならないかもしれません。

//ここで、トグル切り替え前の状態を保存したつもりとする。
//しかし、変数 loc_SaveToggle は、切り替え(トグルスイッチ)Toggle1 そのもの。
UpdateContext({loc_SaveToggle: Toggle1});

//変数 loc_ToggleState は、true -> false, false -> true 反転。
UpdateContext({loc_ToggleState: !loc_ToggleState});

//ここで、トグル切り替え前の状態を読み込んだつもりとする。
UpdateContext({BeforeToggleValue: loc_SaveToggle.Value});
//BeforeToggleValueは、true -> false, false -> true 反転していて、
//切り替え後の状態になっている。
//意図したこと:BeforeToggleValueに切り替え前のトグル値を読み込み。
//実際:BeforeToggleValueに今のトグルの値が読み込まれる。

変数に部品代入

簡潔で分かりやすい例が思い浮かばなかったのですが、フォームでチェックボックスの状態を変更する→『変更前の状態』をメール送信のようなことやっていたときに、ハマりました。


以下のように UpdateContext({loc_SaveToggle: Toggle1}); を UpdateContext({loc_SaveToggle: Toggle1.Value}); に修正して、 .Value で値を代入すると、意図したとおりに動きます。

UpdateContext({loc_SaveToggle: Toggle1.Value});
UpdateContext({loc_ToggleState: !loc_ToggleState});
UpdateContext({BeforeToggleValue: loc_SaveToggle});

変数に部品のValue代入


●修正前

ラベルには、直前のトグル値(現在のトグル値の逆)を表示する趣旨ですが、現在のトグル値が表示されてしまっています。

変数に部品代入動作内容 動画


●修正後

ラベルには、直前のトグル値(現在のトグル値の逆)が表示されています。

変数に部品のValue代入動作内容 動画


ラベルのマウスカーソル

ラベルに OnSelect を設定して何らかの処理をするとします。クリックすると、何らかの処理を行うラベルになりますが、マウスカーソルをラベルの上に当てても I のカーソルのままで、押せる雰囲気がありません。 ラベルの上に当てても I のカーソルのまま


アイコンやボタンの場合は、カーソルが指アイコンに変わって、押せる雰囲気になります。

カーソルが指アイコン


ラベルの上に当てたときにカーソルを指にしたいです。
解決方法を2つみつけました。


1:透明なボタンをラベルの上に(前面に)配置する。
2:HTML テキストを配置して、<span style='cursor:pointer; color:blue'>編集</span>のように style で cursor:pointer; とする。


しかし...2つの方法では、それぞれデメリットがあります。


1:ホバーした時のラベルの色を変えられない。(ラベルより前面にボタンがあるため、ボタンのホバーになります。)
2:文字の範囲でしか反応しない。(カーソルが変わらない。)


2の方は、どういうことかというと、文字の周りの白いところでは反応しないということです。

文字の周りの白いところでは反応しない 動画

これを解決する方法を見つけました!(どうしてもラベルでやりたいんだ!という方は、すみません。)
ボタンのプロパティをラベルっぽくすれば良いです。

Text="見た目がラベルのボタン"
BorderThickness=0
Color=Color.Black
DisabledBorderColor=RGBA(56, 56, 56, 1)
DisabledColor=RGBA(166, 166, 166, 1)
DisabledFill=Color.Transparent
Fill=Color.Transparent
FocusedBorderThickness=0
FontWeight=FontWeight.Normal
HoverBorderColor=Color.Transparent
HoverColor=Color.Red
HoverFill=Color.Transparent
PressedBorderColor=Color.Transparent
PressedFill=Color.Transparent
Size=13

ホバーの時の文字色を赤色にしています。

文字の周りの白いところでも反応 動画


クリックできるラベルの文字列をコピペしたいという要件は無いとします。

HTML の知識がある場合、<div style='cursor:pointer; 大きさとか余白を適当に指定'> でやっても良いと思います。


非同期実行 Concurrent

Concurrent 関数で非同期実行(同時実行)ができます。


書式は、Concurrent( 式1, 式2 [, ...] ) で、


式1:凄く処理時間が長い
式2:式1の結果を待つ必要が無い
の場合、普通に
式1;式2;
とすると、式2は、式1の実行結果とは無関係なのに、式1の実行結果を待ってから実行されます。
こういった状況の時は、Concurrent 関数で非同期実行(同時実行)で全体の実行時間を短くできます。


●Concurrent 未使用

ClearCollect( Product, '[SalesLT].[Product]' );
ClearCollect( Customer, '[SalesLT].[Customer]' );
ClearCollect( SalesOrderDetail, '[SalesLT].[SalesOrderDetail]' );
ClearCollect( SalesOrderHeader, '[SalesLT].[SalesOrderHeader]' )

Concurrent 未使用タイムライン


●Concurrent 使用

Concurrent(
    ClearCollect( Product, '[SalesLT].[Product]' ),
    ClearCollect( Customer, '[SalesLT].[Customer]' ),
    ClearCollect( SalesOrderDetail, '[SalesLT].[SalesOrderDetail]' ),
    ClearCollect( SalesOrderHeader, '[SalesLT].[SalesOrderHeader]' )
)

Concurrent 使用タイムライン


参考:https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-concurrent

Select 関数も非同期です。

例えば、

Select(btnButton1);ClearCollect( Product, '[SalesLT].[Product]' )

と書いた場合、Select(btnButton1) と ClearCollect( Product, '[SalesLT].[Product]' ) は、同時実行されます。

参考:

https://github.com/MicrosoftDocs/powerapps-docs/issues/1208

https://qiita.com/bashaway/items/27e0bcc15f646d7151de


関数が作れる

プログラミング言語で当たり前のように存在する関数

function calculation(a, b) {
  return a + b;
}

みたいなものを書きたい時、最初、Power Apps では、ノーコード/ローコードなだけにそういう概念が無いと思っていました。
後に Power Apps でも関数が定義できることに気付きました。

「実験段階」とされる設定をオンにします。仕様が変わっても対応が不可だったり、そもそもこの行為がNGだったりする場合は、使えません。


ここでは、引数一つで与えられた値が素数の場合、true、それ以外の場合、false を返す isPrimeNumber という名の関数を作成していきます。


設定 → 近日公開の機能 → 実験段階 → 拡張コンポーネントのプロパティ をオンにします。

拡張コンポーネントのプロパティ オン


ツリービューから、コンポーネントを選択して、+新しいコンポーネント でコンポーネントを追加します。コンポーネント名を isPrimeNumber と変更しておきます。

コンポーネントを追加


右下の カスタムプロパティ → +新しいカスタム プロパティ をクリックして、isPrimeNumber というプロパティを作成します。つまり、isPrimeNumber という関数を作成します。ここで、
プロパティの型:出力
データ型:ブール値
とします。

新しいカスタム プロパティ


+新しいパラメーター をクリックして、

パラメーター名:n
データ型:数値
必須:チェック有り
とします。これが関数の引数になります。


作成 をクリックします。


isPrimeNumber プロパティを編集できるようになるため、引数 n を使った式を書き込みます。 n が素数の場合、true、それ以外の場合、false を返します。


isPrimeNumber
If(
    n = 2, true,//2の場合、素数
    If(n < 2 Or Mod(n, 2) = 0,//2未満、2で割り切れたら素数じゃない。
        false,
        With(
            {
                tblNumbers:
                AddColumns(
                    Sequence(n-2, 2, 1),
                    "divisible",
                    If(
                        //ここでのtrue,falseは、素数、素数以外の意味ではない。
                        Mod(n, Value) = 0,
                        true,//割り切れる
                        false//割り切れない
                    )
                )
            },
            //割り切れるものが一つでもあれば、素数じゃない。
            //breakしたいけど、やり方が思いつかず。
            Not true in tblNumbers.divisible
        )
    )
)

isPrimeNumber実装内容


ツリービューの画面編集に戻って、
+ → カスタム → isPrimeNumber にて先ほど作成したカスタムコンポーネントを挿入し、ラベル、テキスト入力を配置します。


テキスト入力の書式を 数値 にして、ラベルの Text には、以下のように記述しておきます。
isPrimeNumber_1 は、カスタムコンポーネントの名前、isPrimeNumber は関数の名前です。

Text
If(isPrimeNumber_1.isPrimeNumber(TextInput1.Text),"素数だがね。","素数じゃにゃーわ。")

なお、値が大きすぎると、エラーになりました。

値が大きすぎると、エラー

他、小数点が入力できたりしますが、このあたりの不都合なことは全く考慮に入れていません。

完成して気付きましたが、この関数いつ使うんだよってなりました。アプリで何回も必要な計算などに関数化する意味があると思います。


isPrimeNumber関数利用動作内容 動画


参考:https://mofumofupower.hatenablog.com/entry/custom_function_apps


ドロップダウン選択肢直指定

ドロップダウン選択肢の 項目 は、Choices(データソースの選択肢列) でセットしたり、コレクションを設定したりできますが、Items に Value 列を持ったテーブルを直接書けば、データソース不要で選択肢が指定できます。
["Seattle", "Tokyo", "London", "Johannesburg", "Rio de Janeiro"]

この書き方の場合、暗黙で Value 列を持っているテーブルになります。


複数列のテーブルを渡すと、デフォルトでは、アルファベット順の最初の列が採用されるようでした。
[{c: 1, b: 10, a: "first"}, {c: 2, b: 20, a: "second"}]

デフォルトでは、アルファベット順の最初の列が採用


Value のところを変更すると、採用される列を選択できます。

Value のところを変更すると、採用される列を選択できる


ちなみに、テーブルは、こうも書けます。(というか、この方が正しいかもしれないです。)
Table({c: 1, b: 10, a: "first"}, {c: 2, b: 20, a: "second"})


レコード {c: 1, b: 10, a: "first"} を渡してもOKで、その場合は、選択肢が1個だけになりました。


リストボックスにもギャラリーのデータソースにも Items にテーブルを直接書けます。

テーブル関数の説明にありました。

参考:https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-table


ギャラリーの中の部品をリセット

リセット= Default の値に戻す
という意味になります。
通常、
Reset(部品)
でできますが、ギャラリーの中の部品は、
Reset(部品)
でリセットできませんでした。


ギャラリーの中の部品リセット


ギャラリーの中に TextInput1 があるとして、
Reset(TextInput1)
のようにすると、以下のエラーになります。


Reset 関数はリセット可能なコントロールのみで使用できます。フォーム内のコントロールまたはギャラリー コントロールをリセットできるのは、同じフォームまたはギャラリー テンプレート内からのみです。

Reset 関数エラー

余談ですが、エラー文言をコピペできませんでした。他にもコピペしたくてもできない部分がちょいちょいあるような気がします。


Reset(Gallery1.AllItems)
Reset(Gallery1.AllItems.TextInput1)
でもダメです。
Reset 関数には、部品(コントロール)を指定しないとダメですが、この場合、テーブルを指定しているからです。


リセットしたい部品の Reset プロパティを true にすると、リセットできました。
この Reset プロパティですが、何回もリセットする場合、true にした後、false に戻しておかないといけないです。


ギャラリー内のテキスト入力:

Reset
loc_TxtReset

リセットボタン:

OnSelect
UpdateContext({loc_TxtReset: true});
UpdateContext({loc_TxtReset: false});

ギャラリーの中の部品リセット 動画


Reset プロパティでリセットする方法は、ギャラリーの中の部品に限らず、有効です。


コレクション → フォームのフィールド

コレクションをフォームのデータソース(Items)に指定して、フォームでコレクションを操作しようとしても、フィールドの追加がグレーアウトされていて、コレクションのフィールドを追加できません。

コレクションをフォームのデータソース(Items)に指定


フィールドの追加がグレーアウト


この仕様に抗う裏技がありました。
一旦 コレクションと同一列を持つ Excel Online、Dataverse、Share Point のリストなどのデータソースを選んで、データカードを選抜後、フォームの Item を変更し、フォームのデータソースをコレクションに戻すと、コレクションと紐づけることができます。
「このフォームはまだデータに接続されていません データに接続してください」の表示が出ますが、問題無く使えます。

フォームの Item を変更


フォームのデータソースをコレクションに戻す


「このフォームはまだデータに接続されていません データに接続してください」


この続きで、例えば、ボタンに
SubmitForm(Form1)
と記述すると、入力値の反映先がコレクションになります。


フォームの DefaultMode = FormMode.Edit
フォームの Item = 変更したいレコード
のとき、
SubmitForm(Form1)
でレコード更新になります。


フォームの DefaultMode = FormMode.New
フォームの Item = 関係無し
のとき、
SubmitForm(frm_IssueData)
でレコード追加になります。


Patch ではなく、フォームを使う利点として、バリデーション(入力値の型チェック)が使える他、フォームの SubmitForm で OnSuccess、OnFailure が使えます。

参考:http://powerappsguide.com/blog/post/set-form-data-source-to-a-collection

loading...