Introduction
Dans l’article précédent, nous
avons vu comment créer et intégrer à Internet Explorer (IE) une barre d’outils
générique en .Net. Cette barre doit, rappelons le, permettre de naviguer sur le web au
moyen du stylet d’un TabletPC. Nous devons donc maintenant implémenter les
parties IHM et traitements d’arrière plan de cette barre.
Cet article
sera le premier volet de la partie IHM. Il y sera question de la création de
trois contrôles utilisateurs, et du réglage de certaines de leurs propriétés.
Le deuxième article de cette partie se concentrera sur le contrôle principal de
cette barre, à savoir la zone de saisie des urls. Dans aucun de ces articles ne
sera rappelée la démarche détaillée de création de contrôles. Cette démarche a,
en effet, déjà fait l’objet d’un article.
Pour
l’heure, nous allons présenter deux contrôles très proches : les FlatIEButton et les CheckBoxEx. Puis nous détaillerons le contrôle permettant
d’afficher l’historique sous la zone de saisie des urls (ListBoxEx).

Se fondre dans Internet Explorer
Le premier
objectif de notre barre d’outils en matière d’IHM était de se fondre le plus
possible dans IE. Pour cela, il fallait réaliser des contrôles dont le
comportement se rapprochait au maximum de celui des contrôles standard d’IE.
Cela impliquait, par exemple, de créer des contrôles sans bord, s’éclaircissant
lorsque la souris les survole. Il fallait également tenir compte des
contraintes spéciales à la programmation pour TabletPC exposées dans le premier
article, à savoir agrandir les zones actives des contrôles (très important
pour les RadioButton et les CheckBox), fournir des zones de saisie
de l’encre assez grandes…
C’est donc
dans cette optique que nous avons créé deux nouveaux contrôles : les FlatIEButton et les CheckBoxEx.
FlatIEButton
Les FlatIEButton, dans la
version finale de notre barre d’outils, correspondent aux boutons de
validation (boutons « .com », « .fr »…) :

Ils ont le
même comportement que les boutons standard (ils en héritent), à quelques
exceptions près. Premièrement, ils ne possèdent pas de bords, ne laissant donc
apparaître que leur texte et leur image. Ensuite, ils prennent une forme 3D et
s’éclaircissent lorsque la souris les survole.

Cette propriété d’éclaircissement se doit d’ailleurs d’être débrayable. Enfin,
ils ne doivent pas afficher les cadres habituels indiquant qu’un contrôle a le focus.
Pour éviter
qu’un cadre ne s’affiche, il suffit de s’abonner à l’événement Paint du bouton. Celui-ci est appelé
immédiatement après que le bouton se soit redessiné. Il s’agit ici d’effacer le
cadre extérieur en dessinant par-dessus un rectangle de la couleur de fond du
bouton :
this.Paint += new
PaintEventHandler(FlatIEButton_OnPaint);
dans le constructeur, puis :
private
void FlatIEButton_OnPaint(object sender, PaintEventArgs e)
{
// Si la souris n'est pas au dessus
if(!MouseOver)
{
// On redessine le contrôle sans bords
ControlPaint.DrawBorder(
e.Graphics,
this.DisplayRectangle,
this.BackColor, 2, ButtonBorderStyle.Solid,
this.BackColor,
2, ButtonBorderStyle.Solid,
this.BackColor, 2, ButtonBorderStyle.Solid,
this.BackColor, 2, ButtonBorderStyle.Solid);
}
}
Ensuite, pour
donner au bouton un aspect 3D au survol de la souris, il faut modifier la
propriété FlatStyle :
//
On donne l'aspect 3D lorsque la souris le survole
FlatStyle =
System.Windows.Forms.FlatStyle.Popup;
Pour éclaircir le bouton, nous
avons créé deux Bitmap, l’une pour
l’image normale et l’autre pour l’image éclairée. Nous leur avons attaché deux
propriétés publiques. Il appartient à l’utilisateur du contrôle de créer et
d’attacher deux Bitmap de son choix.
Nous avons enfin créé une propriété HighlightButton
qui valide ou invalide l’éclaircissement. On permute les Bitmap et on change la couleur de fond dans une méthode RefreshImage qui est appelée sur les
événements MouseEnter et MouseLeave (si HighlightButton est vrai) :
private void
RefreshImage()
{
// Si la demande d'éclaircissement du contrôle est
spécifiée
if(HighlightButton)
{
// si la souris est sur le contrôle, on "éclaire"
l'image
if(MouseOver)
{
// On change la couleur de fond
this.BackColor = System.Drawing.Color.FromArgb(247,247,231);
// On change l'image du bouton
this.Image = Image_Highlighted;
}
// Si la souris n'est pas sur le contrôle
else
{
// On change la couleur de fond
this.BackColor = System.Drawing.SystemColors.Control;
// On change l'image du bouton
this.Image = Image_Unhighlighted; }
}
}
Enfin, le
contrôle ne doit pas prendre le focus (pour éviter, entre autre chose,
l’apparition du « rectangle de focus »). Pour cela, une seule ligne
dans le constructeur suffit :
//
on interdit la prise de focus
SetStyle(ControlStyles.Selectable,
false);
Le code complet de ce contrôle est
téléchargeable ici.
CheckBoxEx
Ce contrôle
est utilisé pour les préfixes d’autocomplétion : « http »,
« www » …

