Xamarin par l’exemple : “Metsys Music Search” 2/3

Il y a plus de 2 ans, Microsoft annonçait le rachat de Xamarin. Aujourd’hui, le résultat de cette alliance aboutit à une large gamme d’outils rendant le développement “Cross-Platform” plus rapide et plus facile pour les développeurs.

La partie 1 se trouve ici : Xamarin par l’exemple : “Metsys Music Search” 1/3

5. Mise en place de la recherche :

5.1 Création des classes “Converters” :

Pour la suite du projet, créez ces deux classes dans le répertoire “Converters” comme suit :

using System;
using System.Globalization;
using Xamarin.Forms;

namespace Metsys.Music.Search.Converter
{
    public class SelectedItemBooleanConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value != null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }

    public class InvertSelectedItemBooleanConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value == null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}
using System;
using System.Globalization;
using Xamarin.Forms;

namespace Metsys.Music.Search.Converter
{
    public class SelectedItemImageConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value != null)
                return ((ArtistInfo)value).picture_xl;

            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}

5.2 Création de la vue “ArtistView” :

Comme pour la vue “HomeView”, ajoutez un nouvel élément de type Xamarin.Forms et nommez le “ArtistView.xaml”. Remplacez le code XAML existant par celui ci-dessous :

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:extension="clr-namespace:Metsys.Music.Search.Extensions"
             xmlns:converter="clr-namespace:Metsys.Music.Search.Converter"         
             x:Class="Metsys.Music.Search.Views.ArtistView"
             NavigationPage.HasNavigationBar="False">

    <ContentPage.Resources>
        <ResourceDictionary>
            <converter:SelectedItemImageConverter x:Key="SelectedItemImageConverter" />
            <converter:SelectedItemBooleanConverter x:Key="SelectedItemBooleanConverter" />
            <converter:InvertSelectedItemBooleanConverter x:Key="InvertSelectedItemBooleanConverter" />
            <converter:InvertBooleanConverter x:Key="InvertBooleanConverter" />
            <converter:BooleanToOpacityConverter x:Key="BooleanToOpacityConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid>

        <Grid IsEnabled="{Binding Source={x:Reference indicator}, Path=IsRunning, Converter={StaticResource InvertBooleanConverter}}"
              Opacity="{Binding Source={x:Reference indicator}, Path=IsRunning, Converter={StaticResource BooleanToOpacityConverter}}">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition Height="2*"/>
            </Grid.RowDefinitions>

            <!--Background Image and navigation-->
            <Image Source="{extension:ImageResource Source=Metsys.Music.Search.Resources.fondbanner.png}" 
               IsVisible="{Binding Source={x:Reference lst}, Path=SelectedItem, Converter={StaticResource InvertSelectedItemBooleanConverter}}"
               Aspect="AspectFill" />
            <Image Source="{Binding Source={x:Reference lst}, Path=SelectedItem, Converter={StaticResource SelectedItemImageConverter}}"                
               IsVisible="{Binding Source={x:Reference lst}, Path=SelectedItem, Converter={StaticResource SelectedItemBooleanConverter}}"
               Aspect="AspectFill" Opacity=".3" Grid.RowSpan="3"/>

            <Button Text="Back" HeightRequest="32" WidthRequest="32" Margin="30" VerticalOptions="Start">
                <Button.IsVisible>
                    <OnPlatform x:TypeArguments="x:Boolean">
                        <On Platform="iOS, Android" Value="true" />
                        <On Platform="UWP" Value="false" />
                    </OnPlatform>
                </Button.IsVisible>
                <Button.GestureRecognizers>
                    <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped_1" />
                </Button.GestureRecognizers>
            </Button>

            <!-- List with artists-->
            <ListView x:Name="lst" ItemsSource="{Binding ListeArtists}" Grid.Row="1" Grid.RowSpan="3"
                      VerticalOptions="FillAndExpand" SeparatorVisibility="None"
                      SelectionMode="Single" 
                      HorizontalOptions="FillAndExpand" Margin="0,-40,0,20" 
                      BackgroundColor="Transparent" RowHeight="170"
                      ItemSelected="lst_ItemSelected">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Frame BackgroundColor="White" Margin="10" 
                               BorderColor="#FFC7ADE5"
                               HasShadow="True">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition />
                                        <ColumnDefinition Width="2*"/>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition />
                                        <RowDefinition />
                                        <RowDefinition />
                                        <RowDefinition Height="auto"/>
                                    </Grid.RowDefinitions>

                                    <Image Source="{Binding picture_medium}" Grid.RowSpan="3" Aspect="AspectFit"/>

                                    <Label Text="{Binding name}" LineBreakMode="TailTruncation" Grid.Column="1" FontAttributes="Bold"
                                       TextColor="#FF783CBD"  HorizontalOptions="StartAndExpand" VerticalOptions="Start">
                                        <Label.FontSize>
                                            <OnPlatform x:TypeArguments="x:Double">
                                                <On Platform="iOS, Android" Value="17" />
                                                <On Platform="UWP" Value="18" />
                                            </OnPlatform>
                                        </Label.FontSize>
                                    </Label>

                                    <Grid Grid.Row="1" Grid.Column="1">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="auto"/>
                                            <ColumnDefinition Width="2*"/>
                                            <ColumnDefinition Width="auto"/>
                                            <ColumnDefinition/>
                                        </Grid.ColumnDefinitions>

                                        <Image  Source="{extension:ImageResource Source=Xamarin.Deezer.Resources.follow.png}" 
                                        HorizontalOptions="Start" VerticalOptions="CenterAndExpand" Margin="5"/>
                                        <Label Grid.Column="1" Text="{Binding nb_fan}"  VerticalOptions="CenterAndExpand"/>

                                        <Image Grid.Column="2" Source="{extension:ImageResource Source=Xamarin.Deezer.Resources.disc.png}" 
                                        HorizontalOptions="Start" VerticalOptions="CenterAndExpand" Margin="5"/>
                                        <Label Grid.Column="3" Text="{Binding nb_album}" VerticalOptions="CenterAndExpand"/>

                                    </Grid>


                                    <Label Grid.Column="1" Grid.Row="2" HorizontalOptions="StartAndExpand">
                                        <Label.FormattedText>
                                            <FormattedString>
                                                <Span Text="View all albums and tracks" TextColor="#FFBC9C16" FontSize="14">
                                                    <Span.GestureRecognizers>
                                                        <TapGestureRecognizer BindingContext="{Binding id}"/>
                                                    </Span.GestureRecognizers>
                                                </Span>
                                            </FormattedString>
                                        </Label.FormattedText>
                                    </Label>

                                    <Label Grid.Row="3" Grid.ColumnSpan="2">
                                        <Label.FormattedText>
                                            <FormattedString>
                                                <Span Text="{Binding link}" TextColor="#FFBC9C16" FontSize="12">
                                                    <Span.GestureRecognizers>
                                                        <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
                                                    </Span.GestureRecognizers>
                                                </Span>
                                            </FormattedString>
                                        </Label.FormattedText>
                                    </Label>


                                </Grid>
                                <Frame.CornerRadius>
                                    <OnPlatform x:TypeArguments="x:Single">
                                        <On Platform="iOS, Android" Value="7" />
                                    </OnPlatform>
                                </Frame.CornerRadius>
                            </Frame>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>               
            </ListView>

        </Grid>

        <ActivityIndicator x:Name="indicator" IsRunning="False" Color="#FF783CBD" 
                           IsVisible="False" VerticalOptions="Center" HorizontalOptions="Center">
            <ActivityIndicator.HeightRequest>
                <OnPlatform x:TypeArguments="x:Double">
                    <On Platform="UWP" Value="250" />
                    <On Platform="Android" Value="70" />
                    <On Platform="iOS" Value="200" />
                </OnPlatform>
            </ActivityIndicator.HeightRequest>
            <ActivityIndicator.WidthRequest>
                <OnPlatform x:TypeArguments="x:Double">
                    <On Platform="UWP" Value="250" />
                    <On Platform="Android" Value="70" />
                    <On Platform="iOS" Value="200" />
                </OnPlatform>
            </ActivityIndicator.WidthRequest>
        </ActivityIndicator>

    </Grid>

