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