Retour d’expérience : comment nous avons continué à travailler pendant le 1er confinement

En mai dernier nous avons eu l’occasion d’échanger avec Florent G., un client en charge de l’agilité et l’amélioration continue au sein d’un grand site web de l’immobilier. 

Les équipes de ce « pure Player » du web ont basculé en 100% télétravail pendant le premier confinement et j’étais curieuse de découvrir quels étaient les ressorts qui avaient permis la poursuite efficace de leur activité à distance alors que d’autres entreprises avaient tout arrêté.

Cet entretien a eu lieu en Mai dernier à l’issue du dé-confinement et au moment où l’entreprise mettait en œuvre une organisation « hybride ». 

GoooD : Quel a été l’impact du confinement sur votre activité ?

Florent G. : Nous sommes une entreprise du digital, nous avons été peu impactés car notre site est resté disponible et le trafic s’y est maintenu. En revanche nous avons connu une baisse de commercialisation du fait de la fermeture des agences immobilières qui sont nos clients. […]. Seules nos équipes commerciales ont été mises en activité partielle, les équipes IT ont continué de travailler à 80 voire 100% pour l’équipe en charge de l’activité B2B qui a profité de cette période de « calme » pour avancer sur des projets structurants. 

GoooD : Qu’est ce qui a bien fonctionné pendant le confinement au sein de vos équipes ?

Florent G. : Le télétravail était en test depuis un peu plus d’un an, sa généralisation a été facile car au-delà de ce test, il avait été décidé en COMEX au moment des grèves et des crises « gilets jaunes » qu’il fallait le généraliser, la volonté était marquée au niveau de la direction du groupe. 

Nos équipements informatiques (la quasi-totalité des collaborateurs ont des ordinateurs portables), nos outils (Suite Google) et notre infrastructure ont permis cette « bascule » à 100% de télétravail très facilement. Côté IT nous étions déjà habitués aux réunions et interactions à distance puisque nous sommes multi-sites (Paris et Aix en Provence). Nous avons également poursuivi nos recrutements et les intégrations qui étaient prévues ont eu lieu « normalement » mais à distance. Tout notre catalogue de formation interne a été repensé pour être réalisé en « remote » également de manière à permettre aux collaborateurs de se connecter à des formations où qu’ils soient.

GoooD : Quel lien faites-vous entre l’agilité et cette réussite pendant le confinement?

Florent G. : Côté équipes IT produit la volonté est de responsabiliser et « d’autonomiser » les équipes pour produire de la valeur et nous travaillons depuis longtemps à rendre l’équipe autonome et responsable pour qu’elle s’auto-organise. Les réunions mixtes, (en « présentiel » et avec les sites distants) que nous tenions auparavant se sont donc poursuivies pendant le confinement en passant d’un rythme hebdomadaire à un rythme quotidien pour garder le lien. 

L’ensemble des rituels a perduré (rétro, démo etc.). La collaboration à distance a été facilitée par l’utilisation de Bcast, Draw.io ou bien encore Google Drawing, côté management visuel on s’est appuyé sur Jira et Trello. Globalement on a noté côté IT une productivité au moins aussi bonne, si ce n’est meilleure pendant le confinement. 

GoooD : Qu’est-ce qui a été mis en œuvre pendant le confinement pour continuer et maintenir le lien entre les collaborateurs ?

Florent G. : L’objectif a été en effet de pérenniser les interactions existantes, des canaux d’équipes (pour le côté PRO) et thématiques (pour le côté PERSO) ont été créés sur Slack, des petits déjeuners virtuels ont été organisés. Notre PDG est resté très présent pendant toute la période, il a réalisé des vidéos quotidiennement pour garder le lien et la motivation des équipes en leur donnant des nouvelles et de la visibilité sur la situation au sein du groupe, tant sur le plan sanitaire que business. 

Une cellule de soutien psychologique a été mise en œuvre par la DRH à destination des collaborateurs et prestataires et il y avait des propositions de coaching sportif à distance qui ont connu un gros succès au début du confinement surtout. 

GoooD : Comment s’organise le déconfinement ? 

Florent G. : Pas de retour à la normale de suite, le retour dans les locaux va se faire progressivement à partir de juin et uniquement pour des besoins impérieux, à l’été il n’y aura pas plus de 30% des effectifs dans les locaux. 

GoooD : Qu’est qui va être conservé de cette « drôle de période » ?

Florent G. : Le renforcement de l’usage d’outils collaboratifs certainement, l’affirmation de l’autonomie des équipes et de leur responsabilité dans la production de valeur. Globalement le groupe va mener des réflexions sur les modes de travail « de demain » faisant plus de place au télétravail, ça a été un accélérateur. 

GoooD : Qu’est ce qui sera stoppé ?

Florent G. : La machine à café virtuelle ouverte pour tout le Groupe, qui n’a pas bien fonctionné car il est difficile pour un grand groupe de créer de la proximité virtuellement lorsque les gens ne se connaissent pas d’avance.

A ce jour, le groupe continue de travailler en mode hybride.

[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.