</ContentPage>

Rajoutez dans le “code-behind” les deux méthodes ci-dessous :

        private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
        {
            var url = ((Label)sender).FormattedText.Spans[0].Text;
            if (!string.IsNullOrEmpty(url))
                Device.OpenUri(new System.Uri(url));
        }

        private void TapGestureRecognizer_Tapped_1(object sender, EventArgs e)
        {
            Navigation.PopAsync();
        }

        private ArtistInfo artistSelected = null;

        private void lst_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            artistSelected = e.SelectedItem as ArtistInfo;
        }

5.3 Recherche et navigation :

Il ne reste plus qu’à initialiser la recherche lorsque l’utilisateur va saisir un nom d’artiste. L’application effectuera, via le service web, une recherche et naviguera vers la vue “ArtistView” qui affichera les résultats obtenus.

Ouvrez le fichier “HomeView.xaml” et rajoutez les éléments surlignés ci-dessous :

  <!--TITLE APP-->
            <Label Text="Metsys Music Search" TextColor="#FF783CBD" 
               HorizontalOptions="Center" 
               Grid.Row="3"    
               VerticalOptions="Start" 
               FontSize="Large"/>

            <!--SEARCH area-->
            <Entry x:Name="authorEntry" Grid.Row="4" TextColor="#FF783CBD" Margin="30,0"
                   PlaceholderColor="#FFC7ADE5"
                   IsSpellCheckEnabled="True"
                   ReturnType="Search"
                   Placeholder="Search your artist..." 
                   VerticalOptions="Center"
                   HeightRequest="70" 
                   Completed="authorEntry_Completed"
                   HorizontalOptions="FillAndExpand"/>

            <!--TITLE and Powered by DEEZER-->
            <Grid Grid.Row="5">
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>

