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

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

Power Appsでピボットテーブルみたいな表を表示させる


スポンサードリンク

はじめに

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

今回はこちらの質問に回答した記事です。

完成イメージ

ついでなので選択した項目編集できるようにしてみました。

Power Appsでピボットテーブルを表示させる

元となる考え

以前紹介した「Power Appsでマトリクス形式の表を作成する」をもとにアプリを作成しました。

koruneko.hatenablog.com

もとというよりこれをもうちょっとイイ感じに改造した。
といった方が良いかもです。

アプリの構成

基本的な構成は以前と同じです。

FormCtnは編集フォームを表示させる用のコンテナですので無視してもらって大丈夫です。

変更しているのはGalleryのTemplateSizeを0にしている点ですね。
これにより、WrapCountが10までしか設定できない。という課題をクリアしています。

水平コンテナー

以前と設定が変更となっている点だけ記載します。

今回、GalleryのTemplateSizeを0にしているので、Galleryでスクロールバーが出なくなっています。

よって、スクロールはコンテナーに任せます。

前回は「水平方向のオーバーフロー」(LayoutOverflowX)のみ「スクロール」(Scroll)に設定していましたが、今回は「垂直方向のオーバーフロー」(LayoutOverflowY)も「スクロール」(Scroll)に設定します。

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点に気を付ける必要があります。

  1. 対象のアイテムをどの座標に配置するか?を持たせる必要がある
  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つけている人とかがマシュマロ使ってて、技術や実務系の質問受け付けている人は質問箱使ってるのかな?

わかんない。


スポンサードリンク