I. Introduction▲
Bien que nous allions aborder le sujet, il ne s'agit pas ici d'apprendre à créer un contrôle utilisateur, mais bien d'apprendre à manipuler du texte Rtf.
Si vous n'avez jamais créé de contrôle utilisateur, je vous engage à lire les articles sur le sujet. Dans cet exercice, j'ai choisi d'utiliser SharpDevelop. Si vous avez déjà créé des contrôles utilisateurs ou si vous avez lu les articles concernant le sujet, il vous sera facile de transposer la procédure à visual studio ou C# builder.
Dotnet met à notre disposition un contrôle graphique permettant d'enregistrer du texte au format Rtf. Il s'agit de la classe RichTextBox. Par l'intermédiaire de cette classe, il met également à notre disposition les méthodes permettant la mise en forme du texte.
Toutefois, ces méthodes ne sont pas directement accessibles à l'utilisateur de l'application, mais doivent être gérées par le programme.
Une solution est de prévoir dans le menu les fonctions spécifiques. Cette partie du menu étant activée ou désactivée en fonction du contrôle actif. Une autre solution consiste à associer un menu contextuel à votre contrôle Rtf qui prendra en charge les différentes méthodes dont vous avez besoin. Il est possible et même conseillé de mettre les deux solutions en œuvre simultanément.
Nous allons ici mettre en œuvre une tout autre technique, pas forcément meilleure, mais plus originale. Nous allons créer notre propre contrôle Rtf qui inclura une barre d'outils pour la manipulation du texte.
Notre contrôle prendra en charge la mise en gras, le soulignement, la mise en italique, le barré, ainsi que la modification de la police ce qui inclus le type (Arial, Courrier…), la taille et la couleur. Nous ajouterons l'alignement à gauche, à droite ou centré. Nous prévoirons également l'indentation gauche, droite et les listes à puces.
II. Création du contrôle utilisateur.▲
Dans SharpDevelop (la version utilisée est la 1.0.1), choisissez : Fichier>Nouveau>Fichier et dans la catégorie C# choisissez Contrôle utilisateur. Cliquez sur Créer. Le code de base est généré :
public
class
UserControl1 :
System.
Windows.
Forms.
UserControl
{
public
UserControl1
(
)
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent
(
);
//
// TODO: Add constructor code after the InitializeComponent() call.
//
}
#region Windows Forms Designer generated code
private
void
InitializeComponent
(
)
{
//
// UserControl1
//
this
.
Name =
"UserControl1"
;
this
.
Size =
new
System.
Drawing.
Size
(
292
,
266
);
}
#endregion
}
Nous pouvons maintenant nommer la classe. Remplacez UserControl1 par AdvancedRichTextBox. Enregistrez le fichier ainsi créé et nommez le AdvancedRichTextBox.
Une petite subtilité de SharpDevelop, si au lieu de créer votre fichier via le menu, vous avez choisi le menu contextuel, vous devez introduire le nom du fichier directement dans la fenêtre de choix du type de code à générer.
Passez en mode Design. Ensuite, depuis la barre des outils (Windows Forms), faites glisser l'icône ToolBar sur la fenêtre générée. Fixez les propriétés ButtonSize à 16/16, cursor à Hand et name à tlbCtrlRtf.
Les valeurs choisies pour les propriétés peuvent évidemment être modifiées selon vos goûts. Mais attention, la taille des boutons dépend évidemment de la taille des images que vous allez inclure.
Nous pouvons maintenant faire glisser l'objet RichTextBox. Nous le nommerons txtCtrlRtf. Agrandissez le contrôle pour qu'il occupe le reste de l'espace disponible. N'oubliez pas de supprimer le contenu de la propriété Text. Vous devriez avoir ceci :
Nous allons avoir besoin d'une ImageList. Faites glisser l'icône sur la fenêtre. Elle se place en bas à gauche. Je l'ai appelée ImgLstCtrlRtf.
Accédez à la collection par la propriété Images. Ajoutez les images que nous allons utiliser. Vous pouvez les trouver en téléchargement au bout de cet article.
Via la propriété Buttons de notre ToolBar, ajoutez un bouton pour chaque fonction souhaitée.
Votre contrôle ressemble maintenant à ceci :
On ajoute quelques informations devant la classe pour le RAD.
[ToolboxBitmap(
typeof
(AdvancedRichTextBox),
""
),Description(
"Interface utilisateur évolué pour un RichTextBox"
)]
public
class
AdvancedRichTextBox :
System.
Windows.
Forms.
UserControl
{
...
}
Il nous reste à rendre accessibles les propriétés de la classe.
[Description(
"RichTextBox associée au contrôle"
),Category(
"Composants"
)]
public
RichTextBox RichTextBox {
get
{
return
txtCtrlRtf ;
}
}
[Description(
"ToolBar associée au contrôle"
),Category(
"Composants"
)]
public
ToolBar ToolBar {
get
{
return
tlbCtrlRtf ;
}
}
[Description] permet d'ajouter des informations pour le RAD.
Pour qu'il soit possible d'adapter la taille du composant, nous devons encore ajouter une méthode qui sera associée à l'événement Paint de notre contrôle utilisateur.
void
CtrlRtfPaint
(
object
sender,
System.
Windows.
Forms.
PaintEventArgs e) {
this
.
txtCtrlRtf.
Size =
new
Size
(
this
.
Width,
this
.
Height-
24
);
}
L'objet RichTextox est redimensionné en fonction de la taille du composant. La toolbar ne demande pas d'adaptation.
Nous allons maintenant pouvoir passer aux choses sérieuses.
III. Associer les fonctions à la ToolBar▲
III-A. L'appel d'une fonctionnalité▲
Pour gérer l'appel des fonctionnalités depuis la toolbar, nous devons associer l'événement ButtonClick à la méthode suivante :
void
ToolBarButtonClick
(
object
sender,
System.
Windows.
Forms.
ToolBarButtonClickEventArgs e){
// on détermine la position du bouton activé.
int
indexButton =
this
.
tlbCtrlRtf.
Buttons.
IndexOf
(
e.
Button);
switch
(
indexButton){
case
0
:
break
;
case
1
:
break
;
case
2
:
break
;
case
3
:
break
;
case
4
:
break
;
case
6
:
break
;
case
7
:
break
;
case
8
:
break
;
case
10
:
break
;
case
11
:
break
;
case
12
:
break
;
}
}
C'est un switch qui permet d'orienter le programme vers la bonne action. Remarquez qu'il n'y a pas de traitement pour les valeurs 5 et 9. Dans l'exemple elles correspondent à des espacements dans la toolbar.
III-B. La mise en gras▲
Le bouton pour la mise en gras est le premier. Nous allons donc changer le code pour la valeur 0 dans notre switch.
case
0
:
this
.
txtCtrlRtf.
SelectionFont =
new
Font
(
this
.
txtCtrlRtf.
SelectionFont
,
this
.
txtCtrlRtf.
SelectionFont.
Style +
FontStyle.
Bold);
break
;
Nous avons pu mettre le texte en gras, mais notre code ne peut être conservé ainsi, car le même bouton sera utilisé pour effectuer l'opération inverse. Nous verrons dans le chapitre suivant comment réaliser cela.
III-C. La mise en italique, le soulignement et le surlignement▲
Pour permettre d'appliquer correctement le style souhaité en fonction du style déjà défini, nous devons connaître l'état actuel. J'ai choisi d'appliquer les styles en une seule opération.
void
ToolBarButtonClick
(
object
sender,
System.
Windows.
Forms.
ToolBarButtonClickEventArgs e){
// on détermine la position du bouton activé.
int
indexButton =
this
.
tlbCtrlRtf.
Buttons.
IndexOf
(
e.
Button);
// on récupère les styles utilisés dans le texte sélectionné.
bool
bold =
this
.
txtCtrlRtf.
SelectionFont.
Bold;
bool
italic =
this
.
txtCtrlRtf.
SelectionFont.
Italic;
bool
underline =
this
.
txtCtrlRtf.
SelectionFont.
Underline;
bool
strikeout =
this
.
txtCtrlRtf.
SelectionFont.
Strikeout;
int
style =
0
;
if
(
indexButton <
4
){
// on inverse le style spécifique
switch
(
indexButton){
case
0
:
bold =
!
bold;
break
;
case
1
:
italic =
!
italic;
break
;
case
2
:
underline =
!
underline;
break
;
case
3
:
strikeout =
!
strikeout;
break
;
}
// on crée un nouveau FontStyle avec les styles activés
if
(
bold)
style +=
(
int
)FontStyle.
Bold;
if
(
italic)
style +=
(
int
)FontStyle.
Italic;
if
(
underline)
style +=
(
int
)FontStyle.
Underline;
if
(
strikeout)
style +=
(
int
)FontStyle.
Strikeout;
// on applique le nouveau FontStyle.
this
.
txtCtrlRtf.
SelectionFont =
new
Font
(
this
.
txtCtrlRtf.
SelectionFont
,(
FontStyle)style);
}
else
{
...
}
}
Remarquez que les propriétés Bold,Underline,etc. de SelectionFont sont en lecture uniquement et ne peuvent être assignées. Ce qui nous oblige à assigner un nouveau font. Pour ne pas perdre les autres caractéristiques du font, nous utilisons un constructeur qui reçoit le font actuel et le nouveau FontStyle.
Il nous reste encore un problème à résoudre. Notre procédure permet d'activer un style pour le texte qui suit le point d'insertion ou le texte sélectionné, mais le bouton devrait afficher l'état du style en fonction de la position du curseur. Pour y arriver, nous allons utiliser l'événement SelectionChanged de notre RichTextBox et y associer la méthode suivante.
void
TxtCtrlRtfSelectionChanged
(
object
sender,
System.
EventArgs e) {
this
.
tlbButtonBold.
Pushed =
this
.
txtCtrlRtf.
SelectionFont.
Bold;
this
.
tlbButtonItalic.
Pushed =
this
.
txtCtrlRtf.
SelectionFont.
Italic;
this
.
tlbButtonUnderline.
Pushed =
this
.
txtCtrlRtf.
SelectionFont.
Underline;
this
.
tlbButtonStrikeout.
Pushed =
this
.
txtCtrlRtf.
SelectionFont.
Strikeout;
}
L'état du bouton s'adapte maintenant en fonction du déplacement du curseur.
III-D. Changer de police ou de couleur.▲
Notre code ressemble maintenant à ceci :
void
ToolBarButtonClick
(
object
sender,
System.
Windows.
Forms.
ToolBarButtonClickEventArgs e){
// on détermine la position du bouton activé.
int
indexButton =
this
.
tlbCtrlRtf.
Buttons.
IndexOf
(
e.
Button);
// on récupère les styles utilisés dans le texte sélectionné.
bool
bold =
this
.
txtCtrlRtf.
SelectionFont.
Bold;
bool
italic =
this
.
txtCtrlRtf.
SelectionFont.
Italic;
bool
underline =
this
.
txtCtrlRtf.
SelectionFont.
Underline;
bool
strikeout =
this
.
txtCtrlRtf.
SelectionFont.
Strikeout;
int
style =
0
;
if
(
indexButton <
4
){
...
}
else
{
switch
(
indexButton){
case
4
:
break
;
case
6
:
break
;
case
7
:
break
;
case
8
:
break
;
case
10
:
break
;
case
11
:
break
;
case
12
:
break
;
}
}
}
Pour changer de font ou de couleur, le plus simple est de faire appel à la fenêtre de dialogue prédéfinie, FontDialog.
case
4
:
FontDialog fontDlg =
new
FontDialog
(
);
// on initialise le font en cours comme valeur par défaut de la fenêtre
fontDlg.
Font =
this
.
txtCtrlRtf.
SelectionFont;
// on demande la possibilité de changer les couleurs.
fontDlg.
ShowColor =
true
;
if
(
fontDlg.
ShowDialog
(
) ==
DialogResult.
OK){
// on applique les changements.
this
.
txtCtrlRtf.
SelectionFont =
fontDlg.
Font;
this
.
txtCtrlRtf.
SelectionColor =
fontDlg.
Color;
}
fontDlg.
Dispose
(
);
break
;
Remarquez qu'ici nous pouvons assigner directement le font sans aucun souci, car dans la fenêtre FontDialog, tout est pris en charge y compris le gras et les autres styles.
III-E. Modifier l'indentation.▲
Il n'y a pas grand-chose à dire ici, il ne s'agit que d'utiliser la bonne propriété. Remarquez toutefois que l'on teste si l'alignement est bien à gauche, car on ne peut indenter un texte centré ou aligné à droite.
case
6
:
if
(
this
.
txtCtrlRtf.
SelectionIndent >=
36
&&
this
.
txtCtrlRtf.
SelectionAlignment ==
HorizontalAlignment.
Left){
this
.
txtCtrlRtf.
SelectionIndent -=
36
;
}
break
;
case
7
:
if
(
this
.
txtCtrlRtf.
SelectionAlignment ==
HorizontalAlignment.
Left){
this
.
txtCtrlRtf.
SelectionIndent +=
36
;
}
break
;
III-F. Liste à puces▲
Encore plus simple. On inverse simplement la propriété.
case
8
:
this
.
txtCtrlRtf.
SelectionBullet =
!
this
.
txtCtrlRtf.
SelectionBullet ;
break
;
III-G. Modification de l'alignement▲
Ici encore pas grand-chose à dire.
case
10
:
this
.
txtCtrlRtf.
SelectionAlignment =
HorizontalAlignment.
Left;
break
;
case
11
:
this
.
txtCtrlRtf.
SelectionAlignment =
HorizontalAlignment.
Center;
break
;
case
12
:
this
.
txtCtrlRtf.
SelectionAlignment =
HorizontalAlignment.
Right;
break
;
Pour pimenter un peu, nous allons griser les boutons d'indentation et les rendre inactifs quand l'alignement n'est pas à droite.
case
10
:
this
.
txtCtrlRtf.
SelectionAlignment =
HorizontalAlignment.
Left;
this
.
tlbButtonIndentLeft.
ImageIndex =
9
;
this
.
tlbButtonIndentRight.
ImageIndex =
11
;
this
.
tlbButtonCenter.
Pushed =
false
;
this
.
tlbButtonRight.
Pushed =
false
;
break
;
case
11
:
this
.
txtCtrlRtf.
SelectionAlignment =
HorizontalAlignment.
Center;
this
.
tlbButtonIndentLeft.
ImageIndex =
10
;
this
.
tlbButtonIndentRight.
ImageIndex =
12
;
this
.
tlbButtonLeft.
Pushed =
false
;
this
.
tlbButtonRight.
Pushed =
false
;
break
;
case
12
:
this
.
txtCtrlRtf.
SelectionAlignment =
HorizontalAlignment.
Right;
this
.
tlbButtonIndentLeft.
ImageIndex =
10
;
this
.
tlbButtonIndentRight.
ImageIndex =
12
;
this
.
tlbButtonLeft.
Pushed =
false
;
this
.
tlbButtonCenter.
Pushed =
false
;
break
;
Pour griser le bouton, nous utilisons en fait une autre image.
Il ne sous reste plus qu'à actualiser l'état des boutons relatifs à l'alignement en fonction du texte. Il nous suffit pour cela d'ajouter trois lignes à notre précédente méthode.
void
TxtCtrlRtfSelectionChanged
(
object
sender,
System.
EventArgs e) {
this
.
tlbButtonBold.
Pushed =
this
.
txtCtrlRtf.
SelectionFont.
Bold;
this
.
tlbButtonItalic.
Pushed =
this
.
txtCtrlRtf.
SelectionFont.
Italic;
this
.
tlbButtonUnderline.
Pushed =
this
.
txtCtrlRtf.
SelectionFont.
Underline;
this
.
tlbButtonStrikeout.
Pushed =
this
.
txtCtrlRtf.
SelectionFont.
Strikeout;
this
.
tlbButtonBullet.
Pushed =
this
.
txtCtrlRtf.
SelectionBullet;
this
.
tlbButtonLeft.
Pushed =
(
this
.
txtCtrlRtf.
SelectionAlignment ==
HorizontalAlignment.
Left);
this
.
tlbButtonCenter.
Pushed =
(
this
.
txtCtrlRtf.
SelectionAlignment ==
HorizontalAlignment.
Center);
this
.
tlbButtonRight.
Pushed =
(
this
.
txtCtrlRtf.
SelectionAlignment ==
HorizontalAlignment.
Right);
}
IV. Conclusion▲
La manipulation du Rtf au travers d'une RichTextBox ne contient en définitive pas de piège particulier et il suffit de peu de connaissances pour la mettre en œuvre. Dans le code source complet de l'exercice, vous trouverez également l'ajout d'un menu contextuel permettant le copier, coller et le défaire, refaire. Cette partie n'est pas expliquée ici simplement parce qu'il n'y a vraiment rien à dire.