Les jeux vidéo, c’est utile ????

Le début

Il y a peu de temps mon bb (big boss, accessoirement également surnommé barbu bavard), lui là :

damien-thouvenin

M’a vu jouer à un jeu vidéo (et oui, même pendant les heures de bureau)

20160527_084856

Pendant que je perds à Overwatch d’après Rémi Lesieur Bridel

Overwatch-6

Et il m’a aussitôt dit « Ah, mais mon fils passe son temps à jouer aussi, je ne sais pas ce qui doit m’inquiéter le plus sachant cela »

J’ai alors du répondre par un point d’interrogation en guise de visage. (Bon vous imaginez aisément, pas besoin de photos hein)

Auquel Damien m’a plus ou moins dit « Je ne vois vraiment pas l’intérêt des jeux vidéo, c’est une pure perte de temps à mes yeux »

J’ai hésité à lui répondre « Mais voyons, n’aurais-tu donc aucun loisir ? » mais je me suis plutôt dit que j’allais en profiter pour écrire quelques lignes.

Lire la suite

Les Shadoks un demi sciècle plus tard

log_shadok

Par inconnu — Scan pochette DVD Les shadoks, édition intégrale, marque déposée, https://fr.wikipedia.org/w/index.php?curid=1330598

Presque 50 ans plus tard, Les Shadoks ont encore beaucoup à nous apprendre.

Google nous le rappelle aujourd’hui, cela fait 48 ans que la série animée culte et absurde a commencé sa diffusion.

Les Shadoks pompaient et pompaient. Ils ne savaient pas pourquoi, mais ils pompaient. Ils en ont déduit un certain nombre de devises.

S’il n’y a pas de solution, c’est qu’il n’y a pas de problème.

Quand on ne sait pas où l’on va, il faut y aller !!! … Et le plus vite possible.

Il vaut mieux pomper et qu’il ne se passe rien que risquer qu’il se passe quelque chose de pire en ne pompant pas.

 

Comme quoi, presque 50 ans plus tard … Les Shadoks pompent toujours.

Erreur 0x80073cf6 après avoir rétrogradé de Windows 10 vers Windows 8.1

Nous avons eu une problématique récemment avec un de nos clients qui a voulu tester Windows 10 sur sa tablette Surface Pro 3 pour vérifier que l’application Windows Store Apps que nous lui avons développée pour Windows 8.1 fonctionne toujours correctement.

En l’occurrence, l’application fonctionnait correctement, mis à part les modifications effectuées par Microsoft avec Windows 10 sur la nouvelle interface de CameraCaptureUI qui est maintenant dans une petite fenêtre. Il a donc décidé de rétrograder de Windows 10 vers Windows 8.1. La rétrogradation étant faite dans le mois suivant l’installation, elle n’était pas compliquée en suivant les informations pour Désinstaller Windows 10 et revenir à Windows 8.1.

2015_11_5-steps-to-downgrade-from-microsoft-windows-10-to-windows-81-or-windows-7

Lire la suite

TFS 2015: Customisation des activités de build (part2)

Auteur original, de notre ancien blog : Laurent Jacques

tfs1

Aujourd’hui nous allons discuter de la customisation des activités de build avec TFS 2015.En effet, alors que dans un prochain billet j’aborderai le tout nouveau système de build vNext ; il me semblait important de souligner une des (nombreuses) possibilités de customisation du mécanisme existant.

Etant donné le nombre d’étapes, j’ai scindé cette présentation en 2 parties : 

  1. la création d’un nouvelle activité (voir TFS 2015: Customisation des activités de build (part1))
  2. l’intégration du workflow dans la définition de la build.
 

PART2 : Intégration du workflow dans la définition de build.

Lire la suite

Déployez vos documents sur OneDrive Entreprise par la programmation

Pour faire suite à mon précédent billet sur le téléchargement de fichiers depuis Google Drive, ce billet traitera de la mise à disposition de documents sur OneDrive Entreprise par le code.

A la fin de ce tutorial, vous serez à même de connecter votre application à votre environnement OneDrive Entreprise et d’y uploader vos fichiers et dossiers.

OneDrive Entreprise

Là où OneDrive est un service gratuit de stockage personnel avec un compte Microsoft, OneDrive Entreprise (ou OneDrive for Business en anglais) est une solution de stockage conçue pour les entreprises : Vous pouvez partager vos documents et collaborer avec vos collègues, à la SharePoint.

