SUPINFO International University

SUPINFO Institute of Information Technology
Laboratoire Microsoft




Tous les Articles du Laboratoire Microsoft

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



 Tous les articles de cet auteur

2,7/5

Assez Bien


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.

 




En Savoir Plus 
Evaluez cet article 


Pour afficher ou poster un commentaire, cliquez sur ce lien : Forum-Microsoft



Retrouvez ci-dessous les autres sections du Laboratoire Microsoft