Connect(); 2016

Volume 31, numéro 12

Cet article a fait l'objet d'une traduction automatique.

Développement mobile - Incorporation de vues natives dans vos applications Xamarin.Forms

Par Charles Petzold

Lorsque Xamarin.Forms a vu le jour il y a moins de trois ans, les programmeurs d’applications reconnu immédiatement comme une solution puissante et souple pour le développement mobile multiplateforme. Vous pouvez créer une solution Xamarin.Forms dans Visual Studio et l’écriture d’une application mobile entière en c#, avec ou sans XAML, que vous pouvez les compiler pour iOS, Android et la plate-forme de Windows universelle (UWP). Sur macOS, vous pouvez utiliser Xamarin Studio pour cible iOS et Android.

Xamarin.Forms inclut certains 20 contrôles, tels que le bouton, curseur et écriture, qui sont souvent appelées vues car ils dérivent de la classe Xamarin.Forms.View. Ceux-ci sont rendus sur les différentes plateformes à l’aide de vues natifs, ou les widgets, ou les contrôles ou les éléments qu’ils sont appelés dans diverses plates-formes. Par exemple, le Xamarin.Forms Entryview est mappé à un iOS UITextField, un EditText Android et un contrôle TextBox UWP.

Ce qui rend cela possible est une infrastructure extensible de convertisseurs de plate-forme qui encapsulent le mode natif et exposer une collection de propriétés et les événements qui correspondent à l’API de la vue Xamarin.Forms correspondante uniforme. Vous pouvez définir vos propres affichages personnalisés et les prendre en charge avec vos propres convertisseurs, mais il n’est pas une tâche triviale. Pour cette raison, Xamarin.Forms a été récemment améliorée pour présenter les différents raccourcis d’extensibilité inutile d’écrire des convertisseurs.

Une des plus évidentes de ces raccourcis est appelée vues natifs, une fonctionnalité qui vous permet d’instancier natives iOS, Android et UWP vues en même temps que les vues Xamarin.Forms normales. C’est ce que cet article sur les. L’histoire de vues natifs commence par code, mais devient beaucoup plus intéressante lorsque XAML impliqué.

Méthodes d’Extension de spécifique à la plateforme

Xamarin.Forms prend en charge les vues natives avec plusieurs classes spécifiques à la plateforme. Chacune des plateformes Xamarin.Forms contient une classe LayoutExtensions avec une méthode d’extension nommée Pourafficher que vous pouvez appeler des descendants des types natifs suivants :

  • iOS : UIKit.UIView
  • Android : Android.Views.View
  • UWP : Windows.UI.Xaml.FrameworkElement

Chaque version de la méthode Pourafficher retourne une instance spécifique à la plateforme de NativeViewWrapper, qui dérive de Xamarin.Forms.View. NativeViewWrapper hérite de tous les membres publics et protégés de Xamarin.Forms.View, et bien qu’il soit spécifique à la plateforme, est traitée dans Xamarin.Forms comme une instance de la vue normale. Une deuxième méthode d’extension est Add, qui effectue l’opération Pourafficher lors de l’ajout de l’objet View à une mise en page telles que StackLayout.

La version de chaque plateforme de NativeViewWrapper possède un convertisseur correspondant : une classe nommée NativeViewWrapperRenderer qui est plus simple que la plupart des convertisseurs, car il n’a pas besoin de prendre en charge les propriétés, les méthodes ou les événements du contrôle natif sous-jacent. (Xamarin.Forms est open source, afin de pouvoir examiner ces et les classes de github.com/xamarin/Xamarin.Forms.)

Voyons comment cela fonctionne.

Une solution Xamarin.Forms contient normalement les projets d’application petit stub pour chaque plateforme et un commun de classes portables bibliothèque (PCL) qui contient l’essentiel de votre application Xamarin.Forms. Toutefois, lorsque vous utilisez des vues natives dans le code, vous ne pouvez pas utiliser une PCL. Au lieu de cela, vous devez placer votre code Xamarin.Forms dans un projet partagé, qui est souvent appelé projet actif partagé ou SAP chez Xamarin. Dans la boîte de dialogue Nouveau projet de Visual Studio sélectionnez application vide (Shared Xamarin.Forms) au lieu de l’application vide habituel (Xamarin.Forms Portable). (Dans Xamarin Studio vous sélectionnez entre la bibliothèque de classes Portable ou une bibliothèque partagée avec les cases d’option.) Le code de ce projet partagé est en fait une extension de chaque application, ce qui signifie que vous pouvez utiliser c# directives de compilation conditionnelle (#if, #elif et #endif) pour délimiter le code spécifique à la plateforme.

