Donner vie à vos images avec PixelContainer !

lorsque l’on souhaite positionner un contrôle à une position précise, XAML nous offre plusieurs alternatives . Par exemple :

  • En utilisant les attributs classique du Layout : Margin + Alignment.
  • Par le panel Canvas et ses propriétés attachés Canvas.Left et Canvas.Top
  • En utilisant l’attribut RenderTransform et la classe TranslateTransform

Mais toutes ces positions sont exprimés en coordonnées spécifiques à XAML. Si l’on désire manipuler des contrôles se trouvant sur une image, il serait plus simple de les exprimer en pixels. Et c’est là que PixelContainer entre en scène !

Description de PixelContainer

PixelContainer fonctionne un peu comme un Canvas avec son Canvas.Top et son Canvas.Left mais possède quelques atouts supplémentaires.

Mais, avant tout de chose, PixelContainer nécessite la source d’une image pour commencer à travailler :

<l:PixelContainer Source="ms-appx:///Assets/City.png"/>

Une fois chargée, l’image s’affiche. Il est bien sur possible d’appliquer la déformation que l’on souhaite par l’attribut “Stretch” comme pour un contrôle Image classique.

<l:PixelContainer Source="ms-appx:///Assets/City.png" Stretch="Uniform"/>
City.png en mode Stretch à Uniform (notez les bords noirs)

Une fois chargée, les dimensions de l’images permettent de calculer les positions en pixels. On peut alors positionner des contrôles facilement dans PixelContainer à l’aide Pixel.X et Pixel.Y.

<l:PixelContainer Source="ms-appx:///Assets/City.png" Stretch="Uniform">
    <!-- On dessine une flèche --> 
    <FontIcon l:Pixel.X="571" l:Pixel.Y="153" Glyph="" Foreground="White" FontSize="50"/>
</l:PixelContainer>

Dans cet exemple on dessine une flèche à l’aide de FontIcon en la positionnant aux coordonnées 571, 153 exprimées en pixels.

Une flèche près du soleil !

Mais en regardant de plus près, on s’aperçoit que si la flèche est placée au bon endroit, c’est sa pointe qui devrait être positionner à ces coordonnées.

Pas de panique ! On va juste rajouter à notre FontIcon l’attribut Pixel.Origin=”0,0.5″ pour lui permettre de se recentrer convenablement.

Pixel.Origin fonctionne de la même façon que RenderTransformOrigin, c’est à dire qu’il normalise la taille de son contrôle (ici FontIcon) pour qu’elle soit représentée dans un carrée de 1×1. Ainsi une taille de “0.5,0.5” représentera le centre du contrôle et un “0,0.5” assigné à notre FontIcon centrera verticalement la flèche en conservant la position horizontale initiale.

<FontIcon l:Pixel.X="571" l:Pixel.Y="153" l:Pixel.Origin="0,0.5" Glyph="" Foreground="White" FontSize="50"/>
Tout est rentré dans l’ordre grâce à Pixel.Origin

Donner une taille en pixel

A l’instar des positions, PixelContainer est également capable de donner une taille exprimée en pixels. C’est très pratique pour les images ou les contrôles à taille dynamique (comme les boutons par exemple).

<Button l:Pixel.X="100" l:Pixel.Y="100" l:Pixel.Width="200" l:Pixel.Height="50" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="Hello"></Button>

Attention à ne pas oublier de modifier les attributs Horizontal / VerticalAlignment car le Bouton possède ses propres valeurs par défaut. En les positionnant à “Stretch” vous êtes sur que le bouton prendra toute la place donnée par PixelContainer.

Le bouton est accroché à Gratte-Ciel

Il est possible de ne donner qu’une seul taille en pixel pour un contrôl donné. Par exemple, on affectera à un bouton uniquement l:Pixel.Width. Dans ce cas la hauteur sera déterminé automatiquement par le contrôle.

Attribut Stretch et changement de taille

PixelContainer prend en compte l’attribut Stretch de son image. Cela implique que les positions et tailles des contrôles seront recalculés en cas de changement de cet attribut ou de tout changement de taille de PixelContainer.

Les contrôles se positionnent et se re-taillent automatiquement (Stretch / et changement de taille)

Cette vidéo montre bien l’intérêt du système. D’un coté un positionnement simplifié, de l’autre on garde la qualité d’affichage des controles XAML qui ne seront jamais pixélisés.

Ajouter de l’interactivité

Comme il est possible de positionner facilement un élément sur une image, on va pouvoir ajouter de l’interactivité simplement. Par exemple si l’on souhaite rendre le soleil de notre image réactif, il suffit d’ajouter une ellipse de couleur transparente en position et taille du soleil puis de détecter les événements d’entrée et de sortie de l’ellipse. facile ! En remplaçant l’ellipse par un rectangle, un path ou encore une image cela offre beaucoup de possibilités d’interaction.

ItemTemplate et Binding

Le binding est également présent dans PixelContainer. Il est même plus simple à mettre en place que pour un Canvas puisqu’il est disponible dès le DataBinding.

