こんにちは。7月に入ってようやく長い出張を終え、約3か月ぶりに帰れなくなった故郷に帰ることができました。
さて、今回の記事ではギャラリーの中にギャラリーを挿入するという手法の魅力を伝えていけたらと思います。
表形式の入力フォームを作りたいが...
Power Appsを使ってエクセルのような表形式のデータテーブルに数値を入力したい場合が少なからずあると思います。このような場合、真っ先に思いつくのがデータテーブルコントロールではないでしょうか。
ところが、データテーブルコントロールのドキュメントを見てみると、どうやらこれは読み取り専用のコントロールのようです。
したがって、データテーブルコントロールだけでは表は用意できたとしても、その表に直接入力してどこかに保存するという操作は難しそうだと感じました。
ギャラリーの中にテキスト入力コントロールを挿入する
そこで次に考えたのは、ギャラリーの中にテキスト入力コントロールを入れるという方法です。この方法は様々な場面でスタンダードに用いられているかと思います。
実際に下のような生徒の科目ごとの点数を表すテーブルを例に表形式のフォームを作ってみましょう。
列名 | 型 |
---|---|
name | Text |
math | Number |
japanese | Number |
english | Number |
空のページを用意し、OnVisibleに以下のコードを入力し、データソースを作ります。
ClearCollect(
scores,
{
name: "ライナー",
math:0,
english: 0,
japanese: 0
},
{
name: "ベルトルト",
math:0,
english: 0,
japanese: 0
},
{
name: "ジーク",
math:0,
english: 0,
japanese: 0
}
);
このようにすることで、画面を開いたときにテーブルが作成されます。
次にギャラリーを作ります。
ツールバーの[挿入]⇒[ギャラリー]⇒[縦方向(空) ]の順でギャラリーを作成し、先ほど作ったscoresというコレクションをデータソースとします。
その後、ギャラリー内に1つのラベルと3つのテキスト入力コントロールを追加し、ラベルのテキストプロパティを[ThisItem.name]、テキスト入力コントロールのDefaultプロパティをそれぞれ[ThisItem.math]、「ThisItem.japanese」、「ThisItem.english]とします。しかしこのままでは表の中の値が何を表しているのかわからないので、ギャラリーの上にラベルを付ければ完成です。
さて、データを表に表示することはできたので、入力して書き換えるという操作を追加するために、先ほど追加したテキスト入力コントロールのぞれぞれのOnchangeに以下のようなコードを入力します。[教科名]のところにはmath、japanese、englishのうち、対応するものを入力し、[参照するのテキスト入力コントロール名]の部分には参照したいテキスト入力コントロールの名前を入れてください。このようにすることで、テキストを入力した際にデータソースの値も変更することができます。
Patch(
scores,
LookUp(
scores,
name = ThisItem.name
),
{
name:ThisItem.name,
[教科名]:Value( [参照するテキスト入力コントロール名].Text )
}
)
それでは表に値を書き加えてみましょう。
変更の前後でビューのコレクションを比較すると、確かにデータソースを編集できていることが確認できます。
以上のようにしてギャラリーの中にテキスト入力コントロールを埋め込む方法で表形式のフォームを作成しましたが、このscoresテーブルでは教科が3教科しかなかったので、追加すべきテキスト入力コントロールは3つで済み、Patch関数を書き込む箇所も3つで済みました。しかし、もし教科の数が10、100、...と増えた場合、このやり方では教科数に比例して追加すべきテキスト入力コントロールの数と書き込むべきPatch関数の数も増えてしまいます。さすがにこの数を手打ちで入力するのは拷問です。そこで活躍するのがギャラリーの中にギャラリーを挿入するという方法です。
ギャラリーの中にギャラリーを挿入する(ギャラリー in ギャラリー)
ギャラリーの中にギャラリー?何を言っているんだ?と思われるかもしれないですが、全然難しくありません。まず先ほどと同じようにツールバーの[挿入]⇒[ギャラリー]⇒[縦方向(空)]のようにしてギャラリーを追加した後、このギャラリーの最初の行を選択した状態で今度は[横方向(空)]のギャラリーを追加します。これで先に追加したギャラリーの各行の中に横方向に延びるギャラリーが挿入されました。
次にこのギャラリーにデータを入れていくのですが、都合上先ほどとは異なる形式でデータを持ちます。
そのための準備として、まずこの画面のOnvisibleプロパティに以下のコードを入力して生徒のリストを作ります。
ClearCollect(
students,
{name: "ライナー"},
{name: "ベルトルト"},
{name: "ジーク"},
{name: "アニ"},
{name: "ピーク"},
{name: "ガリアード"}
);
次に同じOnvisibleに以下のコードを入力して科目のリストを作ります。先ほどは3科目だったのですが、今度は数を増やして10科目にしてみましょう。
ClearCollect(
subjects,
{subject: "math"},
{subject: "japanese"},
{subject: "english"},
{subject: "physics"},
{subject: "chemistry"},
{subject: "biology"},
{subject: "geography"},
{subject: "economics"},
{subject: "history"},
{subject: "ethics"}
);
さて、次に各生徒ごとの点数を追加するのですが、テーブルは以下のようにします。
列名 | 型 |
---|---|
name | Text |
subject | Text |
score | Number |
テーブルをこのようにすると、例えば{name : "ライナー", subject : "math", score : 192}という形でレコードが登録されます。
それでは、以下のコードをOnVisivleに入力してレコードを登録していきます。このコードのこころを軽く説明していくと、まず一つ目のClearCollectでname列、subject列、score列を持ち、レコードとして{name: "",subject: "",score: 0}を持つコレクションscores2を作成します。RemoveIfでこの中のレコードを空にします。その後、二重のForAll文によってstudentsリストの各studentとsubjectsリストの各subjectのすべての組み合わせに対してscoreを0としたレコードをコレクションscores2に登録しています。
ClearCollect(
scores2,
{
name: "",
subject: "",
score: 0
}
);
RemoveIf(
scores2,
true
);
ForAll(
students As x,
ForAll(
subjects As y,
Patch(
scores2,
Defaults(scores2),
{
name: x.name,
subject: y.subject,
score: 0
}
)
)
)
今度は作ったデータソースを先ほど作ったギャラリーに入れていきます。先ほど、縦方向(空)のギャラリーの中に横方向(空)のギャラリーを追加したと思いますが、縦の方のデータソースにはstudentsを、横の方のギャラリーのそれにはsubjectsを設定していきます。
縦方向(空)のギャラリーにラベルを追加し、Textプロパティに「ThisItem.name」と入力します。
横方向(空)のギャラリーにはテキスト入力コントロールを追加します。すると、一行あたり10個のテキスト入力欄が現れます。これはデータソースのsubjectsが10個のレコードを持っているからです。
このテキスト入力コントロールのDefaultプロパティに以下のコードを追加します。このコードのこころを説明しますと、まず縦方向のギャラリーの各行に入っている生徒の名前を参照し、その生徒と一致するレコードをとってくる(Filterのname=のところ)のと同時に、横方向のギャラリーの各列に入っている科目名を参照し、その列の科目に一致するレコードをとってきています(subject=のところ)。ここで、Filterが返す値はテーブルですが、テキスト入力内にはテキスト型の値が入る必要があるので、First()でレコードをとってきて、そのレコード内のscoreを返しています。
First(
Filter(
scores2,
name = [参照するラベル名].Text,
subject = ThisItem.subject
)
).score
同様にこのテキスト入力コントロールのOnChangeに以下のコードを追加します。このコードのこころは現在の行の生徒名と現在の列の科目名が一致するレコードを探してきて、そのscoreの値をテキスト入力内の値に書き換えるという感じです。
Patch(
scores2,
LookUp(
scores2,
name = [参照するラベル名].Text && subject = ThisItem.subject
),
{score: Value([参照するテキスト入力コントロール名].Text)}
)
このままではどこが何の数字を表しているのかがわからないので、作った表とは別にギャラリーを新たに追加し、データソースをsubjectsにします。さらにこの中にラベルを追加してTextプロパティを[ThisItem.subject]とすれば完成です。
値を入力すると、確かにデータソースの値が入力したものに変更されました。
ギャラリー in ギャラリーのメリット
一つはコードの入力箇所の少なさです。もし先に示した方法でこのデータソースを表にしようとした場合、10個のテキスト入力コントロールすべてのDefaultプロパティとOnChangeプロパティをいじることになり、計20箇所をいじらなければなりません。これだと仮にコードの修正が必要になった場合、そのたびに20箇所全部をいじる必要があるため発狂してしまいます。それに比べてギャラリー in ギャラリーでは、それぞれ一箇所ずついじるだけで済みました。
二つ目はオブジェクトの少なさです。ギャラリー in ギャラリーで作った表で使ったオブジェクトはこれだけです。
もしこれが先に示した方法だった場合、テキスト入力コントロールが大量にできてしまいどれがどれだかわからなくなってしまいます。
ギャラリー in ギャラリーのデメリット
一つはデータソースの扱いがややトリッキーというところがあります。これを見ながら実装している最中になんでそうするの?と思う方もいるかもしれません。Power Appsはローコードが売りの反面、いろいろな制約があるため、その中でもなんとかデータをうまく扱うためにこのような実装になっています。もっといいやり方を知っている方がいらっしゃれば是非教えてください!(慣れてしまえばこのやり方でもずいぶん便利だと感じます。)
もう一つはギャラリー in ギャラリーというよりこのやり方のデメリットですが、テキスト入力コントロール内のDefaultプロパティの中の値は値が変更されるたびに再計算しているため、例えばDataVerseなどの外部ソースに接続して値の更新を行う場合、レコードの数によっては反映に時間がかかる場合があります。
まとめ
- ギャラリー in ギャラリーを使うとたくさんのデータを一気に扱える。
- 使い方に慣れればめちゃくちゃ便利で早く作れる。
- 軽さを重視するアプリには不向きかもしれない。
今回はシンプルな表を作っただけですが、他にもいろいろな応用が考えられそうですね。