Parmi le code téléchargeable pour cet article est le programme HelloNativeViews, avec un SAP qui contient la classe de page illustrée Figure 1. (Notez que les pratiques de mise en retrait normale du code ont été modifiés dans des exemples de code en fonction de l’espace disponible). Cette classe crée une étiquette pour chaque plateforme : un UILabel pour iOS, un contrôle TextView pour Android et un TextBlock pour la série UWP. Il appelle ensuite Pourafficher pour convertir un objet Xamarin.Forms.View chacun de ces objets, mais qui est en fait un objet NativeViewWrapper. La page peut ensuite appliquer Xamarin.Forms des propriétés telles que les Options verticale et horizontale à la vue et enfin définir sur la propriété de contenu de la page. Figure 2 montre le programme en cours d’exécution sur toutes les plates-formes de trois, chacun avec une police distinctive pour cette plateforme.

Figure 1 classe HelloNativeViews

using System;
using Xamarin.Forms;
#if __IOS__
using Xamarin.Forms.Platform.iOS;
using UIKit;
#elif __ANDROID__
using Xamarin.Forms.Platform.Android;
using Android.Graphics;
using Android.Widget;
#elif WINDOWS_UWP
using Xamarin.Forms.Platform.UWP;
using Windows.UI.Text;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
#endif
namespace HelloNativeViews
{
  public class HelloNativeViewsPage : ContentPage
  {
    public HelloNativeViewsPage()
    {
      View view = null;
#if __IOS__
      UILabel label = new UILabel
      {
        Text = "Hello iOS Native!",
        Font = UIFont.FromName("Papyrus", 32f),
      };
      view = label.ToView();
#elif __ANDROID__
      TextView textView = new TextView(Forms.Context)
      {
        Text = "Hello Android Native!",
        Typeface = Typeface.Create("cursive", TypefaceStyle.Normal),
        TextSize = 32f
      };
      view = textView.ToView();
#elif WINDOWS_UWP
      TextBlock textBlock = new TextBlock
      {
        Text = "Hello Windows Native!",
        FontFamily = new FontFamily("Georgia"),
        FontStyle = FontStyle.Italic,
        FontSize = 32
      };
      view = textBlock.ToView();
#endif
      view.HorizontalOptions = LayoutOptions.Center;
      view.VerticalOptions = LayoutOptions.Center;
      Content = view;
    }
  }
}

Le programme HelloNativeViews sur iOS, Android et Windows 10 Mobile
Figure 2 le programme de HelloNativeViews sur iOS, Android et Windows 10 Mobile

Bien sûr, vous pouvez obtenir le même effet en Xamarin.Forms standard en définissant la propriété FontFamily d’une étiquette à un appel de méthode Device.OnPlatform qui référence les noms de famille de polices de trois. Mais je pense que vous pouvez voir comment vous pouvez développer cette technique de manière beaucoup plus sophistiquée en tirant parti des API spécifiques qui prend en charge de chaque plateforme.

Parfois, vous devrez peut-être appliquer des méthodes de dimensionnement personnalisé à ces vues afin qu’ils se comportent correctement en tant qu’enfants d’un objet de mise en page Xamarin.Forms. Consultez l’article sur le site développeur Xamarin bit.ly/2dhBxDk pour plus de détails.

Bien que cela soit une technique intéressante, bien entendu ce serait évidemment beaucoup plus agréable instancier ces vues natifs directement dans XAML.

Vues natifs XAML

À compter de Xamarin.Forms 2.3.3 (qui est en état de version préliminaire, j’écris cet article), vous pouvez en effet incorporer vues natives dans vos fichiers XAML de Xamarin.Forms. Vous pouvez définir les propriétés et les gestionnaires d’événements sur ces vues. Vous pouvez inclure des vues à partir de plusieurs plateformes côte à côte dans le même fichier XAML, et ils peuvent interagir avec toutes les autres vues Xamarin.Forms.

Une clé pour cette fonction est une extension de la déclaration d’espace de noms (xmlns) XML pour les fichiers XAML. Espaces de noms XML personnalisés dans Xamarin.Forms utilisent généralement clr-namespace pour désigner l’espace de noms Common Language Runtime (CLR) et l’assembly de l’assembly. Le nouvel élément est targetPlatform, qui indique les plateformes sur lesquelles cet espace de noms XML particulier s’applique. Vous définissez cet élément à un membre de l’énumération Xamarin.Forms TargetPlatform : iOS, Android ou Windows pour la série UWP.

