 
Xamarin逆引きTips
Xamarin.Formsで新しいコントロールを作成するには?
Xamarin.Formsでは、既存のコントロールを拡張できるだけでなく、全く新しいコントロールを作成することもできる。その内部には、iOS/Androidで違うコントロールを含めたりできる。その作成方法を解説。
前回は、既存のコントロールを拡張する方法を解説したが、今回は、Xamarin.Formsに、全く新しいコントロールを作成する方法を解説する。
1. シナリオ
Xamarin.Formsで、「カレンダー」を表示するMyCalendarコントロールを作成する。カレンダーの表示に、Androidでは標準のCalenderViewウィジェットを使用する。iOSには標準のカレンダー部品が無いので、Xamarinコンポーネントの「TimesSquare Calendar」を使用する。
2. Xamarin.Formsプロジェクトを作成する
メニューバーの[ファイル]-[新規]-[ソリューション]から表示したダイアログで、[C#]-[Mobile Apps]-[Blank App (Xamarin.Forms Portable)]を選択し、ソリューション名を「CalendarSample」として[OK]ボタンを押す。
MyCalendarコントロールの作成
 BarChartSampleプロジェクトにMyCalendar.csファイルを追加し、以下のコードのように、日付の取得/設定用のプロパティを持ったコントロール(=ビュー)を作成する。
| using System; using Xamarin.Forms; public class MyCalendar : View // <--1 {   public static readonly BindableProperty CurrentDateProperty = // <--3     BindableProperty.Create<MyCalendar, DateTime>        (p => p.CurrentDate, DateTime.Now);   public DateTime CurrentDate  // <--2   {     get { return (DateTime)GetValue (CurrentDateProperty); }     set { SetValue (CurrentDateProperty, value); }   } } | 
 Xamarin.Formsの新しいコントロールを作成するには、Xamarin.Forms.Viewクラスから派生したクラスを作る(1)。
 カレンダーを表示するためのMyCalendarクラスは、設定した日付を示すCurrentDateプロパティを持つ(2)。
 3で定義しているBindablePropertyとは、WPFでいうところの依存関係プロパティ(DependencyProperty)に相当するものであるが、説明が長くなってしまうので「プロパティの変更を検知できるようにするための記述」という説明に留めておく。
MyCalendarコントロールを配置したページの作成
 本稿のサンプルでは、エディットボックス(=Entryコントロール)とボタン(=Buttonコントロール)、そして上記のMyCalendarコントロールを配置したページを作成する。そこでApp.csファイルは、以下のコードのように修正する。
| ……省略…… public class App {   public static Page GetMainPage()   {        var entryDate = new Entry { };     var buttonApply = new Button { Text = "設定" };     var calendar = new MyCalendar     {       VerticalOptions = LayoutOptions.FillAndExpand,     };     buttonApply.Clicked += (sender, e) =>      {       DateTime d;        if (DateTime.TryParse(entryDate.Text, out d)) // <--1       {         calendar.CurrentDate = d;       }     };     return new NavigationPage(new ContentPage     {       Content = new StackLayout       {         Padding = new Thickness(0,            Device.OnPlatform(20, 0, 0),  // <--2           0, 0),         VerticalOptions = LayoutOptions.Fill,         Orientation = StackOrientation.Vertical,         Children =          {           entryDate,           buttonApply,           calendar         }       }     });   } } | 
 Entry、Button、MyCalendarを、StackLayoutを使って縦に並べている。
 Entryコントロールには、「7/29/2014」のような日付文字列が入力されることを想定していて、1では、入力された文字列を日付型に変換し、MyCalendarオブジェクトのCurrentDateプロパティに設定している。
 2で使用しているDevice.OnPlatform()メソッドは、実行されるプラットフォームに応じて値を変えたいときに使う関数だ。iOSにはステータスバーの高さを考慮する必要があるので、第1引数に「20」を設定している。第2、第3引数はそれぞれ、Android用、Windows Phone用を示す。
3.Xamarin.Formsパッケージを更新する
「Tips: Xamarin.Formsの既存のコントロールを拡張するには?」と同じように、Xamarin.Formsパッケージの更新を行う。執筆時点で最新のパッケージは「1.2.1.6229」だ。
4. AndroidでMyCalendarコントロールの機能を実装する
 CalendarSample.Androidプロジェクトに、MyCalendarRenderer.csファイルを追加し、以下のコードのように実装する。
| using System; using Android.Widget; using Xamarin.Forms.Platform.Android; using Xamarin.Forms; using CalendarSample; using CalendarSample.Android; using System.ComponentModel; [assembly:ExportRenderer(   typeof(MyCalendar),    typeof(MyCalendarRenderer))] // <--1 namespace CalendarSample.Android {   public class MyCalendarRenderer : ViewRenderer<MyCalendar, CalendarView>//2   {     protected override void OnElementChanged(       ElementChangedEventArgs<MyCalendar> e)     {       base.OnElementChanged(e);       SetNativeControl(new CalendarView(this.Context));     }     protected override void OnElementPropertyChanged( // <--3       object sender,        PropertyChangedEventArgs e)     {       base.OnElementPropertyChanged(sender, e);       if (this.Element == null || this.Control == null)       {         return;       }       if (e.PropertyName == MyCalendar.CurrentDateProperty.PropertyName)        {         var start = new DateTime(1970, 1, 1); // <--4         var diff = Element.CurrentDate - start;         Control.Date = Convert.ToInt64(diff.TotalMilliseconds);       }     }      } } | 
 Xamarin.Forms用に新しいコントロールを実装したいときも、ExportRenderer属性による定義を行う(1)(※ExportRenderer属性については、前回のTipsを参照)。
 また、MyCalendarの親クラスはViewなので、ViewRendererから派生したクラスを作成する。1番目の型引数はMyCalendar、2番目の型引数はAndroidのCalendarViewを指定する。これにより、「MyCalendarの実装はCalendarViewである」と定義している(2)。
 3は、MyCalendarコントロールのプロパティ値が変更されたときに呼び出されるメソッドである。値が変更されたプロパティの名前を、そのメソッドの引数であるPropertyChangedEventArgsオブジェクトのPropertyNameから判定し、それに対するネイティブ側の任意の処理を記述する。
 ここでは、MyCalendarコントロールのCurrentDateプロパティ値が変更されたら、Android側のCalendarView.Dateプロパティの値を変更するが、Javaの日付は「1970/1/1」からの経過時間(ミリ秒)であるので、4のような処理をしている。
Androidでのプログラムの実行
ここまでのプログラムをAndroidで実行し、ボタンを長押しすると、次の画面のようになる。
エディットボックスに「6/10/2011」などの日付文字列を入力し、[設定]ボタンを押すと、カレンダーがその日付まで移動する。
5. iOSにカレンダーを表示するコンポーネントを導入する
iOSには、標準のカレンダー部品がないので、Xamarin Componentにあるカレンダー部品の1つである「TimesSquare Calendar」を使う。
CalendarSample.iOSプロジェクトの[Components]フォルダーの右クリックメニューから[Get More Components]を選択し、それにより表示される[Xamarin Components]画面から「Calendar」と検索する。あとは以下の画面の手順で、検索でヒットした「TimesSquare Calendar」を選択し、[Add to App]ボタンをクリックして、コンポーネントをプロジェクトに追加する。
6. iOSでMyCalendarコントロールの機能を実装する
 iOS側の実装もAndroid側と同じ要領だ。CalendarSample.iOSプロジェクトに、MyCalendarRenderer.csファイルを追加し、以下のコードのように実装する。
| using System; using Xamarin.Forms.Platform.iOS; using System.ComponentModel; using CalendarSample; using Xamarin.Forms; using CalendarSample.iOS; using TimesSquare.iOS; using MonoTouch.Foundation; using MonoTouch.UIKit; [assembly:ExportRenderer(   typeof(MyCalendar),    typeof(MyCalendarRenderer))] namespace CalendarSample.iOS {   public class MyCalendarRenderer : ViewRenderer<MyCalendar, TSQCalendarView>   {     protected override void OnElementChanged(       ElementChangedEventArgs<MyCalendar> e)     {       base.OnElementChanged(e);       SetNativeControl(new TSQCalendarView(this.Frame)       {         Calendar = new NSCalendar (NSCalendarType.Gregorian),         FirstDate = new DateTime(2000, 1, 1), // <--1         LastDate = new DateTime(2020, 12, 31),         BackgroundColor = UIColor.LightTextColor,         PagingEnabled = true       });     }     protected override void OnElementPropertyChanged(       object sender,        PropertyChangedEventArgs e)     {       base.OnElementPropertyChanged(sender, e);       if (this.Element == null || this.Control == null)       {         return;       }       if (e.PropertyName == MyCalendar.CurrentDateProperty.PropertyName)        {         var start = new DateTime(1970, 1, 1);         var diff = Element.CurrentDate - start;         var date = NSDate.FromTimeIntervalSince1970(diff.TotalSeconds); // 2         Control.SelectedDate = date;       }     }   } } | 
 CalendarViewの代わりにTSQCalendarViewを使うこと以外は、Android側とほとんど同じコードである。
1はiOSというよりTimesSquare Calendarの仕様で、カレンダーの開始/終了の日付を設定している。この範囲外の日付を設定すると例外が発生するので注意が必要だ。
 2では、MyCalendarコントロールのCurrentDateプロパティ値が変更されたときに、TSQCalendarView.SelectedDateを設定している。iOSでは、NSDate.FromTimeIntervalSince1970メソッドで、「1970/1/1」からの経過秒数を指定して、System.DateTime型をNSDate型に変換している。
iOSでのプログラムの実行
ここまでのプログラムをiOSで実行し、ボタンを長押しすると、次の画面のようになる。

エディットボックスに「6/10/2011」などの日付文字列を入力し、設定ボタンを押すと、カレンダーがその日付まで移動する。Android版と同じように動作するはずだ。
まとめ
ViewRendererクラスを拡張することで、新しいコントロールを作成する方法を解説した。Xamarin.Formsで提供されるMapsコントロールは、iOSではAppleマップ、AndroidではGoogleマップ、Windows PhoneではBingマップがそれぞれ使用されるが、この手法を使って開発されているはずだ。
また、「Xamarin Forms Labs」というプロジェクトでは、標準に含まれないコントロールを拡充している。この中にもカレンダーコントロールが含まれているので、参考になるだろう。
他には、Xamarinの公式ブログ(英語)にも、
- Using Custom Controls in Xamarin.Forms on Android
- Using Custom Controls in Xamarin.Forms on Windows Phone | Xamarin Blog
などの投稿があるので、これらも参考にされたい。
※以下では、本稿の前後を合わせて5回分(第11回~第15回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
 
11. Xamarin Studio(Mac版)で複数のソリューションを開く/複数起動するには?
Macで開発中に、複数のソリューションをXamarin Studioで開く方法と、複数のXamarin Studioを立ち上げる方法を説明する。
 
12. Xamarin.Formsの既存のコントロールを拡張するには?
Xamarin.Formsのコントロールにはプラットフォーム共通の基本的な機能しか含まれていない。既存のコントロールを拡張して、ネイティブ側で機能を追加する方法を解説。
 
13. 【現在、表示中】≫ Xamarin.Formsで新しいコントロールを作成するには?
Xamarin.Formsでは、既存のコントロールを拡張できるだけでなく、全く新しいコントロールを作成することもできる。その内部には、iOS/Androidで違うコントロールを含めたりできる。その作成方法を解説。
 
14. Xamarin.iOSで画面遷移を行うには?(Storyboard使用)
Xamarin.iOSでのStoryboardによる画面遷移を、Xamarin StudioのiOSデザイナーを使用して行う方法を解説。また、コードで画面遷移を実装する方法も説明する。
 
15. Xamarin.iOSで画面遷移先にデータを渡すには?(Storyboard使用)
Xamarin.iOSでStoryboardにより画面遷移を行う際に、遷移元から遷移先にデータを渡す方法を解説する。





 
  
 ![[Xamarin Components]画面: 「TimesSquare Calendar」を選択](https:///re.buildinsider.net/mobile/xamarintips/0013/03_s.gif) 
 ![[Xamarin Components]画面: [Add to App]ボタンをクリック](https:///re.buildinsider.net/mobile/xamarintips/0013/04_s.gif) 
 
