Programmation sur Tablet PC : Réalisation de l’interface de outil de recherche de notes.
Accueil > Articles > Matériels
|
|
Auteurs
|
|
 |
|
 |

31934 46/126
|
Introduction
Ce quatrième article va décrire
la réalisation d’une interface pour notre outil de recherche de notes
manuscrites. Dans le dernier
article, nous avons réalisé le moteur de recherche pour les fichiers du
Windows Journal (entre autres types de fichiers). Nous allons maintenant
décrire la façon de réaliser une interface utilisant ce moteur. Nous allons
tenter de réaliser l’interface la plus ergonomique possible pour l’utilisateur
d’un TabletPC. Cela passera notamment par la conception de contrôles
personnalisés dédiés au TabletPC. Nous allons également voir la création de
deux contrôles permettant la sélection d’un dossier et l’affichage de fichiers
tels que dans l’explorateur Windows.
Construction de contrôles personnalisés
Nous allons décrire ici de façon générale la création et
l’utilisation de contrôles personnalisés. Nous créerons par la suite plusieurs
contrôles dédiés à l’interface de notre application de recherche de documents.
Création d’un contrôle
Avec
VS.Net, il est extrêmement aisé de réaliser ses propres contrôles. Il suffit de
créer un nouveau projet ‘Bibliothèque de contrôles Windows’ :

