Power Appsでアプリを作成していると、日付の入力・選択にDatePickerコントロールを利用することがあります。 このDatePickerコントロール、カレンダーが必要ない場面では微妙に使いにくい印象を受けます。特に月を大きく変更するような日付選択、年の異なる日付選択をする場合です。 これらの課題は、しばしばドロップダウンで年・月・日をそれぞれ選択させることで解決したり、テキスト入力にyyyymmddの形式で入力させて、バリデーション というパターンで対応されます。
今回はギャラリーとボタンを組み合わせて、ちょっときれいな日付選択コントロールの作成方法を紹介します。
材料 | 数 | 用途 |
---|---|---|
ボタン | 3 | 年・月・日の表示+ギャラリーの表示トリガー |
ギャラリー | 3 | 年・月・日のアイテム表示・選択用 |
ギャラリー内ボタン | 各1 | 数字表示&年月日選択用 |
日付変数 | 1 | 選択日付用 |
Boolean変数 | 3 | ギャラリーの表示切り替え |
初期の選択日付はあってもなくてもよいのですが、ここでは簡単のためにToday()を使って初期選択日付を設定しておきます。
Set(dateSelected,Today())
年の選択は比較的簡単です。 ボタンをスクリーンに追加して、このボタンに年を表示、また年変更用のギャラリーを表示させる処理を記載します。
Year(dateSelected)
UpdateContext({visGalYear:true});
次に、スクリーンに縦型ギャラリーを追加し、ボタンをギャラリー内に追加します。 ギャラリーのItemsプロパティには選択範囲の年を頑張って入れます。
[2020,2019,2018,2017,2016]
こんな感じです。 ギャラリーの表示を変数を用いて切り替えたいので、Visibleプロパティに表示制御用の変数を設定します。
visGalYear
次にギャラリー内のボタンですが、Textプロパティにはギャラリーの各アイテムを表示させます。
ThisItem.Value
ボタンをクリックしたときのアクションを設定します。 ボタンをクリックしたら、選択日付の『年』部分だけを入れ替える処理をします。また、選択したらギャラリーを非表示にしたいので、表示制御用の変数更新もしておきます。
Set(dateSelected,
Date(ThisItem.Value, Month(dateSelected),Day(dateSelected)));
UpdateContext({visGalYear:false});
これで年の選択部分ができました。 実際動作させると以下のようになります。
月の選択も年とほとんど同じです。 選択月を表示するためのボタン、変更用のギャラリーを追加します。
Upper(Text(dateSelected,"mmm"))
UpdateContext({visGalMonth:true});
ここは趣味というか、月であることがわかりやすいように、表示をJAN, FEB,MARなどの英語表記にしています。特に必要ない場合にはMonth(dateSelected)
を代わりに設定してください。
月変更用のギャラリーのプロパティは以下の通りです。
[1,2,3,4,5,6,7,8,9,10,11,12]
visGalMonth
ギャラリー内のボタンの表示も、選択月と合わせておきましょう。
Upper(Text(Date(2000,ThisItem.Value,1),"mmm"))
Text関数でmmmを使って月を英語表記するには入力に日付型を求められるので、フェイクで、2000年X月1日を設定しています。
ボタンをクリックしたときのアクションは、今度は選択日付の『月』部分だけを入れ替えます。
Set(dateSelected,
Date(Year(dateSelected),ThisItem.Value,Day(dateSelected)));
UpdateContext({visGalMonth:false});
実際動作させると以下のようになります。
最後に日の表示ですが、ギャラリー側が少し変更をうけます。 日の選択範囲は年と月によって変わるからです。 ほかの部分については同様なので、省略します。(必要であれば巻末のプロパティ一覧を参照してください)
日の変更用ギャラリーのItemsは、それ以外の年・月をもとにして、1~31をフィルターします。
FirstN([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],DateDiff(Date(Year(dateSelected),Month(dateSelected),1),Date(Year(dateSelected),Month(dateSelected)+1,1)))
1~31のテーブルをFirstNで絞っているのですが、Nを決定する必要があります。 DateDiff(DateAdd(...)))部分がそれにあたります。
DateDiffは2つの日付の間にある日数をカウントする関数です。ここでやっているのは、
Date(Year(dateSelected),Month(dateSelected),1)
まずここで選択した年・月の1日を指定し、Date(Year(dateSelected),Month(dateSelected)+1,1)
次の月の1日との間にある日数、つまり月の日数を出しています。
Set(dateSelected,
Date(Year(dateSelected),Month(dateSelected),ThisItem.Value));
UpdateContext({visGalDay:false});
実際動作させると以下のようになります。
大体これで完成なのですが、1つ面倒な問題があります。 年月日は選ぶ順番が前後することがあるので、例えば3/31を選択してから、月を2月に変更すると、2/31→3/3になってしまいます。これはなかなか気持ちの悪いもので、月を2にしたはずなのに、ギャラリーが閉じられたら3に変わっているんです。あまりよくないですよね...
これを緩和するために、『もし選択した日付が次の月に入ってしまう場合には、月の最終日にセットする』という処理を追加します。 先ほどの例でいえば、3/31を選択した状態で、2月にすると、選択日付としては2/28が選ばれるような挙動です。
悪くはないですよね。 この処理は年・月の変更時にのみ入れればよいです。
If(
/*選択した日付がカレンダーで正しく存在するかどうかをチェック*/
Text(Date(Year(dateSelected),ThisItem.Value,Day(dateSelected)),"[$-en-US]yyyy/m/d")
=Year(dateSelected)&"/"&ThisItem.Value&"/"&Day(dateSelected),
/*存在すればdateSelectedをそのまま更新*/
Set(dateSelected,Date(Year(dateSelected),ThisItem.Value,Day(dateSelected))),
/*存在しなければ、一番近い月末を選択 例:3/31を選んでから、月を2月に変更→dateSelectedは2/28*/
Set(dateSelected,DateAdd(Date(Year(dateSelected),ThisItem.Value+1,1),-1,Days))
);
UpdateContext({showMonth:false});
年のほうも同様です。これを入れておかないと、ユーザーが混乱する可能性がありますので、少しの気遣い、すてきなUIの精神で入れておきましょう。
完成したものがこちらになります。
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>#PowerApps Birthday Selector UI pic.twitter.com/KfJF0xY5QV
— Hiro (@mofumofu_dance) December 22, 2019
Power AppsでUIを作る場合、どうしても既定のパーツでは使いにくいシチュエーションが出てきます。 凝った仕組みとかアニメーションを入れる必要は全くないと思いますが、少しでも使いやすいようにする努力はローコードで早く作れる分、かけてもいい時間なのかなと思います。
ここではパーツごとに設定したプロパティを記載します。 テーブルなので、いささか長くなりますが、参考にしてみてください。
パーツ種 | パーツ名 | プロパティ名 | 値 |
---|---|---|---|
ボタン | btnDispMonth | Text | Upper(Text(dateSelected,"[$-en-US]mmm")) |
OnSelect | UpdateContext({visGalYear:false,visGalMonth:true,visGalDay:false}) | ||
ボタン | btnDispDay | Text | Day(dateSelected) |
OnSelect | UpdateContext({visGalYear:false,visGalMonth:false,visGalDay:true}) | ||
ボタン | btnDispYear | Text | Year(dateSelected) |
OnSelect | UpdateContext({visGalYear:true,visGalMonth:false,visGalDay:false}) | ||
ギャラリー | galMonth | Visible | visGalMonth |
Items | [1,2,3,4,5,6,7,8,9,10,11,12] | ||
ボタン | btnMonthSelect | Text | Upper(Text(Date(2000,ThisItem.Value,1),"[$-en-US]mmm")) |
OnSelect | If(Text(Date(Year(dateSelected),ThisItem.Value,Day(dateSelected)),"[$-en-US]yyyy/m/d")=Year(dateSelected)&"/"&ThisItem.Value&"/"&Day(dateSelected),Set(dateSelected,Date(Year(dateSelected),ThisItem.Value,Day(dateSelected))),Set(dateSelected,DateAdd(Date(Year(dateSelected),ThisItem.Value+1,1),-1,Days)));UpdateContext({visGalMonth:false});Reset(galMonth) | ||
ギャラリー | galDay | Visible | visGalDay |
Items | FirstN([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],DateDiff(Date(Year(dateSelected),Month(dateSelected),1),Date(Year(dateSelected),Month(dateSelected)+1,1))) | ||
ボタン | btnDaySelect | Text | ThisItem.Value |
OnSelect | Set(dateSelected,Date(Year(dateSelected),Month(dateSelected),ThisItem.Value));UpdateContext({visGalDay:false});Reset(galDay) | ||
ギャラリー | galYear | Visible | visGalYear |
Items | [2020,2019,2018,217,2016,2015,2014,2013,2012,2011]] | ||
ボタン | btnYearSelect | Text | ThisItem.Value |
OnSelect | If(Text(Date(ThisItem.Value,Month(dateSelected),Day(dateSelected)),"[$-en-US]yyyy/m/d")=ThisItem.Value&"/"&Month(dateSelected)&"/"&Day(dateSelected),Set(dateSelected,Date(ThisItem.Value,Month(dateSelected),Day(dateSelected))),Set(dateSelected,DateAdd(Date(ThisItem.Value,Month(dateSelected)+1,1),-1)));UpdateContext({visGalYear:false});Reset(galYear) |