Par exemple, vous pouvez définir l’espace de noms XML suivant :

xmlns:IOS = "clr-namespace:UIKit ; assembly=Xamarin.iOS ; targetPlatform = iOS »

Vous pouvez utiliser ce préfixe dans le fichier XAML pour référencer toute classe ou structure dans iOS UIKit, espace de noms, par exemple :

<ios:UILabel Text="Hello iOS Native!"
             TextColor="{x:Static ios:UIColor.Red}"
             View.VerticalOptions="Center"
             View.HorizontalOptions="Center" \>

Texte et TextColor sont des propriétés de UILabel et TextColor est définie sur une propriété statique en lecture seule de UIColor. Toutefois, notez que les attributs VerticalOptions et HorizontalOptions sont précédés d’affichage, et ils sont en effet les propriétés de la classe d’affichage de Xamarin.Forms. Cette syntaxe, un nom de classe, un point et propriété — est couramment utilisée pour les propriétés pouvant être liées attachées, et ici, cela signifie que ces propriétés sont appliquées ultérieurement à l’objet de vue qui résulte de la conversion de UILabel à un objet NativeViewWrapper. Vous pouvez utiliser cette syntaxe seulement pour les propriétés qui sont soutenues par les propriétés pouvant être liées.

Pour faire référence à des widgets Android, vous devez avoir un espace de noms XML semblable (illustrée sur trois lignes, mais qui, dans le fichier XAML doit être sur une seule ligne, sans espaces) :

xmlns:androidWidget="clr-namespace:Android.Widget;
                       assembly=Mono.Android;
                         targetPlatform=Android"

Vous pouvez utiliser n’importe quel nom pour cet espace de noms, bien sûr, mais j’ai évité utilisant simplement android Android étant un peu plus compliqué qu’iOS : Constructeurs de widget requièrent généralement l’objet de contexte Android en tant qu’argument. Cet objet de contexte est disponible comme une propriété statique publique de la classe de formulaires dans l’espace de noms Xamarin.Forms dans l’assembly Xamarin.Forms.Platform.Android. Pour obtenir cet objet de contexte, vous aurez besoin de cet espace de noms XML (qui doit être également sur une seule ligne dans le fichier XAML) :

xmlns:formsAndroid="clr-namespace:Xamarin.Forms;
                      assembly=Xamarin.Forms.Platform.Android;
                        targetPlatform=Android"

Vous pouvez ensuite instancier un widget Android dans XAML en passant un argument du constructeur à l’aide de l’attribut x : Arguments avec une extension de balisage x : Static :

<androidWidget:TextView x:Arguments="{x:Static formsAndroid:Forms.Context}"
                        Text="Hello Android Native!" />

Le nom de la série UWP est relativement longs, en particulier : Windows, Version = 255.255.255.255, Culture = neutral, PublicKeyToken = null, ContentType = WindowsRuntime. Pour Android et la série UWP, vous aurez probablement besoin de plusieurs spécifications d’espace de noms XML pour les différents espaces de noms CLR utilisés pour différentes classes, structures et énumérations qui sont susceptibles d’être impliqués dans le balisage de l’interface Utilisateur.

N’oubliez pas que lorsque l’analyseur XAML rencontre une de ces vues natifs, il n’a pas accès couramment utilisés pour convertir les chaînes de texte XAML en objets, donc le balisage a tendance à être un peu plus longue qui correspondent aux types de propriété et objet de convertisseurs de type. Vous devez souvent créer explicitement des objets en XAML à l’aide de constructeurs ou les méthodes de fabrique, ce qui signifie que si vous n’êtes pas familiarisé avec la balise x : Arguments et l’élément x : FactoryMethod, est maintenant un bon moment pour en savoir plus.

Ceci est important : Impossible d’activer la compilation XAML lors de l’utilisation de vues natifs XAML. L’analyseur XAML de compilation n’a pas les références à ces types natifs. L’analyse doit être différée jusqu'à l’exécution, et à ce stade l’analyseur XAML ignore simplement quoi que ce soit avec un préfixe d’espace de noms XML qui possède une propriété targetPlatform qui ne correspond pas à la plate-forme sur laquelle le programme est en cours d’exécution. (J’ai entendu dire que xamarin.Forms développeurs travaillent sur la suppression de cette restriction.)

Vous ne pouvez pas utiliser styles avec des vues natifs. Styles peuvent cibler uniquement les propriétés qui sont soutenues par des objets BindableProperty et vues natifs n’ont pas ces propriétés.