<l:PixelContainer.ItemTemplate>
    <DataTemplate>
        <Button l:Pixel.X="{Binding X}" l:Pixel.Y="{Binding Y}" l:Pixel.Width="300" l:Pixel.Height="300" Background="Red" Content="{Binding Name}"></Button>
    </DataTemplate>
</l:PixelContainer.ItemTemplate>

Il ne reste plus qu’à ajouter des données dans ItemsSource de PixelContainer.

PixelContent

Pour les allergiques aux propriétés attachés (l:Pixel.X par exemple), un petit contrôle a été crée. Il s’agit de ContentControl qui permet d’avoir les attributs PixelX, PixelY, PixelWidth, PixelHeight et PixelOrigin disponibles

Transformer des coordonnées

Pour faciliter la vie du développeur, deux méthodes de transformations de coordonnées sont incluses dans PixelContainer :

  • ConvertToPixelLayout : convertit une valeur (position ou taille) du layout XAML en pixels
  • ConvertToXamlLayout : Méthode inverse de ConvertToPixelLayout
this.PixelContainer.ConvertToPixelLayout(CoordinateAlignment.Horizontal, 10.0)

le paramètre CoordinateAlignment.Horizontal ou Vertical permet d’obtenir l’axe des valeurs à convertir car les axes ne sont pas toujours uniformes (Stretch.Fill).

Storyboard (facile mais pas très rapide)

Bien que le Binding fonctionne comme un charme avec les propriétés attachées (par exemple l:Pixel.X), UWP ne permet pas leurs utilisations dans les Storyboards (et c’est bien dommage !).

Il est tout de même possible d’utiliser le contrôle PixelContent et ses attributs (PixelX,…) pour les animer. ATTENTION : ne pas oublier le EnableDependentAnimation à true pour que l’animation fonctionne.

<Storyboard x:Name="StoryboardPixel" RepeatBehavior="Forever">
  <DoubleAnimation EnableDependentAnimation="True" Storyboard.TargetName="PixelContent" 
  Storyboard.TargetProperty="PixelX" From="572" To="0" AutoReverse="True"/>
</Storyboard>

Storyboard (moins facile mais très rapide)

Malheureusement l’utilisation de EnableDependentAnimation à true implique de piètre performance pour l’animation. Mais on peut contourner le problème en utilisant un TranslateTransform et les méthodes ConvertToXamlLayout.

<!-- Le FontIcon à animer-->
<FontIcon l:Pixel.X="571" l:Pixel.Y="153" l:Pixel.Origin="0,0.5" Glyph="" Foreground="White" FontSize="50">
    <!-- On ajoute un TranslateTransform --> 
    <FontIcon.RenderTransform>
        <TranslateTransform x:Name="TranslateFontIcon"></TranslateTransform>
    </FontIcon.RenderTransform>
</FontIcon>
<!-- Le storyboard vise Le translateTransform -->
<Storyboard x:Name="StoryboardPixelTranslate" RepeatBehavior="Forever">
    <DoubleAnimation x:Name="AnimationTranslateX" Storyboard.TargetName="TranslateFontIcon" Storyboard.TargetProperty="X" From="0"/>
</Storyboard>

L’animation commence à From=”0″ c’est à dire relativement à la position en pixel du FontIcon (ici x=571). Pour la faire bouger à la position en pixel x=600 par exemple on prendra la différence entre le point de départ et d’arrivée (600-571) pour obtenir une distance exprimée en pixel. Puis on utilisera ConvertToXamlLayout pour convertir la distance en pixel en distance XAML. Il ne restera plus qu’à appliquer la valeur obtenue à l’animation AnimationTranslateX.

this.AnimationTranslateX.To = this.PixelContainer.ConvertToXamlLayout(CoordinateAlignment.Horizontal, 600 - 571);
this.StoryboardPixelTranslate.Begin();

Lire et écrire des pixels en code-behind

Pour manager les valeurs en pixel, on utilisera les méthodes suivantes :

Pixel.SetX(this.MonControl, 100);
Pixel.SetY(this.MonControl, 200);
Pixel.SetOrigin(this.MonControl, new Point(0, 0.5));
Pixel.SetWidth(this.MonControl, 100);

var x = Pixel.GetX(this.MonControl);
...

Clipper les enfants

A partir de la version 1.1.0, vous pouvez utiliser l’attribut ClipImageToBounds qui permet de clipper, c’est à dire de couper, les contrôles enfants de PixelContainer qui sont à l’extérieur de l’image.

ClipImageToBounds = “False”
ClipImageToBounds = “True”

Obtenir PixelContainer

Pour démarrer avec PixelContainer, il suffit d’intégrer son nuget à l’intérieur de votre projet et c’est parti ! PixelContainer est disponible pour UWP et WPF.

https://www.nuget.org/packages/PixelContainer

Pour information son namespace est :

xmlns:l=”using:SamuelBlanchard.UI.Panels”

Vous trouverez également le code source de PixelContainer sur GitHub :

https://github.com/samoteph/PixelContainer

Enjoy !

Leave a Reply

Your email address will not be published. Required fields are marked *