Rajoutez dans le “code-behind” les trois méthodes ci-dessous :

        private async void authorEntry_Completed(object sender, EventArgs e)
        {
            SetBusy(true);

            var artists = await GetListArtists();

            if (artists != null)
            {
                ArtistView playPage = new ArtistView();
                playPage.BindingContext = new ArtistViewModel() { ListeArtists = artists };

                await Navigation.PushAsync(playPage, true);
                SetBusy(false);
            }
        }

        private void SetBusy(bool OnOff)
        {
            this.indicator.IsRunning = OnOff;
            this.indicator.IsVisible = OnOff;
        }

        private async Task<List<ArtistInfo>> GetListArtists()
        {
            try
            {
                if (!String.IsNullOrEmpty(authorEntry.Text))
                {
                    List<ArtistInfo> artists = await Core.GetArtists(authorEntry.Text);
                    return artists;
                }

                return null;
            }
            catch
            {
                return null;
            }
        }

5.4 Tests et comparaison sur les appareils :

Exécutez l’application sur les trois plateformes (Windows10, Android et iOS) :

     

Chaque plateforme utilise ses composants natifs pour afficher les mêmes informations. On remarque par exemple que le bouton de navigation arrière se trouve intégré dans la barre de titre pour une application Windows10 alors que les éléments de la liste n’ont pas les bords arrondis contrairement à ceux sur Android ou iOS. De plus, nous avons volontairement masqué le bouton “Back” sur Windows10 car il est déjà présent. Nous pouvons donc personnaliser facilement l’affichage suivant la cible grâce à ces balises :

 <Button.IsVisible>
      <OnPlatform x:TypeArguments="x:Boolean">
            <On Platform="iOS, Android" Value="true" />
            <On Platform="UWP" Value="false" />
      </OnPlatform>
 </Button.IsVisible>

6. Harmonisation du bouton “Back” :

Dans cette partie, nous allons harmoniser le bouton “Back” pour avoir le même rendu sur un appareil Android et iOS. Ajoutez un nouveau fichier dans le répertoire “Controls” comme suit :

Dans la partie XAML, rajoutez les lignes ci-dessous :

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Metsys.Music.Search.Controls.ButtonImage">
    
    <StackLayout Orientation="Horizontal">

        <Image x:Name="ButtonIcon" 
               HorizontalOptions="Start" 
               Source="{Binding Icon}" />

        <Label x:Name="ButtonLabel" 
               HorizontalOptions="StartAndExpand"
               VerticalOptions="Center"
               Text="{Binding Label}"
               TextColor="#FF783CBD" />

    </StackLayout>
    
</ContentView>

