Vues natives en XAML

Les vues natives iOS, Android et les plateforme Windows universelle peuvent être directement référencées à partir de Xamarin.Forms fichiers XAML. Les propriétés et les gestionnaires d’événements peuvent être définis sur des vues natives, et ils peuvent interagir avec Xamarin.Forms les vues. Cet article montre comment utiliser des vues natives à partir de Xamarin.Forms fichiers XAML.

Pour incorporer une vue native dans un Xamarin.Forms fichier XAML :

  1. Ajoutez une déclaration d’espace xmlns de noms dans le fichier XAML de l’espace de noms qui contient la vue native.
  2. Créez une instance de la vue native dans le fichier XAML.

Important

Le code XAML compilé doit être désactivé pour toutes les pages XAML qui utilisent des vues natives. Pour ce faire, décorez la classe code-behind de votre page XAML avec l’attribut [XamlCompilation(XamlCompilationOptions.Skip)] . Pour plus d’informations sur la compilation XAML, consultez Compilation XAML dans Xamarin.Forms.

Pour référencer une vue native à partir d’un fichier code-behind, vous devez utiliser un projet sap (Shared Asset Project) et encapsuler le code spécifique à la plateforme avec des directives de compilation conditionnelle. Pour plus d’informations, consultez Reportez-vous aux vues natives à partir du code.

Consommer des vues natives

L’exemple de code suivant illustre l’utilisation de vues natives pour chaque plateforme à un Xamarin.FormsContentPage:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
            Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        x:Class="NativeViews.NativeViewDemo">
    <StackLayout Margin="20">
        <ios:UILabel Text="Hello World" TextColor="{x:Static ios:UIColor.Red}" View.HorizontalOptions="Start" />
        <androidWidget:TextView Text="Hello World" x:Arguments="{x:Static androidLocal:MainActivity.Instance}" />
        <win:TextBlock Text="Hello World" />
    </StackLayout>
</ContentPage>

En plus de spécifier l’espace de noms et assembly pour clr-namespace un espace de noms d’affichage natif, il targetPlatform doit également être spécifié. Cela doit être défini sur iOS, , UWPAndroid, ( Windows qui est équivalent à UWP), macOS, GTK, , Tizenou WPF. Au moment de l’exécution, l’analyseur XAML ignore les préfixes d’espace de noms XML dont targetPlatform la plateforme ne correspond pas à la plateforme sur laquelle l’application s’exécute.

Chaque déclaration d’espace de noms peut être utilisée pour référencer n’importe quelle classe ou structure à partir de l’espace de noms spécifié. Par exemple, la ios déclaration d’espace de noms peut être utilisée pour référencer n’importe quelle classe ou structure à partir de l’espace de noms iOS UIKit . Les propriétés de la vue native peuvent être définies via XAML, mais les types de propriété et d’objet doivent correspondre. Par exemple, la UILabel.TextColor propriété est définie à UIColor.Red l’aide de l’extension x:Static de balisage et de l’espace ios de noms.

Les propriétés pouvant être liées et les propriétés pouvant être jointes peuvent également être définies sur des vues natives à l’aide de la Class.BindableProperty="value" syntaxe. Chaque vue native est encapsulée dans une instance spécifique NativeViewWrapper à la plateforme, qui dérive de la Xamarin.Forms.View classe. La définition d’une propriété pouvant être liée ou d’une propriété pouvant être liée sur une vue native transfère la valeur de propriété au wrapper. Par exemple, une disposition horizontale centrée peut être spécifiée en définissant View.HorizontalOptions="Center" sur la vue native.

Remarque

Notez que les styles ne peuvent pas être utilisés avec des vues natives, car les styles ne peuvent cibler que les propriétés sauvegardées par BindableProperty des objets.

Les constructeurs de widgets Android nécessitent généralement l’objet Android Context en tant qu’argument, et cela peut être rendu disponible via une propriété statique dans la MainActivity classe. Par conséquent, lors de la création d’un widget Android en XAML, l’objet Context doit généralement être transmis au constructeur du widget à l’aide de l’attribut x:Arguments avec une x:Static extension de balisage. Pour plus d’informations, consultez Passer des arguments aux vues natives.