Un espace OneDrive Entreprise est géré par un administrateur de collection de site.

Il est ainsi possible en vous donnant les droits suffisants, d’accéder aux espaces de stockages de vos collaborateurs sous OneDrive Entreprise et ainsi, d’y déployer leurs documents.

Techniquement, OneDrive Entreprise est une bibliothèque de documents SharePoint, ainsi, pour accéder à celle-ci par la programmation, nous utiliserons une API SharePoint : CSOM.

Lire la suite

TFS 2015 : automatiser la création d’une définition de build XAML.

Auteur original, de notre ancien blog : Laurent Jacques

xaml1

xaml2

Aujourd’hui nous étudierons un cas pratique d’utilisation avancée de TFS  (2012,2013 et 2015) et de ses mécanismes de build.

Nous chercherons à automatiser la création et la suppression des définitions de builds avec l’utilisation de l’API de Team Foundation Server avec Visual Studio 2013.

Pour le cas d’étude de ce billet, l’organisation du code source se présente ainsi :

  • une branche dev : le travail courant des développeurs, tous check-in leurs modificationdedans au jour le jour.
  • plusieurs branches uat : en fin de cycle de développement, une nouvelle branche uat_X.Y.Z est créée à partir du dev pour partir en validation (test d’intégration)
  • une branche master : une fois validée la branche uat_X.Y.Z devient la nouvelle branche master : version référence pour les développements.

Ce qui donne par exemple l’arborescence suivante :

/Projet/
       /dev
	       /Appli1
		/Appli2
       /master
		/Appli1
		/Appli2
	/uat_1.2.3
		/Appli1
		/Appli2
	/uat_1.3.0
		/Appli1
		/Appli2

 

L’équipe de développement a mis au point deux définitions de build basées sur la branche DEV :

  • une intégration continue, tournant les tests unitaires automatiques, générant la doc, fournissant les livrables dans un répertoire dépôt;
  • une nightly : déployant les applications sur l’environnement de test tous les jours à 23h.

Problématique 

Une fois validée en interne, le DEV est branché en UAT_X.Y.Z : les définitions de builds ne fonctionnent donc pas sur cette branche.

Actuellement : pour chaque nouvelle branche UAT le lead technique vient créer une nouvelle définition de build UAT et ajouter les différents paramètres.

Besoin 

Automatiser le processus.

Mise en oeuvre

Pour cette première version nous visons la mise en place d’une app console en c# qui ira, à intervalles réguliers, vérifier l’état des branches et gérer les définitions de builds.[…]

Avant toute chose: ajouter les références (v12) dans le projet :

xaml3

1ière étape:  récupérer la listes des branches UAT existantes.:

List<Branch> branchList = new List<Branch>();
using (var tfs = new TfsTeamProjectCollection(_TFSserver))
{
    VersionControlServer vcs = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));
    var items = vcs.GetItems(sourceCodeArea, VersionSpec.Latest, RecursionType.OneLevel,
        DeletedState.NonDeleted, ItemType.Folder).Items;
    foreach (Item item in items)
    {
        // skip self
        if (!item.ServerItem.Equals(sourceCodeArea))
        {
            if (item.ServerItem.ToLower().Contains(filter.ToLower()))
                branchList.Add(new Branch(item.ServerItem, item.CheckinDate, project));
        }
    }
}

Dans le code présent la logique est la suivante:

  1. on fournit comme paramètres :
  • l’url d’accès au serveur TFS ,
  • la zone de départ dans le source (sourceCodeArea) , 
  • un fitre est indiqué qui va permettre de ne ressortir que les branches qui nous intéressent (filter) 
     2. on récupère la liste des folders (branche) ;
var items = vcs.GetItems(sourceCodeArea, VersionSpec.Latest, RecursionType.OneLevel,                                          DeletedState.NonDeleted, ItemType.Folder).Items;

3. pour chacun de ses folders, on vérifie la cohérence du nom par rapport au filtre précisé :

if (item.ServerItem.ToLower().Contains(filter.ToLower()))

4. une fois récupérées, les branches seront ajoutées dans une liste d’objets maisons pour être gérées plus tard.

 

2ième étape:  récupérer les définitions de build existantes:

    var tfs = new TfsTeamProjectCollection(_TFSserver);
    tfs.Authenticate();
    var buildServer = (IBuildServer)tfs.GetService(typeof(IBuildServer));
    var buildDefinitionList = new List<IBuildDefinition>(buildServer.QueryBuildDefinitions(TEAM_PROJECT));