Et dans le “code-behind” rajoutez les propriétés suivantes :

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Metsys.Music.Search.Controls
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
	public partial class ButtonImage : ContentView
	{
		public ButtonImage ()
		{
			InitializeComponent ();
		}

        public ImageSource Icon
        {
            get { return ButtonIcon.Source; }
            set { ButtonIcon.Source = value; }
        }

        public string Label
        {
            get { return ButtonLabel.Text; }
            set { ButtonLabel.Text = value; }
        }

        public double HeightDezired
        {
            get { return ButtonIcon.HeightRequest; }
            set { ButtonIcon.HeightRequest = value; }
        }

        public double WidthDezired
        {
            get { return ButtonIcon.WidthRequest; }
            set { ButtonIcon.WidthRequest = value; }
        }
    }
}

Ce contrôle va permettre d’afficher un bouton contenant une image et un texte. Rajouter le fichier “backbutton.png” dans le répertoire “Ressources” comme nous l’avons fait dans la partie 1 (ne pas oublier de mettre “Ressource incorporée” dans “Action de génération”) :

Fichier ressources : backbutton.png

Éditez le fichier “ArtistView.xaml” afin de rajouter le nouveau contrôle que nous venons de créer. Il suffit de changer la balise “Button” par celui ci-dessous :

            <controls:ButtonImage Icon="{extension:ImageResource Source=Metsys.Music.Search.Resources.backbutton.png}" Label="Back"
                    HeightDezired="32" WidthDezired="32" Margin="30" VerticalOptions="Start">
                <controls:ButtonImage.IsVisible>
                    <OnPlatform x:TypeArguments="x:Boolean">
                        <On Platform="iOS, Android" Value="true" />
                        <On Platform="UWP" Value="false" />
                    </OnPlatform>
                </controls:ButtonImage.IsVisible>
                <controls:ButtonImage.GestureRecognizers>
                    <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped_1" />
                </controls:ButtonImage.GestureRecognizers>
            </controls:ButtonImage>

Attention, n’oubliez pas de rajouter le namespace : xmlns:controls=”clr-namespace:Metsys.Music.Search.Controls”

Exécutez l’application sous Android et sous iOS pour constater le résultat (plutôt sympa) :

    

7. Renderers :

Les “renderers” permettent d’adapter le rendu des contrôles suivant la plateforme ciblée. Nous allons créer un “renderer” pour la zone de saisie d’un artiste afin de la rendre plus attrayante. Pour cela, créez dans le répertoire “Controls” une classe nommée “ImageEntry.cs” et ajouter le code ci-dessous :

using System;
using Xamarin.Forms;

namespace Metsys.Music.Search.Controls
{
    public class ImageEntry : Entry
    {
        public ImageEntry()
        {
            this.HeightRequest = 50;
            this.SizeChanged += ImageEntry_SizeChanged;
        }

        private void ImageEntry_SizeChanged(object sender, EventArgs e)
        {
            this.InvalidateMeasure();
        }

        public static readonly BindableProperty ImageProperty =
            BindableProperty.Create(nameof(Image), typeof(string), typeof(ImageEntry), string.Empty);

        public static readonly BindableProperty LineColorProperty =
            BindableProperty.Create(nameof(LineColor), typeof(Xamarin.Forms.Color), typeof(ImageEntry), Color.White);

        public static readonly BindableProperty ImageHeightProperty =
            BindableProperty.Create(nameof(ImageHeight), typeof(int), typeof(ImageEntry), 40);

        public static readonly BindableProperty ImageWidthProperty =
            BindableProperty.Create(nameof(ImageWidth), typeof(int), typeof(ImageEntry), 40);

        public static readonly BindableProperty ImageAlignmentProperty =
            BindableProperty.Create(nameof(ImageAlignment), typeof(ImageAlignment), typeof(ImageEntry), ImageAlignment.Left);

        public Color LineColor
        {
            get { return (Color)GetValue(LineColorProperty); }
            set { SetValue(LineColorProperty, value); }
        }

        public int ImageWidth
        {
            get { return (int)GetValue(ImageWidthProperty); }
            set { SetValue(ImageWidthProperty, value); }
        }

        public int ImageHeight
        {
            get { return (int)GetValue(ImageHeightProperty); }
            set { SetValue(ImageHeightProperty, value); }
        }

        public string Image
        {
            get { return (string)GetValue(ImageProperty); }
            set { SetValue(ImageProperty, value); }
        }

        public ImageAlignment ImageAlignment
        {
            get { return (ImageAlignment)GetValue(ImageAlignmentProperty); }
            set { SetValue(ImageAlignmentProperty, value); }
        }
    }