Étant donné que ces vues natifs XAML sont instanciés par l’analyseur XAML de runtime, vous pouvez les inclure dans un fichier XAML dans un projet PCL ou un SAP. Toutefois, si vous avez besoin faire référence à un mode natif à partir du fichier code-behind, vous devez utiliser un SAP et délimiter le code spécifique à la plateforme avec des directives de compilation conditionnelle c#.

Voici une autre restriction : Avec une PCL ou un SAP, vous ne pouvez pas utiliser x : Name sur une vue native XAML. Le problème est que l’analyseur XAML de compilation génère un fichier de code contenant ces objets en tant que champs nommés, mais si ces champs sont basés sur les types spécifiques à la plateforme, le code généré ne peut pas être compilé pour toutes les plateformes.

Le programme XamlNativeViewDemo contienne le fichier XAML illustré Figure 3 avec trois chaînes de texte rouge spécifique à la plateforme et trois boutons spécifiques à la plateforme. Appuyer sur un bouton appelle un gestionnaire d’événements dans le fichier code-behind qui fait pivoter le texte dans un cercle.

Figure 3 le fichier XAML pour le programme XamlNativeViewDemo

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlNativeViewDemo"
             xmlns:ios="clr-namespace:UIKit; ... "
             xmlns:androidWidget="clr-namespace:Android.Widget; ... "
             xmlns:androidGraphics="clr-namespace:Android.Graphics; ... "
             xmlns:formsAndroid="clr-namespace:Xamarin.Forms; ... "
             xmlns:winui="clr-namespace:Windows.UI; ... "
             xmlns:winText="clr-namespace:Windows.UI.Text; ... "
             xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls; ... "
             xmlns:winMedia="clr-namespace:Windows.UI.Xaml.Media; ... "
             x:Class="XamlNativeViewDemo.XamlNativeViewDemoPage">
  <Grid x:Name="grid">
    <ContentView x:Name="textToRotateParent"
                 Grid.Row="0"
                 VerticalOptions="Center"
                 HorizontalOptions="Center">
      <ios:UILabel Text="Text to Rotate"
                   TextColor="{x:Static ios:UIColor.Red}">
        <ios:UILabel.Font>
          <ios:UIFont x:FactoryMethod="FromName">
            <x:Arguments>
              <x:String>Papyrus</x:String>
              <x:Single>32</x:Single>
            </x:Arguments>
          </ios:UIFont>
        </ios:UILabel.Font>
      </ios:UILabel>
      <androidWidget:TextView x:Arguments=
                            "{x:Static formsAndroid:Forms.Context}"
                              Text="Text to Rotate"
                              TextSize="32">
        <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="Text to Rotate"
                             FontSize="32"
                             FontStyle="{x:Static winText:FontStyle.Italic}">
        <winControls:TextBlock.FontFamily>
          <winMedia:FontFamily>
            <x:Arguments>
              <x:String>Georgia</x:String>
            </x:Arguments>
          </winMedia:FontFamily>
        </winControls:TextBlock.FontFamily>
        <winControls:TextBlock.Foreground>
          <winMedia:SolidColorBrush Color="{x:Static winui:Colors.Red}" />
        </winControls:TextBlock.Foreground>
      </winControls:TextBlock>
    </ContentView>
    <ContentView x:Name="rotateButtonParent"
                 Grid.Row="1"
                 VerticalOptions="Center"
                 HorizontalOptions="Center">
      <ios:UIButton TouchUpInside="OnButtonTap" />
      <androidWidget:Button x:Arguments="{x:Static formsAndroid:Forms.Context}"
                            Text="Rotate the Text"
                            Click="OnButtonTap" />
      <winControls:Button Content="Rotate the Text" />
    </ContentView>
  </Grid>
</ContentPage>

J’ai commencé optimiste XamlNativeViewDemo comme un projet PCL, mais il est bientôt apparu que le code XAML nécessaire un peu d’aide. Vous ne peut pas encore définir le texte sur un bouton UIButton iOS à partir de XAML. Vous devez appeler une méthode, et qui requiert le code. De même, Impossible de définir la couleur du texte sur un TextView Android avec une propriété, et le Gestionnaire de clic du bouton UWP est de type RoutedEventHandler, ce qui implique un objet RoutedEventArgs qui ne dérive d’EventArgs et, par conséquent, requiert un gestionnaire spécifique à la plateforme.

