 
Xamarin逆引きTips
Xamarin.Formsの既存のコントロールを拡張するには?
Xamarin.Formsのコントロールにはプラットフォーム共通の基本的な機能しか含まれていない。既存のコントロールを拡張して、ネイティブ側で機能を追加する方法を解説。
Xamarin.Formsには40個以上のコントロールが用意されており、基本的な機能はカバーしているが、クロスプラットフォームに対応するために、最大公約数的な機能しか提供していない。今回は、既存のコントロールを拡張して、ネイティブ側で機能を追加する方法を解説する。
1. シナリオ
 Xamarin.FormsのButtonコントロールは、クリック(Click)イベントは提供されているものの、“長押し”した時のイベントは提供されていない。このButtonに長押し時のLongTapイベントを追加する。
2. Xamarin.Formsプロジェクトを作成する
メニューバーの[ファイル]-[新規]-[ソリューション]から表示したダイアログで、[C#]-[Mobile Apps]-[Blank App (Xamarin.Forms Portable)]を選択し、ソリューション名を「LongTapButtonSample」として[OK]ボタンを押す。
 LongTapButtonSampleプロジェクトにLongTapButton.csファイルを追加し、以下のコードを追記する。
| public class LongTapButton : Xamarin.Forms.Button {   public event EventHandler LongTap;   public void OnLongTap()   {     if (this.LongTap != null)     {       this.LongTap(this, new EventArgs());     }   } } | 
 このコードでは、Xamarin.FormsのButtonクラスを拡張してLongTapButtonクラスを作成し、イベントLongTapと、それを発生させるためのメソッドOnLongTapを実装している。
 本稿のサンプルでは、ボタンを1つ配置したページを作成する。そこでApp.csファイルには、LongTapButtonコントロールを配置するページを実装する。具体的には以下のコードのように修正する。
| public class App {   public static Page GetMainPage()   {        var button = new LongTapButton     {       Text = "Long tap me",       VerticalOptions = LayoutOptions.Center,       HorizontalOptions = LayoutOptions.Fill,     };     button.LongTap += (sender, e) =>        button.Text = "Long tapped!";     return new ContentPage     {        Content = button     };   } } | 
これで、プラットフォーム側を実装すれば、ボタンを長押しするとボタン名が変更される。
3. Xamarin.Formsパッケージを更新する
Xamarin.Formsのソリューションを作成すると、各プロジェクトにXamarin.Formsパッケージが読み込まれるが、作成直後はバージョンが古い(執筆時点で「1.0.6186」)ので更新する。特にここで解説するコントロールの拡張に関連するクラスは、古いパッケージでは動作しないので、必ず行う必要がある。
Xamarin Studioのメニューバーの[プロジェクト]-[Update Packages]を選択すると(図1)、ソリューションに含まれる全てのパッケージが更新される。現在のバージョンは「1.2.1.6229」だ(図2)。

4. AndroidでLongTapButtonの機能を実装する
 LongTapButtonSample.Androidプロジェクトに、LongButtonRenderer.csファイルを追加し、以下のコードのように実装する。
| using System; using Xamarin.Forms.Platform.Android; using Xamarin.Forms; using LongTapButtonSample; using LongTapButtonSample.Android; [assembly:ExportRenderer(   typeof(LongTapButton),    typeof(LongTapButtonRenderer))] // ←2 namespace LongTapButtonSample.Android {   public class LongTapButtonRenderer : ButtonRenderer // ←1   {     protected override void OnElementChanged(ElementChangedEventArgs<Button> e)      {       base.OnElementChanged(e);       var formsButton = e.NewElement as LongTapButton; // ←3       var droidButton = this.Control;       droidButton.LongClick += (sender, _) => // ←4         formsButton.OnLongTap();       }   } } | 
 Xamarin.Formsの描画は、各コントロールに用意されているRendererによって行われており、Buttonの場合はButtonRendererがその役割を担う。1で、ButtonRendererクラスを拡張してLongTapButtonRendererクラスとして、Androidでの機能実装を行う。
 2が特に重要な要素で、このExportRenderer属性によって、「LongTapButtonコントロールの描画にはLongTapButtonRendererクラスを使用する」と定義している。Xamarin.Formsのフレームワークはこの属性を読み、コントロールの描画を委譲する。
 3を見ると分かるように、OnElementChangedメソッドのパラメーターであるElementChangedEventArgs<Button>オブジェクトのNewElementプロパティで、Xamarin.Forms側のコントロールが取得できる。ButtonRendererなのでButton型のオブジェクトが取得されるが、2の定義により、その実体は必ずLongTapButton型である。そして、Android側のボタン(Android.Widget.Button)は、ButtonRendererオブジェクト(=this)のControlプロパティより取得できる。
 Android.Widget.ButtonにはLongClickイベントが備わっているので、このイベントハンドラーでLongTapButtonコントロールのLongTapイベントを発生させる(4)。
ここまでのプログラムをAndroidで実行し、ボタンを長押しすると、次の画面のようになる。

5. iOSでLongTapButtonの機能を実装する
 iOS側も実装もAndroid側と同じ要領だ。LongTapButtonSample.iOSプロジェクトに、LongButtonRenderer.csファイルを追加し、以下のコードのように実装する。
| using System; using Xamarin.Forms.Platform.iOS; using Xamarin.Forms; using LongTapButtonSample; using LongTapButtonSample.iOS; using MonoTouch.UIKit; [assembly:ExportRenderer(typeof(LongTapButton), typeof(LongTapButtonRenderer))] namespace LongTapButtonSample.iOS {   public class LongTapButtonRenderer : ButtonRenderer   {     protected override void OnElementChanged(ElementChangedEventArgs<Button> e)      {       base.OnElementChanged(e);       var formsButton = e.NewElement as LongTapButton;       var iosButton = this.Control;        iosButton.AddGestureRecognizer(new UILongPressGestureRecognizer(x =>        {         if (x.State == UIGestureRecognizerState.Recognized)          {           formsButton.OnLongTap();         }       }));     }   } } | 
 Android側とほとんど同じコードである。異なるのはiOSなのでButtonRenderer.Controlプロパティから取得できるのがUIButton型のオブジェクトであること、UIButtonにはLongClickのようなイベントがないので、UILongPressGestureRecognizerクラスを使って長押しを検知していることだ。
ここまでのプログラムをiOSで実行し、ボタンを長押しすると、次の画面のようになる。

まとめ
Rendererを拡張することで、Xamarin.Formsの既存のコントロールを容易に拡張できる。このRendererの仕組みは、新しいコントロールを作成したり、画面(Page)すらも制御したりできる。これらの方法は次回以降、取り上げていきたい。
他のクロスプラットフォーム開発ツールでも、既存の部品に足りない機能をネイティブ側に委譲する仕組みは存在するが、Java言語やObjective-C言語など、ネイティブの開発言語・開発ツールで作成する必要があり、アプリと共にデバッグすることができない(また、アプリとの「つなぎ」が一番トラブルになりやすい)。Xamarin.Formsは、Xamarin.Android、Xamarin.iOSが提供するネイティブSDKの.NETラッパーが利用できるので、同じ言語で、同じソリューション内でデバッグしながら開発できるのが大きなメリットだ。
コントロールの拡張に関しての公式な解説は、「Customizing Controls for Each Platform | Xamarin(英語)」を参照されたい。
なお、公式ドキュメントに「The renderer APIs are not yet final」とある通り、今後、APIが変更される可能性があることを留意いただきたい。
※以下では、本稿の前後を合わせて5回分(第10回~第14回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
 
10. Xamarin.Formsでカスタムダイアログを表示するには?(MessagingCenter利用)
Xamarin.Formsで共通のAPIが提供されているダイアログ(=iOSのUIAlertView/AndroidのAlertDialog)以外のプラットフォーム個別のダイアログを表示する方法を解説。その呼び出しにはMessagingCenterが利用できる。
 
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デザイナーを使用して行う方法を解説。また、コードで画面遷移を実装する方法も説明する。





 
 
