#PowerApps 予定表スクリーンの基本と応用 - 基本的構成

#はじめに
今回は2回に分けて、PowerAppsのキャンバスアプリ作成画面に用意された、予定表スクリーン(以下カレンダースクリーン)の基本と、その応用として、年間カレンダーを表示するアプリの作成を解説します。
![Cal1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/a6bffea3-6c05-0af4-b1fd-0473d533f6f3.png)

#カレンダースクリーンの基本
PowerAppsにはログインユーザーのOutlook予定表を、カレンダーとともに表示するためのスクリーンテンプレートが用意されています。
まずはこのスクリーンで何を行っているのか/どうやってカレンダーを表示しているのかを説明します。

カレンダースクリーンは主に3つのパーツ・ギャラリーで構成されています。
- 予定表のイベント・アイテムを表示する **CalendarEventGallery**
- カレンダー部分の本体である **MonthDayGallery**
- 曜日を表示するための **WeekdayGallery**

![画面構成.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/914e4840-1461-6933-a6ed-58ba856a9ffa.png)

##WeekdayGallery
WeekdayGalleryのItemsには```Calendar.WeekdayShort()```という関数が設定されています。
Calendar関数の詳細は [こちら](https://docs.microsoft.com/en-us/powerapps/maker/canvas-apps/functions/function-clock-calendar)の公式ドキュメントを参照してください。WeekdayShort()は単一列のテーブルを返す関数です。
これをGalleryに設定し、ラベルコントロールを追加、その値に ```ThisItem.Value```を設定することで、テンプレートのような横並びの曜日表示ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/6cadae75-73a8-a787-52ef-040e8bd50917.png)

##MonthDayGallery
カレンダー本体なので、それなりに設定がたくさんあります。
まず、MonthDayGalleryのItemですが、非常に単純に0~41の数字からなるテーブルが設定されています。
※ひと月は最大6週間あるので、7日ｘ6週でカレンダーには最大42日の日付が必要になります。このためGalleryには42のアイテムが表示できるようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/3fad9d6e-9c24-e748-8910-f279423aaff4.png)
単純な0~41の数字列から、選択した月のカレンダーを構成するために、カレンダースクリーンでは以下のような手続きを行っています。

**~MonthDayGalleryでの処理~**
1. 表示する月の初日を求める
2. 表示する月の最初の週の日曜日の日付を求める（上図の「表示上の初日」）
3. 表示する月の最終日を求める
4. 元のテーブルの値と、表示している実際の日付の差分が10より小さければ塗りつぶしなし。それ以外の日付ではグレーに塗りつぶす

このような処理を施すことで、選択した年・月に応じた整形されたギャラリーを表示することができます。
![日付決定１.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/d572acaf-53b6-f543-4e06-64679704f9e0.png)


###1. 月の初日を求める
月の初日を求める操作は単純です。(テンプレートでは少し難しく設定していますが)

スクリーン内のパーツのうち、カレンダーのアイコンを選択すると、そのOnSelect プロパティに以下のように書かれています。

```
//選択された日付 を指定
Set(_dateSelected, Today());

//選択された日付をもとに、その月の初日を求める   
Set(_firstDayOfMonth, DateAdd(Today(), 1 - Day(Today()), Days));

//表示する範囲の初日を決定(1日が週の途中なら前月おわりも表示対象)
Set(_firstDayInView, DateAdd(_firstDayOfMonth, -(Weekday(_firstDayOfMonth) - 2 + 1), Days));
        
//月の最終日を決定
Set(_lastDayOfMonth, DateAdd(DateAdd(_firstDayOfMonth, 1, Months), -1, Days));

```