Ces problèmes implicitement que le fichier code-behind est nécessaire pour compenser les limitations du XAML, qui davantage implicite que j’ai dû abandonner la PCL et utiliser un SAP à la place. Même avec un SAP, vous ne pouvez pas utiliser x : Name sur les vues natives, afin de placer les vues natifs dans un ContentView avec un attribut x : Name pour obtenir d’y accéder dans le fichier code-behind, qui s’affiche dans Figure 4. Le ContentView est également pratique pour définir des propriétés de mise en page (par exemple, VerticalOptions et HorizontalOptions) pour éviter un grand nombre de répétitions dans les affichages natifs.

Figure 4 le fichier code-behind pour le programme XamlNativeViewDemo

using System;
using Xamarin.Forms;
namespace XamlNativeViewDemo
{
  public partial class XamlNativeViewDemoPage : ContentPage
  {
    View viewTextToRotate;
    public XamlNativeViewDemoPage()
    {
      InitializeComponent();
      viewTextToRotate = textToRotateParent.Content;
      View rotateButton = rotateButtonParent.Content;
#if __ANDROID__
      // Set Android text color
      var wrapper =
        (Xamarin.Forms.Platform.Android.NativeViewWrapper)
          viewTextToRotate;
      var textView = (Android.Widget.TextView)wrapper.NativeView;
      textView.SetTextColor(Android.Graphics.Color.Red);
#endif
#if __IOS__
      // Set iOS button text and color
      var wrapper = (Xamarin.Forms.Platform.iOS.NativeViewWrapper)rotateButton;
      var button = (UIKit.UIButton)wrapper.NativeView;
      button.SetTitle("Rotate the Text", UIKit.UIControlState.Normal);
      button.SetTitleColor(UIKit.UIColor.Black, UIKit.UIControlState.Normal);
#endif
#if WINDOWS_UWP
      // Set UWP button Click handler
      var wrapper = (Xamarin.Forms.Platform.UWP.NativeViewWrapper)rotateButton;
      var button = (Windows.UI.Xaml.Controls.Button)wrapper.NativeElement;
      button.Click += (sender, args) => OnButtonTap(sender, EventArgs.Empty);
#endif
    }
    void OnButtonTap(object sender, EventArgs args)
    {
      viewTextToRotate.RelRotateTo(360);
    }
  }
}

J’ai qualifié complet de tous les types spécifiques à la plateforme dans le fichier code-behind pour plus de clarté et pour éviter une série de plateforme à l’aide de directives. La clé pour obtenir la vue native sous-jacente à partir de la NativeViewWrapper est une propriété nommée NativeView (pour iOS et Android) ou NativeElement (pour UWP).

L’iOS et Android boutons peuvent partager le même gestionnaire d’événements dans le fichier code-behind parce qu’il est défini comme un délégué EventHandler. Mais le bouton UWP doit utiliser un gestionnaire d’événements distinct du type RoutedEventHandler, qui est implémentée en appelant simplement le gestionnaire utilisé pour iOS et Android.

Une autre approche de l’accès aux vues natives dans le fichier code-behind implique l’énumération des enfants des objets de structure et la recherche de types ou des ID différents. Les trois plateformes définissent une propriété de balise : entier sur iOS et Android et UWP objet, que vous pouvez utiliser à cet effet.

Le programme XamlNativeViewDemo trouver satisfaisante, car je n’aime pas à l’aide de SAP pour mes applications Xamarin.Forms. Je ne sais pas si vous êtes passionné par que je suis sur préférant PCL à SAP, mais si vous êtes, vous serez heureux d’apprendre que les deux programmes finales dans cet article sont PCL pure.

Liaisons de données et MVVM

Un des meilleurs moyens d’éviter que le code dans le fichier code-behind consiste à structurer votre application autour de l’architecture Model-View-ViewModel (MVVM). Toutes les interactions entre les vues sur la page se produisent dans le ViewModel indépendant de la plate-forme. Le ViewModel se connecte à la vue (l’interface Utilisateur) à l’aide de liaisons de données Xamarin.Forms. Les sources de liaison de données sont des propriétés du ViewModel tandis que les cibles de liaison de données sont des propriétés d’une vue.

Mais attendez une minute. Je l’ai mentionné plus haut ne peut pas utiliser un Style pour les vues natifs car les propriétés de styles doivent être soutenues par les propriétés pouvant être liées. Liaisons de données ont la même restriction : La propriété cible d’une liaison de données, et avec MVVM ces cibles sont toujours affichées dans la page — doit également être une propriété stockée par un objet BindableProperty. Comment définir les liaisons sur les vues natifs ?