    public enum ImageAlignment
    {
        Left,
        Right
    }
}

7.1 Renderer iOS :

Dans le projet iOS, créez un nouveau répertoire “Renderers”. Ajoutez dans celui-ci une classe “ImageEntryRenderer.cs” et coller le code ci- dessous :

using CoreAnimation;
using CoreGraphics;
using Metsys.Music.Search.Controls;
using Metsys.Music.Search.iOS.Renderers;
using System.Drawing;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(ImageEntry), typeof(ImageEntryRenderer))]
namespace Metsys.Music.Search.iOS.Renderers
{
    public class ImageEntryRenderer : EntryRenderer
    {      

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || e.NewElement == null)
                return;

            var element = (ImageEntry)this.Element;

            var textField = this.Control;
            if (!string.IsNullOrEmpty(element.Image))
            {
                switch (element.ImageAlignment)
                {
                    case ImageAlignment.Left:
                        textField.LeftViewMode = UITextFieldViewMode.Always;
                        textField.LeftView = GetImageView(element.Image, element.ImageHeight, element.ImageWidth);
                        break;
                    case ImageAlignment.Right:
                        textField.RightViewMode = UITextFieldViewMode.Always;
                        textField.RightView = GetImageView(element.Image, element.ImageHeight, element.ImageWidth);
                        break;
                }
            }

            textField.BorderStyle = UITextBorderStyle.None;
            CALayer bottomBorder = new CALayer
            {
                Frame = new CGRect(0.0f, element.HeightRequest - 1, 1000, 1.0f),
                BorderWidth = 2.0f,
                BorderColor = element.LineColor.ToCGColor()
            };
                       

            textField.Layer.AddSublayer(bottomBorder);
            textField.Layer.MasksToBounds = true;
        }              

        private UIView GetImageView(string imagePath, int height, int width)
        {
            var uiImageView = new UIImageView(UIImage.FromBundle(imagePath))
            {
                Frame = new RectangleF(0, 0, width, height)
            };
            UIView objLeftView = new UIView(new System.Drawing.Rectangle(0, 0, width + 10, height));
            objLeftView.AddSubview(uiImageView);

            return objLeftView;
        }
    }
}

Ajoutez dans le répertoire “Resources” le fichier user.png sans oublier d’y assigner la propriété “BundleResource”.

7.2 Renderer Android :

Dans le projet Android, créez un nouveau répertoire “Renderers”. Ajoutez dans celui-ci une classe “ImageEntryRenderer.cs” et coller le code ci -dessous :

using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Support.V4.Content;
using Android.Views;
using Metsys.Music.Search.Controls;
using Metsys.Music.Search.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(ImageEntry), typeof(ImageEntryRenderer))]
namespace Metsys.Music.Search.Droid.Renderers
{
    public class ImageEntryRenderer : EntryRenderer
    {
        ImageEntry element;
        public ImageEntryRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || e.NewElement == null)
                return;

            element = (ImageEntry)this.Element;

            var editText = this.Control;

            if (!string.IsNullOrEmpty(element.Image))
            {
                switch (element.ImageAlignment)
                {
                    case ImageAlignment.Left:
                        editText.SetCompoundDrawablesWithIntrinsicBounds(GetDrawable(element.Image), null, null, null);
                        break;
                    case ImageAlignment.Right:
                        editText.SetCompoundDrawablesWithIntrinsicBounds(null, null, GetDrawable(element.Image), null);
                        break;
                }
            }
            editText.CompoundDrawablePadding = 25;
            editText.Gravity = GravityFlags.CenterVertical;
            Control.Background.SetColorFilter(element.LineColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
        }

        private BitmapDrawable GetDrawable(string imageEntryImage)
        {
            int resID = Resources.GetIdentifier(imageEntryImage, "drawable", this.Context.PackageName);
            var drawable = ContextCompat.GetDrawable(this.Context, resID);
            var bitmap = ((BitmapDrawable)drawable).Bitmap;

            return new BitmapDrawable(Resources, Bitmap.CreateScaledBitmap(bitmap, element.ImageWidth * 2, element.ImageHeight * 2, true));
        }

    }
}

Ajoutez dans le répertoire “Resources/drawable” le fichier user.png sans oublier d’y assigner la propriété “AndroidResource”.

7.3 Renderer UWP :