Remarque

Notez que le nommage d’une vue native avec x:Name n’est pas possible dans un projet de bibliothèque .NET Standard ou dans un projet sap (Shared Asset Project). Cela génère une variable du type natif, ce qui entraîne une erreur de compilation. Toutefois, les vues natives peuvent être encapsulées dans ContentView des instances et récupérées dans le fichier code-behind, à condition qu’un SAP soit utilisé. Pour plus d’informations, consultez Reportez-vous à la vue native à partir du code.

Liaisons natives

La liaison de données est utilisée pour synchroniser une interface utilisateur avec sa source de données et simplifie l’affichage et l’interaction d’une Xamarin.Forms application avec ses données. À condition que l’objet source implémente l’interface INotifyPropertyChanged , les modifications apportées à l’objet source sont automatiquement envoyées à l’objet cible par l’infrastructure de liaison, et les modifications apportées à l’objet cible peuvent éventuellement être envoyées (push) à l’objet source .

Les propriétés des vues natives peuvent également utiliser la liaison de données. L’exemple de code suivant illustre la liaison de données à l’aide des propriétés des vues natives :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
            Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:local="clr-namespace:NativeSwitch"
        x:Class="NativeSwitch.NativeSwitchPage">
    <StackLayout Margin="20">
        <Label Text="Native Views Demo" FontAttributes="Bold" HorizontalOptions="Center" />
        <Entry Placeholder="This Entry is bound to the native switch" IsEnabled="{Binding IsSwitchOn}" />
        <ios:UISwitch On="{Binding Path=IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=ValueChanged}"
            OnTintColor="{x:Static ios:UIColor.Red}"
            ThumbTintColor="{x:Static ios:UIColor.Blue}" />
        <androidWidget:Switch x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
            Checked="{Binding Path=IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=CheckedChange}"
            Text="Enable Entry?" />
        <win:ToggleSwitch Header="Enable Entry?"
            OffContent="No"
            OnContent="Yes"
            IsOn="{Binding IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=Toggled}" />
    </StackLayout>
</ContentPage>

La page contient une Entry propriété dont IsEnabled la propriété est liée à la NativeSwitchPageViewModel.IsSwitchOn propriété. La BindingContext page est définie sur une nouvelle instance de la NativeSwitchPageViewModel classe dans le fichier code-behind, avec la classe ViewModel implémentant l’interface INotifyPropertyChanged .

La page contient également un commutateur natif pour chaque plateforme. Chaque commutateur natif utilise une TwoWay liaison pour mettre à jour la valeur de la NativeSwitchPageViewModel.IsSwitchOn propriété. Par conséquent, lorsque le commutateur est désactivé, il Entry est désactivé et lorsque le commutateur est activé, il Entry est activé. Les captures d’écran suivantes montrent cette fonctionnalité sur chaque plateforme :

Commutateur natif désactivéCommutateur natif activé

Les liaisons bidirectionnelle sont automatiquement prises en charge, à condition que la propriété native implémente INotifyPropertyChanged, ou prend en charge l’observation clé-valeur (KVO) sur iOS, ou est une DependencyProperty sur UWP. Toutefois, de nombreuses vues natives ne prennent pas en charge la notification de modification de propriété. Pour ces vues, vous pouvez spécifier une UpdateSourceEventName valeur de propriété dans le cadre de l’expression de liaison. Cette propriété doit être définie sur le nom d’un événement dans la vue native qui signale lorsque la propriété cible a changé. Ensuite, lorsque la valeur du commutateur natif change, la Binding classe est avertie que l’utilisateur a modifié la valeur du commutateur et que la valeur de propriété NativeSwitchPageViewModel.IsSwitchOn est mise à jour.

Passer des arguments aux vues natives

Les arguments du constructeur peuvent être passés à des vues natives à l’aide de l’attribut x:Arguments avec une x:Static extension de balisage. En outre, les méthodes de fabrique d’affichage natif (public static méthodes qui retournent des objets ou des valeurs du même type que la classe ou la structure qui définit les méthodes) peuvent être appelées en spécifiant le nom de la méthode à l’aide de l’attribut x:FactoryMethod et ses arguments à l’aide de l’attribut x:Arguments .

