Dot.Blog

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

Silverlight : Control vs UserControl

[new:28/07/2010]La différence est importante entre Control et UserControl, et en même temps il est souvent difficile de la comprendre ! Quand créer l’un ou l’autre est une question récurrente. Comment choisir entre l’un ou l’autre dans ses développement ?

A l’origine…

A l’origine le développeur génial créa Visual Basic. Un EDI simple, fondé sur ce qu’on appellera plus tard le “RAD” (Rapid Application Development). Dans ce nouveau monde incroyable on cassait le modèle de programmation triste qui prédominait alors. Au lieu de tout faire en code sous un éditeur de texte, comme une sténo-dactylo, on pouvait enfin concevoir des applications Windows en posant des “briques visuelles”, des “composants” : boutons, checkbox, etc, devenait des objets visuels pour le développeur aussi !

Visual Basic a fait mieux d’un mage, un dieu ou un guru : il a rendus la vue aux développeurs !

Aveugles tâtonnant “au jugé” dans leurs éditeurs de texte, ils ont d’un seul coup vu la lumière ! Enfin sous leurs yeux écarquillés le bouton était réellement un bouton qu’il suffisait de placer visuellement là où on voulait qu’il soit !

Nous sommes dans les années 80, le génial inventeur s’appelle Alan Cooper. Il montre son projet, “Tripod”, à un certain Bill Gates. Ayant le sens aigu des affaires qu’on lui sait et un flair incomparable pour savoir ce qui va être vendeur ou non, Bill négocie le concept et l’achète le 6 mars 88. Le projet s’appelle désormais “Ruby” chez Microsoft.

C’est là qu’un autre grand futé, Anders Hejlsberg, père de C#, qui était alors chez Borland où il avait créé le Turbo Pascal, eut l’idée de faire la même chose. Delphi 1.0 sortira en 95 comme un “VB Killer”. Et même si Delphi dépassa de loin VB sous Win32 (techniquement, mais jamais en nombre d’utilisateurs) il ne s’agissait que d’une copie presque parfaite de VB, ce sont dans les petites nuances que l’avantage technique de Delphi allait lui permettre de jouer les outsiders.

Pourquoi je vous parle de ces vieilleries ?

Parce que c’est là que se trouve l’origine de la question du jour !

Composants visuels “à créer non visuellement”

Sous Visual Basic Win16 puis Win32 les fameux “composants visuels” qu’on plaçait (et place toujours) sur la surface de design devaient être écrits en C++. Un comble pour un langage (VB) qui était et est surtout utilisé par des informaticiens “formés sur le tas” ou des amateurs (hobbistes). Les universitaires ont toujours eu le Basic et plus encore VB en horreur (pour des raisons techniques valables mais aussi parce que finalement cela rendait le développement trop simple ce qui les obligeait à descendre un peu de leur piédestal !).

Comme un pied de nez, ce fut ces mêmes C++istes qui firent le vrai succès de VB au départ. En effet, pour gagner du temps sur les concurrents, certains s’étaient rendus compte que VB permettait de présenter très vite aux clients potentiels une maquette fonctionnelle… Commercialement cela donnait un coup d’avance. Mais aucun de ces  développeurs n’utilisait VB pour développer : une fois le prototype vendu, ils développaient l’application finale avec un “vrai” langage, le C++, et les affreuses MFC (affreuses, à utiliser).

Ce faisant ils firent le succès de VB et renforcèrent l’envie de Hejlsberg de faire Delphi !

Sous VB il fallait donc être un super développeur C++ pour créer des composants, tâches complexe, ingrate et absolument pas visuelle. Et surtout, situation totalement paradoxale. Une brèche dans laquelle Delphi allait s’engouffrer.

L’idée géniale de Delphi fut de tout en faire en Delphi. “Delphi est fait en Delphi” fut l’un des premiers arguments techniques avancés pendant longtemps par Borland. Les composants étaient écrits dans le même langage que l’EDI et que les programmes créés par le développeur. Cela permit l’explosion des composants sous Delphi, en grande partie responsable du succès du produit.