Afin de déterminer si les builds n’existent pas déjà, il faut en premier lieu récupérer la liste des builds existantes.

Les instructions ci-dessus permettent d’interroger le serveur TFS pour cela.

Ici, le TEAM_PROJECT  est simplement une constante représentant le nom du projet d’équipe.

L’appel à  buildServer.QueryBuildDefinitions va fournir l’ensemble des builds existantes pour le projet.

 

3ème étape:  parcourir la liste des branches et créer les builds inexistantes:

if (!buildDefinitionList.Exists(x => x.Name == TfsBuildServices.BuildName(projectName, branch)))
                {
                    IBuildDefinition CIBuild = buildDefinitionList.Where(x => x.Name == projectName + "_CI").First();
                    if (CIBuild != null)
                    {
                        var newbuild = buildServer.CreateBuildDefinition(TEAM_PROJECT);
                        newbuild.CopyFrom(CIBuild);
                        newbuild.Name = TfsBuildServices.BuildName(projectName, branch);
                        BuildSettings settings = new BuildSettings();
                        var process = WorkflowHelpers.DeserializeProcessParameters(newbuild.ProcessParameters);
                        settings.ProjectsToBuild = new StringList(((BuildSettings)process["BuildSettings"]).ProjectsToBuild.ToString().Replace("DEV",branch));
                        process.Remove("BuildSettings");
                        process.Remove("ProjectsToBuild");
                        process.Add("BuildSettings", settings);
                        process.Add("ProjectsToBuild", new string[] { settings.ProjectsToBuild.ToString() });
                        newbuild.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(process);
                        newbuild.DefaultDropLocation += @"" + projectName;
                        newbuild.Workspace.Mappings[0].ServerItem = newbuild.Workspace.Mappings[0].ServerItem.Replace("DEV", branch);
                        newbuild.Save();
                    }
                }

1. une simple requête linq sur le nom de la build permet de passer la création si la build existe déjà

if (!buildDefinitionList.Exists(x => x.Name == TfsBuildServices.BuildName(projectName, branch)))

L’appel  à TfsBuildServices.BuildName  permet de centraliser et de formaliser  la création du nom de la build 

2. on récupère la build d’intégration continue qui va nous servir de modèle pour la nouvelle définition 

IBuildDefinition CIBuild = buildDefinitionList.Where(x => x.Name == projectName + "_CI").First();

3. on initialise la nouvelle build avec le modèle

var newbuild = buildServer.CreateBuildDefinition(TEAM_PROJECT);
  newbuild.CopyFrom(CIBuild);

4. la modification des paramètres

La build definition utilise des BuildSettings pour enregistrer ses paramètres :

BuildSettings settings = new BuildSettings();
                        var process = WorkflowHelpers.DeserializeProcessParameters(newbuild.ProcessParameters);
                        settings.ProjectsToBuild = new StringList(((BuildSettings)process["BuildSettings"]).ProjectsToBuild.ToString().Replace("DEV",branch));
                        process.Remove("BuildSettings");
                        process.Remove("ProjectsToBuild");
                        process.Add("BuildSettings", settings);
                        process.Add("ProjectsToBuild", new string[] { settings.ProjectsToBuild.ToString() });
                        newbuild.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(process);
                        newbuild.DefaultDropLocation += @"" + projectName;
                        newbuild.Workspace.Mappings[0].ServerItem = newbuild.Workspace.Mappings[0].ServerItem.Replace("DEV", branch);

Ces settings sont sérialisés sous forme de dictionnaires et doivent donc être réédités pour cadrer avec le besoin spécifique UAT:

  • la copie de la build CI pointe par exemple vers la solution à compiler $/Project/dev/Appli1/Appli1.sln . il faut donc modifier ce répertoire pour celui UAT : 
settings.ProjectsToBuild = new StringList(((BuildSettings)process["BuildSettings"]).ProjectsToBuild.ToString().Replace("DEV",branch));
  • de la même manière le default drop location contient celle de la build CI et doit être modifié : 
newbuild.DefaultDropLocation += @"" + projectName;
  • et enfin le modèle objet de la définition de build contient aussi le mapping vers le répertoire source controle mappé qui doit lui aussi être remplacé : 