L’exemple de code suivant illustre les deux techniques :

<ContentPage ...
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidGraphics="clr-namespace:Android.Graphics;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:winMedia="clr-namespace:Windows.UI.Xaml.Media;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:winText="clr-namespace:Windows.UI.Text;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:winui="clr-namespace:Windows.UI;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows">
        ...
        <ios:UILabel Text="Simple Native Color Picker" View.HorizontalOptions="Center">
            <ios:UILabel.Font>
                <ios:UIFont x:FactoryMethod="FromName">
                    <x:Arguments>
                        <x:String>Papyrus</x:String>
                        <x:Single>24</x:Single>
                    </x:Arguments>
                </ios:UIFont>
            </ios:UILabel.Font>
        </ios:UILabel>
        <androidWidget:TextView x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
                    Text="Simple Native Color Picker"
                    TextSize="24"
                    View.HorizontalOptions="Center">
            <androidWidget:TextView.Typeface>
                <androidGraphics:Typeface x:FactoryMethod="Create">
                    <x:Arguments>
                        <x:String>cursive</x:String>
                        <androidGraphics:TypefaceStyle>Normal</androidGraphics:TypefaceStyle>
                    </x:Arguments>
                </androidGraphics:Typeface>
            </androidWidget:TextView.Typeface>
        </androidWidget:TextView>
        <winControls:TextBlock Text="Simple Native Color Picker"
                    FontSize="20"
                    FontStyle="{x:Static winText:FontStyle.Italic}"
                    View.HorizontalOptions="Center">
            <winControls:TextBlock.FontFamily>
                <winMedia:FontFamily>
                    <x:Arguments>
                        <x:String>Georgia</x:String>
                    </x:Arguments>
                </winMedia:FontFamily>
            </winControls:TextBlock.FontFamily>
        </winControls:TextBlock>
        ...
</ContentPage>

La UIFont.FromName méthode de fabrique est utilisée pour définir la UILabel.Font propriété sur un nouveau UIFont sur iOS. Le UIFont nom et la taille sont spécifiés par les arguments de méthode qui sont des enfants de l’attribut x:Arguments .

La Typeface.Create méthode de fabrique est utilisée pour définir la TextView.Typeface propriété sur un nouveau Typeface sur Android. Le Typeface nom de famille et le style sont spécifiés par les arguments de méthode qui sont des enfants de l’attribut x:Arguments .

Le FontFamily constructeur est utilisé pour définir la TextBlock.FontFamily propriété sur une nouvelle FontFamily propriété sur le plateforme Windows universelle (UWP). Le FontFamily nom est spécifié par l’argument de méthode qui est un enfant de l’attribut x:Arguments .

Remarque

Les arguments doivent correspondre aux types requis par le constructeur ou la méthode de fabrique.

Les captures d’écran suivantes montrent le résultat de la spécification des arguments de méthode et de constructeur de fabrique pour définir la police sur différentes vues natives :

Définition de polices sur des vues natives

Pour plus d’informations sur le passage d’arguments en XAML, consultez Passage d’arguments en XAML.

Reportez-vous aux vues natives à partir du code

Bien qu’il ne soit pas possible de nommer une vue native avec l’attribut x:Name , il est possible de récupérer une instance de vue native déclarée dans un fichier XAML à partir de son fichier code-behind dans un projet d’accès partagé, à condition que la vue native soit un enfant d’une ContentView valeur d’attribut x:Name . Ensuite, dans les directives de compilation conditionnelle dans le fichier code-behind, vous devez :

  1. Récupérez la valeur de la propriété et convertissez-la ContentView.Content en type spécifique NativeViewWrapper à la plateforme.
  2. Récupérez la propriété et convertissez-la NativeViewWrapper.NativeElement en type d’affichage natif.