Dans le projet Windows10, créez un nouveau répertoire “Renderers”. Ajoutez dans celui-ci une classe “ImageEntryRenderer.cs” et coller le code ci-dessous :

using Windows.UI.Xaml.Media;
using Metsys.Music.Search.Controls;
using Metsys.Music.Search.UWP.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.UWP

[assembly: ExportRenderer(typeof(ImageEntry), typeof(ImageEntryRenderer))]
namespace Metsys.Music.Search.UWP.Renderers
{
    public class ImageEntryRenderer : EntryRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || e.NewElement == null)
                return;

            var element = (ImageEntry)this.Element;
            var textField = this.Control;

            //convert xamarin color control to uwp color
            var wpColor = Windows.UI.Color.FromArgb(
            (byte)(element.LineColor.A * 255),
            (byte)(element.LineColor.R * 255),
            (byte)(element.LineColor.G * 255),
            (byte)(element.LineColor.B * 255));

            textField.BorderThickness = new Windows.UI.Xaml.Thickness(0, 0, 0, 1);
            textField.VerticalContentAlignment = Windows.UI.Xaml.VerticalAlignment.Center;
            textField.BorderBrush = new SolidColorBrush(wpColor);
            textField.Background = new SolidColorBrush(Windows.UI.Colors.Transparent);
            textField.BackgroundFocusBrush = textField.Background;
            textField.Tag = element.ImageHeight;

            textField.Style = App.Current.Resources["myFormsTextBoxStyle"] as Windows.UI.Xaml.Style;
        }
    }
}

Pour la partie UWP, nous nous servons d’un style pour effectuer le rendu. Ajoutez le dans le fichier App.xaml du projet UWP comme ci-dessous :