Voici la bonne nouvelle : Pour prendre en charge les liaisons de données sur des vues XAML natives, chaque plateforme contient des méthodes d’extension SetBinding qui génèrent automatiquement des objets BindableProperty ad hoc à la volée. Ces objets BindableProperty autorisent les valeurs de propriété native à partir de ce dernier.

Maintenant vous pouvez envisager un autre problème : Dans de nombreux cas, ces liaisons de données doivent passer les deux méthodes, pas uniquement à partir de la source à la cible mais à partir de la cible vers source. Modifications dans l’affichage de l’interface Utilisateur doivent être reflétées dans les propriétés ViewModel. L’infrastructure Xamarin.Forms BindableProperty prend en charge les notifications de modifications de propriété via l’interface INotifyPropertyChanged, mais les vues natifs ne prennent en charge cette interface, comment l’objet de liaison savoir lorsqu’une propriété d’un mode natif modifie la valeur ?

La classe Binding dispose désormais d’une nouvelle propriété : UpdateSourceEventName. Dans l’extension de balisage de liaison en XAML, vous pouvez définir cette propriété sur le nom d’un événement dans la vue native qui signale le moment où la propriété cible a changé.

Le programme PlatformRgbSliders illustré Figure 5 est un exemple simple. (Certaines balises répétitives a été remplacé par les points de suspension.) Le fichier XAML contient trois jeux de curseurs de spécifique à la plateforme, j’ai améliorée en leur donnant une couleur correspondant à leur fonction dans le programme. (Il n’était pas possible de le faire pour la barre de recherche Android.) Le RgbColorViewModel la valeur de la page BindingContext définit les propriétés de rouge, vert et bleu qu’il utilise pour construire une propriété de couleur.

Figure 5 le fichier XAML pour le programme PlatformRgbSliders

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:PlatformRgbSliders"
             xmlns:ios="clr-namespace:UIKit; ... "
             xmlns:androidWidget="clr-namespace:Android.Widget; ... "
             xmlns:formsAndroid="clr-namespace:Xamarin.Forms; ... "
             xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls; ... "
             xmlns:winMedia="clr-namespace:Windows.UI.Xaml.Media; ... "
             xmlns:winui="clr-namespace:Windows.UI; ... "
             x:Class="PlatformRgbSliders.PlatformRgbSlidersPage">
  <ContentPage.Padding>
    <OnPlatform x:TypeArguments="Thickness"
                iOS="0, 20, 0, 0" />
  </ContentPage.Padding>
  <ContentPage.Resources>
    <ResourceDictionary>
      <local:DoubleToSingleConverter x:Key="doubleToSingle" />
      <local:DoubleToIntConverter x:Key="doubleToInt"
                                        Multiplier="256" />
    </ResourceDictionary>
  </ContentPage.Resources>
  <ContentPage.BindingContext>
    <local:RgbColorViewModel Color="Gray" />
  </ContentPage.BindingContext>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="3*" />
      <RowDefinition Height="*" />
      <RowDefinition Height="*" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <BoxView Grid.Row="0"
             Color="{Binding Color}" />
    <ios:UISlider Grid.Row="1"
                  ThumbTintColor="{x:Static ios:UIColor.Red}"
                  MinimumTrackTintColor="{x:Static ios:UIColor.Black}"
                  MaximumTrackTintColor="{x:Static ios:UIColor.Red}"
                  Value="{Binding Red,
                          Mode=TwoWay,
                          UpdateSourceEventName=ValueChanged,
                          Converter={StaticResource doubleToSingle}}"/>
    ...
    <androidWidget:SeekBar x:Arguments=
                           "{x:Static formsAndroid:Forms.Context}"
                           Grid.Row="2"
                           Max="256"
                           Progress="{Binding Green,
                             Mode=TwoWay,
                             UpdateSourceEventName=ProgressChanged,
                             Converter={StaticResource doubleToInt}}" />
    ...
    <winControls:Slider Grid.Row="3"
                        Maximum="1"
                        StepFrequency="0.01"
                        IsThumbToolTipEnabled="True"
                        Value="{Binding Blue,
                                Mode=TwoWay,
                                UpdateSourceEventName=ValueChanged}">
      <winControls:Slider.Foreground>
        <winMedia:SolidColorBrush Color=
          "{x:Static winui:Colors.Blue}" />
      </winControls:Slider.Foreground>
    </winControls:Slider>
  </Grid>
</ContentPage>

