はじめに
マシュマロにて以下のような質問をいただきました。
今回はこちらの質問に回答した記事です。
完成イメージ
#PowerApps でピボットテーブルを表示する pic.twitter.com/WnZo4jW40O
— コルネ (@koruneko32767) 2022年12月30日
ついでなので選択した項目編集できるようにしてみました。
Power Appsでピボットテーブルを表示させる
元となる考え
以前紹介した「Power Appsでマトリクス形式の表を作成する」をもとにアプリを作成しました。
もとというよりこれをもうちょっとイイ感じに改造した。
といった方が良いかもです。
アプリの構成
基本的な構成は以前と同じです。
FormCtn
は編集フォームを表示させる用のコンテナですので無視してもらって大丈夫です。
変更しているのはGalleryのTemplateSize
を0にしている点ですね。
これにより、WrapCount
が10までしか設定できない。という課題をクリアしています。
水平コンテナー
以前と設定が変更となっている点だけ記載します。
今回、GalleryのTemplateSize
を0にしているので、Galleryでスクロールバーが出なくなっています。
よって、スクロールはコンテナーに任せます。
前回は「水平方向のオーバーフロー」(LayoutOverflowX
)のみ「スクロール」(Scroll
)に設定していましたが、今回は「垂直方向のオーバーフロー」(LayoutOverflowY
)も「スクロール」(Scroll
)に設定します。
Gallery
Items
の設定は後程。
GalleryのTemplateSize
を0に設定することで、各コントロールを自由な座標に設定できるよにします。
この設定を行うので、「テンプレートのパディング」(TemplatePadding
)は0に設定します。
TemplatePadding
0
また、コンテナ内でスクロールが行われるように高さと幅はテーブルを表示したい領域だけとるようにしてあげます。
Width
(CountRows(Distinct(TaskLists, Title)) + 1) * 250
Height
(CountRows(Distinct(TaskLists, User)) + 1) * 100
GalleryのItems
今回設定するGalleryのItems
は以下2点に気を付ける必要があります。
- 対象のアイテムをどの座標に配置するか?を持たせる必要がある
- 行ヘッダーと列ヘッダーがソートされているわけではないので、それを考慮して値を設定する必要がある
これらを考慮して式を設定すると、以下のようになります。
Items
With( { // Distinct結果を一時変数として取得 tasks: Distinct(TaskLists, Title), users :Distinct(TaskLists, User), // マトリクスはヘッダー分1行(1列)増える row: CountRows(Distinct(TaskLists, Title)) + 1, column: CountRows(Distinct(TaskLists, User)) + 1, // セルの要素 width: 250, height: 100 }, ForAll( Sequence(row * column) As loopCnt, { Id: loopCnt.Value, X: width * (Mod(loopCnt.Value - 1, row)), Y: height * RoundDown((loopCnt.Value - 1) / row, 0), Width: width, Height: height, OriginItemID: // ヘッダーでない場合のみ If( loopCnt.Value > row && Mod(loopCnt.Value, row) <> 1, LookUp( TaskLists, Title = Index(tasks, loopCnt.Value - row * RoundDown((loopCnt.Value - 1) / row, 0) - 1).Result && User.Claims = Index(users, RoundDown((loopCnt.Value - 1) / row, 0)).Result.Claims, ID ) ), Title: // ヘッダーでない場合のみ If( loopCnt.Value > row && Mod(loopCnt.Value, row) <> 1, Index(tasks, loopCnt.Value - row * RoundDown((loopCnt.Value - 1) / row, 0) - 1).Result ), User: // ヘッダーでない場合のみ If( loopCnt.Value > row && Mod(loopCnt.Value, row) <> 1, Index(users, RoundDown((loopCnt.Value - 1) / row, 0)).Result ), Date: // ヘッダーでない場合のみ If( loopCnt.Value > row && Mod(loopCnt.Value, row) <> 1, LookUp( TaskLists, Title = Index(tasks, loopCnt.Value - row * RoundDown((loopCnt.Value - 1) / row, 0) - 1).Result && User.Claims = Index(users, RoundDown((loopCnt.Value - 1) / row, 0)).Result.Claims, Date ) ), rowHeader: // 行ヘッダー(マトリクスの左上は空白) If( loopCnt.Value <= row && loopCnt.Value <> 1, Index(tasks, loopCnt.Value - 1).Result ), columnHeader: // 列ヘッダー(マトリクスの左上は空白) If( Mod(loopCnt.Value, row) = 1 && loopCnt.Value <> 1, Index(users, RoundDown((loopCnt.Value - 1) / row, 0)).Result ) } ) )
前回からの変更点(改良点)をかいつまんで解説します。
まずループ回数ですが、「行ヘッダーの数 × 列ヘッダーの数」で簡単に出せましたね。
前回のより、こっちのほうがロジックてきに正しいです。。。
また、ForAll
処理内で名前解決できるようにloopCnt
としています。
Sequence(row * column) As loopCnt
今回、GalleryのTemplateSize
を0にしているので、アイテムの座標はアイテムごとに保持しておく必要があります。
したがって、以下のように各アイテムの座標とついでにサイズを定義しています。
X: width * (Mod(loopCnt.Value - 1, row)), Y: height * RoundDown((loopCnt.Value - 1) / row, 0), Width: width, Height: height
続いて値ですが、前回は値のないのケースやソートされていないケースが考慮されていなかったので、LookUp
関数で対象の値を引っ張ってくるようにしています。
ちなみにこのやり方だと、行ヘッダーと列ヘッダーは重複がない想定です。
例)
行 | 列 | 値 | |
---|---|---|---|
行1 | 列1 | A | |
行1 | 列2 | B | |
行2 | 列1 | A | |
行2 | 列1 | C | ←こういうのはない想定 |
行2 | 列2 | B |
もしこういった重複もありとしたいのであれば、Filter
関数で取得して、Concat
関数とかで文字列結合した結果をラベルに表示とかしてあげればいいと思います。
今回はめんどくさいので割愛。
OriginItemID: // ヘッダーでない場合のみ If( loopCnt.Value > row && Mod(loopCnt.Value, row) <> 1, LookUp( TaskLists, Title = Index(tasks, loopCnt.Value - row * RoundDown((loopCnt.Value - 1) / row, 0) - 1).Result && User.Claims = Index(users, RoundDown((loopCnt.Value - 1) / row, 0)).Result.Claims, ID ) ), Date: // ヘッダーでない場合のみ If( loopCnt.Value > row && Mod(loopCnt.Value, row) <> 1, LookUp( TaskLists, Title = Index(tasks, loopCnt.Value - row * RoundDown((loopCnt.Value - 1) / row, 0) - 1).Result && User.Claims = Index(users, RoundDown((loopCnt.Value - 1) / row, 0)).Result.Claims, Date ) )
これでデータの加工が完了したので、あとはGallery内のコントロールの設定です。
Gallery内のコントロール
Gallery内のコントロールですが、GalleryのTemplateSize
を0にする。
といいましたが、こちら厳密にいうとTemplateSize
は0に設定しても1となります。
TemplateSize
の最小設定値は1だからですね。
これにより、座標が微妙にずれてくるという点に気を付けてください。
ラベル系の設定
ヘッダーや値なんかがラベルコントロールで表示することになるかと思います。
「テキスト」(Text
)には表示させたい値を設定させてください。
表示(Visible
)には前回同様、ラベルに値が設定れテイル場合のみ表示されるようにします。
!IsBlank(Self.Text)
コントロールの座標にはGalleryのItems
で設定した座標系を設定します。
ここで先ほど説明した、GalleryのTemplateSize
は最小1なので、1だけずれが生じてしまう。
ということを考慮する必要が出てきます。
といっても、そのアイテムがテーブル内で何行目のアイテムなのか?で簡単に修正できますね。
ラベルは1アイテム当たりの描画領域に対して、中心にくるようにしてあげます。
X
ThisItem.X
Y
(ThisItem.Y - (ThisItem.Id - 1)) + (ThisItem.Height - Self.Height) / 2
Width
ThisItem.Width
Height
ThisItem.Height / 2
区切り線の設定
区切り線の設定は前回の設定から、参照先をGalleryからアイテムに設定された座標系に切り替えただけです。
例えば縦の線だと
X
ThisItem.X
Y
ThisItem.Y - (ThisItem.Id - 1)
Width
1
Height
ThisItem.Height
となります。
これの方がY座標の修正がわかりやすいですね。
表示条件は以下にしていま。
RowSeparator.Visible
Mod(ThisItem.Id, CountRows(Distinct(TaskLists, Title)) + 1) <> 1
ColumnSeparator.Visible
ThisItem.Id > CountRows(Distinct(TaskLists, Title)) + 1
アイコン
アイテムを新規/編集するためのアイコンは値のセルでのみ表示されるようにしています。
値のセル = ヘッダーではなく、左上隅のセルでもない
ですね。
Visible
IsBlank(ThisItem.columnHeader) && IsBlank(ThisItem.rowHeader) && ThisItem.Id <> 1
また、値に設定されているものがあるかどうか?でFormを新規モードで編集するか編集モードで編集するかが変わってきますので、
OnSelect
UpdateContext({taskEdit: true}); If( IsBlank(ThisItem.Date), NewForm(EditForm), EditForm(EditForm) )
としています。
フォームの設定
ここは本題から外れるのでおまけ程度
編集モードであれば、選択したアイテムを編集する必要があります。
ただし、今回ギャラリーはピボットテーブルを表示させるために加工したもので元データとは異なっています。
この問題を回避するために、データの加工の際にSPOリストでアイテムを一意に特定可能なID
列を取得していました。
これで、編集対象のアイテムが特定できますね。
Item
LookUp(TaskLists, ID = Gallery.Selected.OriginItemID)
編集モードであれば、これで元データが入力されます。
ただ新規作成モードであっても、選択したセルによって、行と列の値はわかるはずなので、それが予め入力されているようにしたいですよね。
ということで以下のようにします。
Title.Default
If( EditForm.Mode = FormMode.New, Gallery.Selected.Title, Parent.Default )
User.DefaultSelectedItems
If( EditForm.Mode = FormMode.New, Gallery.Selected.User, Parent.Default )
気を付けるのはユーザ列なんかで設定されるコンボボックスのデフォルトはDefault
ではなく、DefaultSelectedItems
で設定する必要がある。
という点ですかね。
おわりに
今回は前回投稿した内容の発展みたいな形でしたので少し説明を簡略化して書いてみました。
なのでわかりにくいところが多々あるような気がするので、不明点は質問していただければと思います。
質問受付にマシュマロ使ってますが、質問箱の方が質問しやすいですかね?
勝手な偏見ですが、クリエイター系の人や緩い質問上kつけている人とかがマシュマロ使ってて、技術や実務系の質問受け付けている人は質問箱使ってるのかな?
わかんない。