Mais même sous Delphi, créer un composant restait une affaire de spécialistes. Ceux qui écrivaient des composants (et encore plus s’ils étaient orientés données) avaient une aura de “super pro”. Une certaine hiérarchie sociale se créa à cette époque : les jeunots, les débutants étaient mis à la création des états, les confirmés au développement des écrans, et les caïds adulés et mieux payés à la création des composants. Une hiérarchie qui perdure encore dans beaucoup de SSII d’ailleurs…

De ce point vue Delphi ne révolutionna rien, il entérina plutôt une situation : la bleusaille à la génération d’état sous Delphi et VB, les bons aux composants (en Delphi ou en C++).

Les composants de VB et de Delphi étaient malgré tout une idée fantastique.

Quand .NET fut créé avec C# (par le père de Delphi donc, passé de Borland à Microsoft), le rêve ne s’arrêta pas en si bon chemin. .NET reprit bien entendu la notion d’IDE “RAD” avec Visual Studio.

Les composants Windows Forms ou ASP.NET se bâtissaient comme ceux de VB et Delphi : non visuellement en réclamant un niveau de qualification un cran au dessus du développeur “de base”. Ce que Delphi appelait “Components” (composants), Visual Studio les appela “Controls” (contrôles). Mais en dehors de cette différence mineure on restait dans les mêmes règles du même jeu.

Apparition de la dualité

Mais très vite, les Windows Forms (et ASP.NET) offrirent une autre façon de créer des contrôles : les User Control. L’idée, fort simple (à avoir mais pas à réaliser puisque cela a attendu tout ce temps !), était de rendre le développement des composants totalement visuel. Briser cette frontière entre les compétences de base pour créer des applis et super compétences pour créer des contrôles.

.NET offrit ainsi rapidement un nouveau niveau d’abstraction : le User Control. On les construisait visuellement, comme des “mini applications” autonomes, on pouvait ensuite les utiliser comme les “vrais” composants fournis de base avec l’EDI. Un progrès immense.

Mais voilà, tout ne pouvait se faire visuellement, créer un contrôle, un composant, c’est comme une application : c’est un travail très “ouvert” on peut faire tout de ce qu’on veut. Hélas concevoir un environnement graphique qui par drag’n drop offre autant de souplesse que du code a toujours été un challenge jamais atteint (jusqu’il y a peu, je vais y arriver).

De fait, malgré la bonne volonté de vouloir simplifier les choses, cette époque restera marquée par la complexification de la situation en créant deux types de contrôles : les “vrais”, les durs les tatoués, ceux créés entièrement par code qui peuvent tout faire et dont la conception est réservée à l’élite des développeurs, et les “faux”, les User Controls, créés facilement sans besoin de compétences très pointues mais ne pouvant couvrir toutes les situations…

Pendant des années cette dualité Control / User Control domina le monde des composants. Renforçant les castes évoquées plus haut, entretenant la confusion chez les débutants.

L’enfer est souvent pavé de bonnes intentions, c’en est une illustration parfaite.

Et Dieu créa la flemme…

Le bon développeur est un fainéant. Et comme la création de “vrais” contrôles étaient une tâches réservées à ces cadors du clavier, il fallait bien qu’un jour ils se lassent de se taper le job le moins rigolo. Eux aussi voulaient faire joujou avec le drag’n drop !

Dans un retournement de situation de type arroseur/arrosé, les “bons” se trouvèrent coincés derrière leur clavier, avec des machines moins puissantes que les jeunots développant à coup de drag’n drop des User Control sur de beaux écrans couleurs. C’est tout juste si certains patrons trop pingres n’allaient pas leur supprimer la souris pour faire des économies vu que de toute façon créer de “vrais” contrôles se fait exclusivement au clavier !

Une telle situation ne pouvait s’éterniser, c’est une évidence. Il fallait que l’Ordre revienne, que les hiérarchies retrouvent leur place comme “au bon vieux temps”.

Hélas pour les réactionnaires l’avenir n’a pas pris cette forme !

… et Microsoft Blend…

Expression Blend, ce produit magnifique, fut créé et mis sur le marché à peu près en même temps que la première version de Silverlight (même si Blend traite aussi bien WPF).

Blend, tout comme VB a été certainement au départ un produit acheté à un développeur génial. Mais le Web n’en garde pas la trace (ou elle est bien cachée!). Mais qu’importe, ce qui nous intéresse c’est ce qu’est Blend et ce qu’il est devenu en quelques années.