newbuild.Workspace.Mappings[0].ServerItem = newbuild.Workspace.Mappings[0].ServerItem.Replace("DEV", branch);

5. on sauvegarde, laisse 15 min au four : et hop. la build est créée !

newbuild.Save();

 

4ième étape:  supprimer les builds des anciennes branches :

Récupération de la liste des branches supprimées :

var items = vcs.GetItems(area, VersionSpec.Latest, RecursionType.OneLevel, 
DeletedState.Deleted,
ItemType.Folder).Items;

Pour chacune des anciennes branches on récupère la définition de la build associée (si elle existe)

IBuildDefinition UAT_CIBuild = buildDefinitionList.Where(x => x.Name == TfsBuildServices.BuildName(projectName, branch)).FirstOrDefault();
if (UAT_CIBuild != null)
{
   buildServer.DeleteBuilds(UAT_CIBuild.QueryBuilds());
}

A noter: pour pouvoir supprimer une définition de builds il faut que les historiques de run soient eux-aussi supprimés, c’est pourquoi l’appel buildServer.DeleteBuilds(UAT_CIBuild.QueryBuilds());  est fait ainsi  : cela supprime l’ensemble.

 

5ième étape:  emballer dans une petite application :

Je ne rentrerai pas dans les détails sur ce point, mais avec les éléments fournis rien de plus simple que de réunir tout cela dans une application qui tourne à intervalle régulier et exécute ces appels afin de synchroniser les définitions de builds et les branches.

 

Pour aller plus loin 

http://nakedalm.com/

https://msdn.microsoft.com/fr-fr/vstudio/ff637362.aspx

ALM ranger’s site

Prochaine étape 

Dans le prochain billet j’expliquerai comment se passer de l’application console en intégrant ce code au sein d’activités de build maisons et comment créer notre propre template de build avec tout ceci.

 

Laurent.

 

Le chemin vers la qualité est-il unique ? (Part 2 : la Production)

le chemin

Nous avons vu précédemment qu’il n’existait pas de méthodologie parfaite (agile ou non). Que nos besoins en tant qu’unité (équipe, société ou individu) allaient dicter les méthodologies que nous voulons ou pouvons suivre.

A présent, que se passe-t-il lorsque nous produisons ? Comment organiser notre travail effectif afin d’atteindre un niveau de qualité le plus élevé possible ?

Là où les méthodologies peuvent parfois s’arrêter, certaines nous donnent des clés pour améliorer notre code en plus de notre façon de travailler. Et comme le nombre de méthodologies, il est aisé de crouler sous le nombre de « bonnes pratiques » et finalement ne pas savoir par quoi commencer. Voire de s’y perdre et de ne rien faire du tout.

Ici aussi, y a-t-il une bonne méthode à appliquer ?

En plus de notre expérience, deux conférences de Paris Web peuvent nous aiguiller dans cette réflexion.

Tout d’abord, « Code de qualité : ce qu’il faut savoir«  par Julien Wajsberg et Anthony Ricaud et « 100 % de revue de code«  par Agnès Haasser.

 

Quels outils sont utilisés, pourquoi et comment sont-ils intégrés ?

La revue de code est le point commun entre ces deux conférences. Et à raison : cette pratique est initialement la plus […] simple à mettre en place (une chaise suffit !).

La manière classique consiste à faire venir un pair à côté de soi et relire ensemble le code produit à la recherche d’erreurs ou d’oublis techniques comme fonctionnels. Ensuite, de partager sur les améliorations possibles.

Son principal inconvénient reste son coût en temps effectif, tout comme les interruptions que le demandeur réalise auprès de ses collègues.

Chez Soft’it, nous avons pris la décision de ne pas nécessairement impliquer le demandeur dans la revue de code. Le temps est provisionné et c’est à chacun de vérifier ce que les autres ont envoyé. Si besoin, le relecteur fera une remarque directement ou validera simplement le code produit.

Du côté d’Agnès Haasser, les « pull requests » de Git se sont avérés être la meilleure réponse aux besoins de son équipe. Le principe est le même : soumettre son code à la validation des pairs de manière asynchrone. Ce qu’ils ont rajouté est une surcouche de sécurité : préserver la branche master de toute erreur éventuelle. De plus, tous les devs peuvent participer et/ou suivre cette relecture de code, réduisant drastiquement le bus factor.

Une autre pratique pouvant remplacer la revue de code est le pair programming. On place ici aussi 2 développeurs face à une même machine. La revue de code a lieu en même temps que le développement.

