Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS, MAUI

Comment faire un binding des lignes et colonnes d’une grille ?

Si vous essayez un binding sur ces deux propriétés de l’objet Grid vous serez déçu, ça ne marche pas. Existe-t-il un moyen pour contourner cette limitation gênante dès lors qu’on se lance dans le design réactif et adaptatif ? C’est bien possible…

La Grid Xamarin.Forms

J’ai déjà eu l’occasion de vous présenter cet objet si simple, si basique mais aux possibilités souvent mal comprises et mal utilisées, notamment dans un article d’octobre 2020 lui-même largement inspiré d’un ancien article de 2011 sur ce sujet dans le contexte de WPF et Silverlight. C’est dire si cette méconnaissance n’est pas d’aujourd’hui à tel point qu’il faille une piqûre de rappel tous les 9 ans ! Inutile donc de faire des redites, suivez les liens pour vous plongez dans cette saine lecture !

Dans un autre papier de 2020 je vous parlais du comportement étranges des ColumSpan dans les grilles Xamarin.Forms. Autre problématique mais toujours ce satané objet Grid qui n’est pas si simple que ça au final.

D’ailleurs dans le dernier article, le premier indiqué plus haut, celui d’octobre 2020, je vous parlais des dernières simplifications de la syntaxe permettant de fixer les lignes et les colonnes. Là encore, pas de redite, cliquez sur le premier lien.

Donc la grille n’est pas un objet d’UI simpliste, et encore n’ai-je pas tout dit à son sujet car il existe de nombreuses utilisations astucieuses de cet incontournable de la mise en page XAML. Néanmoins, et tous ces articles le prouvent, on ne peut me reprocher de ne pas vous en parler !

Qu’y aurait-il encore à dire sur ce contrôle ?

Beaucoup car je n’ai aucunement la prétention d’avoir “régler son compte” au sujet et à ce contrôle aussi indispensable que versatile.

Toutefois quelque chose peut mériter encore quelques mots …

Faire un binding sur les propriétés lignes et colonnes

Bien que les simplifications de syntaxe récentes soient bienvenues il reste toujours un petit problème, les propriétés servant à définir les lignes et colonnes ne sont pas bindable ! Et ce depuis toujours, depuis la première version de WPF. Ce manque a ainsi traversé le temps et les plateformes comme Silverlight, UWP, Metro, et les Xamarin.Forms alors que dans le même temps XAML connaissait tellement d’améliorations comme le Visual State Manager (auquel j’ai consacré une série de trois vidéos !) apparu dans Silverlight, reporté dans WPF et dans Xamarin.Forms.

Etrange cet “oubli”. D’autant que si pouvoir modifier à la volée les lignes et les colonnes semble un peu risqué (que faire des objets qui référencent des lignes ou colonnes qui n’existeraient plus d’un seul coup par exemple ?) pouvoir en revanche définir par calcul une grille à la création d’une page peut s’avérer très utile pour s’adapter à l’écran, aux données ou à l’utilisateur. De telles décisions ne peuvent être prises qu’en C# et non en XAML, et il devient alors nécessaire de pouvoir modifier la configuration de la grille par des bindings (seules interactions autorisées entre logique C# et mise en page XAML dans le pattern MVVM).

Mais voilà, ce n’est pas possible.

Enfin ce n’est pas évident.

Première approche

L’objet RowDefinitions de XAML est une collection de type RowDefinitionCollections. Il est donc parfaitement possible de construire un tel objet en C#.

Prenons l’exemple le plus simple possible : une page XAML avec son unité de code Behind qu’on utilisera comme source de binding (une sorte de faux MVVM rapide qui simplifie beaucoup l’exemple mais qui n’est pas à reproduire).

// MainPage.xaml.cs

public RowDefinitionCollection MyRows => new RowDefinitionCollection() {
    new RowDefinition { Height = GridLength.Auto },
    new RowDefinition { Height = 100 },
    new RowDefinition { Height = GridLength.Star },
    new RowDefinition { Height = 50 },
};

public MainPage()
{
    InitializeComponent();
    BindingContext = this;
}

// MainPage.xaml
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns=http://xamarin.com/schemas/2014/forms
xmlns:x=http://schemas.microsoft.com/winfx/2009/xaml
x:Class="TestSwitch.MainPage"> <Grid RowDefinitions="{Binding MyRows}"> <Frame Grid.Row="0" BackgroundColor="#2196F3" Padding="24" CornerRadius="0"> <Label Text="Welcome to Xamarin.Forms!"
HorizontalTextAlignment="Center" TextColor="White" FontSize="36" /> </Frame> <Label Text="1" Grid.Row="1" /> <Label Text="2" Grid.Row="2" /> <Label Text="3" Grid.Row="3" /> </Grid> </ContentPage>

Et voilà ! Ici la collection est créée come une sorte de constante mais on comprend bien qu’une telle construction pourrait se faire au sein d’une méthode plus complexe faisant intervenir des décisions pour s’adapter au contexte.

Cela suppose aussi que les objets visuels ont une position fixée pour éviter que l’un d’eux ne soient affecté à une ligne ou une colonne inexistante. On peut contourner ce problème en créant les objets d’UI en C# aussi, ce n’est pas “mal”, dans les cas  où une forte adaptabilité est réclamée cela est même une bonne idée ! Il suffit de le faire dans un code qui n’est pas lié à un ViewModel (même s’il reçoit des instructions, des valeurs depuis ce dernier). On peut aussi considérer que l’adaptation ne concernera pas le nombre de lignes et de colonnes mais plutôt leurs dimensions respectives. Dans ce cas les objets d’UI peuvent être placés en code XAML, chacun étant assuré d’avoir sa place dans la grille.

