SUPINFO International University

SUPINFO Institute of Information Technology
Laboratoire Microsoft




Tous les Articles du Laboratoire Microsoft

Programmation sur Tablet PC : IHM de la barre de recherche IE (partie 2)
Accueil > Articles > Matériels
Auteurs 
Julien BAKMEZDJIAN



 Tous les articles de cet auteur

2,5/5

Assez Bien


29833
2/5

Introduction

 

            Comme annoncé dans le précédent article, nous allons terminer ici la partie IHM de notre barre d’outils personnalisée en présentant son contrôle central : la zone de saisie d’encre numérique. La barre dont nous parlons ici est un projet présenté dans cet article : il s’agit d’une barre d’outils pour Internet Explorer spécialement conçue pour le TabletPC et réalisée en .NET.

            Le contrôle dont il va être question aurait très bien pu hériter de InkEdit, ce que nous avons déjà fait pour notre outil de recherche. En effet, tout comme le contrôle créé à l’occasion, celui de notre barre d’outils doit fournir une zone de saisie pour le stylet, zone qui s’agrandit lorsque l’utilisateur s’apprête à écrire dedans. Cependant, comme nous souhaitons pouvoir personnaliser le plus possible des propriétés (liées à la gestion de l’encre) de notre contrôle, nous allons être obligés de recréer toutes les fonctionnalités de InkEdit à partir de RichTextBox. Cela nous permettra une plus grande souplesse, même si le travail sera plus important...

            Il va donc falloir réaliser un contrôle personnalisé en utilisant les possibilités du SDK TabletPC. Pour la création d’un tel contrôle, référez-vous à cet article, et pour le SDK TabletPC que nous allons utiliser, voyez celui-ci.

Nous allons ici décrire la méthode utilisée pour implémenter les fonctionnalités d’une InkEdit à partir d’une RichTextBox et tous les problèmes que cela soulève : dérivation de RichTextBox, gestion de l’encre, protection des éléments critiques et agrandissement de la zone de saisie.


 

 

Les bases de InkInputEx

 

Nous souhaitons réaliser un contrôle dans lequel l’utilisateur peut écrire de l’encre numérique. Au bout de deux secondes, la saisie manuscrite est reconnue et est remplacée par du texte dactylographié comme dans une RichTextBox. Nous souhaitons que la zone de saisie s’agrandisse lorsque l’utilisateur s’apprête à écrire dedans (ie lorsqu’il approche le stylet de la surface blanche) :

 


 

Jusqu’ici, nous pouvons imaginer créer un contrôle hérité de InkEdit, exactement comme nous l’avons fait dans cet article. Cependant, nous souhaitons pouvoir accéder à la propriété Ink de notre contrôle. Ink représente l’encre numérique telle qu’elle a été dessinée. Nous souhaitons pouvoir y accéder afin de lancer immédiatement (ie pendant que l’utilisateur continue à écrire) une reconnaissance en arrière plan sans que l’affichage du contrôle ne soit modifier. Nous voulons en effet, à partir du résultat de cette reconnaissance, mettre à jour un historique juste sous la zone de saisie :

 


Malheureusement, la propriété Ink de InkEdit est une propriété privée, donc totalement inaccessible, même des héritiers. InkEdit n’expose aucun membre permettant de récupérer d’une manière ou d’une autre l’encre numérique, ni même le résultat de la reconnaissance sans effacer l’encre de l’affichage, ce que nous ne souhaitons pas.