Seulement, ce type de mise en place est coûteuse en temps si les développements ne sont faits que de cette manière. Notre choix interne a été de ne l’utiliser que dans trois cas très précis : l’arrivée de nouvelles personnes dans l’équipe, la découverte de nouvelles technologies ou langages et enfin le développement de parties complexes et/ou critiques. Dans le premier cas, le nouvel arrivant participe à la production tout en apprenant. Et nous avons un filet de sécurité en cas de soucis. Ce filet existera également dans le deuxième cas, tout en apportant un recul aux développeurs qui s’émuleront mutuellement. Dans le dernier, l’intérêt est assez évident : deux paires d’yeux valent mieux qu’une. Et si la fonctionnalité est trop complexe, revenir sur le code de quelqu’un peut s’avérer compliqué et finalement faire perdre plus de temps que d’en gagner.

Même si de manière générale le pair programming implique qu’un des développeurs s’occupe du code de production et l’autre des tests, on peut coupler tests et vérification.

 

Le testing, justement. S’il est considéré comme obligatoire chez certains (c’est notre cas chez Soft’it), il ne l’est pas nécessairement d’après messieurs Wajsberg et Ricaud. Ou du moins dès le début.

La décision prise chez Mozilla est de ne pas rendre obligatoire les tests à la création d’une classe. Mais une fois que la première brique est posée, il ne doit plus y avoir de développement sans test.

Pourquoi ce choix ? Parce qu’il est parfois compliqué de définir un premier test concluant lorsque l’on commence à développer une nouvelle fonctionnalité.

Travailler en TDD rendrait évidemment cette règle absurde. Mais cette pratique n’est pas si évidente à mettre en place : certaines architectures, utilisant des ORMs par exemple, rendent l’utilisation de doublures de tests trop lourdes pour être réellement efficaces. On se tournera alors vers des tests d’intégration (qui arrivent plus tard dans le cycle de développement) afin de valider les cas d’utilisation.

Alors quand commencer à tester ?

Lorsque votre architecture le permet. Et elle le pourra toujours très tôt. Lorsqu’il est intéressant de tester également (le faire sur les getters et setters n’a pas d’intérêt en soi). Plus les tests commencent tard, plus il est compliqué de commencer (quel sera le premier test ? Dois-je revenir sur ce qui n’a pas encore été testé ?). 

Leur présence offre un filet de sécurité à l’équipe. Techniquement d’abord : en cas de besoin de refactoring ou de reprise de code par un autre développeur. Ils sécurisent la modification : si on casse quelque chose, on le sait très tôt.
Fonctionnellement, écrire des tests correspondant au cheminement d’un user donnera une meilleure confiance dans le produit terminé. Notamment lors de corrections de bugs. Idéalement, un ou plusieurs tests devront valider la correction, scelleront sa correction et serviront de gardiens face aux régressions.

 

Enfin, l’intégration continue. Le nom du produit varie, mais l’intérêt et l’implémentation restent les mêmes : s’assurer qu’à tout moment, on ne livre pas un produit cassé aux clients.
La solution est modulable : vous pouvez limiter son utilisation à une machine de build, les développeurs sont alors assurés que les fichiers disponibles sur le repository ne sont pas à même de les empêcher de travailler. Exécuter les tests automatiques (unitaires et d’intégration), voire utiliser un analyseur de code (FxCop, NDepend, …) afin de sécuriser les règles de développement. 

Si les pratiques se retrouvent souvent dans les organisations, leurs applications diffèrent parfois très fortement. L’intention est la même : sécuriser les développements et les développeurs. Mais comme dans toute équipe, les besoins et contraintes ne sont jamais les mêmes. Et suivant ces contraintes, chacun doit plier plus ou moins chaque principe ou philosophie. Il se peut que vous arriviez dans une équipe et que certaines de leurs pratiques, notions ou terminologies vous semblent étranges, voire contre-productives. Mais gardez en tête qu’ils ont peut-être des contraintes très fortes qui ne leur permettent pas d’appliquer à la lettre les bonnes pratiques.

Comprenez vos besoins et ceux de l’équipe avant d’amener une réponse toute faite. Prenez en compte leurs peurs, limites et attentes et apportez-leur les pratiques qui leur conviennent.
C’est là le fondement même de l’agilité, utilisez des canevas de solutions plutôt que de vous baser sur celles que vous avez pu connaître avant.

