J’ai ma radio qui plante

DISCLAIMER : Cet article a été sponsorisé intégralement par moi-même.

J’ai récemment développé une application Radio pour le groupe Radio France tout à fait bénévolement (voir l’article) afin que Windows Phone puisse bénéficier lui aussi d’une application de qualité en la matière (#radiofrance4WP).

Vous pouvez retrouver l’application ici : https://www.microsoft.com/fr-fr/store/apps/radio-france-direct/9nblggh1kctr

C’est la première fois que je touchais à ce genre d’application et j’avoue que c’est relativement simple à mettre en place.

Pourtant lors du déploiement de la seconde version de l’app une personne me contacte pour me remonter que l’application ne fonctionne pas chez lui.
Elle se ferme immédiatement sur ces deux appareils. Je m’oriente vers un problème réseau.
Je lui demande de rebooter son app et tout fonctionne à nouveau.

Deux jours plus tard, je remonte une nouvelle version, et là deux nouvelles personnes me contactent via Twitter.
L’application ne se lance plus ! Après un reboot plus de problème.

Le mystère s’épaissit

En regardant de plus près les commentaires des apps radio sur le store, il s’avère que quelques personne souffrent du même mal et se plaignent que leur application radio favorite ne fonctionne plus après parfois des semaines d’utilisation sans problème.

 » Ca me met une erreur de connexion et l’appli se ferme tout seul :/  » – Inconnu – Radio.fr

< Impossible d'écouter depuis 2 semaine " Erwan - Radio.fr " Fonctionnement très aléatoire " - Franciso - Radio.fr

Inspecteur Blanchard à la rescousse !

l_acteur_de_derrick___tait_un_ancien_ss___9233_north_615x0

La chose étrange c’est que mon application ne plante pas en Debug mais seulement en Release.
Le débuggage ne va donc pouvoir se faire que par log de fichier.

En analysant les logs je m’aperçois que l’app plante souvent après le lancement de la tache du player Audio.
Je me concentre donc sur celle-ci.
Dans cette tache, comme tout bon player qui se respecte, j’ai implementé le BackgroundMediaPlayer.Shutdown() dans l’événement d’annulation de la tâche.
J’ai alors une intuition : Que se passe t’il quand l’application est killée avant que l’annulation de la tâche n’a pu être appelée et donc que le BackgroundMediaPlayer.Shutdown n’a pas fonctionné ?
Apparement cela rend le BackgroundMediaPlayer légérement furax et il refuse de se réinitialiser de nouveau.

Résultat l’app plante sans autre forme de procès.

J’ai l’impression que cela arrive particulièrement lors des mises à jour des apps qui sont encore en train de fonctionner.

Et la solution est…

Dès l’initialisation de l’app, avant même que le Background soit lancé pour éviter tout problème, n’hésitez par à ajouter cette ligne :

        public App()
        {
#if WINDOWS_PHONE_APP
   try
   {
      // je suis tranquille maintenant
      BackgroundMediaPlayer.Shutdown();
   }
   catch
   {
   }
#endif
   
   this.InitializeComponent();
   ...
}

Vous serez ainsi complètement sûr qu’un vieux BackgroundMediaPlayer pas propre ne vienne vous embêter !
Il existe peut être une manière plus propre d’effectuer ce nettoyage mais c’est la seul qui fonctionne à ma connaissance.

Et n’oubliez pas : soutenez la cause #radiofrance4WP les amis !

0  

[POC] Grid vNext



J’aime bien les contrôles de positionnement dans XAML et je suis particulièrement fan du contrôle Grid.
Pour rappel, celui ci se comporte comme un tableau que l’on définit via ses propriétés RowDefinitions et ColumnDefinitions.
Les enfants du Grid sont positionnables individuellement en affectant les propriétés Grid.Row et Grid.Column.

Voici un Grid classique dont les deux enfants, un bouton et une checkbox, sont positionnés via Grid.Row et Grid.Column.

<Grid>

    <!-- Déclaration de 3 lignes --> 
    <Grid.RowDefinitions>
        <!-- Place du Titre -->
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <!-- Déclaration de 3 colonnes -->
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>

    <Button Content="Title" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch"></Button>
    <CheckBox Content="Footer" Grid.Row="2" Grid.Column="2" ></CheckBox>
</Grid>

Simple non ?
Pourtant lorsque l’on se sert régulièrement du panel Grid, on s’aperçoit que cette façon de placer les éléments est aussi une faiblesse.
La faute à son positionnement par index !

Pour s’en convaincre il suffit d’ajouter un RowDefinition de plus à notre déclaration (ici pour donner de la place à un élément SystTray):

    <!-- Déclaration de 3+1 lignes --> 
    <Grid.RowDefinitions>
        
        <!-- Place de la Systray -->
        <RowDefinition Height="32"/>
        <!-- Place du Titre -->
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>

Que va t’il se passer ?

Le bouton a la propriété Grid.Row affecté 0 et va donc prendre la place allouée normalement à l’élément Systray
La CheckBox de la même manière remonte d’une ligne.

Les éléments étant positionnés par des index ils ne sont pas relatif à l’emplacement auquel ils ont été initialement affectés.
Pour nos deux éléments ce n’est pas très grave, il suffit d’incrémenter les Grid.Row et chacun retrouve sa place mais imaginez maintenant que ce problème s’applique sur une interface complexe comportant des dizaines d’éléments. Cela peut rapidement devenir un cauchemar à gérer.

Positions relatives

Pour ne plus être sous le coup de l’indexation, il suffirait d’adresser les RowDefinitions non plus par index mais par nom.
Ainsi même si une ligne est rajouté, l’index bougera mais pas le nom.

Comme la fonctionnalité n’existe pas en XAML, j’ai crée un petit Behavior permettant la prise en compte des noms.

DISCLAIMER: Ce Behavior un POC et ne devrait pas, normalement, être utilisé en production (même si le résultat fonctionne plutôt bien 😛 )

Son fonctionnement est très simple : lorsque la propriété sb:GridEx.RowName est affectée dans le Button et la CheckBox, il va inspecter la liste des RowDefinitions de la Grid et voir si une des lignes possède le même nom. Si c’est le cas il récupère son index et l’affecte à ses enfants (Grid.Row=’0′).

<Grid>

    <Grid.RowDefinitions>
        <RowDefinition x:Name="Title"/>
        <RowDefinition x:Name="Body"/>
        <RowDefinition x:Name="Footer"/>
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition x:Name="Left"/>
        <ColumnDefinition x:Name="Center"/>
            <ColumnDefinition x:Name="Right"/>
    </Grid.ColumnDefinitions>

    <Button Content="Title" sb:GridEx.RowName="Title" sb:GridEx.ColumnName="Center" HorizontalAlignment="Stretch"></Button>
    <CheckBox Content="Footer" sb:GridEx.RowName="Footer" sb:GridEx.ColumnName="Right" ></CheckBox>

</Grid>

En plus de ne plus être assujetti à l’indexation, les placements des éléments sont beaucoup plus clairs.
Par exemple Le bouton est positionné dans la colonne « Center » et la ligne « Title ».
C’est beaucoup plus parlant que la colonne 1 et ligne 0 !

Conclusion

L’idée de ce POC était de mettre en avant une des faiblesses du panel Grid : Son positionnement par index.
Une vNext de ce panel pourrait intégrer un positionnement relatif par nom.
D’ailleurs lorsque l’on regarde le tout nouveau RelativePanel de Windows 10, on constate qu’il se positionne relativement aux noms des contrôles de la page.

Vous trouverez le code source du behavior.

0  

[Universal App] Deviens un Keyboard Master !



Lorsque l’on gère des Textbox dans une application, et à plus forte raison des devices mobiles, on se retrouve très souvent avec une taille d’écran limitée car le clavier virtuel prend une bonne partie de la place disponible.

Bing

En général XAML s’en tire en proposant un scrolling de l’ensemble de l’écran.
Cela peut s’avérer pratique dans le cas de formulaire comportant plusieurs Textbox par exemple, le scrolling permettant une navigation facile entre les différents contrôles de saisie.

En revanche, il existe plusieurs cas ou ce n’est clairement pas optimal. A savoir :

– Lorsque le Textbox sert à ajouter une entrée dans un ListView (application de type Chat). Le scrolling de l’écran induit par la TextBox associé à celui de la ListView ne fait pas bon ménage.
– Quand on veut afficher ou non certains éléments afin de créer une vue spécifique à la taille réduite de l’écran
– pour Améliorer le clavier Virtuel (Afficher un bandeau de saisie custom au dessus du clavier).

Nous verrons dans cet article comment mettre en place très simplement ces cas un peu particuliers.

Chacun cherche son Chat

Nous prendrons comme exemple la mise en place d’un chat.
Que nous faut-il pour mettre en place ce cas particulier ?

– Annuler le scrolling de l’écran afin que le ListView du chat ne soit plus affecter.
– Obtenir la taille du clavier virtuel pour donner au ListView la taille de l’écran disponible .
– Détecter l’ouverture du clavier pour cacher des éléments non essentielles (le nom de l’application).

Pour rendre les choses plus visuelles, l’objectif est de partir d’un écran sans clavier :

wp_ss_20150617_0001

A un écran restreint, concentré sur la ListView, c’est à dire sans titre avec une ListView et TextBox plein écran :

wp_ss_20150617_0002

De nouveaux événements pour le clavier

Généralement dans les applications Silverlight pour Windows Phone on utilise deux événements pour détecter que le clavier apparaît à l’écran: GotFocus et LostFocus.
Le fait de donner ou non le focus à la textbox lance l’un ou l’autre des événements.
Mais la gestion de la souris ou du clavier physique sur les applications Windows fait que le GotFocus n’est plus nécessairement synonyme d’affichage du clavier virtuel en Universal App.

Heureusement une nouvelle API est arrivée pour venir à notre secours !
InputPane est une classe permettant de détecter les événements d’affichage et de retrait du clavier virtuel !

InputPane inputPane = InputPane.GetForCurrentView();
// Affichage
inputPane.Showing += inputPane_Showing;
// Retrait
inputPane.Hiding += inputPane_Hiding;

Les arguments des événements contiennent des informations intéressantes comme la taille du clavier et la désactivation du scrolling de l’écran (et de son animation).

void inputPane_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
{
    // Taille du clavier
    double heightKeyboard = args.OccludedRect.Height;
    // Désactivation du scrolling écran
    args.EnsuredFocusedElementInView = true;
}

void inputPane_Hiding(InputPane sender, InputPaneVisibilityEventArgs args)
{
    // Activation du scrolling écran
    args.EnsuredFocusedElementInView = false;
}

Une fois le scrolling désactivé c’est au développeur de prendre en compte la taille du clavier car la page XAML passe désormais sous le clavier !

Attention tout de même à ces deux bugs :
– L’événement Showing peut-être appelé une nouvelle fois lorsque l’on quitte la TextBox (mais heureusement l’événement Hiding ne sera appelé qu’une seule fois).
– La taille du clavier renvoyé par args.OccludedRect.Height n’est pas toujours valide. Seul la valeur obtenue lors du premier appel de l’événement Showing peut être considérée comme correcte. Il faut donc la stocker pour une utilisation ultérieure.

Ca devient compliqué ? Voici qui devrait simplifier les choses !

Keyboard pour XAML

Vu que nous souhaitons que le clavier virtuelle affecte les éléments de XAML, autant qu’il soit traité directement dans le XAML.
J’ai donc concocté pour vous un contrôle et un behavior afin de vous aider dans la gestion du clavier virtuel.

Le premier s’appelle KeyboardGhost. C’est un contrôle qui prend la taille du clavier virtuelle lorsque celui ci est affiché.

Vous devez tout d’abord rajouter le namespace suivant dans votre page :

xmlns:sb="using:SamuelBlanchard.Helpers"

Le contrôle s’utilise comme suit :

<Page
...
>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <!-- Pour le KeyboardGhost --> 
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        
        <!-- Ecran adaptif-->
        <Grid x:Name="Ecran">
            <!-- Le reste de la page est ici-->
            <TextBox FontSize="40" Margin="30" VerticalAlignment="Bottom"></TextBox>
        </Grid>
        
        <!-- Taille du Keyboard -->
        <sb:KeyboardGhost Grid.Row="1" />        
    </Grid>
</Page>

Ainsi vous êtes assuré que le Grid « Ecran » fera la taille de l’écran disponible.
Dans le cadre de notre Chat, le ListView fera la taille de l’écran et pourra scroller en plein écran.

Le second contrôle est un Behavior c’est à dire une capacité qui se rajoute à n’importe quel controle XAML de la page.
Ce behavior permet de cacher ou non un element de la page selon que le clavier soit affiché ou non.
Pour le faire fonctionner il faut d’abord rajouter dans les références de l’application le Behavior SDK (XAML).

BehaviorXaml

Ensuite dans la page rajouter les namespaces suivant :

    xmlns:i="using:Microsoft.Xaml.Interactivity"
    xmlns:sb="using:SamuelBlanchard.Helpers"

Il ne reste plus qu’à ajouter le behavior :

<Grid>
    <TextBlock Text="Titre de la page" Margin="16" VerticalAlignment="Top" HorizontalAlignment="Center" FontSize="40">
        <i:Interaction.Behaviors>
            <sb:KeyboardBehavior KeyboardShowingAction="SetVisibilityCollapse"></sb:KeyboardBehavior>
        </i:Interaction.Behaviors>
    </TextBlock>
    <TextBox FontSize="40" Margin="100" VerticalAlignment="Bottom" Text="Bas"></TextBox>
</Grid>

Le TextBlock chargé d’afficher le titre de l’app disparaîtra lorsque le clavier s’affichera laissant plus de place aux informations essentielles.
Dans le cadre de notre chat, Le ListView permet d’afficher un item de plus à l’écran, le titre ayant disparu. C’est toujours bon à prendre !

Vous pouvez également cacher des éléments lorsque le clavier n’est pas présent avec la propriété KeyboardShowingAction à SetVisibilityVisible.
La taille du clavier peut être fixée via la valeur SetHeight ce qui revient à utiliser le contrôle KeyboarGhost.

Conclusion

La prise en compte de la taille réduite de l’écrans lorsque le clavier virtuel est affiché peut être un vrai plus dans une application.
L’utilisateur ayant une meilleur vision des éléments important à l’écran tout en conservant un scrolling lorsque c’est nécessaire.
Les contrôles KeyboardGhost et KeyboardBehavior rend la mise en place simple et rapide en privilégiant le coté XAML à celui du code-behind.

Vous trouverez le code source complet des contrôles ici

0  

Gestion de CommandBar multiples



En Universal App la déclaration d’une barre de commande s’effectue par le biais du contrôle CommandBar.
En XAML la déclaration du contrôle CommandBar est très simple (ici, dans la barre du bas) :

<Page.BottomAppBar>
    <CommandBar Background="#f0f0f0" Foreground="#606060">
        <AppBarButton Icon="Add" Label="Add"></AppBarButton>
        <AppBarButton Icon="Delete" Label="Delete"></AppBarButton>
        <AppBarButton Icon="ReShare" Label="Share"></AppBarButton>
        <AppBarButton Icon="List" Label="Manage"></AppBarButton>
    </CommandBar>
</Page.BottomAppBar>

CommandBar

Mais voila, lorsque je veux gérer plusieurs CommandBar dans la même page comment faire ?

Les ressources

La première chose à laquelle on pense c’est bien évidemment de positionner les CommandBar dans les ressources; celles de la page par exemple.
Ainsi il sera facile de choisir la CommmandBar à affecter à la BottomAppBar.
Peine perdue ! Au lancement de l’application, une exception sera levée :

CommandBars can only be set in Page.BottomAppBar.

Dommage, c’était la solution la plus élégante pour conserver la facilité d’écriture qu’offre XAML.

Par le code

On peut passer par l’écriture complète de la CommandBar via le code.
Oui cela marche très bien, mais ma motivation du moment m’empêche d’écrire une ligne de code à ce sujet.
D’autant plus qu’une autre solution beaucoup plus élégante existe 😉

On revient aux ressources

La CommandBar ne peut être gérée directement dans les ressources.
Soit ! Mais dans un autre contrôle cela ne semble pas lui poser de problème.

En créant un petit contrôle « CommandBarResource » chargé d’accueillir la CommandBar dans ses propriétés on remplie toutes les conditions nécessaires pour qu’il puisse être positionné dans les ressources.

Ainsi le code du contrôle CommandBarResource est un ContentControl qui ne contient qu’une propriété de type CommandBar:

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;

namespace SamuelBlanchard.Helpers
{
    [ContentProperty(Name="CommandBar")]
    public class CommandBarResource : ContentControl
    {
        public CommandBar CommandBar
        {
            get { return (CommandBar)GetValue(CommandBarProperty); }
            set { SetValue(CommandBarProperty, value); }
        }

        public static readonly DependencyProperty CommandBarProperty =
            DependencyProperty.Register("CommandBar", typeof(CommandBar), typeof(CommandBarResource), new PropertyMetadata(null));
    }
}

Son utilisation dans XAML reste très simple :

    <Page.Resources>        
        <sb:CommandBarResource x:Name="Bar1">
            <CommandBar Background="#f0f0f0" Foreground="#606060">
                <AppBarButton Icon="Add" Label="Add"></AppBarButton>
                <AppBarButton Icon="Delete" Label="Delete"></AppBarButton>
                <AppBarButton Icon="ReShare" Label="Share"></AppBarButton>
                <AppBarButton Icon="List" Label="Manage"></AppBarButton>
            </CommandBar>
        </sb:CommandBarResource>
        
        <sb:CommandBarResource x:Name="Bar2">
            <CommandBar Background="#f0f0f0" Foreground="#606060">
                <AppBarButton Icon="Add" Label="Add"></AppBarButton>
            </CommandBar>
        </sb:CommandBarResource>
    </Page.Resources>

Coté C# on aura plus qu’a choisir la barre que l’on souhaite afficher :

this.BottomAppBar = this.Bar1.CommandBar
...
this.BottomAppBar = this.Bar2.CommandBar

Conclusion

Difficile de comprendre pourquoi la CommandBar, qui est pourtant un contrôle XAML, n’est pas accessible directement depuis les Ressources de la page.
Mais tant qu’un hack reste possible on arrête de bouder et on retourne coder dans la joie et la bonne humeur !

Toi petit fainéant du code tu trouveras le source du contrôle ici.

0  

[Universal App 8.1] ListView : Trucs et astuces

Le contrôle ListView est très populaire dans les applications universelles.
Et pour cause, Il est facile à mettre en place, possède un Header et un Footer, permet d’afficher des listes d’items groupés, tout en utilisant des TemplateSelectors.
Malgré tout, il souffre de problèmes assez déroutants pour les novices.
Dans cet article, je vous propose de décrire les problèmes les plus courants et de les corriger quand cela s’avère possible.

Les événements de manipulation

Vous cherchez à contrôler un autre élément de votre UI à partir des events de manipulation de votre ListView (ManipulationStart/Delta/Stop) ?
Je vous arrête tout de suite, ce n’est plus possible en Universal App.
En effet, afin d’obtenir des performances proche du système, Le Listview interdit tout bonnement l’accès à ces événements pourtant bien pratique.
Pour être plus précis, cela concerne tous les contrôles utilisant en interne un ScrollViewer (ListView, GridView, FlipView,…)
Il ne reste que les événements PointerPressed et PointerRelease qui restent actifs. Aie !

Le Scrolling dans la liste

Heureusement, il est quand même possible d’accéder à la position du Scrolling MAIS pas nativement.
En effet, le ListView contient un ScrollViewer que l’on va devoir rechercher car il n’est pas public.
Pour ce faire, dans le template du ListView (que vous trouverez à cette adresse) je récupère le nom du ScrollViewer chargé d’effectuer le scrolling de la ListView. Ne cherchez pas, il s’appelle « ScrollViewer » 😛

Ensuite, j’ajoute une méthode qui permet de rechercher un enfant dans l’arborescence visuelle du ListView (en gros le template) pour retrouver le Scrollviewer par son nom.

        private FrameworkElement FindVisualChild(DependencyObject element, string nameOfChildToFind)
        {
            for( int x = 0; x < VisualTreeHelper.GetChildrenCount(element); x++ )
            {
                var child = VisualTreeHelper.GetChild(element, x);
            
                if( child is FrameworkElement)
                {
                    string name = (string)child.GetValue(FrameworkElement.NameProperty);
                
                    if( name == nameOfChildToFind)
                    {
                        return (FrameworkElement)child;
                    }
                    else if( VisualTreeHelper.GetChildrenCount(child) > 0 )
                    {
                        return this.FindVisualChild(child, nameOfChildToFind);
                    }
                }
            }

            return null;
        }

et je n’ai plus qu’à m’inscrire aux événements de changement de vue :

ScrollViewer scrollViewer = (ScrollViewer)this.FindVisualChild(listview, "ScrollViewer");
        
if( scrollViewer != null)
{
   scrollViewer.ViewChanging += scrollViewer_ViewChanging;
}

puis dans l’événement, je suis désormais capable de récupérer la position du scrolling (ici verticale) :

void scrollViewer_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
{
     this.TextBlockScrollingValue.Text = e.NextView.VerticalOffset.ToString();
}

Pour affecter la position du scroll (avec animation ou non), j’utilise la méthode ChangeView du ScrollViewer :

// Je scrolle verticalement à la position 0 sans animation
bool isAnimated = false;
this.ScrollViewer.ChangeView(null, 0, null, !isAnimated);

Aligner à droite dans un item

Comme tous les membres de sa famille (ListBox, LongListSelector), ListView souffre d’un mal ancien et mystérieux.
Les items qui la composent sont alignés à gauche et ne prennent pas toute la place qui pourraient leur être alloués !
Il n’est pas possible, par exemple, d’aligner un textblock afin qu’il touche le bord droit de la ListView sans avoir recours à une astuce.
On va tout simplement affecter la propriété « HorizontalContentAlignment » à « Stretch » du ListViewItem chargé de rendre le contenu de l’item afin qu’il s’étende à la taille du ListView qui le contient.
Tout se passe en définissant un style dans ItemContainerStyle :

<ListView Grid.Row="1" ItemsSource="{Binding Names}">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextBlock HorizontalAlignment="Right" FontSize="20" Text="{Binding}"></TextBlock>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Ca gondole !

Même si l’on a recourt à l’astuce ci dessus qui nous permet d’avoir un ListViewItem de même taille que le ListView ce n’est pas toujours suffisant.
En effet, on retrouve souvent un comportement étrange lorsque l’on scrolle rapidement une liste dont les items sont complexes.
Je ne suis pas sûr de la cause exacte de ce problème mais il semblerait que le calcul interne du Layout du ListViewItem ne s’effectue pas correctement lorsqu’un de ses éléments change de taille en cours de route (une image qui s’affiche après chargement par exemple).
On observe alors une tendance à se décaler vers la droite puis à revenir vers la gauche. ça gondole !
Inutile de vous dire que ce genre de phénomène ne fait pas du tout pro dans une application.

Sur internet vous trouverez des tricks qui incitent à positionner un Binding sur la taille de l’itemTemplate afin que celle-ci soit fixée à partir de la taille de la ListView :

<ListView x:Name="ListViewNames" ItemsSource="{Binding Names}">
    <ListView.ItemTemplate>
        <DataTemplate>
          <Grid With="{Binding Path=ActualWidth,ElementName=ListViewNames}">
            <TextBlock HorizontalAlignment="Right" FontSize="20" Text="{Binding}"></TextBlock>
          </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

JE VOUS DECONSEILLE FORTEMENT CETTE PRATIQUE !

Dèjà, côté performance c’est un Binding de plus sur chaque item visible affiché.
Mais le plus dangereux c’est que cela ne marche pas toujours !
Et pour cause, ActualWidth n’est pas une propriété qui notifie ses changements.
Si la ListView change ou recalcule sa taille (lors d’un passage de Collapsed à Visible par exemple), ses items seront parfois avertis trop tard et certains items (les premiers en général) n’apparaitront pas.

Préférez plutôt cette technique qui consiste à fixer une fois la taille du panel interne du ListView d’après sa taille:

<ListView.ItemsPanel>
    <ItemsPanelTemplate>
        <ItemsStackPanel
            Loaded="ItemsStackPanel_Loaded">
        </ItemsStackPanel>
    </ItemsPanelTemplate>
</ListView.ItemsPanel>

et côté C# :

private void ItemsStackPanel_Loaded(object sender, RoutedEventArgs e)
{
    ItemsStackPanel panel = sender as ItemsStackPanel;
    panel.Width = this.ListViewGondole.ActualWidth;
}

Plus de transitions

Les contrôles WinRT ont pris la sale manie d’ajouter des transitions partout dans leurs comportements.
Dans une ListView on peut les voir lorsque l’on ajoute ou retire un élément de la liste.
Parfois, cela donne des effets intéressant, parfois, c’est loin d’être idéal.
Pour supprimer les transitions d’une ListView on fera donc comme suit :

<ListView>
  <ListView.ItemContainerTransitions>
    <TransitionCollection>
    </TransitionCollection>
  </ListView.ItemContainerTransitions>
</ListView>

Drag and drop et Reorder Items

Bien qu’il fonctionne sous Windows, le drag and drop ne marche pas côté Windows Phone.
En revanche ReorderItem fonctionne très bien sur les deux plateformes.

En gros il suffit de modifier les propriétés suivantes du ListView:

Pour Windows:

<ListView CanReorderItems="True" CanDragItems="True" AllowDrop="True"/>

Pour Windows Phone:

<ListView ReorderMode = "Enabled" />

Je vous renvoie sur la page de Toss net qui explique très bien la mise en place, vidéo à l’appui !
http://www.peug.net/2014/10/29/reorder-items-universal-app/

Les Blocs noirs

Parfois, lorsque la Listview peine à afficher ses éléments, des blocs noirs apparaissent lors des scrollings.
Si vous voulez régler le problème rapidement, il suffit de mettre la propriété ShowsScrollingPlaceholders à False.
Ainsi, les blocs deviendront transparents et cela sera moins choquant pour l’utilisateur.

<ListView ShowsScrollingPlaceholders="False" />

Pour faire les choses bien vous pouvez gérer les PlaceHolders à la main.
Malheureusement, cela sera aux dépens du Binding des itemTemplate du Listview.
Cela se met en place relativement simplement en s’abonnant à ContainerContentChanging du ListView.
Il faudra ensuite afficher les éléments par couches successives comme décrit dans la documentation MSDN

ListView inversée

Dans certain cas il peut être utile de pouvoir inverser le fonctionnement d’une Listview. Par exemple, dans le cadre de l’affichage d’un chat :

wp_ss_20150610_0001

La première chose à laquelle on pense face à ce problème est d’inverser le fonctionnement interne des items, c’est à dire faire des Insert(0,item) à la place de Add(item).
Si vous devez gérer des collections incrémentales, cela peut devenir un cauchemar car, rapidement, la ListView perdra les pédales en affichant n’importe quoi.

Le plus simple reste de jouer avec le GPU et d’inverser la Liste puis chacun de ses éléments:

<ListView RenderTransformOrigin="0.5,0.5">
  <ListView.RenderTransform>
    <ScaleTransform ScaleY="-1"/>
  </ListView.RenderTransform>

<ListView.ItemContainerStyle>
    <Style TargetType="ListViewItem">
        <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
        <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
        <Setter Property="RenderTransform">
            <Setter.Value>
                <ScaleTransform ScaleY="-1"></ScaleTransform>
            </Setter.Value>
        </Setter>
    </Style>
</ListView.ItemContainerStyle>

</ListView>

La vue de la liste sera donc inversée mais pas le model. C’est finalement plutôt logique.

Pour conclure

Lorsque l’on regarde l’ensemble des astuces à mettre en place pour obtenir un ListView prêt à l’emploi, je me dis que ce contrôle mériterait d’être revu par les équipes de développement de Microsoft afin qu’il soit plus facilement intégrable par des novices. C’est dommage car, une fois maîtrisé, le contrôle fait bien son travail. Mieux ! Il n’a jamais été aussi rapide et customisable !

Je remercie Gwendal « Virgule » Marchand pour sa relecture attentive :)

0