La liaison sur iOS UISlider nécessite un convertisseur de valeurs pour convertir les valeurs doubles dans le ViewModel à valeurs float et la barre de recherche Android nécessite un convertisseur de valeurs pour convertir les valeurs de deux valeurs entières. Vous pouvez voir que toutes les liaisons de données utilisent la propriété UpdateSourceEventName et la classe de liaison peut être notifiée lorsque l’utilisateur a modifié la valeur du curseur. Le résultat est indiqué dans Figure 6.

Le programme PlatformRgbSliders sur les trois plates-formes
Figure 6 le programme PlatformRgbSliders sur les trois plates-formes

Voici une expérience intéressante : Supprimez les éléments UpdateSourceEventName les liaisons de curseur de Windows. Le programme continue à fonctionner. Il s’agit de Xamarin.Forms étant en mesure d’utiliser le mécanisme de notification créé dans les propriétés de dépendance UWP. En outre, travail est effectué pour permettre la UpdateSourceEventName être éliminées sur des vues iOS si la vue implémente observation clé-valeur.

PlatformRgbSliders n’a aucun code propre au problème dans le fichier code-behind, il n’existe aucun problème à l’aide d’une PCL. Mais, certes, PlatformRgbSliders est simple. Vous pourrez utiliser PCL dans des programmes plus importants ?

Dans un premier temps, il semble prometteuse : Nombreux iOS et Android vues natives ne sont pas propices à l’instanciation dans XAML, et il n’existe aucune raison pourquoi ils doivent être. Le problème peut être résumé : Il n’existe pas toujours suffisamment propriétés dans iOS et Android affichages pour les options importantes qui doivent être définies et accessibles. Au lieu de cela, il existe trop de méthodes.

Trop de méthodes

Pour rendre plus faciles à XAML iOS et Android, vous devez remplacer certaines méthodes avec des propriétés. À l’évidence la UWP est préférable à cet égard, car il est conçu pour XAML, mais comme vous l’avez vu, les gestionnaires d’événements UWP sont souvent basées sur une plateforme spécifique les délégués.

La solution évidente à ce problème consiste à sous-classe les affichages spécifiques à la plateforme de wrappers qui définissent une API plus XAML conviviale qui se compose de propriétés et les événements indépendant de la plate-forme. Vous pouvez également commencer à voir que la véritable puissance de l’incorporation de vues natives dans XAML est fourni lorsque vous créez (ou consommez) personnalisé iOS, Android et UWP vues à utiliser dans votre application Xamarin.Forms.

Mais où vous placez ces classes ?

Si vous utilisez un SAP, vous pourrez placer dans SAP et les entourer de directives de compilation conditionnelle c#. Mais si vous souhaitez utiliser une PCL, et vous souhaitez généralement utiliser une PCL, alors vous ne pouvez pas le faire. Une PCL ne peut référencer une autre PCL, et par définition une PCL ne peut pas contenir n’importe quel iOS, le code Android ou UWP. C’est un peu d’un puzzle.

Au moins au départ.

N’oubliez pas que vous n'êtes pas compiler le XAML. Le code XAML est analysé au moment de la compilation, mais c’est principalement pour générer un fichier de code contenant des champs correspondant aux attributs x : Name. Vous avez déjà vu que XAML non compilé peut contenir des références à iOS, Android et UWP classes. Ces références sont résolues au moment de l’exécution, plutôt que de la compilation et qui fait toute la différence dans le monde. Au moment de l’exécution, l’analyseur XAML a accès à tous les assemblys qui composent l’application, et qui inclut les projets de démarrage de chaque plate-forme.

Cela signifie que vous pouvez placer des classes spécifiques à la plateforme dans les projets d’application de plate-forme, ou vous pouvez placer le code dans des bibliothèques spécifiques à la plateforme référencés par ces projets d’application. Ces classes peuvent être référencées à partir de XAML. Il peut sembler étrange et non naturelles en premier pour un fichier XAML dans une PCL pour référencer des classes dans les assemblys d’application, mais cela fonctionne parfaitement.

La solution PlatformSpinners illustre cette technique. L’idée est d’utiliser l’iOS UIPickerView, le compteur Android et ComboBox UWP pour sélectionner un élément dans une liste, mais supposée les UIPickerView Spinner ont des méthodes qui doivent être exposés en tant que propriétés. En outre, le UIPickerView nécessite un modèle de données qui doit être implémenté dans le code.

