Tutoriel 3.2 : Workflow Machine d’états–Partie 2

Les étapes contenues dans ce post sans la suite du tutoriel précédent consistant à implémenter un workflow d’embauche.

Etape 4 : Création de l’application WPF d’hébergement

L’objectif de cette étape est de créer une application WPF qui va héberger, exécuter et interagir avec le workflow. Une fois démarré, l’application donne la main à la commission technique et à la commission orale pour évaluer le dossier et selon les notes attribuées, le dossier est finalement accepté ou rejeté.

  • Ouvrez la fenêtre principale « MainWindow.xaml » en mode design
  • Décomposez la grille en deux colonnes où la colonne à gauche à une hauteur de 250 px et la colonne de droite pend toute la largeur.
  • Glissez sun stackPanel sur la colonne à gauche qui prend toute la place.
  • Glissez un « TextBox » sur la colonne de droite qui prend toute la place. Appelez-la « txtConsole ».
  • Ajoutez une combobox (applez-la « cbEval »), et trois boutons au « StackPanel » : « btnStart »,  « btnTechnique » et « btnOral ». le premier bouton démarrera le workflow, le deuxième est utilisé pour l’évaluation technique tandis que le troisième sera utilisé pour l’évaluation orale.
  • L’interface devrait être comme ceci :

image

  • Le code XAML correspondant devrait être comme suit :
<Window x:Class="Tutoriel32UI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="550" Width="850">
    <Grid >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox Name="txtConsole" HorizontalAlignment="Stretch" TextWrapping="Wrap" VerticalAlignment="Stretch"  AcceptsReturn="True"
                Grid.Column="1" />
        <StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
            <Button x:Name="btnStart" Content="Démarrer le workflow" Margin="5" />
            <Label Content="Evaluation :" Margin="5"/>
            <ComboBox x:Name="cbEval" SelectedIndex="0" Margin="5">
                <ListBoxItem Content="1"/>
                <ListBoxItem Content="2"/>
                <ListBoxItem Content="3"/>
                <ListBoxItem Content="4"/>
                <ListBoxItem Content="5"/>
            </ComboBox>
            <Button Content="Evaluer Examen Technique" Margin="5" Name="btnTechnique" IsEnabled="True" />
            <Button Content="Evaluer Examen Oral" Margin="5" Name="btnOral" IsEnabled="True" />
        </StackPanel>
    </Grid>
</Window>
 
  • Nous allons maintenant ajouter le code qui permettra d’activer ou de désactiver un bouton. Parce que le workflow s’exécute dans un processus séparé, les actions touchant à l’UI directement provoquent une exception. Nous allons développer donc la méthode « ActiverBouton » qui permet d’activer ou de désactiver un bouton même à partir d’un thread séparé. Pour ce, on utilise la propriété « Dispatcher ».
  • Ouvrez le fichier MainWindow.xaml.cs en mode code
  • Ajoutez la méthode « ActiverBouton » comme suit :
private void ActiverBouton(Button bouton, bool valeur)
        {
            bouton.Dispatcher.BeginInvoke(new Action(() => bouton.IsEnabled = valeur));
        }
  • Nous allons ajouter une autre méthode qui désactive tous les boutons :
private void DesactiverBoutons()
        {
            ActiverBouton(btnOral, false);
            ActiverBouton(btnTechnique, false);
            
        }
  • Ajouton la méthode « WorkflowVeille » qui se déclenche lorsque le workflow est en mode veille. Cette méthode active le bon bouton selon le signet activé en cours :
private void WorkflowVeille(WorkflowApplicationIdleEventArgs args)
      {
          var signet = args.Bookmarks.FirstOrDefault();
          if (signet == null)
              return;
          DesactiverBoutons();
          switch (signet.BookmarkName)
          {
              case "signetTechnique" :
                  ActiverBouton(btnTechnique, true);
                  break;
              case "signetOral":
                  ActiverBouton(btnOral, true);
                  break;
          }
      }
  • Ajoutons la méthode « WorkflowTermine » qui se déclenche lorsque l’exécution du workflow se termine normalement.
private void WorflowTermine(WorkflowApplicationCompletedEventArgs args)
        {
            DesactiverBoutons();
            ActiverBouton(btnStart, true);
            
            Console.WriteLine("Workflow terminé");
            Console.WriteLine("--------------------------------------");
        }
  • Nous allons maintenat déclarer une variable de type « WorkflowApplication » qui exécutera notre workflow mais avons-nous devons ajouter une référence sur « System.Activities »

image

  • Ajoutez un using vers « System.Activities »
  • Ajoutez une référence vers la bibliothèque « EmbaucheLibrary » et ajoutez « EmbaucheLibrary » à la liste des « using »
  • Déclarez une variable appelée « _app » de type « WorkflowApplication »
private WorkflowApplication _app;
  • Ouvrez « MainWindow » en mode designer
  • Doublez-cliquez sur l’évènement « Click » du bouton « btnStart » dans la fenêtre de propriétés. La méthode « btnStart_Click » est générée. Changez-la comme suit :