[WPF] Création d’un Popup MVVM au sein d’une application Desktop

WPF

Récemment, sur un projet WPF mono fenêtré, on m’a demandé de réaliser un Popup réutilisable afin de pouvoir y insérer n’importe quel autre contrôle utilisateur, et cela de façon très simple.

Cette demande présentait plusieurs problématiques, dont la réutilisabilité du Popup, la simplicité d’utilisation, la liaison avec un autre UserControl parent et le respect de MVVM pour les futurs contrôles hébergés par le Popup.

Afin d’y répondre, j’ai opté pour la création d’un Behavior, via l’utilisation des Attached Properties. Pour ceux qui ne connaissent pas, les Attached Properties sont des propriétés que l’on peut attacher à n’importe quel Control, c’est une DependencyProperty. On pourrait dire que les Attached Properties sont aux propriétés ce que les méthodes d’extensions sont aux méthodes.
Par exemple, Grid.Row est une Attached Properties. (pour en savoir plus sur les Attached Properties, je vous invite à aller voir ici).

Création d’une Custom Window

Avant de nous attaquer aux Attached Properties, nous devons également créer une Custom Window afin de contrôler entièrement l’action Close de la Window. En effet, une fois qu’un utilisateur ferme une Window, l’action par défaut est de fermer la Window qui ne pourra plus être réouverte, ce qui n’est pas le comportement souhaité.

Vous trouvez beaucoup d’exemples de création de Custom Window sur internet, et cela pourrait faire l’objet de multiples tutoriels, mais l’essentiel à retenir est de remplacer le bouton de fermeture par un autre, un qui va cacher la fenêtre par exemple:

<Button Command="{Binding Path=HideWindowCommand, 
               RelativeSource={RelativeSource AncestorType={x:Type local:PopupWindow}}}" 
        Style="{StaticResource SystemCloseButton}">
         <Button.Content>
              <Grid Width="13" Height="12" RenderTransform="1,0,0,1,0,1">
                   <Path Data="M0,0 L8,7 M8,0 L0,7 Z" Width="8" Height="7" 
                         VerticalAlignment="Center" HorizontalAlignment="Center" 
                         Stroke="White" StrokeThickness="1.5" />
              </Grid>
         </Button.Content>
</Button>

Avec le HideWindowCommand […] côté code-behind:

public event Action Hiding;
 
private RelayCommand _hideWindowCommand;
 
public RelayCommand HideWindowCommand
{
     get { return _hideWindowCommand ??
               (_hideWindowCommand = new RelayCommand(OnHideWindowCommandExecute)); }
}

protected void OnHideWindowCommandExecute()
{
     Hide();
     if (Hiding != null)
         Hiding();
}

Création des propriétés essentielles aux comportements basiques

4 DependencyProperty sont nécessaires pour intégrer les comportements basiques au Popup:
  • IsShown: Pour contrôler l’affichage ou non du popup via le Binding
public static readonly DependencyProperty IsShownProperty =
            DependencyProperty.RegisterAttached(
                "IsShown"typeof(bool), typeof(PopupBehavior),
                new FrameworkPropertyMetadata(false, 
                      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 
                      OnIsShownChanged, 
                      false, System.Windows.Data.UpdateSourceTrigger.Explicit));
private static void OnIsShownChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = d as Control;
 
            Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate
            {
                PlayPopup(control);
            });
 
            var binding = control.GetBindingExpression(IsShownProperty);
            binding.UpdateSource();
        }
  • IsModal: Pour définir le comportement Modal de la Popup
public static readonly DependencyProperty IsModalProperty =
            DependencyProperty.RegisterAttached(
                "IsModal"typeof(bool), typeof(PopupBehavior),
                new FrameworkPropertyMetadata(false, 
                      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 
                      OnIsModalChanged, 
                      false, System.Windows.Data.UpdateSourceTrigger.Explicit));

La méthode OnIsModalChanged reprend le même principe que la méthode OnIsShownChanged, le but est qu’à chaque changement de valeur d’une propriété, on gère l’affichage via la méthode PlayPopup.

  • PopupOriginalContainer: En lecture seule, pour stocker le UserControl lié