Le code de ce contrôle est quasiment identique à celui de FlatIEButton, mis à part que nous
héritons de CheckBox au lieu de Button. De plus, il faut donner
l’apparence « Bouton » à la CheckBox
pour agrandir sa zone active. Ceci se fait dans le constructeur en une
ligne :
//
On donne à la case à cocher l'apparence d'un bouton
this.Appearance = Appearance.Button;
Enfin, quatre Bitmap sont maintenant nécessaires
(coché/décoché et éclairé/non éclairé). Cette fois-ci, elles sont initialisées
afin qu’une case à cocher soit affichée par défaut.
Le code complet de la CheckBoxEx
est disponible ici.
Exemple d’utilisation du FlatIEButton et du
CheckBoxEx
Nous allons
ici vous donner un exemple de création et d’initialisation d’un FlatIEButton. Ceci s’appliquerait
également à une CheckBoxEx.
Après avoir
créé un FlatIEButton, il
faut créer les deux Bitmap à
afficher :
private System.Drawing.Bitmap okBitmap;
private System.Drawing.Bitmap ok_highlightedBitmap;
Il faut ensuite charger les
Bitmap, leur affecter une couleur transparente (si souhaité) puis les associer
au bouton, dans le constructeur du Container
par exemple :
//
Chargement des images à mettre dans les Bitmaps
okBitmap = new System.Drawing.Bitmap(assembly.GetManifestResourceStream("InkToolBar. Images.ok.bmp"));
ok_highlightedBitmap = new System.Drawing.Bitmap(assembly.GetManifestResourceStream("InkToolBar. Images.ok_highlighted.bmp"));
...
//
La couleur blanche est maintenant la couleur transparente
okBitmap.MakeTransparent(Color.White);
ok_highlightedBitmap.MakeTransparent(Color.White);
...
//
Lien entre l'image et son Bitmap
this.okButton.Image_Unhighlighted = okBitmap;
this.okButton.Image_Highlighted
= ok_highlightedBitmap;
Tous ces contrôles sont dans
un assembly qui vous est proposé en fin d’article.
Affichage de l’historique : ListBoxEx
Présentation du contrôle ListBoxEx
Nous
désirons ici réaliser un menu déroulant affichant l’historique en fonction des
premières lettres saisies dans la zone d’adresses, comme dans la barre
traditionnelle d’IE. Cette liste devra donc pouvoir sortir de la barre. De
plus, elle devra suivre les changements de taille de la zone de saisie qui,
comme nous le verrons dans le prochain article, s’élargit lorsque le stylet
s’approche :