L’API native peut ensuite être appelée sur la vue native pour effectuer les opérations souhaitées. Cette approche offre également l’avantage que plusieurs vues natives XAML pour différentes plateformes peuvent être des enfants du même ContentView. L’exemple de code suivant illustre cette technique :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
            Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:local="clr-namespace:NativeViewInsideContentView"
        x:Class="NativeViewInsideContentView.NativeViewInsideContentViewPage">
    <StackLayout Margin="20">
        <ContentView x:Name="contentViewTextParent" HorizontalOptions="Center" VerticalOptions="CenterAndExpand">
            <ios:UILabel Text="Text in a UILabel" TextColor="{x:Static ios:UIColor.Red}" />
            <androidWidget:TextView x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
                Text="Text in a TextView" />
              <winControls:TextBlock Text="Text in a TextBlock" />
        </ContentView>
        <ContentView x:Name="contentViewButtonParent" HorizontalOptions="Center" VerticalOptions="EndAndExpand">
            <ios:UIButton TouchUpInside="OnButtonTap" View.HorizontalOptions="Center" View.VerticalOptions="Center" />
            <androidWidget:Button x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
                Text="Scale and Rotate Text"
                Click="OnButtonTap" />
            <winControls:Button Content="Scale and Rotate Text" />
        </ContentView>
    </StackLayout>
</ContentPage>

Dans l’exemple ci-dessus, les vues natives pour chaque plateforme sont des enfants de ContentView contrôles, avec la x:Name valeur d’attribut utilisée pour récupérer le ContentView code-behind :

public partial class NativeViewInsideContentViewPage : ContentPage
{
    public NativeViewInsideContentViewPage()
    {
        InitializeComponent();

#if __IOS__
        var wrapper = (Xamarin.Forms.Platform.iOS.NativeViewWrapper)contentViewButtonParent.Content;
        var button = (UIKit.UIButton)wrapper.NativeView;
        button.SetTitle("Scale and Rotate Text", UIKit.UIControlState.Normal);
        button.SetTitleColor(UIKit.UIColor.Black, UIKit.UIControlState.Normal);
#endif
#if __ANDROID__
        var wrapper = (Xamarin.Forms.Platform.Android.NativeViewWrapper)contentViewTextParent.Content;
        var textView = (Android.Widget.TextView)wrapper.NativeView;
        textView.SetTextColor(Android.Graphics.Color.Red);
#endif
#if WINDOWS_UWP
        var textWrapper = (Xamarin.Forms.Platform.UWP.NativeViewWrapper)contentViewTextParent.Content;
        var textBlock = (Windows.UI.Xaml.Controls.TextBlock)textWrapper.NativeElement;
        textBlock.Foreground = new Windows.UI.Xaml.Media.SolidColorBrush(Windows.UI.Colors.Red);
        var buttonWrapper = (Xamarin.Forms.Platform.UWP.NativeViewWrapper)contentViewButtonParent.Content;
        var button = (Windows.UI.Xaml.Controls.Button)buttonWrapper.NativeElement;
        button.Click += (sender, args) => OnButtonTap(sender, EventArgs.Empty);
#endif
    }

    async void OnButtonTap(object sender, EventArgs e)
    {
        contentViewButtonParent.Content.IsEnabled = false;
        contentViewTextParent.Content.ScaleTo(2, 2000);
        await contentViewTextParent.Content.RotateTo(360, 2000);
        contentViewTextParent.Content.ScaleTo(1, 2000);
        await contentViewTextParent.Content.RelRotateTo(360, 2000);
        contentViewButtonParent.Content.IsEnabled = true;
    }
}

La ContentView.Content propriété est accessible pour récupérer la vue native encapsulée en tant qu’instance spécifique à NativeViewWrapper la plateforme. La NativeViewWrapper.NativeElement propriété est ensuite accessible pour récupérer la vue native en tant que type natif. L’API de la vue native est ensuite appelée pour effectuer les opérations souhaitées.

Les boutons natifs iOS et Android partagent le même OnButtonTap gestionnaire d’événements, car chaque bouton natif consomme un EventHandler délégué en réponse à un événement tactile. Toutefois, l’plateforme Windows universelle (UWP) utilise à son tour un gestionnaire d’événements distinctRoutedEventHandler, qui consomme à son tour le OnButtonTap gestionnaire d’événements dans cet exemple. Par conséquent, lorsqu’un bouton natif est cliqué, le OnButtonTap gestionnaire d’événements s’exécute, ce qui met à l’échelle et fait pivoter le contrôle natif contenu dans le ContentView nom contentViewTextParent. Les captures d’écran suivantes illustrent ce problème sur chaque plateforme :