public static readonly DependencyPropertyKey _popupOriginalContainerProperty =
            DependencyProperty.RegisterAttachedReadOnly(
                "PopupOriginalContainer"typeof(FrameworkElement), typeof(PopupBehavior),
                new FrameworkPropertyMetadata());
 
        public static DependencyProperty PopupOriginalContainerProperty = 
               _popupOriginalContainerProperty.DependencyProperty;
  • PopupWindow: En lecture seule, pour stocker la Window
public static readonly DependencyPropertyKey _popupWindowProperty =
            DependencyProperty.RegisterAttachedReadOnly(
                "PopupWindow"typeof(PopupWindow), typeof(PopupBehavior),
                new FrameworkPropertyMetadata());
 
        public static DependencyProperty PopupWindowProperty = 
               _popupWindowProperty.DependencyProperty;

Initialisation et gestion du Popup

Afin d’initialiser le Popup, j’ai opté pour une cinquième propriété qui donne tout son sens à la Popup, la propriété Title:
public static readonly DependencyProperty PopupTitleProperty =
            DependencyProperty.RegisterAttached(
                "PopupTitle"typeof(string), typeof(PopupBehavior),
                new FrameworkPropertyMetadata("Title", OnPopupTitleChanged));

Le callback permettant l’initialisation. Initialisation effectuée en prenant bien soin de vérifier que le UserControl lié a bien été chargé:

private static void OnPopupTitleChanged(DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
{
            var control = d as Control;
 
            var parent = control.Parent as FrameworkElement;
 
            if (parent != null)
            {
                if (parent.IsLoaded)
                    InitWindow(control, (string)e.NewValue);
                else parent.Loaded += (sender, args) => 
                         InitWindow(control, (string)e.NewValue);
            }
}

Toute l’initialisation en elle-même se passe dans la méthode InitWindow:

private static void InitWindow(Control control, string title)
{
    var pwindow = GetPopupWindow(control) as PopupWindow;
 
    if (pwindow == null)
    {
        //nous récupérons ici le container d'origine 
        //(on parlait d'UserControl, mais ça peut être n'importe quel FrameworkElement)
        var originalContainer = control.Parent;
        SetPopupOriginalContainer(control, originalContainer as FrameworkElement);
 
        //on sauvegarde également la fenêtre parente, ainsi que le DataContext d'origine
        var originalWindow = FindAncestor(control);
        var originalDataContext = control.DataContext;
 
        //on détache le contenu à mettre dans le Popup de son container d'origine
        var containerInfo = originalContainer.GetType().GetProperty("Content");
        if (containerInfo != null)
        {
            containerInfo.SetValue(originalContainer, null);
        }
        else
        {
            containerInfo = originalContainer.GetType().GetProperty("Children");
            if (containerInfo != null)
                RemoveChildren(originalContainer, control);
        }
 
        //on créé le nouveau container Window auquel on réaffecte le contenu, 
        //on l'attache également à la fenêtre principale
        var window = new PopupWindow
        {
            Title = title,
            SizeToContent = SizeToContent.WidthAndHeight,
            WindowStartupLocation = WindowStartupLocation.CenterOwner
        };
        window.Owner = originalWindow;
        window.Content = control;
 
        //on réaffecte le DataContext qui a été perdu lors du 
        //transfère du container original vers le nouveau
        control.DataContext = originalDataContext;
 
        window.Hiding += () =>
        {
            control.SetCurrentValue(IsShownProperty, false);
        };
 
        //on gère les évènements basiques permettant de gérer la visibilité et 
        //la fermeture du Popup en fonction de son container d'origine
        (originalContainer as FrameworkElement).IsVisibleChanged += (sender, args) =>
        {
            PlayPopup(control);
        };
        (originalContainer as FrameworkElement).Unloaded += (sender, args) =>
        {
            PlayPopup(control);
        };
 
        SetPopupWindow(control, window);
    }
    else pwindow.Title = title;
 
    //Enfin, on gère la méthode de mise à jour de l'état de la Popup
    PlayPopup(control);
}

On remarquera au passage la méthode PlayPopup. Cette méthode est joué à l’initialisation, mais également à chaque changement de IsModal, ou bien de IsShown. Le but de cette méthode est d’afficher, ou de faire disparaître la Popup en fonction de son état:

private static void PlayPopup(Control control)
{
    var window = GetPopupWindow(control) as PopupWindow;
 
    if (window != null)
    {
        var originalContainer = GetPopupOriginalContainer(control);
        var shouldShown = GetIsShown(control);
        var ismodal = GetIsModal(control);
        var title = GetPopupTitle(control);
 
        if (shouldShown && window.Visibility != Visibility.Visible 
            && originalContainer.Visibility == Visibility.Visible
            && originalContainer.IsLoaded == true)
        {
            window.Title = title;
            if (ismodal)
            {
                window.ShowDialog();
            }
            else window.Show();
        }
        else if (window.Visibility == Visibility.Visible)
        {
            window.Hide();
        }
    }
}

Utilisation du Popup

<local:BusinessChildView p:PopupBehavior.PopupTitle="test" />

Et voilà, c’est aussi simple que ça, n’importe quel contrôle peut être utilisé dans un Popup tout en gardant les bénéfices du MVVM.

[Témoignage] Voluntis : Comment passer du WebForms au MVC sans risque : ASP.NET MVC & Testing

Suite à notre récente formation sur le passage de l’ASP.NET WebForms à l’ASP.NET MVC en exploitant le testing, notre client, Erwan de Cadoudal (Team leader chez Voluntis [éditeur de logiciels dans le domaine médical], et demandeur de cette formation), a eu l’amabilité de répondre à nos questions.

Soft’it – Quelles sont vos motivations à l’origine de ce besoin de formation MVC et Testing MVC ?

E. de Cadoudal : « Voluntis met en œuvre des applications médicales dont le code peut présenter un risque pour les patients. Par exemple nos solutions mettent en œuvre des algorithmes médicaux complexes qui permettent d’aider le patient dans sa décision thérapeutique.

Les solutions de Voluntis sont au carrefour du numérique et du thérapeutique. Dans ce contexte, MVC nous parait une solution moderne et efficace pour des déploiements dans de multiples contextes d’usage, depuis le smartphone du patient jusqu’à l’écran du médecin. Par ailleurs nous sommes convaincus que l’approche MVC et les tests unitaires sont des solutions aujourd’hui très efficaces et très pertinentes pour développer rapidement des solutions industrialisées dans le contexte très réglementé des dispositifs médicaux. »

Soft’it – De quelle manière Soft’it y a répondu ? Qu’est-ce qui vous a plu dans la réponse de Soft’it à votre besoin ?

E. de Cadoudal : « L’approche de Soft’it nous a convaincu car le plan proposé a été était fait sur mesure avec une prise en compte de nos besoins et de nos équipes. La décision de suivre la formation dans nos locaux pour un nombre significatif de développeurs a été déterminante.
De plus certains exemples illustrant les exercices étaient proches de notre métier et nous parlaient bien. Par exemple pour nous il est plus clair de parler de liste de patients que de liste de bons de commande. Bien que cette session nous a paru un peu trop concentrée, chacun d’entre nous est reparti avec bases communes et des exemples concrets : du code et des présentations.

Nous avons également pu apprécier le dynamisme, l’expertise et le professionnalisme des équipes de formation, qui ont permis de conserver un bon rythme et de faciliter des échanges constructifs, et ont été des facteurs clés du succès des formations que nous avons suivies.« 

Soft’it – Quels sont les résultats et/ou quelle dynamique cette approche a-t-elle amené ?

E. de Cadoudal : « L’intégration des patterns « MVC », initiés par nos équipes de R&D en 2014, devient le modèle de référence pour les nouveaux projets de Voluntis. La formation nous a permis de mieux appréhender cette technologie et ses impacts sur notre méthodologie de travail.

En parallèle, nous travaillons en étroite collaboration avec nos équipes de tests et validation pour augmenter la couverture de notre code par du test unitaire. Nous anticipons que l’automatisation de ceux-ci, couplée à nos méthodologies Agile, nous permettra de réduire significativement notre investissement de tests tout en améliorant la qualité de nos produits. »

Soft’it – Nous recommanderiez-vous ? Comptez-vous refaire appel à nous ?

E. de Cadoudal : « Seulement une partie des développeurs a suivi la formation, nous comptons remonter une session identique à la précédente.
L’approche de Soft’it a été la bonne dans notre contexte et je recommande la formule pour des équipes techniques qui travaillent sur un framework partagé avec les développeurs. »

 

Un grand merci à Erwan pour ces réponses et ce feedback positif. L’équipe ayant suivie la formation a par ailleurs donné un 4/4 au ROTI pour Marien et Rémi !

L’équipe Soft’it se plie donc en quatre pour vous fournir les meilleures formations adaptées à vos besoins.
Contactez-nous si vous souhaitez avoir plus de renseignements.