Autant le dire tout de suite, n’étant qu’un outil par dessus le Framework, Blend ne révolutionne pas les concepts sous-jacents : .NET et ses langages principaux supportent toujours, même sous WPF 4, la double notion ambigüe de Control/User Control.

Le génie de Blend est d’offrir une plateforme graphique dont la puissance permet enfin d’aborder la création des “vrais” composants de façon totalement graphique comme les User Controls.

Le principe reste un peu différent, et le fonctionnel reste du code, mais au final le visuel est enfin créé visuellement !

Il s’agit là d’une révolution qui a connu une gestation de plus de vingt ans ! Difficile pour les plus jeunes lecteurs de se rendre compte, forcément.

La création d’applications Windows visuellement, c’est VB en 1988.

La création des composants dans le même langage que les applications, c’est Delphi 1.0 en 1995.

Puis plus rien jusqu’à la fin de la première décennie du 3eme millénaire…

Le projet Sparkle, en 2006, devient finalement “Blend” au lieu de “Microsoft Expression Interactive Designer”. C’est plus court. Mais c’est en 2007 que la première CPT publique sera releasée.

Blend 3 en 2009 marque une première étape de maturité confirmée par Blend 4 sorti dernièrement.

Plus de vingt ans à attendre pour que l’idée de composant, unique au départ, ne redevienne unique et que cesse la dualité Control / User Control.

La création visuelle des Controls

Les User Controls, c’est facile, il suffit de faire “nouveau User Control” dans les menus de Blend ou VS pour en créer. Ensuite on dispose d’une surface graphique pour y dessiner ce qu’on veut et d’une page de code pour y placer le fonctionnel.

Mais les Controls, les “vrais”. Comment fait-on alors ?

Avec Visual Studio les choses changent peu. C’est avant tout un environnement de codeur, pas de designer.

Mais avec Blend, l’impossible devient réalisable…

La méthode

Elle est bien simple… On ajoute une nouvelle Classe. Comme on le ferait pour une classe métier ou autre.

Aucun visuel n’est bien entendu attaché à ce code. Pas de page Xaml cachée derrière. Juste une classe.

Commençons par la faire héritée d’un Control, par exemple ContentControl. Comme toujours le choix de la classe mère est essentiel dans la construction d’une nouvelle classe, mais c’est un sujet que nous n’aborderons pas ici tellement il nous éloignerait du but.

Un Control possède des états visuels gérés par le Visual State Manager. Il suffit de décorer la classe des bons attributs :

   1: [TemplateVisualState(GroupName = CommonStates, Name = StateA)]
   2: [TemplateVisualState(GroupName = CommonStates, Name = StateB)]
   3:  
   4: public class MonControl : ContentControl { ... }

Dans l’exemple ci-dessus on créé un seul groupe d’état (dont le nom réel se trouve dans une constante chaîne contenu dans le code de la classe) “CommonStates” et deux états dans ce groupe “StateA” et “StateB”,peu importe leur rôle et signification nous restons ici dans le principe. La classe MonControl se trouve ainsi dotée de deux états contenus dans un groupe d’états, le tout sera accessible dans le panneau du VSM sous Blend.

Le fonctionnel

Le fonctionnel c’est du code, nous n’en sommes pas à la création de programmes par commande vocale ou lecture des pensées… Donc le fonctionnel est codé “normalement” tout dépend de ce qu’on veut faire. Cela peut être très simple ou ultra sophistiqué, l’étendue est telle qu’il est même impossible d’en dire plus. On trouve des propriétés CLR, des propriétés de dépendance, des méthodes.

Bien entendu on retrouvera la logique qui permet de changer d’état (entre StateA et StateB dans l’exemple ci-dessus).

Mais le visuel alors ?

En fait le visuel ne compte pas beaucoup pour le fonctionnel… et cette déconnexion totale est une avancée incroyable dont le crédit est à mettre aux équipes de développement Microsoft attachées au Framework, à Blend et à Xaml.

En fait, votre code fonctionnel se moque royalement de la “tête” que le contrôle aura. La seule chose qui lui importe c’est de pouvoir y trouver certains éléments dont il va avoir besoin. Par exemple un bouton, une zone de titre, etc.