Pour cette raison, le projet d’application PlatformSpinners.iOS contient un PickerDataModel et un PropertiedUIPickerView qui dérive de UIPickerView, appelée ainsi car il ajoute des propriétés essentielles à UIPickerView. Le projet PlatformSpinners.Droid contient un PropertiedSpinner qui dérive de compteur.

Les PCL PlatformSpinners contient un modèle de vue simple qui expose une collection des noms de couleurs Xamarin.Forms, convertit ces noms de couleurs aux couleurs réelles. Figure 7 montre le fichier XAML complet à l’exception des espaces de noms XML long, et Figure 8 montre qu’il est en cours d’exécution sur les trois plateformes avec le compteur Android et UWP ComboBox ouvert pour afficher les options.

Figure 7 le fichier XAML de PlatformSpinners

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:PlatformSpinners"
             xmlns:iosLocal="clr-namespace:PlatformSpinners.iOS; ... "
             xmlns:androidLocal="clr-namespace:PlatformSpinners.Droid; ... "
             xmlns:formsAndroid="clr-namespace:Xamarin.Forms; ... "
             xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls; ... "
             x:Class="PlatformSpinners.PlatformSpinnersPage">
  <ContentPage.Padding>
    <OnPlatform x:TypeArguments="Thickness"
                iOS="0, 20, 0, 0" />
  </ContentPage.Padding>
  <ContentPage.BindingContext>
    <local:ColorNameViewModel SelectedColorName="Black" />
  </ContentPage.BindingContext>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <BoxView Grid.Row="0"
             Color="{Binding SelectedColor}" />
    <iosLocal:PropertiedUIPickerView Grid.Row="1"
                                     Items="{Binding ColorNames}"
                                     SelectedItem=
                                       "{Binding SelectedColorName,
                                       Mode=TwoWay,
                                       UpdateSourceEventName=ValueChanged}"/>
    <androidLocal:PropertiedSpinner x:Arguments=
                                      "{x:Static formsAndroid:Forms.Context}"
                                    Grid.Row="1"
                                    View.VerticalOptions="Center"
                                    Items="{Binding ColorNames}"
                                    SelectedObject=
                                      "{Binding SelectedColorName,
                                      Mode=TwoWay,
                                      UpdateSourceEventName=ItemSelected}" />
    <winControls:ComboBox Grid.Row="1"
                          View.VerticalOptions="Center"
                          ItemsSource="{Binding ColorNames}"
                          SelectedItem=
                            "{Binding SelectedColorName,
                            Mode=TwoWay,
                            UpdateSourceEventName=SelectionChanged}"/>
  </Grid>
</ContentPage>

PlatformSpinners en cours d’exécution sur les trois plates-formes
Figure 8 PlatformSpinners en cours d’exécution sur les trois plates-formes

Cette technique de fournir des propriétés supplémentaires est quelque chose que vous souhaiterez probablement faire lors de l’utilisation par des tiers iOS et Android vues personnalisées. Sous-classe de la vue pour le rendre compatible avec des liaisons de données XAML et au modèle MVVM et en général, vous serez gratuitement.

Il n’est pas vraiment plus simple ?

Vous avez vu comment Xamarin.Forms permet désormais de faire référence à des vues natifs, ou les classes dérivées de vues natifs — directement dans XAML plutôt que de masquer le code spécifique à la plateforme de suite dans un convertisseur et définition d’une interface indépendante de la plate-forme lui.

Par conséquent, vous devez vous poser : C’est vraiment plus facile que la création de convertisseurs ?

Oui, tout à fait.


Charles Petzold a écrit de nombreux articles pour MSDN Magazine et son prédécesseur, Microsoft Systems Journal, au cours des 30 dernières années.  Maintenant, il fonctionne dans le groupe de documentation Xamarin chez Microsoft et est l’auteur de « Création Mobile Apps avec Xamarin.Forms, « un livre gratuit disponible pour téléchargement à partir du site Web de Xamarin.

Merci aux experts techniques Microsoft suivants d'avoir relu cet article : David Britch, Stéphane Delcroix et Rui Marinho
David Britch travaille dans le groupe de documentation Xamarin chez Microsoft. Il a écrit pour une plage de publications de développement de logiciels, y compris la documentation, documentation du Guide, implémentations de référence, des livres blancs, vidéos et des cours de formation dispensée par un.

Stéphane Delcroix travaille dans l’équipe de Xamarin.Forms chez Microsoft sur XAML et bien plus encore.

Rui Marinho est ingénieur logiciel portugais dans l’équipe Xamarin.Forms chez Microsoft et est un codeur avid et open source.