<Application
    x:Class="Metsys.Music.Search.UWP.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:uwp="using:Xamarin.Forms.Platform.UWP"
    RequestedTheme="Light">

    <Application.Resources>
        <ResourceDictionary>
            <SolidColorBrush Color="Transparent" x:Key="SystemControlHighlightListAccentLowBrush" />
            <SolidColorBrush Color="Transparent" x:Key="SystemControlHighlightListLowBrush" />
            <SolidColorBrush Color="Transparent" x:Key="SystemControlHighlightListAccentMediumBrush" />
            <SolidColorBrush Color="Transparent" x:Key="SystemControlHighlightListMediumBrush" />
            <SolidColorBrush Color="Transparent" x:Key="SystemControlHighlightListAccentHighBrush" />

            <uwp:TextAlignmentToHorizontalAlignmentConverter x:Key="AlignmentConverter" />

            <Style x:Key="myFormsTextBoxStyle" TargetType="uwp:FormsTextBox">
                <Setter Property="MinWidth" Value="{ThemeResource TextControlThemeMinWidth}" />
                <Setter Property="MinHeight" Value="{ThemeResource TextControlThemeMinHeight}" />
                <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
                <Setter Property="ForegroundFocusBrush" Value="{ThemeResource SystemControlForegroundChromeBlackHighBrush}" />
                <Setter Property="PlaceholderForegroundBrush" Value="{ThemeResource SystemControlPageTextBaseMediumBrush}" />
                <Setter Property="PlaceholderForegroundFocusBrush" Value="{ThemeResource SystemControlPageTextChromeBlackMediumLowBrush}" />
                <Setter Property="Background" Value="{ThemeResource SystemControlBackgroundAltHighBrush}" />
                <Setter Property="BackgroundFocusBrush" Value="{ThemeResource SystemControlBackgroundChromeWhiteBrush}" />
                <Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundChromeDisabledLowBrush}" />
                <Setter Property="SelectionHighlightColor" Value="{ThemeResource SystemControlHighlightAccentBrush}" />
                <Setter Property="BorderThickness" Value="{ThemeResource TextControlBorderThemeThickness}" />
                <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
                <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
                <Setter Property="IsSpellCheckEnabled" Value="True" />
                <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
                <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
                <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" />
                <Setter Property="Padding" Value="{ThemeResource TextControlThemePadding}" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="uwp:FormsTextBox">
                            <Grid>
                                <Grid.Resources>
                                    <Style x:Name="DeleteButtonStyle" TargetType="Button">
                                        <Setter Property="Template">
                                            <Setter.Value>
                                                <ControlTemplate TargetType="Button">
                                                    <Grid x:Name="ButtonLayoutGrid" BorderBrush="{ThemeResource TextBoxButtonBorderThemeBrush}"
											      BorderThickness="{TemplateBinding BorderThickness}"
											      Background="{ThemeResource TextBoxButtonBackgroundThemeBrush}">
                                                        <VisualStateManager.VisualStateGroups>
                                                            <VisualStateGroup x:Name="CommonStates">
                                                                <VisualState x:Name="Normal" />
                                                                <VisualState x:Name="PointerOver">
                                                                    <Storyboard>
                                                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
																                               Storyboard.TargetName="GlyphElement">
                                                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAccentBrush}" />
                                                                        </ObjectAnimationUsingKeyFrames>
                                                                    </Storyboard>
                                                                </VisualState>
                                                                <VisualState x:Name="Pressed">
                                                                    <Storyboard>
                                                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
																                               Storyboard.TargetName="ButtonLayoutGrid">
                                                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAccentBrush}" />
                                                                        </ObjectAnimationUsingKeyFrames>
                                                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
																                               Storyboard.TargetName="GlyphElement">
                                                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltChromeWhiteBrush}" />
                                                                        </ObjectAnimationUsingKeyFrames>
                                                                    </Storyboard>
                                                                </VisualState>
                                                                <VisualState x:Name="Disabled">
                                                                    <Storyboard>
                                                                        <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity"
																                 Storyboard.TargetName="ButtonLayoutGrid" />
                                                                    </Storyboard>
                                                                </VisualState>
                                                            </VisualStateGroup>
                                                        </VisualStateManager.VisualStateGroups>
                                                        <TextBlock x:Name="GlyphElement" AutomationProperties.AccessibilityView="Raw"
												           Foreground="{ThemeResource SystemControlForegroundChromeBlackMediumBrush}" FontStyle="Normal"
												           FontSize="12" FontFamily="{ThemeResource SymbolThemeFontFamily}" HorizontalAlignment="Center"
												           Text="" VerticalAlignment="Center" />
                                                    </Grid>
                                                </ControlTemplate>
                                            </Setter.Value>
                                        </Setter>
                                    </Style>
                                </Grid.Resources>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="*" />
                                </Grid.RowDefinitions>
                                <VisualStateManager.VisualStateGroups>

                                    <VisualStateGroup x:Name="FormsManaged">
                                        <!-- These "Forms" prefixed states exist to support the Forms VSM; once custom 
								visual state managers are working correctly for TextBox, we'll be able to remove them
								(see notes in FormsVisualStateManager.cs) -->
                                        <VisualState x:Name="FormsNormal" />
                                        <VisualState x:Name="FormsDisabled">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
																		Storyboard.TargetName="HeaderContentPresenter">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
																		Storyboard.TargetName="BackgroundElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Background, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BorderElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
										                               Storyboard.TargetName="PlaceholderTextContentPresenter">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding PlaceholderForegroundBrush, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                    </VisualStateGroup>

                                    <VisualStateGroup x:Name="CommonStates">
                                        <VisualState x:Name="Disabled">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
										                               Storyboard.TargetName="HeaderContentPresenter">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
										                               Storyboard.TargetName="BackgroundElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledTransparentBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BorderElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledLowBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
										                               Storyboard.TargetName="PlaceholderTextContentPresenter">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledLowBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="Normal" />
                                        <VisualState x:Name="PointerOver">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightChromeAltLowBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBackgroundHoverOpacity}" />
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="Focused">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
										                               Storyboard.TargetName="PlaceholderTextContentPresenter">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding PlaceholderForegroundFocusBrush, RelativeSource={RelativeSource TemplatedParent}}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
										                               Storyboard.TargetName="BackgroundElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0"
											                        Value="{Binding BackgroundFocusBrush, RelativeSource={RelativeSource TemplatedParent}}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
										                               Storyboard.TargetName="BorderElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0"
											                        Value="{Binding BackgroundFocusBrush, RelativeSource={RelativeSource TemplatedParent}}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBackgroundFocusedOpacity}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAccentBrush}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding ForegroundFocusBrush, RelativeSource={RelativeSource TemplatedParent}}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="RequestedTheme"
										                               Storyboard.TargetName="ContentElement">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="Light" />
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                    </VisualStateGroup>
                                    <VisualStateGroup x:Name="ButtonStates">
                                        <VisualState x:Name="ButtonVisible">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="DeleteButton">
                                                    <DiscreteObjectKeyFrame KeyTime="0">
                                                        <DiscreteObjectKeyFrame.Value>
                                                            <Visibility>Visible</Visibility>
                                                        </DiscreteObjectKeyFrame.Value>
                                                    </DiscreteObjectKeyFrame>
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="ButtonCollapsed" />
                                    </VisualStateGroup>
                                </VisualStateManager.VisualStateGroups>
                                <Image Source="Assets/user.png" Stretch="UniformToFill" Height="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}" 
                                       Width="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"
                                       Grid.RowSpan="2"/>
                                <Border x:Name="BackgroundElement" Background="{TemplateBinding Background}" Grid.ColumnSpan="3" 
						        Margin="{TemplateBinding BorderThickness}" Opacity="{ThemeResource TextControlBackgroundRestOpacity}"
						        Grid.Row="1" Grid.RowSpan="1" />
                                <Border x:Name="BorderElement" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
						        BorderThickness="{TemplateBinding BorderThickness}" Grid.ColumnSpan="3" Grid.Row="1" Grid.RowSpan="1" />
                                <ContentPresenter x:Name="HeaderContentPresenter" Grid.ColumnSpan="3" 
						                  ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}"
						                  Foreground="{ThemeResource SystemControlForegroundBaseHighBrush}" FontWeight="Normal"
						                  Margin="0,0,0,8" Grid.Row="0" Visibility="Collapsed" x:DeferLoadStrategy="Lazy"
                                          />
                                <ScrollViewer x:Name="ContentElement" AutomationProperties.AccessibilityView="Raw"
                                      VerticalAlignment="Center" Grid.Column="1"
						              HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
						              HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
						              IsTabStop="False" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
						              IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
						              IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
						              Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" Grid.Row="1"
						              VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
						              VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="Disabled" />
                                <ContentControl x:Name="PlaceholderTextContentPresenter" Grid.ColumnSpan="2" Grid.Column="1"
						                Content="{TemplateBinding PlaceholderText}"
						                Foreground="{TemplateBinding PlaceholderForegroundBrush}" IsHitTestVisible="False"
						                IsTabStop="False" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"
						                Grid.Row="1" VerticalAlignment="Center"
						                HorizontalAlignment="{Binding TextAlignment, 
                                            RelativeSource={RelativeSource Mode=TemplatedParent}, 
                                            Converter={StaticResource AlignmentConverter}}" />
                                <Button x:Name="DeleteButton" BorderThickness="{TemplateBinding BorderThickness}" Grid.Column="2"
						        FontSize="{TemplateBinding FontSize}" IsTabStop="False" Margin="{ThemeResource HelperButtonThemePadding}"
						        MinWidth="34" Grid.Row="1" Style="{StaticResource DeleteButtonStyle}" Visibility="Collapsed"
						        VerticalAlignment="Stretch" />
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

        </ResourceDictionary>
    </Application.Resources>