private void btnStart_Click(object sender, RoutedEventArgs e)
      {
          // démarre le workflow
          btnStart.IsEnabled = false;
          _app = new WorkflowApplication(new EmbaucheLibrary.EmbaucheWorkflow());
          _app.Idle += new Action<WorkflowApplicationIdleEventArgs>(WorkflowVeille);
          _app.Completed += new Action<WorkflowApplicationCompletedEventArgs>(WorflowTermine);
          _app.Run();
      }
  • La métode crée une application workflow, lui associe des évènements puis l’exécute.
  • Ajoutez un gestionnaire de clic au bouton « btnTechnique » et modifiez-le comme suit :
private void btnTechnique_Click(object sender, RoutedEventArgs e)
        {
            txtConsole.AppendText(string.Format("La commission technique a donné une note de {0}\n",cbEval.SelectedIndex + 1));
            _app.ResumeBookmark("signetTechnique", cbEval.SelectedIndex + 1);
        }
  • Ce bouton permet de prendre la note de la commission technique à partir de la combo box puis de la passer au signet « signetTechnique » en reprenant ce dernier.
  • De la même façon, implémentez le clic pour l’évènement « btnOral » :
private void btnOral_Click(object sender, RoutedEventArgs e)
        {
            txtConsole.AppendText(string.Format("La commission orale a donné une note de {0}\n", cbEval.SelectedIndex + 1));
            _app.ResumeBookmark("signetOral", cbEval.SelectedIndex + 1);
        }
  • Il nous reste à régler un dernier problème technique. Les activités du workflow comme « AttendreReponse » et « WriteLine » écrivent sur la console alors que notre application est une application windows de type WPF. Nous ne pourrons pas voir les messages générés par le workflow.
  • La solution consiste à rediriger la sortie de la console vers la zone de texte de la fenêtre principale sans oublier que les sorties du workflow proviennent d’un thread séparé.
  • Pour ce, nous allons implémenter la classe « TextBoxTextWriter » qui hérite de la classe abstraite « TextWriter » :
class TextBoxTextWriter : TextWriter
    {
        private TextBox _control;
 
        public TextBoxTextWriter(TextBox control)
        {
            _control = control;
        }
 
        public override Encoding Encoding
        {
            get { return System.Text.Encoding.UTF8; }
        }
 
        public override void Write(char value)
        {
            Action action = () => _control.AppendText(value.ToString());
            _control.Dispatcher.BeginInvoke(action);      
        }
  • Revenez à la fenêtre principale
  • Créez un évènement de chargement « Load » à la fenêtre ou à la grille enfant, nous y redirigerons la sortie de la console :
private void Grid_Loaded(object sender, RoutedEventArgs e)
      {            
          Console.SetOut(new TextBoxTextWriter(txtConsole));
          DesactiverBoutons();            
      }
  • Le listing complet de « MainWindow » devrait être comme suit :
public partial class MainWindow : Window
    {
        private WorkflowApplication _app;
 
        public MainWindow()
        {
            InitializeComponent();
        }
 
        private void Grid_Loaded(object sender, RoutedEventArgs e)
        {            
            Console.SetOut(new TextBoxTextWriter(txtConsole));
            DesactiverBoutons();            
        }
 
        private void DesactiverBoutons()
        {
            ActiverBouton(btnOral, false);
            ActiverBouton(btnTechnique, false);
            
        }
 
        private void ActiverBouton(Button bouton, bool valeur)
        {
            bouton.Dispatcher.BeginInvoke(new Action(() => bouton.IsEnabled = valeur));
        }
 
     
 
        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            // démarre le workflow
            btnStart.IsEnabled = false;
            _app = new WorkflowApplication(new EmbaucheLibrary.EmbaucheWorkflow());
            _app.Idle += new Action<WorkflowApplicationIdleEventArgs>(WorkflowVeille);
            _app.Completed += new Action<WorkflowApplicationCompletedEventArgs>(WorflowTermine);
            _app.Run();
        }
 
        private void WorkflowVeille(WorkflowApplicationIdleEventArgs args)
        {
            var signet = args.Bookmarks.FirstOrDefault();
            if (signet == null)
                return;
            DesactiverBoutons();
            switch (signet.BookmarkName)
            {
                case "signetTechnique" :
                    ActiverBouton(btnTechnique, true);
                    break;
                case "signetOral":
                    ActiverBouton(btnOral, true);
                    break;
            }
        }
 
        private void WorflowTermine(WorkflowApplicationCompletedEventArgs args)
        {
            DesactiverBoutons();
            ActiverBouton(btnStart, true);
            
            Console.WriteLine("Workflow terminé");
            Console.WriteLine("--------------------------------------");
        }
 
        private void btnTechnique_Click(object sender, RoutedEventArgs e)
        {
            txtConsole.AppendText(string.Format("La commission technique a donné une note de {0}\n",cbEval.SelectedIndex + 1));
            _app.ResumeBookmark("signetTechnique", cbEval.SelectedIndex + 1);
        }
 
        private void btnOral_Click(object sender, RoutedEventArgs e)
        {
            txtConsole.AppendText(string.Format("La commission orale a donné une note de {0}\n", cbEval.SelectedIndex + 1));
            _app.ResumeBookmark("signetOral", cbEval.SelectedIndex + 1);
        }
    }
  • Exécutez l’application et simulez des candidatures d’embauche.

image

Cliquez ici pour télécharger le code source du tutoriel.

Cliquez ici pour revenir à la première partie du tutoriel.

Enjoy !

Add comment

Loading