Syncfusion SfCalendarを使う(Xamarin Forms)

1. はじめに

Xamarin Formsで、Syncfusion SfCalendarを使って以下のようなサンプル実装を行いました。

f:id:daigo-knowlbo:20180328013715p:plain:w300
f:id:daigo-knowlbo:20180328020349p:plain:w300

  • 月カレンダー形式で予定を表示する
  • 日をクリックすると、対象日のスケジュール詳細(件名)が表示される
  • スワイプもしくはボタンクリックで前後の月に移動できる
  • Prismを使ってMVVMアーキテクチャとする

2. 実装手順

2.1. VS 2017でプロジェクト作成

Visual Studio 2017でPrism Blank App(Xamarin.Forms)プロジェクトを作成。
プロジェクト名は「UseSfCalendarWithPrism」としました。

f:id:daigo-knowlbo:20180328014042p:plain

2.2. Nugetパッケージ管理でSyncfusion SfCalendarを追加

XamarinNugetパッケージ管理で「Syncfusion.Xamarin.SfCalendar」をインストール。

f:id:daigo-knowlbo:20180328014001p:plain

2.3. 実装

で、実装は以下に置きました。。。。

github.com

以下にサンプル実装のポイントを・・・

ポイント1 カレンダーの月変更イベントをViewModelにバインド

カレンダーの月変更イベントは SfCalendar.MonthChanged です。
MVVMとしているのでイベントをViewMode(MainPageViewModel)で受け取りたいです。
その為、Prismの「EventToCommandBehavior」を利用して、イベントをView→ViewModelにコマンドとして伝播させています。

[MainPage.xaml]
<ContentPage ...省略
                        xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms" />
<SfCal:SfCalendar x:Name="calendar" 
                  ShowInlineEvents="True"
                  MinDate="{Binding MinDate}"
                  MaxDate="{Binding MaxDate}"
                  BlackoutDates="{Binding BlackoutDates}"
                  DataSource="{Binding CalendarEventCollection}"
                  SelectionMode="{Binding SelectionMode, Mode=OneWay,Converter={StaticResource SelectionModeConverter}}">
    <SfCal:SfCalendar.Behaviors>
        <b:EventToCommandBehavior EventName="MonthChanged" 
                              Command="{Binding MonthChangedCommand}"
                              EventArgsParameterPath="args.CurrentValue" />
    </SfCal:SfCalendar.Behaviors>
</SfCal:SfCalendar>
[MainPageViewModel.cs]

public class MainPageViewModel : ViewModelBase
{
  ...省略
  public ICommand MonthChangedCommand => new Command<DateTime>((currentDate) =>
  {
    this.UpdateEvents(currentDate);
  });
}

ポイント2 カレンダーにバインドするイベントコレクション作成

SfCalendarに表示するイベントは「SfCalendar.DataSourceプロパティ」に設定しますが、PrismでViewModelにバインドしているので「MainPageViewModel.CalendarEventCollectionプロパティ」にデータバインドしています。
カレンダーの月変更イベントに対して当該月+前後一週間のイベントをバインドデータに設定しています。

[MainPageViewModel.cs]
  public class MainPageViewModel : ViewModelBase
  {
    
    /// <summary>
    /// イベントを更新します。
    /// </summary>
    /// <remarks>
    /// 今月のイベントを表示するために、対象月+前後一週間のイベントをコレクションに設定します。
    /// 1ヶ月のカレンダーの前後に 前月・次月 の日付が表示されるため、前後1週間のイベントを設定します。
    /// </remarks>
    /// <param name="calendarDate"></param>
    private void UpdateEvents(DateTime calendarDate)
    {
      // バインド対象のthis.CalendarEventCollectionを直接、繰り返しAdd()するとパフォーマンスが著しく落ちるのでテンポラリにデータコレクションを用意して差し替える
      CalendarEventCollection newCalendarEventCollection = new CalendarEventCollection();

      DateTime dt = new DateTime(calendarDate.Year, calendarDate.Month, 1);
      int thisMonthLastDay = dt.AddMonths(1).AddDays(-1).Day;
      for (int i = -7; i < thisMonthLastDay+7; i++)
      { // イベントはサンプルなので適当に2日に1回散歩と仕事、毎日のランチを設定
        DateTime eventDt = dt.AddDays(i);

        if (eventDt.Day % 2 == 1)
        {
          //this.calendarEventCollection.Add(
          newCalendarEventCollection.Add(
          new CalendarInlineEvent()
          {
            Subject = $"{eventDt.Day}日 散歩",
            StartTime = eventDt.AddHours(10),
            EndTime = eventDt.AddHours(11),
            Color = Color.Green
          });
        }
        //this.calendarEventCollection.Add(
        newCalendarEventCollection.Add(
          new CalendarInlineEvent()
          {
            Subject = $"{eventDt.Day}日 ランチ",
            StartTime = eventDt.AddHours(12),
            EndTime = eventDt.AddHours(13),
            Color = Color.Orange
          });
        if(eventDt.Day % 2 == 0)
        {
          //this.calendarEventCollection.Add(
          newCalendarEventCollection.Add(
            new CalendarInlineEvent()
            {
              Subject = $"{eventDt.Day}日 仕事",
              StartTime = eventDt.AddHours(10),
              EndTime = eventDt.AddHours(19),
              Color = Color.Blue
            });
        }
      }
      this.CalendarEventCollection.Clear();
      this.CalendarEventCollection = newCalendarEventCollection;
    }
  }

つまずいた点

2018/3/28現在版 Syncfusion SfCalendar(android)に不具合がありました。
イベントを設定しているのに画面上で、対象日をクリックすると「No Appointments」と表示されてしまう不具合でした。
twitterでつぶやいたところSyncfusionの方よりリプライを頂き、即座にパッチアッセンブリを頂き不具合の修正を確認できました。
アップデートとしては2018年3月末の「2018 Vol 1 SP1」で対応されるとのことです。

SfCalendarへの要望

表示形式が「YearViewとMonthView」の2つなのですが、Weeklyとかandroidのカレンダーみたいな形式とか、色々なバリエーションがあればもっと嬉しいなぁ、と思いました。

※今日のブログ、雑ですね。。。まあ、動くソースをgithubに置いたので、「おかしいぞ!分からんぞ!」と思った方はコメント頂ければと思いますm( )m