Pour déclarer ces éléments il n’y a qu’à décorer encore un peu plus notre classe avec d’autres attributs :

   1: [TemplatePart(Name = PanelContent, Type = typeof(FrameworkElement))]
   2: [TemplatePart(Name = DoItButton, Type = typeof(FrameworkElement))]
   3:  
   4: [TemplateVisualState(GroupName = CommonStates, Name = StateA)]
   5: [TemplateVisualState(GroupName = CommonStates, Name = StateB)]
   6:  
   7: public class MonControl : ContentControl { ... }

le code ci-dessus montre les deux “TemplatePart” ajoutés. Notre code aura besoin d’une zone pour un contenu, “PanelContent” qui pourra être de tout type dérivé de FrameworkElement (ici on balaye large !) et d’un bouton pour lancer le fonctionnement, “DoItButton” que nous voyons aussi comme un descendant de FrameworkElement.

Pourquoi voir “si large” ? Le bouton pourrait être limité à un descendant de ButtonBase par exemple. Le panneau de contenu pourrait descendre de Panel.

En réalité, et sauf cas vraiment particulier, le concepteur d’un Control se doit de rester le plus large possible dans les contraintes qu’il impose au “template parts” (parties de template). Pourquoi brider la créativité de celui qui utilisera le contrôle et essayera de le templater ?

Par exemple pour le bouton, notre code a besoin (c’est un pur exemple) d’un bouton pour déclencher certaines actions de MonControl. Nous nous moquons totalement qu’au final le bouton soit remplacé par un carré ou une image PNG… Tous ces éléments descendent de FrameworkElement qui supporte les événements de souris…

Ainsi, s’il s’agit vraiment d’un bouton, nous programmerons son “Click”, mais s’il s’agit de n’importe quoi d’autre nous nous contenterons de son MouseLeftButtonDown par exemple.

Comment relier le visuel au code ?

C’est très gentil tout çà, mais où est le visuel ?

Pour l’instant il n’y en a pas. Et notre classe pourrait très bien s’en contenter. Visuellement elle ne donnerait rien c’est sûr… Il faut donc aller un cran plus loin.

La première étape consiste d’abord à relier le code au visuel.

Comment ? Hein ? je viens de dire qu’il n’existait pas ? Oui. Et non.

Le visuel à proprement parlé n’existe pas encore, c’est vrai, mais rappelez-vous, nous l’avons complètement conceptualisé ce visuel… totalement dématérialisé. Donc d’un certain point de vue il existe bel et bien : ce sont les attributs TemplatePart qui donne naissance “virtuellement” aux éléments graphiques dont notre code aura absolument besoin pour tourner. C’est très abstrait, j’en conviens. Mais il n’empêche, à ce stade du développement de notre Control le visuel est déjà “présent” uniquement grâce à ces attributs. Si vous me demandez “graphiquement” si le visuel existe, la réponse est non. Mais “conceptuellement” oui.

Comment le code peut-il se servir de concepts abstraits ?

Il y a une astuce, par force. Il faut bien obtenir, à un moment ou un autre, des pointeurs vers les instances des éléments graphiques si on veut pouvoir les manipuler par code, réagir à des événements.

Quelles instances ?  … Peu importe…

C’est agaçant non ? :-)

En fait il “existera” bien des instances à un moment donné, ce moment n’est pas arrivé dans le cycle de conception, mais il ne va pas tarder. Pour l’instant les attributs décrivant les Template Parts nous suffisent. Mais pour lier le concept au réel, nous allons tout de même devoir ajouter un peu de code en surchargeant la méthode OnApplyTemplate du Control :

   1: public override void OnApplyTemplate()
   2: {
   3:     base.OnApplyTemplate();
   4:     doItButton = GetTemplateChild(DoItButton) as FrameworkElement;
   5:     content = GetTemplateChild(PanelContent) as FrameworkElement;
   6:     if (content != null)
   7:         content.MouseLeftButtonDown += content_MouseLeftButtonDown;
   8:     if (doItButton is ButtonBase)
   9:         (doItButton as ButtonBase).Click += doItButton_Click;
  10:     else if (doItButton != null) doItButton.MouseLeftButtonDown += doItButton_MouseLeftButtonDown;
  11: }

Regardez bien car c’est là que la magie s’opère…