ContentView contenant un contrôle natif

Vues natives de sous-classes

De nombreuses vues natives iOS et Android ne conviennent pas à l’instanciation en XAML, car elles utilisent des méthodes, plutôt que des propriétés, pour configurer le contrôle. La solution à ce problème consiste à sous-classer des vues natives dans des wrappers qui définissent une API plus conviviale XAML qui utilise des propriétés pour configurer le contrôle et qui utilise des événements indépendants de la plateforme. Les vues natives encapsulées peuvent ensuite être placées dans un projet sap (Shared Asset Project) et entourées de directives de compilation conditionnelle, ou placées dans des projets spécifiques à la plateforme et référencées à partir de XAML dans un projet de bibliothèque .NET Standard.

L’exemple de code suivant illustre une Xamarin.Forms page qui consomme des vues natives sous-classifiées :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
        xmlns:iosLocal="clr-namespace:SubclassedNativeControls.iOS;assembly=SubclassedNativeControls.iOS;targetPlatform=iOS"
        xmlns:android="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SimpleColorPicker.Droid;assembly=SimpleColorPicker.Droid;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:SubclassedNativeControls.Droid;assembly=SubclassedNativeControls.Droid;targetPlatform=Android"
        xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255,
            Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
        xmlns:local="clr-namespace:SubclassedNativeControls"
        x:Class="SubclassedNativeControls.SubclassedNativeControlsPage">
    <StackLayout Margin="20">
        <Label Text="Subclassed Native Views Demo" FontAttributes="Bold" HorizontalOptions="Center" />
        <StackLayout Orientation="Horizontal">
          <Label Text="You have chosen:" />
          <Label Text="{Binding SelectedFruit}" />      
        </StackLayout>
        <iosLocal:MyUIPickerView ItemsSource="{Binding Fruits}"
            SelectedItem="{Binding SelectedFruit, Mode=TwoWay, UpdateSourceEventName=SelectedItemChanged}" />
        <androidLocal:MySpinner x:Arguments="{x:Static androidLocal:MainActivity.Instance}"
            ItemsSource="{Binding Fruits}"
            SelectedObject="{Binding SelectedFruit, Mode=TwoWay, UpdateSourceEventName=ItemSelected}" />
        <winControls:ComboBox ItemsSource="{Binding Fruits}"
            SelectedItem="{Binding SelectedFruit, Mode=TwoWay, UpdateSourceEventName=SelectionChanged}" />
    </StackLayout>
</ContentPage>

La page contient un Label qui affiche les fruits choisis par l’utilisateur à partir d’un contrôle natif. L’objet Label est lié à la SubclassedNativeControlsPageViewModel.SelectedFruit propriété. La BindingContext page est définie sur une nouvelle instance de la SubclassedNativeControlsPageViewModel classe dans le fichier code-behind, avec la classe ViewModel implémentant l’interface INotifyPropertyChanged .

La page contient également un affichage sélecteur natif pour chaque plateforme. Chaque vue native affiche la collection de fruits en liant sa ItemSource propriété à la SubclassedNativeControlsPageViewModel.Fruits collection. Cela permet à l’utilisateur de choisir un fruit, comme illustré dans les captures d’écran suivantes :

Vues natives sous-classifiées

Sur iOS et Android, les sélecteurs natifs utilisent des méthodes pour configurer les contrôles. Par conséquent, ces sélecteurs doivent être sous-classés pour exposer les propriétés pour les rendre compatibles XAML. Sur le plateforme Windows universelle (UWP), il ComboBox est déjà convivial en XAML, et ne nécessite donc pas de sous-classe.

iOS

L’implémentation iOS sous-classe la UIPickerView vue et expose les propriétés et un événement qui peut être facilement consommé à partir de XAML :

public class MyUIPickerView : UIPickerView
{
    public event EventHandler<EventArgs> SelectedItemChanged;