VS crée une classe héritant de System.Windows.Forms.UserControl. Un UserControl permet de rassembler plusieurs System.Windows.Forms.Control en une seule unité. Vous pouvez
hériter d’un autre contrôle déjà existant (un Button, une CheckBox…).
Remplacez simplement UserControl par
le nom de la classe du contrôle parent. Attention toutefois, en faisant cela,
vous n’avez plus accès à l’éditeur de formulaire.
Lorsque vous ajoutez de nouveaux
contrôles à la bibliothèque (clic droit sur le projet, puis ajouter un nouvel
élément…), vous pouvez :
-
Créer un ‘contrôle utilisateur’. Dans ce cas, votre
contrôle hérite de UserControl, comme
pour le premier contrôle créé.
-
Créer un contrôle personnalisé. Votre contrôle hérite de
Control. Control inclut les propriétés et événements de base (comme Font, ForeColor, Click, …). En
revanche, c’est à vous d’écrire tout le code concernant la représentation
graphique. Avec ce type de contrôle, vous ne pouvez pas utiliser le designer.
Tout comme dans le premier cas, rien ne vous empêche de changer le nom de la
classe parent en celle de votre choix (Button,
Label ou autre contrôle
personnalisé…).
Vous écrivez ensuite votre classe
contrôle comme une classe traditionnelle. Les propriétés que vous créez sont
celles qui apparaîtront dans la fenêtre ‘propriétés’ de l’éditeur de formulaire
lors de l’utilisation de votre nouveau contrôle.
Il est possible d’affecter une
propriété créée à une catégorie de propriétés via l’attribut Category. Vous pouvez également définir
le texte qui sera affiché dans la zone ‘description’ de la fenêtre ‘propriétés’ (Category et Description sont deux attributs de System.ComponentModel) :
private Color colorOnFocus;
[Category("Apparence")
Description("Couleur du contrôle
lorsqu’il a le focus")]
public
Color ColorOnFocus ()
{
get{return
colorOnFocus;}
set{colorOnFocus
= value;}
}
Si la catégorie existait déjà, la propriété y est ajoutée,
sinon une nouvelle catégorie est créée.
Pour personnaliser votre contrôle
dans le moindre détail, il est également possible d’associer une image bitmap
au nouveau contrôle. Ce bitmap sera affiché dans la boîte à outils de VS lors
de l’importation du contrôle, à la place de l’icône ‘wheel’ par défaut. Il existe
deux possibilités d’association d’une image à un contrôle :
- Vous
pouvez demander au compilateur d’incorporer la ressource image à l’assembly.
- Ou alors
vous utilisez le fichier image en tant qu’élément externe.
La seconde solution signifie que le fichier image devra être
systématiquement joint à la dll du contrôle pour pouvoir être utilisé ; la
première solution est largement préférable. C’est celle que nous allons
décrire :
Vous devez tout d’abord réaliser
un bitmap de 16x16 en 16 couleurs. Vous pouvez le réaliser dans un autre
programme du type ‘Paint’ puis l’ajouter (clic droit sur le nom du projet dans
l’explorateur de solution, puis ‘Ajouter un élément existant’ ou bien le
réaliser directement avec l’éditeur de bitmap de VS (clic droit sur le nom du
projet puis ‘Ajouter un nouvel élément’). Il faut que le nom de l’image tel
qu’il apparaît dans l’explorateur de solution soit exactement celui de votre
contrôle, suivi de ‘.bmp’ (par exemple ‘InkEditEx.bmp’)
Notez que la couleur verte (0,
255, 0) sera transparente lors de l’affichage de l’icône dans la boîte à
outils.
Dans les propriétés de l’image
bitmap, choisissez ‘ressource incorporée’ (‘embed ressource’ dans la version
américaine) dans la zone ‘action de génération’.
Il faut maintenant vérifier une
propriété du projet lui-même. Pour cela, cliquez-droit sur le nom du projet et
choisissez ‘propriétés’. Dans la boîte de dialogue, vérifiez la propriété
‘espace de noms par défaut’. A la création du projet, c’est le nom du projet
lui-même qui est dans ce champ. La ressource bitmap que vous avez créée
appartient à ce nom d’espace par défaut précisé ici. Malheureusement, lorsque
vous faîtes référence à cette ressource dans votre code, le nom d’espace dans lequel elle sera
recherchée sera celui déclaré dans le code... Si dans votre code vous avez
précisé un namespace différent du nom du projet, donc du namespace par défaut
pour les ressources, votre bitmap sera introuvable ! Vous devez donc
entrer le bon namespace dans la zone ‘espace de noms par défaut’ de votre
projet.
Note : Pour vérifier à quel
espace de noms est attachée une ressource lors de la compilation, chargez votre
dll dans l’outil ildasm.exe et regardez la rubrique ‘MANIFEST’ : les
déclarations de ressources y apparaissent avec l’espace de noms complet auquel
elles appartiennent :

Vous pouvez maintenant utiliser le bitmap en l’associant au
contrôle pour l’affichage dans la barre d’outils. Pour cela, il suffit
d’ajouter l’attribut :
[ToolboxBitmap(typeof(Bitmap))]
class myControl : UserControl
{
}
Notez que l’on ne précise pas ici
le nom du bitmap : c’est le bitmap ayant le même nom que la classe
qui sera choisi.
Utilisation d’un contrôle personnalisé
|

|
Pour utiliser une bibliothèque de
contrôles personnalisés, cliquez droit sur l’onglet ‘Windows Forms’ de la boîte
à outils, puis ‘Personnaliser la boîte à outils’. Dans l’onglet ‘.Net’ de la
boîte de dialogue qui apparaît, cliquez sur ‘Parcourir’. Là, sélectionnez la
dll de votre bibliothèque. En validant, vous voyez apparaître à la fin de la
boîte à outils tous les contrôles contenus dans la dll (avec leurs icônes
personnelles !).
|
La première fois que vous
instanciez l’un de ces contrôles (par glisser-déposer dans le formulaire), la
dll est automatiquement ajoutée à la liste des références du projet et copiée
dans le dossier de sortie de compilation.
Note : si vous n’utilisez pas
le concepteur de formulaires, vous devez vous-même ajouter la référence vers la
dll.
Passons maintenant à la pratique en créant un premier
contrôle dédié au TabletPC.
Un nouveau contrôle : InkEditEx
Nous désirons réaliser une zone
de saisie proche de l’InkEdit (voir le deuxième
article de cette série) possédant certaines particularités
facilitant l’écriture à main levée. Ce contrôle, de la taille habituelle d’un EditBox s’agrandira automatiquement
lorsque l’utilisateur commencera à écrire dedans. Ainsi, le contrôle prend la
même place qu’un EditBox lorsque aucune saisie n’a lieu. En revanche il
présente une zone d’écriture très importante lorsque l’utilisateur écrit à main
levée dedans.
Les spécifications de notre InkEditEx sont les suivantes :
- Le
contrôle est initialement affiché en taille normale.
- Il y a
deux modes d’activation possibles pour passer à l’affichage en grande
taille :
Soit lorsque
l’utilisateur passe le pointeur de souris (donc son stylet) au dessus du
contrôle. L’inconvénient est que le contrôle peut s’agrandir intempestivement
alors que l’utilisateur le survole pour aller vers un autre contrôle. On peut
donc ajouter une temporisation qui
indique le temps pendant lequel le pointeur doit rester dans la zone du InkEdit pour que celui-ci s’agrandisse.
Soit lorsque
l’utilisateur clique dans la zone (ce qui correspond au début d’une saisie
manuscrite).
- Le
contrôle revient à sa taille normale lorsque le pointeur de la souris quitte la
zone d’écriture ou lorsqu’il reste parfaitement immobile (l’utilisateur a
éloigné son stylet de l’écran pour faire une pause) au-dessus de la zone. Le
choix entre les deux modes se fait par l’affectation d’une propriété.
- Il existe
deux temporisations possibles pour le retour à sa taille normale du
contrôle :
Une qui
définit le temps pendant lequel le pointeur doit rester immobile pour que le
contrôle revienne à sa taille classique.
L’autre qui
définit le temps après lequel le contrôle revient à sa taille classique après
la sortie du curseur de la zone. Le choix de ce délai doit se faire en tenant
compte de deux impératifs antagonistes. Il faut d’un côté un délai court pour
que l’utilisateur ne s’impatiente pas en attendant le retour du contrôle à sa
taille normale (car le contrôle agrandi risque de cacher d’autres contrôles
auxquels l’utilisateur souhaite maintenant accéder). Par ailleurs, il faut un
délai assez long car en écrivant à main levée dans le contrôle, l’utilisateur
risque d’en sortir temporairement (au changement de ligne par exemple) et il ne
faut pas que le contrôle change de taille au milieu de la saisie ! Ces deux
délais sont accessibles par des propriétés.
- Une
dernière propriété sera l’autorisation ou pas du changement automatique de
taille.
Pour la réalisation du contrôle,
nous allons donc créer 7 propriétés correspondant aux attributs
suivants :
private Size maxSize = new Size(200,400);
// La taille du contrôle lorsqu'il est
étendu
private Size minSize = new
Size(100, 96);
//
La taille du contrôle lorsqu'il est réduit
private
bool dynamicSize = true;
// Autoriser le changement de taille
private
bool increaseSizeOnClick = false;
//
Augmenter la taille de la zone sur click et non sur entrée
private
double inertDelay = 2000;
// Délai de réduction après inactivité
private
double outDelay = 800;
// Délai de réduction après sortie
private double
enterDelay = 450;
//
Délai d'augmentation de la taille lorsque la souris entre
Nous allons également créer deux System.Timers.Timer
pour gérer tous les délais :
private System.Timers.Timer decreaseTimer;
private System.Timers.Timer increaseTimer;
Ensuite, nous allons traiter tous
les événements : MouseEnter, MouseLeave, MouseDown, MouseMove.
C’est dans ces méthodes que se gèrent tous les changements de taille. A noter
que le changement de taille du contrôle génère un événement MouseMove, ce qui nous embête un peu
lorsqu’on change la taille du contrôle après un délai d’inactivité de la
souris : tout se passe comme si c’était l’utilisateur qui avait bougé la
souris, alors qu’il n’en est rien… Nous avons donc ajouté un flag qui invalide
la méthode InkEditEx_MouseMove pendant le changement de taille. Nous
aurions également pu jouer sur l’abonnement et le désabonnement à l’événement MouseMove, mais cela est très délicat
car on ne peut pas prévoir l’ordre et le moment de déclenchement de ce genre
d’opération.
Il aurait était intéressant dans
la programmation de ce contrôle de pouvoir différencier l’approche de la souris
de celui du stylo. Malheureusement, cela n’est possible qu’avec l’utilisation
d’un InkOverlay, et il est impossible
d’attacher un InkOverlay à un InkEdit… La seule solution serait de
dériver notre contrôle non pas de InkEdit
mais de RichTextBox et d’y attacher
un InkOverlay. Evidemment, cela
obligerait à reprogrammer toute la partie concernant la reconnaissance de
l’encre numérique… Cela ne nous est pas nécessaire dans le cas présent. Nous
verrons cependant dans un prochain article que dériver un contrôle de RichTextBox nous apporte d’autres
possibilités…
En ce qui
concerne l’utilisation de notre InkEditEx,
plusieurs points sont à noter :
- Il faut mettre
le contrôle au premier plan pour qu’il recouvre les autres contrôles en
mode étendu : soit dans l’éditeur de formes en cliquant droit dessus puis
‘Mettre au premier plan’, soit par code, en le mettant en premier dans l’ajout
des contrôle à la forme :
this.Controls.AddRange(new
System.Windows.Forms.Control[] {
this.inkEditEx1,
this.button1,
this.checkBox1}
);
- Il convient de fixer le InertDelay
à la valeur du RecoTimeout pour que l’utilisateur
ne voie pas ‘sauter’ l’interface plusieurs fois d’affiler.
- Lors de la création du contrôle,
le développeur doit veiller à ce que MinSize
soit égal à Size.
- MaxSize est obligatoirement plus grand que MinSize.
Voici le résultat
lorsque l’utilisateur écrit à main levée dans le contrôle :

Et après un petit
délai d’attente (et après quelques corrections de mise en page…) :

Le code du contrôle
est disponible ici.
Ce premier contrôle
personnalisé est destiné à notre application de recherches de documents encre
numérique. Nous reprendrons son concept de base dans un futur projet (qui fera
l’objet des prochains articles), mais en prenant cette fois RichTextBox comme classe de base.
Un deuxième contrôle : CheckBoxEx
Notre second contrôle consiste en
une case à cocher un peu plus ergonomique que les CheckBox déjà existantes. Pour cela, nous allons créer un contrôle
dérivant de CheckBox.
Dans le constructeur, nous changeons
l’apparence en Appearance.Button, et
nous centrons le texte. Deux propriétés définissent les couleurs du bouton
lorsque la case est cochée ou décochée. La couleur de fond change chaque fois
que l’état du contrôle change (événement CheckedChanged).
Nous changeons également le style de la police (gras ou normal) ; comme la
propriété Font de CheckBox est immuable (on ne peut pas
changer ses propriétés), nous devons assigner un nouvel objet Font à la propriété Font :
this.Font = new Font(this.Font,
FontStyle.Bold); // On change la police
Enfin, nous allons afficher dans
le bouton le bitmap d’une case cochée ou décochée en fonction de l’état du
contrôle. Pour cela, nous créons deux bitmaps (16x16) dans l’éditeur et nous
mettons leur propriété ‘action de génération’ à ‘ressource incorporée’. Enfin,
nous chargeons les bitmaps avec le code :
public class
CheckBoxEx : CheckBox
{
private System.Reflection.Assembly assembly =
System.Reflection.Assembly.GetExecutingAssembly();
private
Bitmap Image_Checked;
private
Bitmap Image_UnChecked;
//. . .
//. . .
public
CheckBoxEx()
{
Image_Checked = new Bitmap(
assembly.GetManifestResourceStream(
"myInkControls.checked_button.bmp"));
Image_UnChecked = new Bitmap(
assembly.GetManifestResourceStream(
"myInkControls.unchecked_button.bmp"));
Image_Checked.MakeTransparent(Color.White);
Image_UnChecked.MakeTransparent(Color.Green);
//. . .
//. . .
}
Pour charger un bitmap incorporé
dans l’assembly au runtime dans un objet Bitmap,
il faut le charger en utilisant la méthode Assembly.GetManifestResourceStream.
Le nom de la ressource passé en paramètre doit être complet (avec l’espace de
noms précisé dans le champ ‘espace de noms par défaut’ des propriétés du
projet).
A chaque changement d’état, nous
changeons l’image qui est affichée dans le bouton :
if(this.Checked)
{
this.BackColor
= ColorChecked; // On change la couleur de fond
this.Font
= new Font(this.Font,
FontStyle.Bold);//On change la police
this.Image = Image_Checked;// On change l'image de la case
}
else
{
this.BackColor = ColorUnChecked; // On change la couleur de fond
this.Font = new
Font(Font, FontStyle.Regular);//On change la police
this.Image = Image_UnChecked; // On change l'image
de la case
}
Le code de ce contrôle est disponible ici. Voici son apparence :

Une dll contenant nos deux contrôles TabletPC InkEditEx et CheckBoxEx est disponible ici. Nous
allons maintenant réaliser une dll contenant deux contrôles : un pour la
sélection de dossiers, le deuxième pour l’affichage d’une liste de fichiers.
Boîte de dialogue pour la sélection de dossiers
Pour le premier contrôle de cette
dll, il s’agit de réaliser une boîte de dialogue permettant de sélectionner un
dossier. En effet, il existe bien un contrôle pour la sélection de fichiers,
mais l’équivalent pour les dossiers n’est pas directement accessible. Il existe
bien la classe System.Windows.Forms.Design.FolderBrowser,
mais on ne peut pas l’utiliser directement. Cette classe est en effet définie
dans la classe System.Windows.Forms.Design.FolderNameEditor
et est protégée.
La seule possibilité est donc de l’agréger
dans une classe héritant de FolderNameEditor
:
using System;
namespace CtrlFiles
{
public class
OpenFolderDialog : System.Windows.Forms.Design.FolderNameEditor
//référence
à System.Design.dll et donc à System.Drawing.dll
{
FolderBrowser myFolderBrowser;
public
OpenFolderDialog()
{
myFolderBrowser = new FolderBrowser();
}
public
string GetFolder()
{
myFolderBrowser.ShowDialog();
return myFolderBrowser.DirectoryPath;
}
}
}
L’utilisation de cette boîte de dialogue se fait comme
ceci :
CtrlFiles.OpenFolderDialog myOFD = new CtrlFiles.OpenFolderDialog();
string
folder = myOFD.GetFolder();
Affichage d’une liste de fichiers
Nous allons maintenant réaliser notre
dernier contrôle dédié à l’affichage d’une liste de fichiers. On cherche à
obtenir un affichage identique à celui de l’Explorateur en mode détails. Nous
allons créer un contrôle adapté à notre moteur de recherche puisque nous allons
ajouter une colonne ‘Score’. Pour cela, on dérive une classe de ListView et on crée les
colonnes :
using System;
using System.Drawing; // Pour ToolboxBitmap
using System.Windows.Forms;
namespace CtrlFiles
{
[ToolboxBitmap(typeof(Bitmap))] // Associe un bitmap au cpntrôle
public class
FilesListView : ListView // System.Windows.Forms
{
private
System.Windows.Forms.ColumnHeader FileName;
// colonne du nom
private System.Windows.Forms.ColumnHeader FileScore;
//
colonne du score
//
. . .
//
. . .
public
FilesListView()
{
// On crée toutes les colonnes :
FileName = new
System.Windows.Forms.ColumnHeader();
FileScore = new System.Windows.Forms.ColumnHeader();
// . . .
// . . .
// On donne le nom qui sera affiché en tête de colonnes :
FileName.Text
= "Nom";
FileScore.Text
= "Score";
// . . .
// . . .
// La taille de chaque colonne :
FileName.Width = 150;
FileScore.Width = 40;
// On ajoute toutes les colonnes à la ListView :
Columns.AddRange(new System.Windows.Forms.ColumnHeader[]
{
this.FileName,
this.FileScore,
// . . .
//
. . .
});
// on passe en mode détails :
View =
System.Windows.Forms.View.Details;
// on autorise les sélections multiples :
this.MultiSelect = true;
// on veut que toute la ligne soit sélectionnée :
FullRowSelect
= true;
// . . .
//
. . .
}
Pour permettre l’activation d’éléments, par exemple
l’ouverture du document lorsqu’on double-clique dessus, il faut réécrire le méthode
OnItemActivate :
protected override
void OnItemActivate(EventArgs ea)
{
// Pour chaque
item sélectionné
foreach(ListViewItem
si in this.SelectedItems)
{
try
{
// On récupère son nom complet
string document = System.IO.Path.Combine(
si.SubItems[2].Text,si.Text);
// et on ouvre le document
System.Diagnostics.Process.Start(document);
}
catch
{
continue; // Si problème, on passe à l'item suivant
}
}
}
C’est la méthode statique Process.Start qui permet l’ouverture du document. Path.Combine construit le nom complet du
fichier à partir d’un chemin et d’un nom de fichier.
ListView n’est
capable de trier les éléments de la liste qu’en fonction de la première colonne,
et dans l’ordre alphabétique (propriété Sorting).
Nous allons faire en sorte que le tri s’effectue par rapport à la colonne où
l’utilisateur vient de cliquer. Pour cela, il faut tout d’abord créer une
classe dérivant de System.Collections.IComparer.
A l’intérieur, nous n’allons définir que la méthode publique Compare. Cette méthode prend en
paramètre deux objets x et y qu’il s’agit de comparer (Compare renvoie -1, 1 ou 0 selon que y<x, y>x, ou y=x). On
transtype x et y en ListViewItem, et
en fonction du numéro de colonne sur laquelle doit s’effectuer le tri, on
compare nos deux éléments.
On instancie notre nouvelle classe :
private Sorter columnSorter = new Sorter();
Puis dans le constructeur
de FilesListView, on ajoute :
ListViewItemSorter =
columnSorter;
// on écoute l'événement 'clique sur une tête de colonne' :
ColumnClick += new ColumnClickEventHandler(OnColumnClick);
La deuxième ligne correspond à l’abonnement à l’événement
ColumnClick. Dans la méthode OnColumnClick, nous allons appeler la
méthode Sort() de ListView qui elle-même triera la liste
en utilisant notre méthode columnSorter.Compare.
A nouveau, les codes des contrôles OpenFolderDialog et FilesListView
sont téléchargeables ici. La dll correspondante
est téléchargeable ici.
Le moteur de recherche complet
Maintenant que tous nos contrôles sont prêts et que notre
moteur de recherche est réalisé (voir article
3), nous pouvons achever notre application de recherche de notes. Le code
de l’application est très simple et se passe de longs commentaires. Il est
disponible ici. L’application
exécutable complète est ici. Voici un
aperçu de l’interface :

Conclusion
Nous avons réalisé une application permettant de
rechercher un document Office ou Windows Journal. Les critères de recherche
incluent des expressions à chercher dans le contenu du fichier, en tenant
compte de son format. Le résultat est une liste de fichiers notés en fonction
de la pertinence de leur contenu.
La réalisation de cette application nous a amené à
concevoir 3 dll :
- une
dll qui constitue le moteur de recherche, implémentant IFilter pour la recherche du contenu dans un format propriétaire (voir troisième article).
- une dll contenant 2 contrôles dédiés au TabletPC.
- une dll offrant un contrôle permettant la sélection
d’un dossier et d’un autre contrôle dans lequel on peut afficher une liste de
fichiers à la manière de l’Explorateur Windows.
Les prochains articles porteront sur un nouveau projet
également dédié aux TabletPC : le développement d’une barre d’outils
intégrée à Internet Explorer et présentant une interface très adaptée à une
utilisation du TabletPC avec un stylet.
|
|
 |
Pour afficher ou poster un commentaire, cliquez sur ce lien : Forum-Microsoft
|
|