OnApplyTemplate est la porte d’entrée que les concepteurs du Framework ont créée pour que le développeur d’un contrôle puisse prendre la main. Cette méthode est en fait appelée par ApplyTemplate qui, pour simplifier, est invoquée avant qu’un Control ne soit affiché. Nous n’entrerons pas dans ces détails aujourd’hui.

Or, que voit-on dans ce bout de code exemple ?

On voit que nous récupérons dans des variables privées (mais ce n’est qu’une possibilité) les fameuses instances des parties de template. Par exemple la ligne 4 essaye de récupérer la partie “DoItButton”, la ligne 5 le “PanelContent”. Des parties visuelles que nous avons déclarées dans les attributs TemplatePart.

Une fois ce “minimum syndical graphique" récupéré par le Control ce dernier peut agir sur les éléments. Par exemple, en lignes 6 et 7, le Control accroche un gestionnaire d’événement du  bouton gauche de la souris à ce qui joue le rôle de “PanelContent”, peu importe par quoi il est représenté graphiquement.

Les lignes 8 à 10 font la même chose avec l’élément qui joue le rôle de “DoItButton” (ce qui serait un bouton pour lancer une action du Control). S’il s’agit d’un descendant de ButtonBase, alors nous pouvons programmer son Click ce qui est parfait. Mais si jamais ce n’est pas un ButtonBase, qu’importe, nous programmons le bouton gauche de la souris.

Bien entendu, derrière ces gestionnaires événements se cache du code fonctionnel, le bouton “DoItButton” fait “quelque chose”. Peut-être lancer une impression, accéder à des données, peu importe, “MonControl” n’est pas un vrai Control c’est un squelette qui permet d’illustrer mon propos. Dans la réalité il n’y aura peut-être pas de gestionnaires d’événements, ou d’autres, c’est totalement “open” c’est vous qui voyez… selon ce que vous voulez faire de votre contrôle (son utilité finale).

Et la grande révolution elle est où ?

Elle se trouve d’abord dans le fait que nous venons d’écrire un Control entièrement par code (Youpi ! comme il y a vingt ans ! – Pfff … vous n’êtes que des grincheux !).

Il y a tout de même une grosse différence : ce code utilise et manipule de l’interface, des graphismes, de la 3D sous WPF, des transformations, des effets, des animations mais tout cela n’a pas besoin d’exister réellement !

Le découplage fort entre code et design prend ici tout son sens. Du code il y en aura toujours. Du moins tant que les ordinateurs ne se programmeront pas tous seuls, ce qui n’est quand même pas demain la veille.

En revanche, permettre au développeur de faire son travail sans se prendre les pieds dans la réalisation graphique, laisser même le droit au changement, à l’erreur, ça c’est nouveau.

Mais ce n’est pas tout !

La vraie révolution vient du fait que vous allez pouvoir maintenant ajouter la partie visuelle à votre contrôle, et ce, visuellement, grâce à Blend !

Un Contrôle comme nous venons de le voir n’a pas de Xaml accroché au code. Mais il en faut bien un peu pour donner figure à ce qui ne resterait que virtuel. Mais au lieu d’un objet Xaml de type Page par exemple, notre Control se nourrit d’autre chose : de ressources.

Quelles ressources ? Au moins une, un Template de Control. Par habitude ce template est stocké dans un dictionnaire de ressource. Cela peut être App.Xaml mais ce serait relier le contrôle à une application donnée. Pas très malin. Donc le template en question sera stocké dans un dictionnaire de ressource séparé. Souvent appelé “Generic.xaml” et stocké dans un sous répertoire “Theme”.

Ce template définit un style dont le TargetType est la classe de notre contrôle. Ce style définit ensuite un Template qui donne le visuel.

Voici le début d’un tel dictionnaire :

   1: <ResourceDictionary 
   2:   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
   3:   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:   xmlns:local="clr-namespace:MaSociété.MesControls"
   5:   xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
   6:   >
   7:     <Style TargetType="local:MonControl">
   8:         <Setter Property="Template">
   9:             <Setter.Value>
  10:                 <ControlTemplate TargetType="local:ContentPanel">
  11:                     <Grid>                     
  12:                         <vsm:VisualStateManager.VisualStateGroups> 
  13:                         .... 

Un dictionnaire de ressource Xaml tout ce qu’il y a de plus classique donc, avec un template de contrôle lui aussi très classique comme tous les templates qu’on peut créer avec Blend.