    public MyUIPickerView()
    {
        var model = new PickerModel();
        model.ItemChanged += (sender, e) =>
        {
            if (SelectedItemChanged != null)
            {
                SelectedItemChanged.Invoke(this, e);
            }
        };
        Model = model;
    }

    public IList<string> ItemsSource
    {
        get
        {
            var pickerModel = Model as PickerModel;
            return (pickerModel != null) ? pickerModel.Items : null;
        }
        set
        {
            var model = Model as PickerModel;
            if (model != null)
            {
                model.Items = value;
            }
        }
    }

    public string SelectedItem
    {
        get { return (Model as PickerModel).SelectedItem; }
        set { }
    }
}

La MyUIPickerView classe expose et SelectedItem répertorie ItemsSource les propriétés et un SelectedItemChanged événement. Un UIPickerView modèle de données sous-jacent UIPickerViewModel est requis, accessible par les propriétés et l’événement MyUIPickerView . Le UIPickerViewModel modèle de données est fourni par la PickerModel classe :

class PickerModel : UIPickerViewModel
{
    int selectedIndex = 0;
    public event EventHandler<EventArgs> ItemChanged;
    public IList<string> Items { get; set; }

    public string SelectedItem
    {
        get
        {
            return Items != null && selectedIndex >= 0 && selectedIndex < Items.Count ? Items[selectedIndex] : null;
        }
    }

    public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
    {
        return Items != null ? Items.Count : 0;
    }

    public override string GetTitle(UIPickerView pickerView, nint row, nint component)
    {
        return Items != null && Items.Count > row ? Items[(int)row] : null;
    }

    public override nint GetComponentCount(UIPickerView pickerView)
    {
        return 1;
    }

    public override void Selected(UIPickerView pickerView, nint row, nint component)
    {
        selectedIndex = (int)row;
        if (ItemChanged != null)
        {
            ItemChanged.Invoke(this, new EventArgs());
        }
    }
}

La PickerModel classe fournit le stockage sous-jacent pour la MyUIPickerView classe, via la Items propriété. Chaque fois que l’élément sélectionné dans les MyUIPickerView modifications, la Selected méthode est exécutée, ce qui met à jour l’index sélectionné et déclenche l’événement ItemChanged . Cela garantit que la SelectedItem propriété retourne toujours le dernier élément sélectionné par l’utilisateur. En outre, la PickerModel classe remplace les méthodes utilisées pour configurer l’instance MyUIPickerView .

Android

L’implémentation Android sous-classe la Spinner vue et expose les propriétés et un événement qui peut être facilement consommé à partir de XAML :

class MySpinner : Spinner
{
    ArrayAdapter adapter;
    IList<string> items;

    public IList<string> ItemsSource
    {
        get { return items; }
        set
        {
            if (items != value)
            {
                items = value;
                adapter.Clear();

                foreach (string str in items)
                {
                    adapter.Add(str);
                }
            }
        }
    }

    public string SelectedObject
    {
        get { return (string)GetItemAtPosition(SelectedItemPosition); }
        set
        {
            if (items != null)
            {
                int index = items.IndexOf(value);
                if (index != -1)
                {
                    SetSelection(index);
                }
            }
        }
    }

    public MySpinner(Context context) : base(context)
    {
        ItemSelected += OnBindableSpinnerItemSelected;

        adapter = new ArrayAdapter(context, Android.Resource.Layout.SimpleSpinnerItem);
        adapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);
        Adapter = adapter;
    }

    void OnBindableSpinnerItemSelected(object sender, ItemSelectedEventArgs args)
    {
        SelectedObject = (string)GetItemAtPosition(args.Position);
    }
}

La MySpinner classe expose et SelectedObject répertorie ItemsSource les propriétés et un ItemSelected événement. Les éléments affichés par la MySpinner classe sont fournis par l’élément Adapter associé à la vue, et les éléments sont renseignés dans le moment où la ItemsSource propriété est définie pour la Adapter première fois. Chaque fois que l’élément sélectionné dans la MySpinner classe change, le OnBindableSpinnerItemSelected gestionnaire d’événements met à jour la SelectedObject propriété.