Xamarin.Forms et images dans un Pivot UWP sans coder un control custom, c’est possible ?

Avec Xamarin.Forms, on peut faire presque tout. Quand on ne peut avoir le rendu souhaité, il suffit de surcharger un control de base et à lui fournir un Renderer custom.

Pour un développeur Xamarin, l’approche est courante. Au fil du temps, elle est devenue une habitude.

Pour un développeur XAML, cette approche est aberrante. Pendant des années, on a appris aux développeurs XAML (WPF, Silverlight, WinRT, UWP) qu’il ne fallait pas coder un control custom quand celui-ci n’avait pas un comportement différent que celui du control de base. Pour le rendu, on peut créer des Styles et substituer des Templates. Et en fonction de la plateforme, on a des Triggers, des Behaviors ou des Attached Properties.

Heureusement, Xamarin permet de cumuler les avantages de Xamarin.Forms et du Xaml UWP.

Par exemple, pour inclure les images dans les entêtes d’un Pivot, il est possible de créer un Style. Le Style est à déposer dans l’application UWP (fichier app.xaml par exemple). Il s’appliquera à défaut à tous les Pivots et remplacera le Template de base du PivotHeaderItem.

Voici un code possible pour le Style du PivotHeaderItem :

<converters:ImageConverter x:Key="ImageConverter"/>

<Style TargetType="PivotHeaderItem">
    <Setter Property="FontSize" Value="18" />
    <Setter Property="FontFamily" Value="{ThemeResource PivotHeaderItemFontFamily}" />
    <Setter Property="FontWeight" Value="{ThemeResource PivotHeaderItemThemeFontWeight}" />
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
    <Setter Property="Padding" Value="{ThemeResource PivotHeaderItemMargin}" />
    <Setter Property="Height" Value="52" />
    <Setter Property="Width" Value="60" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="PivotHeaderItem">
                <Grid
                    x:Name="Grid"
                    Background="{TemplateBinding Background}">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="SelectionStates">
                            <VisualStateGroup.Transitions>
                                <VisualTransition From="Unselected" To="UnselectedLocked" GeneratedDuration="0:0:0.33" />
                                <VisualTransition From="UnselectedLocked" To="Unselected" GeneratedDuration="0:0:0.33" />
                            </VisualStateGroup.Transitions>
                            <VisualState x:Name="Disabled" />
                            <VisualState x:Name="Unselected" />
                            <VisualState x:Name="UnselectedLocked" />
                            <VisualState x:Name="Selected">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
                                                               Storyboard.TargetProperty="Background" >
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}" />
                                    </ObjectAnimationUsingKeyFrames>

                                    <ColorAnimation Storyboard.TargetName="Underline" Storyboard.TargetProperty="(Fill).(Color)" To="White"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="UnselectedPointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
                                                               Storyboard.TargetProperty="Background" >
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="SelectedPointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
                                                               Storyboard.TargetProperty="Background" >
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ColorAnimation Storyboard.TargetName="Underline" Storyboard.TargetProperty="(Fill).(Color)" To="White"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="UnselectedPressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
                                                               Storyboard.TargetProperty="Background" >
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="SelectedPressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
                                                               Storyboard.TargetProperty="Background" >
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ColorAnimation Storyboard.TargetName="Underline" Storyboard.TargetProperty="(Fill).(Color)" To="Black"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Unfocused"/>
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
                                               Storyboard.TargetProperty="Opacity"
                                               Duration="0"
                                               To="1"/>
                                    <DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
                                               Storyboard.TargetProperty="Opacity"
                                               Duration="0"
                                               To="1"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Image
                        DataContext="{TemplateBinding Content}"
                        Source="{Binding Icon,Converter={StaticResource ImageConverter}}"
                        VerticalAlignment="Center"
                        HorizontalAlignment="Center"
                        Stretch="Uniform"
                        Height="24"
                        Width="24"         
                        />
                    <Rectangle
                        x:Name="Underline"
                        Fill="Transparent"
                        VerticalAlignment="Bottom"
                        HorizontalAlignment="Stretch"
                        Height="3"
                        Margin="0"/>
                    <Rectangle 
                        x:Name="FocusVisualWhite"
                        IsHitTestVisible="False"
                        Stroke="{ThemeResource SystemControlForegroundAltHighBrush}"
                        StrokeThickness="2"
                        Margin="2"
                        Opacity="0"/>
                    <Rectangle 
                        x:Name="FocusVisualBlack"
                        IsHitTestVisible="False"
                        Stroke="{ThemeResource SystemControlForegroundBaseHighBrush}"
                        StrokeThickness="2"
                        Opacity="0"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Ainsi que le code du Converter qui permet de convertir les noms des images en liens vers les images embarquées dans l’application UWP (dossier Images, à la racine de l’application UWP).

public sealed class ImageConverter:IValueConverter
{
    private const string SourceLocation = "ms-appx:///Images/";

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value == null) return null;

        String source = (value as Xamarin.Forms.FileImageSource)?.File ?? value.ToString();
        if (String.IsNullOrWhiteSpace(source)) return null;

        return SourceLocation + source;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return null;
    }
}


Ce qui permet d’avoir une application UWP pour mobile ou desktop :

XamarinStyleSansCotrol02

Semblable à la version Androïd :

XamarinStyleSansCotrol01

Mais pourquoi est-ce que cela fonctionne ? Pourquoi a-t-on accès à une propriété Image ?

L’explication est très simple. Xamarin utilisant un wrapper interne pour instancier un control UWP quand on demande un control Xamarin.Forms TabbedPage, il laisse l’objet Xamarin à disposition d’UWP. La propriété Image est donc accessible à UWP au moment de l’exécuter. Voilà pourquoi on peut faire le Binding sur celle-ci et afficher une image.


Note : la version UWP utilise une couleur de fond comme sur la version Androïd, du fait du Style Xamarin.Forms suivant :


<Style x:Key="TabbedPageStyle" TargetType="TabbedPage">
    <Setter Property="BarBackgroundColor" Value="{StaticResource Primary}" />
    <Setter Property="BarTextColor" Value="White" />
</Style>


Jérémy Jeanson

Comments

You have to be logged in to comment this post.