Ce visuel “de base” est l’aspect qu’aura votre contrôle “par défaut”. Certains l’écrivent totalement à la main (si c’est très simple pourquoi pas), mais franchement mieux vaut utiliser le bon outil : Blend.

Comment créer le Style par défaut visuellement ?

Notre Control est écrit, il se compile bien. Ajoutons juste un projet de test à la solution, référençons notre librairie de contrôles, et plaçons une instance de notre contrôle sur la page principale du projet de test.

On ne voit rien, ou presque. Normal, le pauvre contrôle n’a aucun visuel !

Un clic dans l’arbre visuel pour sélectionner le contrôle, puis on va dans le menu Objet et on choisit “Edit Style” (Modifier le Style dans la VF je pense). Cela ouvre un nouveau style qu’on prendra soin de placer dans un dictionnaire de ressource à part qu’on appellera par exemple “Generic.xaml”… Une fois dans le Style ne reste plus qu’à créer le Template (clic droit, modifier le template, et choisir d’en créer un à partir de rien).

Et vous voici en visuel sur la création totalement libre du visuel de votre Control !

Accrocher les wagons au train

Vous allez placer ce que vous voulez graphiquement, mais rappelez-vous, votre contrôle à besoin de certains objets particuliers pour fonctionner. Ce sont ceux définis dans les attributs TemplatePart. Blend offre un panneau “Parts”, là on peut voir toutes les parties dont a besoin le contrôle pour fonctionner, et on peut savoir si on a déjà affecté quelque chose ou non à chacune d’entre elles.

Par exemple, je pose un bouton sur le visuel et je me dis “je veux que ce bouton joue le rôle de la partie "DoItButton"”. Comment faire ? Très simple : je clique sur le bouton pour le sélectionner, et clic-droit “Make into part of MonControl”, ce qui ouvre un sous-menu dans lequel je vois toutes les parties, dont “DoItButton”. Je clique sur ce dernier et voilà ! Le bouton est maintenant associé à la partie “DoItButton”. Partie qui sera donc récupérée dons le OnApplyTemplate du Control, code que nous avons vu plus haut… La boucle est bouclée, les wagons graphiques sont accroché au train code…

C’est pas magique ça ?

Extraire les ressources

Maintenant nous disposons d’un projet de test possédant un fichier Generic.xaml qui contient tout le template visuel de notre contrôle. Son “look” par défaut.

Il suffit de copier ce fichier dans la librairie où se trouve MonControl, de vérifier que le Style a bien pour TargetType la classe MonControl. Peut-être faudra-t-il vérifier et accorder le namespace. Rien de bien méchant.

On peut maintenait supprimer le projet de test, recompiler la librairie de MonControl.

Créer une nouvelle application, référencez la librairie en question, poser une instance de MonControl sur la page : voici un joli Control avec un look par défaut… A ce stade vous devenez designer utilisateur du contrôle, et vous pouvez le (re) templater pour personnaliser son aspect, voire tout changer !

Conclusion

Créer de “vrais” contrôles étaient jusqu’à il y a peu une tâche ingrate, totalement non visuelle.

Grâce au Framework .NET et à Expression Blend il devient possible, pour la première fois depuis vingt ans, de rendre la création graphique d’un “vrai” contrôle totalement visuelle.

C’est une avancée énorme, une révolution silencieuse. Etrangement les gens sont plus ébaubis par le support des vidéo HD. C’est peut-être plus facile à voir tout simplement…

En tout cas, entre les vidéos HD, le Out-Of-Browser et tout un tas de choses sympathiques et la création visuelle de “vrais” contrôles, moi je n’hésite pas un seul instant, la vraie révolution technique est bien dans cette possibilité jusqu’à lors un pur rêve. Delphi, Java, Php, et tous les autres environnements plus récents sont conceptuellement largués. Lire des videos c’est rigolo, faire avancée la technique en profondeur me semble plus essentiel.

J’espère que ce billet vous aura permis de vous en rendre compte et vous aura donnée l’envie d’en savoir plus. Je n’ai pas la prétention ici d’avoir fait le tour de la conception des contrôles. Juste d’un point crucial qui marque l’histoire du développement…

Stay Tuned !

blog comments powered by Disqus