</Application>

Ajoutez dans le répertoire “Assets” le fichier user.png sans oublier d’y assigner la propriété “Contenu”.

7.4 Utilisation du nouveau contrôle :

Il ne reste plus qu’à éditer le fichier “HomeView.xaml” comme ci-dessous pour remplacer le contrôle “Entry” par “ImageEntry” :

 <local:ImageEntry x:Name="authorEntry" Grid.Row="4" TextColor="#FF783CBD" Margin="30,0"
                   PlaceholderColor="#FFC7ADE5"
                   LineColor="#FF783CBD"
                   ImageHeight="50" ImageWidth="50"
                   Image="user" 
                   IsSpellCheckEnabled="True"
                   ReturnType="Search"
                   Placeholder="Search your artist..." 
                   VerticalOptions="Center"
                   HeightRequest="70" 
                   Completed="authorEntry_Completed"
                   HorizontalOptions="FillAndExpand"/>

N’oubliez pas d’ajouter le namespace : xmlns:local=”clr-namespace:Metsys.Music.Search.Controls”. Le résultat obtenu sur les trois appareils :

      

 

A propos de l'auteur

Je suis Architecte Technique et passionné par le développement mobile et la réalité augmentée. Je baigne dans l’univers Microsoft depuis maintenant une vingtaine d’année.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.