Lorsque le stylet est au dessus de la zone de saisie,
l’historique s’agrandit également :
Enfin, nous souhaitons que les
lignes de l’historique soient mises en surbrillance lorsque le pointeur de la
souris passe au dessus.
Nous allons
donc créer un nouveau contrôle hérité de ListBox :
ListBoxEx. Ce contrôle est instancié
dans notre barre d’outils sous le nom d’historyListBox.
Il ne sera pas inclus dans l’assembly
contenant FlatIEButton et CheckBoxEx. En effet, il est totalement
adapté à notre barre d’outils et ne pourra a priori jamais servir dans un autre
projet. Ce contrôle sera donc inclus dans le projet principal de la barre
d’outils qui vous sera livré à la fin de cette série d’articles. Le code est
cependant disponible à la fin de cette partie.
Une
précision avant d’aller plus loin : Une fonction incluse dans la dll shdocvw.dll permet d’utiliser le même
contrôle d’affichage de l’historique et d’autocomplétion que IE : il
s’agit de SHAutocomplete. Cette
fonction s’utilise très simplement, puisque après l’avoir déclarée grâce à
l’attribut DllImport, il suffit
d’appeler cette fonction en lui passant en paramètre le handle du contrôle
auquel il faut l’attacher. Tout est ensuite automatique. Malheureusement, il
nous était impossible d’utiliser cette fonction puisqu’elle affiche
l’historique en fonction de la propriété Text
du contrôle auquel elle est attachée. Elle ne peut pas, comme nous le
souhaiterions, tenir compte également de l’encre saisie. C’est pourquoi nous
avons décidé de retravailler nous même cette partie (ceci sera exposé dans le
dernier de cette série).
Changer le parent d’un contrôle
Passons
maintenant à l’écriture de ce nouveau contrôle. Nous allons tout d’abord voir une
fonctionnalité qui est traitée à l’extérieur du contrôle, c’est-à-dire depuis
son conteneur : il s’agit de faire dépasser historyListBox de la barre. Il faut signaler qu’un contrôle ne peut
normalement pas sortir de son contrôle parent. Cependant, il est possible,
grâce à une fonction de l’API Windows, de changer la propriété Parent d’un contrôle. Il suffit lorsque
nous voulons faire dépasser la historyListBox, de
lui donner la fenêtre de IE comme parent. Il ne faut pas oublier de changer les
coordonnées de la historyListBox car
sa position est calculée par rapport à son parent. Les deux fonctions de l’API
nécessaires sont :
// Import d'une méthode de l'API
permettant de changer le parent d'un contrôle
[DllImport("user32")]
private static
extern bool
SetParent(int hWndChild, int hWndNewParent);
// Import d'une méthode de l'API
permettant de connaître le parent d'un contrôle
[DllImport("user32")]
private static
extern int
GetParent(int hWnd);
Leur utilisation sur notre historyListBox se fait comme suit :
// Si le Parent de la ListBox n'est pas
IE donc que c'est InkToolBar
if(GetParent(this.historyListBox.Handle.ToInt32())
!= Explorer.HWND)
{
//
On change le Parent de la ListBox en IE
SetParent(this.historyListBox.Handle.ToInt32(),
Explorer.HWND);
// On
change la position relative de la ListBox (-> elle ne bouge pas à l'écran)
this.historyListBox.Top += this.Top;
this.historyListBox.Left += this.Left;
// On
affiche la ListBox
this.historyListBox.Show();
}
Détecter la sortie du pointeur de la souris
Notre ListBoxEx doit ensuite suivre les
changements de taille de la zone de saisie d’adresses. Cependant, lorsque la
zone de saisie se réduit, si le pointeur de la souris est au dessus la ListBoxEx (l’utilisateur s’apprête à
sélectionner un élément ou à faire défiler la liste), elle ne doit pas se
réduire immédiatement. Il faut donc la réduire seulement lorsque le pointeur
quitte la zone de la ListBoxEx.
A priori, cela se fait simplement en surveillant les
événements MouseEnter et MouseLeave liés à la ListBox. Cependant, un problème se pose
lorsqu’une ScrollBar est présente à
droite de la ListBox : les
événements de la souris considèrent que la
ScrollBar ne fait pas partie de la ListBox !
Une des premières choses à implémenter dans notre ListBoxEx est donc la création d’une propriété booléenne publique
qui permette de savoir si le curseur de la souris est au dessus de la zone de
la ListBoxEx, ScrollBar incluse… Nous allons pour cela réaliser du polling : nous vérifions toutes les
100 ms la position du curseur de la souris et la comparons à la position de la ListBoxEx, en tenant compte de ses
dimensions globales (= avec la ScrollBar).
Nous utilisons pour cela un timer ainsi
qu’une méthode de l’API Windows. Nous créons également au passage un événement
qui est levé lorsque le pointeur quitte notre ListBoxEx :
public class
ListBoxEx : ListBox
{
//
Flag indiquant si la souris survole actuellement le contrôle private bool mouseOver = false;
//
Timer permettant de faire du polling pour savoir si la
souris
est au dessus
private Timer mouseTimer;
//
Création d'un événement déclenché lorsque la souris quitte la zone
public event
EventHandler MouseLeaveListBoxZone;
...
public ListBoxEx()
{
mouseTimer = new Timer(); // Création du
timer de polling
//
Abonnement à l'événement de déclenchement du timer :
mouseTimer.Tick += new EventHandler(MouseTimer_OnElapsed);
mouseTimer.Interval
= 100; // Initialisation de la durée
mouseTimer.Start(); // Départ du timer
...
}
private void MouseTimer_OnElapsed(object myObject, EventArgs
myEventArgs)
{
// Si ce code
s'exécute pendant le Design Time
if(this.DesignMode)
return; // Ne rien faire
//Sinon :
// On
récupère le rectangle correspondant au contrôle (Scroll inclus)
Rectangle myRect = GetRectangleToScreen(this);
// Si le
curseur de la souris est dans le rectangle du contrôle
if(myRect.Contains(Cursor.Position))
mouseOver = true;
// On positionne le Flag à true
//Sinon
else
{
//Si le Flag était positionné à true
if(mouseOver)
// La souris
vient de quitter le contrôle
// On envoie l'événement perso
MouseLeaveListBoxZone(this, new
EventArgs());
mouseOver
= false; // On
positionne le Flag à false
}
mouseTimer.Start(); // On relance le timer
}
...
}
Notre méthode MouseTimer_OnElapsed
connaît
la position du rectangle de la ListBoxEx grâce
à la méthode GetRectangleToScreen que
voici :
private Rectangle
GetRectangleToScreen(System.Windows.Forms.Control ctrl)
{
//
Initialisation de rectangle résultat
Rectangle Rect = new Rectangle(0, 0, 0, 0);
// Appel à
la méthode de l'API Windows permettant de récupérer le
rectangle
d'un contrôle
GetWindowRect((IntPtr)(ctrl.Handle.ToInt32()), ref Rect);
// Retour du
rectangle du contrôle
return new
Rectangle(Rect.Left, Rect.Top,
Rect.Width - Rect.Left, Rect.Height
- Rect.Top);
}
Cette méthode utilise à son tour une fonction de l’API
Windows qui rend le rectangle du contrôle dont le handle est passé en paramètre :
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static
extern int
GetWindowRect(IntPtr hwnd, ref Rectangle
aRect);
Le problème ici est que la fonction GetWindowRect renvoie dans son paramètre aRect, quatre entiers qui représentent respectivement les
coordonnées du coin supérieur gauche, par rapport à l’écran, du contrôle et les
coordonnées du coin inférieur droit. Hors la définition de la classe Rectangle du framework .Net contient
seulement les membres Top, Left, Width et Height. Le GetWindowRect de l’API remplit donc les
champs Width et Height de aRect avec les
coordonnées écran du coin inférieur droit du contrôle ! D’où la nécessité
de notre méthode GetRectangleToScreen qui effectue la
conversion lors du return...
La position du curseur de la souris est, elle, obtenu plus
simplement, avec une propriété statique du framework : Cursor.Position.
Ainsi, le conteneur d’une ListBoxEx peut savoir à tout moment si le curseur de la souris se
trouve au-dessus du contrôle (propriété MouseOver),
et reçoit un événement MouseLeaveListBoxZone
lorsque le curseur quitte la zone (ScrollBar
incluse).
Sélection sur déplacements de la souris
La sélection d’un élément de la liste sur le simple
déplacement de la souris se fait par l’abonnement à l’événement MouseMove. La méthode associée
est :
private void
ListBoxEx_OnMouseMove(object sender,
MouseEventArgs e)
{
try
{
// On sélectionne la ligne au-dessus de laquelle se trouve
la souris
SelectedIndex
=
(Items.Count * ItemHeight - 2 > e.Y) ?
e.Y
/ ItemHeight + TopIndex : -1;
}
// On
empêche toute erreur minime d'indice
catch(System.ArgumentOutOfRangeException){}
}
Cette méthode n’appelle pas de plus longs commentaires...
Pour conclure sur cette partie
consacrée à notre contrôle ListBoxEx,
nous vous proposons le code complet en téléchargement ici.
Conclusion
Nous avons
créé dans cet article trois contrôles pour notre barre d’outils IE :
ListBoxEx permet d’afficher un historique web. Très adapté à notre
cas, il a été inclus dans le projet principal de notre barre d’outils :
aucune version compilée ne vous est donc proposée maintenant ; nous vous
rappelons que la totalité de la solution source vous sera proposé dans le
dernier article de cette série.
Les deux autres contrôles, FlatIEButton et CheckBoxEx sont inclus dans un assembly
nommé InkTollBarControls.dll téléchargeable
ici. Cet assembly contient en réalité
trois contrôles : le troisième, InkInputEx,
est utilisé pour la zone de saisie d’adresses web. Beaucoup plus élaboré, il
mérite que nous y accordions un article entier, en l’occurrence le prochain.