Ces remarques s’appliquent aussi à la seconde approche :

Seconde approche

Sil la construction des objets RowDefinition en C# vous rebute vous pouvez utiliser la nouvelle notation simplifiée (sous forme de string) mais il faudra appeler le convertisseur pour traduire tout cela à nouveau en RowDefinitionCollection. C’est ce que montre le code qui suit :

// MainPage.xaml.cs

public RowDefinitionCollection MyRows { get; set; }

public MainPage()
{
    InitializeComponent();
    
    // Magic happening here
    MyRows = (RowDefinitionCollection)
new RowDefinitionCollectionTypeConverter()
.ConvertFromInvariantString("Auto, 100, *, 50"); BindingContext = this; } // MainPage.xaml <?xml version="1.0" encoding="utf-8"?> <ContentPage xmlns=http://xamarin.com/schemas/2014/forms
xmlns:x=http://schemas.microsoft.com/winfx/2009/xaml
x:Class="TestSwitch.MainPage"> <Grid RowDefinitions="{Binding MyRows}"> <Frame Grid.Row="0"
BackgroundColor="#2196F3" Padding="24" CornerRadius="0"> <Label Text="Welcome to Xamarin.Forms!"
HorizontalTextAlignment="Center" TextColor="White" FontSize="36" /> </Frame> <Label Text="1" Grid.Row="1" /> <Label Text="2" Grid.Row="2" /> <Label Text="3" Grid.Row="3" /> </Grid> </ContentPage>

Ici aussi on pourra intégrer la construction dans un processus plus complexe avec prises de décisions pour adapter la grille aux conditions de l’instant.

Et les colonnes ?

Lignes et colonnes fonctionnant exactement de la même façon je vous laisse imaginer un code parfaitement identique mais utilisant des ColumDefinitionCollection en place et lieu des RowDefinitionCollection, des ColulmnDefinition au lieu de RowDefinition, etc… Item pour le convertisseur.

Respect de MVVM

Ce n’est pas parce qu’il y a binding qu’il y a respect de MVVM… Le binding permet en effet de séparer le code d’UI de certaines valeurs présentes dans le ViewModel, ce qui ressemble bien à une architecture MVVM mais ce pattern indique aussi que jamais un ViewModel ne doit connaître les objets d’UI !

Donc hors de question de faire joujou avec des RowDefinition (ou ses amis) dans un ViewModel.

Alors comment marier binding des lignes et colonnes et MVVM ?

Il n’existe que deux possibilités : dire adieu à MVVM ponctuellement pour une page donnée, en l’assumant, en le commentant, et en faisant cela le plus proprement possible (puis en faisant pénitence pendant de longues journées de jeûnes après avoir déposé des offrandes sur la tombe de Ada Lovelace et avoir fait un donation à la fondation Bill et Melinda Gates). Ou bien vous débrouiller pour externaliser tout le calcul dans une ou plusieurs méthodes se trouvant dans le code behind, méthodes appelées via une messagerie MVVM depuis le ViewModel qui leur fournira les données nécessaires et qui aura pris les décisions importantes en lien avec les données.

C’est plus lourd à coder mais vous n’avez à vous faire pardonner de personne, c’est un avantage !

Conclusion

Comme pour les films de cascadeurs qu’on fait précéder de l’avertissement “ne tentez pas de refaire cela chez vous, les acteurs sont des professionnels entrainés !” j’aurai pu commencer cet article par une phrase proche. En effet ce binding des lignes et colonnes s’il n’existe toujours pas dans XAML ce n’est pas qu’à cause d’un bête oubli qui aurait traverser les âges. Il y a des raisons de ne pas autoriser ce genre de gymnastique. J’ai évoqué déjà le problème des objets qui d’un seul coup se trouveraient privés de ligne ou de colonne par exemple, un cas de plantage assuré. La séparation du code et de l’UI imposé par MVVM pourrait être une autre raison. On peut vouloir le faire ponctuellement mais il ne serait pas raisonnable de le proposer comme une feature (à moins de l’enrober d’un code complexe qui assurerait la consistance et la stabilité en toute occasion).

Il est donc essentiel de comprendre qu’ici je vous ai présenté une solution à un problème qui n’a de justification que dans de très rares occasions. Et encore vaudrait-il mieux dans ce cas construire toute l’UI en C#. Mais il existe toujours des cas tordus, des conjectures bizarres où certaines astuces peuvent sauver la mise.

C’est à vous de décider d’utiliser ou non le binding sur les lignes et les colonnes, selon le projet et son contexte. Et si vous décidez de ne pas utiliser l’astuce, vous aurez au moins appris comment dépasser ce qu’on croit impossible en XAML. Car C# et XAML s’ils sont toujours là, c’est que pour l’instant ils ne sont jamais tombés sur des problèmes qui ne puissent être contournés avec un peu d’astuce. Des PC d’il y a près de vingt ans aux smartphones les plus sophistiqués qui viennent à peine de sortir, aucune situation n’a mis en défaut ce couple infernal et si sympathique !

S’imbiber de cet état d’esprit, du fonctionnement de ce tandem de langages est dans tous les cas un un exercice qui prépare mieux à s’en servir correctement en toute circonstance …


Stay Tuned !

blog comments powered by Disqus