La seule solution pour nous consiste donc à recréer un InkEdit pour lequel la propriété Strokes soit accessible... Pour cela, nous allons prendre RichTextBox comme classe de base. La nouvelle classe dérivée s’appellera InkInputEx. Nous créons ensuite un membre de type InkOverlay dans notre classe : ce sera inkManagement, auquel nous associons la propriété publique InkManagement (attention à la majuscule !) :

 

      public class InkInputEx : RichTextBox // on hérite de RichTextBox

      {

            // Chargé de récupérer l'encre numérique :

            private InkOverlayEx inkManagement;

//...

//...

 

            /// <summary>

            /// Gère l'encre numérique

            /// </summary>

            [Browsable(false),

            ReadOnly(true)]

            public Microsoft.Ink.InkOverlay InkManagement

            {

                  get{return inkManagement;} // En lecture seule

            }

//...

//...

 

InkOverlay est une classe que l’on attache à la construction à un contrôle et qui permet ensuite de collecter de l’encre numérique puis de la gérer lorsque l’utilisateur écrit sur le contrôle. Pour plus de détails sur InkOverlay, référez-vous à cet article, ou à MSDN. Dans notre projet, nous réalisons la construction de inkManagement lors de la création du handle d’une instance de InkInputEx. En effet, un InkOverlay ayant besoin du handle du contrôle auquel il est attaché, nous devons être sûrs que ce handle existe ; nous le construisons donc notre inkManagement lors de l’événement HandleCreated de notre contrôle :

 

      private void InkInputEx_HandleCreate(object sender, EventArgs e)

      {

            // on crée le InkOverlay

            inkManagement = new InkOverlayEx(this.Handle);

 

            // On collecte encre et gestures :

            inkManagement.CollectionMode = CollectionMode.InkAndGesture;

// ...

// Autres initialisations du InkManagement (gestures notamment)

// Vues plus tard dans cet article

// ...

// Abonnements à des événements

 

Notre contrôle possède maintenant une propriété InkManagement publique. Cette propriété va nous permettre d’accéder à l’encre numérique de n’importe quel endroit de notre programme. Cependant, nous devons implémenter dans InkInputEx les fonctionnalités d’une InkEdit : les traitements automatiques permettant la reconnaissance et la conversion en texte de l’encre numérique. Ces traitements se font après un certain délais.

 

Timer pour la reconnaissance

 

La reconnaissance et la conversion ont lieu automatiquement après un laps de temps configurable (environ deux secondes) lorsque l’utilisateur n’écrit plus. La première chose à faire dans InkInputEx est donc de créer un timer et une propriété publique de type int permettant de paramétrer le délai de reconnaissance :

 

public class InkInputEx : RichTextBox

{

// ...

      private double recoTimeOut = 2000; // Délai pour la reconnaissance

// ...

// Timer pour la reconnaissance :

private System.Windows.Forms.Timer recoTimeOutTimer;

 

      [Category("Ink"),

      Description("Temps après lequel la reconnaissance est effectuée")]

      public double RecoTimeOut

      {

            get{return recoTimeOut;}

            set{recoTimeOut = value;}

      }

 

On initialise ce timer dans le constructeur de la classe :

 

      public InkInputEx()

      {          

            // on initialise les timer :

           

            // Pour la reconnaissance

            recoTimeOutTimer = new System.Windows.Forms.Timer();

            recoTimeOutTimer.Tick +=new EventHandler(RecoTimer_Elapsed);

            recoTimeOutTimer.Enabled = false;

// ... Autres timers (décrits dans la suite de l’article)

 

Il faut maintenant lancer ce timer dès que l’utilisateur achève un stroke (un stroke est trait continu tracé d’un seul geste). Et chaque fois que l’utilisateur commence un nouveau stroke, nous interrompons la course du timer. Ainsi, il faudra que l’utilisateur n’écrive plus pendant deux secondes pour que la reconnaissance et la conversion aient lieu.

Nous nous sommes donc abonnés aux événements correspondant au début et à la fin d’un stroke. Ces abonnements se font dans InkInputEx_HandleCreate, à la suite de la construction de inkManagement :

 

      private void InkInputEx_HandleCreate(object sender, EventArgs e)

      {

// Construction du InkOverlay vue plus haut dans cet article

// ...

// Autres initialisations du InkManagement (gestures notamment)

// Vues plus tard dans cet article

// ...

// Abonnements à des événements :

      // Fin d'un stroke

this.InkManagement.Stroke +=

            new InkCollectorStrokeEventHandler(InkManagement_StrokeEnds);

 

      // Lorsqu'on clique (=début d’un stroke)

      this.InkManagement.MouseDown += new

            InkCollectorMouseDownEventHandler(InkManagement_StrokeBegins);

// ...

 

Notez que le InkOverlay ne possède pas d’événement correspondant au début d’un stroke : nous utilisons donc l’événement MouseDown. Dans la méthode InkManagement_StrokeBegins, nous arrêtons le timer, le cas échéant :

 

recoTimeOutTimer.Stop(); // On arrête le timer pour la reconnaissance

 

Et dans InkManagement_StrokeEnds, nous le relançons :

 

recoTimeOutTimer.Stop();

recoTimeOutTimer.Interval = (int)recoTimeOut; // On fixe le délai

recoTimeOutTimer.Start(); // On (re)lance le timer  pour la reconnaissance

 

Il faut maintenant lancer la reconnaissance et la conversion du texte écrit lorsque le timer se déclenche.

Reconnaissance

 

La reconnaissance et la conversion proprement dites se font dans la méthode de callback du timer, à savoir RecoTimer_Elapsed. Avant une opération de reconnaissance sur les stroke d’un InkOverlay, il faut invalider celui-ci. On lance ensuite la reconnaissance (voir cet article pour plus de détails), et on insère le texte reconnu au niveau du curseur. Au passage, il faut, après cette insertion, repositionner le curseur au bon endroit. Enfin, on valide à nouveau le InkOverlay. Tout cela se fait avec beaucoup de précautions, les manipulations sur le InkOverlay pouvant entraîner bon nombre d’erreurs.

Faisons deux remarques avant de présenter le code associé à ces opérations :

-          myRecoContext est un membre global de la classe, initialisé dans le constructeur.

-          Nous utilisons la propriété TextSafe de notre classe. Cette propriété, que nous avons créée et que nous détaillerons plus loin dans cet article, permet un accès à la propriété Text sécurisé au niveau des threads. Il en est de même pour SelectionStartSafe.

 

private void RecoTimer_Elapsed(object source, EventArgs e)

{

      // Pour la reconnaissance :

      RecognitionStatus myRecoStatus;

      RecognitionResult myResult;

// sauvegarde de la position du curseur avant traitement du gesture

      int oldSelectionStart;

      recoTimeOutTimer.Stop();

// ...

// autres opérations liées aux gestures décrites plus loin      

// ...

      try

      {

            // S'il y a effectivement des strokes de dessinés

            if(this.InkManagement.Ink.Strokes.Count != 0)

            {

                  try

                  {

                        // On invalide le InkOVerlay

                        // (il ne faut pas écrire sur le InkOverlay

                        // pendant la reconnaissance)

                        this.InkManagement.Enabled = false;

                  }

                  catch(System.Exception err)

                  {

                        return;

                  }

 

                  // On recopie les strokes

                  // dans le contexte de reconnaissance

                  myRecoContext.Strokes = this.InkManagement.Ink.Strokes;

 

                  // On efface les strokes du InkOverlay

                  this.InkManagement.Ink.DeleteStrokes();

 

                  // Récupération du résultat de la reconnaissance

                  myResult = myRecoContext.Recognize(out myRecoStatus);

 

                  // On sauvegarde la position du curseur

                  // car on va la perdre avec le 'Text =...'

                  oldSelectionStart = this.SelectionStartSafe;

     

                  // On reconstitue la nouvelle chaîne de texte

                  int selectionStartTempo = this.SelectionStartSafe;

                  int selectionLengthTempo = this.SelectionLength;

                  TextSafe = this.TextSafe.Substring(0,selectionStartTempo)

                             + myResult.TopAlternate.ToString()

                             + this.TextSafe.Substring(selectionStartTempo

                             + selectionLengthTempo);

 

                  // On remet le curseur à la bonne position

                  this.SelectionStartSafe = oldSelectionStart

                             + myResult.TopAlternate.ToString().Length;

                 

                  try

                  {

                        // On revalide le InkOverlay :

                        this.InkManagement.Enabled = true;

                  }

                  catch(System.Exception err){}

            }

      }

      catch{}

      finally

      {

            this.Refresh(); // On raffraichit tout l'affichage

      }

}

 

Mis à part les protections réalisées, ce code ne contient pas d’autres difficultés. La partie concernant la gestion de l’encre numérique n’en est pas finie pour autant, car il reste notamment un problème auquel on ne pense pas tout de suite...

Sélection du texte

 

            Lorsque l’utilisateur dessine un stroke, il sélectionne en même temps le texte déjà converti, le cas échéant. En effet, si le stylet permet de dessiner des stroke, il agit par ailleurs exactement comme la souris :

 

 


Evidemment, cet effet est très gênant pour nous. Pour désactiver la sélection du texte lorsque l’utilisateur écrit, il suffit d’ajouter cette ligne dans le constructeur de la classe :

 

      // On passe UserMouse à true car sinon, le texte est sélectionné

      // lorsque l'utilisateur dessine un stroke

      SetStyle(System.Windows.Forms.ControlStyles.UserMouse,true);

 

Mais qu’en est-il lorsque l’utilisateur souhaite justement sélectionner du texte avec le stylet ? Nous devons faire en sorte que lorsqu’un stroke est commencé à un endroit de InkInputEx où se trouve du texte déjà reconnu, aucun stroke n’est dessiné ; nous souhaitons à la place que le texte soit sélectionné.

 


En effet, lorsque l’utilisateur pose son stylet dans la zone de texte (la zone rouge dans la copie ci-dessus), c’est certainement pour réaliser une sélection.

Il nous faut dans un premier temps connaître la zone où du texte dactylographié est présent. Cela se fait grâce à l’abonnement à l’événement Painted de InkManagement :

 

private void InkManagement_Painted(object sender, PaintEventArgs e)

{

      // On en profite pour connaître le rectangle où se trouve le texte

      textRegion = e.Graphics.MeasureString(this.TextSafe,this.Font);

// ...

 

textRegion (membre privé de type Size de InkInputEx) contient maintenant le rectangle de texte. Lorsque l’utilisateur commence un stroke, nous comparons les coordonnées du début du stroke avec celles de la zone de texte. Si le stroke débute dans le texte, nous autorisons la sélection du texte et nous annulons le stroke. L’autorisation ou non de sélection se passe dans InkManagement_StrokeBegins :

 

private void InkManagement_StrokeBegins(object s, CancelMouseEventArgs e)

{

 

      recoTimeOutTimer.Stop(); // On arrête le timer pour la reconnaissance

 

// On note les coordonnées du début du stroke

// (utilisées aussi dans StrokeEnds)

      startStroke.X = e.X;

      startStroke.Y = e.Y;

 

      // Si le stroke est commencé dans le texte de la RichTextBox

      if((e.X < textRegion.Width + 10) && (e.Y < textRegion.Height))

      {

            // On permet que le texte soit sélectionné

            SetStyle(System.Windows.Forms.ControlStyles.UserMouse,false);

      }

      else // Si le stroke est commencé en dehors du texte

      {

            // On empêche que le texte soit sélectionné

            SetStyle(System.Windows.Forms.ControlStyles.UserMouse,true);

      }

}

 

L’annulation du stroke, elle, ne peut être réalisée que lorsque le stroke se termine (d’où la nécessité d’enregistrer les coordonnées de début du stroke dans la variable startStroke au niveau de InkManagement_StrokeBegins) :

 

private void InkManagement_StrokeEnds(object s, InkCollectorStrokeEventArgs e)

{

 

            // Si le stroke avait été commencé dans le texte de la RichTextBox

            if((startStroke.X < textRegion.Width + 10)

                             && (startStroke.Y < textRegion.Height))

            {

                  // On annule le stroke (il s'efface aussi de l'écran)

                  e.Cancel = true;

                  // S'il reste tout de même des strokes dessinés avant celui-ci

                  if(this.InkManagement.Ink.Strokes.Count > 1)

                  {    

                        // On relance le timer :

                        recoTimeOutTimer.Stop();          

                        recoTimeOutTimer.Interval = (int)recoTimeOut;

                        recoTimeOutTimer.Start();

                  }

            }

            // Si le stroke avait été commencé en dehors du texte de la RichTextBox :

            else

{

                  // On relance le timer :

                  recoTimeOutTimer.Stop();

                  recoTimeOutTimer.Interval = (int)recoTimeOut;

                  recoTimeOutTimer.Start();

// ...

// autres opérations liées aux gestures décrites plus loin      

// ...

            }

}

 

Pour terminer (enfin !) la gestion de l’encre numérique dans notre contrôle, il nous reste à traiter le cas des gesture.

Gesture

 

Comme un InkEdit, notre InkInputEx doit également gérer quelques gestures. Un gesture est un geste du stylet, pointe en contact avec l’écran, qui réalise une opération telle que l’effacement, l’insertion d’un espace, la validation... Pour cela, il faut indiquer au InkOverlay notre intérêt pour les gesture, ainsi que la liste des gesture que nous souhaitons détecter. Enfin, il faut s’abonner à l’événement Gesture. Tout cela est fait lors de la création du handle de notre contrôle, juste après la construction du InkOverlay :

 

// on crée le InkOverlay

inkManagement = new InkOverlayEx(this.Handle);

 

// On collecte encre et gestures :

inkManagement.CollectionMode = CollectionMode.InkAndGesture;

           

// on écoute certains mouvements (gestures)

this.InkManagement.SetGestureStatus(ApplicationGesture.Left, true);

this.InkManagement.SetGestureStatus(ApplicationGesture.Right, true);

this.InkManagement.SetGestureStatus(ApplicationGesture.Scratchout, true);

this.InkManagement.SetGestureStatus(ApplicationGesture.DownLeftLong, true);

this.InkManagement.SetGestureStatus(ApplicationGesture.Tap, true);

                 

// Abonnemetn à l’événement Gesture :

this.InkManagement.Gesture += new

      Microsoft.Ink.InkCollectorGestureEventHandler(InkManagement_Gesture);

 

La liste détaillée de tous les gesture disponibles, de leur description et de leur utilisation recommandée est accessible dans la documentation de la SDK TabletPC, à la rubrique « Application Gestures and Semantic Behavior » ou sur MSDN à cette adresse.  Pour notre part, nous n’allons utiliser que les cinq ci-après :

 

 

Left

Effacer le caractère avant le curseur (=backspace)

Right

Espace

Scratch-out

Tout effacer

DownLeftLong

Valider

Tap

Pas utilisé directement

 

Le gesture Tap n’est pas utilisé ici en tant que tel. En fait, nous ne le déclarons que pour éviter qu’un point ne soit dessiné lorsque l’utilisateur ‘tape’ dans le contrôle pour lui donner le focus.

Le traitement de ces gesture se fait dans la méthode de callback InkManagement_Gesture. On vérifie tout d’abord que le gesture n’a pas été commencé dans la zone de texte (comme dans le cas des stroke, voir plus haut). Dans ce cas, on annule le gesture, et on retourne. Sinon, on fait un aiguillage en fonction du gesture détecté :

 

private void InkManagement_Gesture(object s, InkCollectorGestureEventArgs e)

{

 

      // Si le gesture a été commencé dans du texte

      if((startStroke.X < textRegion.Width + 10) && (startStroke.Y < textRegion.Height))

      {

            e.Cancel = true; // On annule le gesture

            return; // et on retourne

      }

 

      // En fonction du gesture détecté

      switch(e.Gestures[0].Id)

      {

            // Si c'est un trait vers la gauche (équivalent de backspace)

            case(ApplicationGesture.Left) :

// ...

// Suppression du caractère précédent le curseur

// ou de la sélection le cas échéant

// ...

                  break;

 

            // Si le gesture est un 'cribouilli' horizontal :

            case(ApplicationGesture.Scratchout) :

                  TextSafe = ""; // On efface tout

                  // on efface toutes les écritures manuscrites :

                  this.inkManagement.Ink.DeleteStrokes();

                  break;

 

            // Si le gesture est un trait horizontal vers la droite :

            case(ApplicationGesture.Right) :

                 

// ...

// Insertion d’un espace

// ou remplacement de la sélection par un espace

// ...

                  break;

           

            // Si la gesture est une validation

            // (angle vers le bas puis à gauche long)

            case(ApplicationGesture.DownLeftLong) :

                  // On génére l'événement ValidatingGesture :

                  ValidatingGesture(this, new EventArgs());

                  break;

 

            default :

                  break;

      }

 

}

 

Une chose est à noter concernant le code ci-dessus : dans le cas d’un gesture ‘validation’, nous générons un événement que nous avons créé spécialement dans notre contrôle : ValidatingGesture. Cet événement, déclaré comme suit, peut être surveillé de l’extérieur de la classe afin de réagir à la validation du contrôle :

 

            public event EventHandler ValidatingGesture;

 

Pour terminer sur les gesture, signalons que nous désinscrivons certains des gesture lorsque l’utilisateur a déjà dessiné au moins un stroke. Cela se fait dans InkManagement_StrokeEnds :

 

this.InkManagement.SetGestureStatus(ApplicationGesture.Left, false);

// Puis encore trois fois la même chose pour Right, DownLeftLong et Tap

 

Nous revalidons ces gesture dans RecoTimer_Elapsed. Cela permet à l’utilisateur de tracer la barre de ses ‘T’ ou les points de ses ‘i’ sans exécuter une commande involontaire !

 

Protection du texte

 

Comme nous l’avons fait remarquer plus haut, nous avons créé pour notre InkInputEx une propriété TextSafe. Cette propriété a été rendue nécessaire par la présence d’un deuxième thread, concurrent du thread principal de notre barre d’outils, dont la nécessité sera détaillée dans le prochain article. Ce second thread accède en effet au Text de notre InkInputEx. Il peut alors se produire des conflits avec le thread principal si celui-ci est en train d’accéder au même instant à la même propriété Text.

La solution que nous proposons est donc la réalisation de cette propriété TextSafe. Tous les accès au texte de InkInputEx, qu’ils soient effectués depuis la classe ou de l’extérieur, du même thread ou pas, devront se faire maintenant par TextSafe et non par Text. Le fonctionnement de notre propriété TextSafe utilise de manière simple mais efficace les sémaphores, par l’intermédiaire de la classe System.Threading.Mutex de la FCL :

 

private System.Threading.Mutex textMutex = new

                        System.Threading.Mutex(false, "textInkToolBar");

 

 

 

Puis :

 

            /// <summary>

            /// Permet d'opérer sur le texte en mode ThreadSafe

            /// </summary>

            public string TextSafe

            {

                  get

                  {

                        string tempo;

 

                        textMutex.WaitOne();  // On protège

                        tempo = this.Text;

                        textMutex.ReleaseMutex(); // On libère

                        return tempo;

                  }

                  set

                  {

                        textMutex.WaitOne();  // On protège

                        this.Text = value;

                        textMutex.ReleaseMutex(); // On libère

                  }

            }

 

Nous avons effectué la même opération (avec le même objet textMutex) pour les propriétés SelectionStart et SelectedText.

 

Enfin, un dernier problème subsiste : lorsque le contrôle est repeint, il y a accès à la propriété Text, ce qui peut être très gênant pour les mêmes raisons d’accès concurrents ! Il faut donc également protéger cet accès, mais il est impossible d’indiquer à la méthode Paint d’utiliser TextSafe plutôt que Text, sauf à réécrire cette méthode entièrement !

Notre solution consiste à acquérir textMutex lors de l’événement Painting (juste avant le Paint) et à le relâcher lors de l’événement Painted (juste après le Paint, donc), tout en prenant diverses précautions. Nous réalisons cela dans une classe dérivée de InkOverlay : il s’agit de InkOverlay qui est la classe que nous utiliserons en lieu et place de InkOverlay pour notre InkManagement :

 

class InkOverlayEx : Microsoft.Ink.InkOverlay

{

      // Mutex utilisé pour protéger les Strokes

      private System.Threading.Mutex textMutex = new

            System.Threading.Mutex(false, "textInkToolBar");

 

      public InkOverlayEx(IntPtr h) :  base(h){      }

 

      protected override void OnPainting(InkOverlayPaintingEventArgs e)

{

            try

            {

                  textMutex.WaitOne();

                  base.OnPainting(e); // On appelle la méthode de base

            }

            catch(System.Exception err)

            {

                  textMutex.ReleaseMutex();

            }

}

 

 

protected override void OnPainted(PaintEventArgs e)

{

            try

            {

                  base.OnPainted(e); // On appelle la méthode de base

            }

            catch(System.Exception err){}

            finally

            {

                  textMutex.ReleaseMutex();

            }

}

}

 

Voilà  notre contrôle paré à toute erreur violente de type « access violation » !

Pour que notre contrôle soit complet, il ne nous reste plus qu’à gérer l’agrandissement de sa taille lorsque l’utilisateur écrit dedans.

 

Détection du pointeur et agrandissement

 

Les phases d’agrandissements et de réductions sont assez complexes : elles doivent en effet tenir compte des sorties éventuelles du stylet de la zone pendant la saisie, du passage du stylet au dessus de la zone sans vouloir y écrire (accès à un autre contrôle de l’application), longue pause (stylet totalement immobile dans la zone de saisie)... Nous allons repartir des spécifications exposées pour notre InkEditEx dans cet article. Nous ajoutons cependant une contrainte, dont la réalisation sera rendue ici possible grâce à la réécriture complète du contrôle que nous venons de décrire : nous n’agrandirons le contrôle que si c’est bien la stylet qui le survole (et non la souris).

L’ensemble de ces agrandissements et réductions est géré par deux timers. Trois propriétés publiques de type int permettent de régler les délais. Voici un diagramme d’états, un peu simplifié, représentant les transitions possibles. Les états représentés en rouge correspondent aux états pendant lesquels un timer pour l’agrandissement tourne. Lorsque l’on passe dans un état rouge, le timer est lancé. Lorsqu’on en sort, c’est que le timer s’est déclenché où qu’il a été stoppé. Le même principe gouverne les états représentés en bleu, mais il s’agit dans ce cas du timer tourne pour la réduction de la taille du contrôle :

 


 

Ce schéma fait intervenir la distinction entre la taille du contrôle (petite ou grande) et le type du pointeur : pointeur (= indéterminé), souris ou stylet.

 

Au niveau du code, tous les états possibles sont déterminés par un jeu de trois flags : justEntered, integratedDevice et cursorInRange, et, évidemment, pat l’état des timers (en course ou  arrêtés). Voici un tableau qui résume leurs valeurs :

 

 

Etat n°

Taille du contrôle

Position du pointeur

Type du pointeur

justEntered

integratedDevice

cursorInRange

1

petite

dehors

?

?

?

F

2

petite

dedans

stylet

F

?

TRUE

3

grande

dehors

?

F