月の初日は、ある日(この場合はToday())の日付分だけ、Todayから遡り、1を足して求めることができます。
DateAdd関数については[こちら](https://docs.microsoft.com/en-us/powerapps/maker/canvas-apps/functions/function-dateadd-datediff)をご覧ください。
例えば、2019/10/28が今日なら、上の数式では、2019/10/28から28日を引いて(ここで2019/9/30)、そこに1日を足す(2019/9/30＋1日)をやっています。

###2. 表示上の初日を求める
ちょっと複雑な数式なのですが、図解をみていただければ納得いただけると思います。ここで使われているWeekday()関数は、日曜日を1として、指定された日付が週の何番目かを返す関数です。

```
//表示する範囲の初日を決定(1日が週の途中なら前月おわりも表示対象)
Set(_firstDayInView, DateAdd(_firstDayOfMonth, -(Weekday(_firstDayOfMonth) - 2 + 1), Days));
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/d5c02d7e-e3b2-8c54-bd70-c9c38c60aa9a.png)

このような手続きによって、カレンダーの最初の週の日曜日に該当する日付を求めることができました。

###3. 月の最終日を求める
週の最終日は、ギャラリーの5行目(月の5週目)以降を表示するかどうかの判定に利用されています。
多くの月では6週目はないので、これを表示しないようにするためです。

月の最終日はPowerAppsに限らず、よく用いられる方法で決定されています。

```
Set(_lastDayOfMonth, DateAdd(DateAdd(_firstDayOfMonth, 1, Months), -1, Days));
```
単純に、月の初日(例えば2019/10/1)に1か月足して(2019/11/1)、そこから1日遡る(2019/10/31)というものです。

また、5/6週目を表示するかどうかの判定は、日付表示用ラベルのVisibleプロパティに以下を設定することで実現されています。

```
/*This item is in a row containing no days of the visible month. It is in the row after the row where the last day of the month occurs*/ 
 !(DateAdd(_firstDayInView,ThisItem.Value,Days) - Weekday(DateAdd(_firstDayInView,ThisItem.Value,Days)) + 1 > _lastDayOfMonth)

```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/0ee7a66d-802c-84a9-6b24-ac730a562485.png)

やっていることは、「とある日付の週初日が、月の最終日より大なら表示しない」というものです。
分解してみてみると・・・

- まず```DateAdd(_firstDayInView,ThisItem.Value,Days)```これでギャラリー中の各日付が得られます。
- ここから、その日付が週何日目かを引きます(``` - Weekday(DateAdd(_firstDayInView,ThisItem.Value,Days))```)
- 最後に1を足すことで、週の初日が得られます。(図解は省略しますが、表示上の初日を求めたときと同じ考え方です)

あとの不等号は、各日付の所属している週、その初日が、月の最終日よりも大きいか小さいかを判定していると読み取れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/c07eef0e-0a7a-6a3e-8658-9515d7404b98.png)
図解は上のとおりです。
長くなりましたが、以上のようにして、カレンダー本体の表示がなされています。


##CalendarEventGallery
最後にOutlook予定表から取得したイベントを表示するギャラリーです。
カレンダーのイベント取得のトリガーはいろいろありますが、一番単純には、スクリーン上のドロップダウンで表示する予定表を選択したときをみれば、何を行っているかがわかります。

```
//まずOutlookカレンダーから、表示対象をドロップダウンに基づいて選択 
Set(_myCalendar, dropdownCalendarSelection1.Selected);

//_minDate, _maxDateはカレンダーを取得する際の範囲      
Set(_minDate, DateAdd(_firstDayOfMonth, -(Weekday(_firstDayOfMonth) - 2 + 1), Days));//_minDateは基本的には_firstDayInView       
Set(_maxDate, DateAdd(DateAdd(_firstDayOfMonth, -(Weekday(_firstDayOfMonth) - 2 + 1), Days), 40, Days)); //_maxDateはカレンダー本体の最終日-1

//Outlookカレンダーから、_minDate, _maxDateの範囲でイベントを取得      
ClearCollect(MyCalendarEvents, Office365Outlook.GetEventsCalendarViewV2(_myCalendar.Name, Text(_minDate, UTC), Text(_maxDate, UTC)).value);
```
基本的な流れは上のコメントの通りですが、
1. 表示する予定表をドロップダウンの選択アイテムから決定
2. カレンダーの取得範囲の上下限(_minDate,_maxDate)を決定
3. Office365Outlookコネクターでイベントを取得
4. 取得したイベントをコレクションに追加
という構成です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/c0ae48f6-6e49-f794-6b87-d3c19e09e8db.png)

もう一つの取得トリガーは、月を変更する＜や＞のボタン(アイコン)です。
次の月に進むアイコンを例にとると、ここでは
1. カレンダー用日付のアップデート
2. _maxDateと、次の月の最終日を比較
3. 次の月の最終日が_maxDateより大なら・・・_maxDateの次の日～表示上の初日+40日までの範囲でイベントを取得
4. コレクションに追加
5. _maxDateを更新
としています。
3が少しわかりづらいですが、要は、「まだカレンダーの取得をしていなければ、その月分のイベントを表示せよ」という処理です。
また、4と5からわかる通り、取得したイベントは基本的には捨てませんし、maxDate(minDate)は表示月を変えるごとに更新されていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202779/dcd2e3cd-48ac-ac35-66b4-403830c8cc3d.png)



#最後に
以上のようにして、カレンダースクリーンは構成されています。
主に3つのギャラリーが重要な役割をはたしていて、日付の足し引きDateAddによって表示を制御していることがお分かりいただけたかと思います。
次回はこのカレンダースクリーンの変数・パーツを流用して、1年分のカレンダーと予定表を表示するアプリの作成を解説します。