This article has been localized into French by the community.
Comment : Créer un éditeur de texte riche
Voici un autre article pratique, inspiré par les possibilités du contrôle RichTextBox et la facilité avec laquelle il est possible de créer un éditeur de texte riche, petit, mais très performant. Pensez à Windows Wordpad! Bien que WPF nous facilite la tâche, nous aurons un peu plus de code XAML et C # que d'habitude, mais ça ira. Nous allons parcourir les sections intéressantes une à une, puis à la fin, je vous montrerai le code complet.
Dans cet article, nous utiliserons de nombreux contrôles et techniques que nous avons déjà utilisés dans d'autres parties du tutoriel, de sorte que les explications ne seront pas trop détaillées. Si vous avez besoin de vous remémorer certaines parties, vous pouvez toujours revenir en arrière pour consulter les descriptions détaillées.
Pour commencer, voyons ce que nous allons faire. Ce devrait être le résultat final :
Interface
L'interface consiste en une "Barre d'outils" avec "Boutons" et "Listes déroulantes". Des boutons pour ouvrir et sauvegarder un document, d'autres pour gérer les styles d'écriture comme gras, italique, ... ainsi que 2 "Barre d'outils" pour la police et la taille des caractères.
En dessous de la "Barre d'outils" le contrôle "RichTextBox" ou toute les modifications seront effectuées
Commandes
Le premier point à noter est l'utilisations de "Commandes WPF" dont nous avons déjà parler dans un article précédent. Nous utiliserons les commandes Openet Savede la classe "ApplicationCommands" pour ouvrir et sauvegarder un document. Pour le style Gras, Italique et souligné nous utiliseront les commandes correspondantes de la class "EditingCommands".
L'avantage d'utiliser les commandes est encore une fois évident : le contrôle RichTextBox implémente déjà les commandes Gras, Italique et Souligné. Ce qui signifie qu'aucun code n'est requis pour les faire fonctionner en dehors de l'affectation des commandes au boutons correspondants.
<ToggleButton Command="EditingCommands.ToggleBold" Name="btnBold">
<Image Source="/WpfTutorialSamples;component/Images/text_bold.png" Width="16" Height="16" />
</ToggleButton>
Pour le même prix nous aurons également les raccourcis clavier - Ctrl+B pour Gras, Ctrl+I pour Italique et Ctrl+U pour Souligné.
A noter que j'utilise des contrôles ToggleButton à la place de Button.Je souhaite des boutons à bascule : si la sélection est en gras, alors la propriété IsChecked du ToggleButton devra représenter cet état. Malheureusement WPF n'a aucun moyen de gérer cela à notre place. Nous aurons besoin de code pour gérer les états des différents boutons et listes déroulantes. Les informations à ce sujet arriveront dans un second temps.
Les commandes Ouvrir (Open) et Sauvegarder (Save) peuvent être gérer automatiquement, nous devons donc le faire, comme habituellement, avec un CommandBinding sur le designer et un gestionnaire d’événement en Code-behind :
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" />
<CommandBinding Command="ApplicationCommands.Save" Executed="Save_Executed" />
</Window.CommandBindings>
Je vous montrerai l'implémentation du gestionnaire plus tard dans cet article
Famille et taille de police
Pour afficher et changer la police et la taille des caractères, nous utilisons un couple de liste déroulantes. Elles sont alimentées l'une par la liste des polices système et l'autre par une liste de taille de caractère possible, comme ceci :
public RichTextEditorSample()
{
InitializeComponent();
cmbFontFamily.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f => f.Source);
cmbFontSize.ItemsSource = new List<double>() { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
}
Encore une fois, WPF nous facilite la récupération de la liste des polices de caractères en utilisant la propriété SystemFontFamilies. En revanche pour la liste des tailles de caractères c'est plutôt une suggestion : Nous laissons la liste déroulante modifiable, du coup l'utilisateur peu saisir une taille personnaliser.
<ComboBox Name="cmbFontFamily" Width="150" SelectionChanged="cmbFontFamily_SelectionChanged" />
<ComboBox Name="cmbFontSize" Width="50" IsEditable="True" TextBoxBase.TextChanged="cmbFontSize_TextChanged" />
Cela veut aussi dire que nous gérons les modifications différemment. Pour la liste des polices nous nous abonnons simplement à l'événement SelectionChanged alors que nous gérons, dans l'événement TextBoxBase.TextChanged, le choix de l'utilisateur de sélectionner ou de saisir la taille des caractères.
WPF gère pour nous l'implémentation des commandes Gras, Italique et Souligné, mais pour les police et la taille des caractères nous devons coder les changements. Heureusement, ce n'est pas trop compliqué de le faire en utilisant la méthode ApplyPropertyValue(). Le gestionnaire d’événement ci-dessus ressemble à ceci :
private void cmbFontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(cmbFontFamily.SelectedItem != null)
rtbEditor.Selection.ApplyPropertyValue(Inline.FontFamilyProperty, cmbFontFamily.SelectedItem);
}
private void cmbFontSize_TextChanged(object sender, TextChangedEventArgs e)
{
rtbEditor.Selection.ApplyPropertyValue(Inline.FontSizeProperty, cmbFontSize.Text);
}
Rien d'extraordinaire ici : nous appliquons simplement la méthode ApplyPropertyValue() à la sélection saisie sur la propriété à changer.
Mettre à jour le statut
Comme mentionné précédemment, WPFgère, pour nous, les commandes Gras, Italique et Souligné. En revanche, nous devons gérer manuellement l'état des boutons, étant donné que cela ne fait pas partie des fonctionnalités des Commandes. Mais ce n'est pas gênant, car nous devons également gérer la mise à jour des liste déroulantes pour afficher les polices et taille de caractères de la sélection.
Nous voulons modifier l'état au moindre déplacement du curseur ainsi que du changement de sélection et pour cela le gestionnaire d"événement SelectionChanged du RichTextBox est parfait. Voici comment cela fonctionne :
private void rtbEditor_SelectionChanged(object sender, RoutedEventArgs e)
{
object temp = rtbEditor.Selection.GetPropertyValue(Inline.FontWeightProperty);
btnBold.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold));
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontStyleProperty);
btnItalic.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic));
temp = rtbEditor.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
btnUnderline.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(TextDecorations.Underline));
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontFamilyProperty);
cmbFontFamily.SelectedItem = temp;
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontSizeProperty);
cmbFontSize.Text = temp.ToString();
}
pas mal de lignes de codes, mais le travail réel n'en requière que quelques unes - nous les répétons ensuite pour gérer les modifications de chacun des 3 boutons tout comme celles des Listes déroulantes.
Le principe est plutôt simple. Pour chaque bouton on utilise la méthode GetPropertyValue() pour récupérer la valeur saisie de la propriété correspondante, ex texte en gras, et nous modifions ensuite la propriété IsChecked selon que la valeur retournée est la même que celle que nous recherchons ou non
Même chose pour les listes déroulantes, mais au lieu de modifier la propriété IsChecked, nous définisson les propriétés "SelectedItem" et"Text" directement avec les valeurs récupérées.
Charger et enregistrer un fichier
Pour gérer les commandes Ouvrir et Sauvegarder, le code utilisé est très proche :
private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
if(dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Open);
TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
range.Load(fileStream, DataFormats.Rtf);
}
}
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
SaveFileDialog dlg = new SaveFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
if(dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Create);
TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
range.Save(fileStream, DataFormats.Rtf);
}
}
Pour définir l'emplacement et le nom du fichier nous utilisons une OpenFileDialog ou une SaveFileDialog. Ensuite, le texte est chargé ou sauvegardé en utilisant un objet TextRange que nous obtenons à partir du RichTextBox combiné avec un FileStream qui donne directement accès au fichier physique. Le fichier est chargé et sauvegardé au format RTF mais vous pouvez choisir un autre format si vous souhaitez que votre éditeur prenne en charge d'autres formats comme par exemple le texte brut.
L'exemple complet
voici le code complet de l'application - en premier le XAML puis le Code-behind en C# :
<Window x:Class="WpfTutorialSamples.Rich_text_controls.RichTextEditorSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RichTextEditorSample" Height="300" Width="400">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" />
<CommandBinding Command="ApplicationCommands.Save" Executed="Save_Executed" />
</Window.CommandBindings>
<DockPanel>
<ToolBar DockPanel.Dock="Top">
<Button Command="ApplicationCommands.Open">
<Image Source="/WpfTutorialSamples;component/Images/folder.png" Width="16" Height="16" />
</Button>
<Button Command="ApplicationCommands.Save">
<Image Source="/WpfTutorialSamples;component/Images/disk.png" Width="16" Height="16" />
</Button>
<Separator />
<ToggleButton Command="EditingCommands.ToggleBold" Name="btnBold">
<Image Source="/WpfTutorialSamples;component/Images/text_bold.png" Width="16" Height="16" />
</ToggleButton>
<ToggleButton Command="EditingCommands.ToggleItalic" Name="btnItalic">
<Image Source="/WpfTutorialSamples;component/Images/text_italic.png" Width="16" Height="16" />
</ToggleButton>
<ToggleButton Command="EditingCommands.ToggleUnderline" Name="btnUnderline">
<Image Source="/WpfTutorialSamples;component/Images/text_underline.png" Width="16" Height="16" />
</ToggleButton>
<Separator />
<ComboBox Name="cmbFontFamily" Width="150" SelectionChanged="cmbFontFamily_SelectionChanged" />
<ComboBox Name="cmbFontSize" Width="50" IsEditable="True" TextBoxBase.TextChanged="cmbFontSize_TextChanged" />
</ToolBar>
<RichTextBox Name="rtbEditor" SelectionChanged="rtbEditor_SelectionChanged" />
</DockPanel>
</Window>
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.Win32;
using System.Windows.Controls;
namespace WpfTutorialSamples.Rich_text_controls
{
public partial class RichTextEditorSample : Window
{
public RichTextEditorSample()
{
InitializeComponent();
cmbFontFamily.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f => f.Source);
cmbFontSize.ItemsSource = new List<double>() { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
}
private void rtbEditor_SelectionChanged(object sender, RoutedEventArgs e)
{
object temp = rtbEditor.Selection.GetPropertyValue(Inline.FontWeightProperty);
btnBold.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold));
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontStyleProperty);
btnItalic.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic));
temp = rtbEditor.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
btnUnderline.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(TextDecorations.Underline));
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontFamilyProperty);
cmbFontFamily.SelectedItem = temp;
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontSizeProperty);
cmbFontSize.Text = temp.ToString();
}
private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
if(dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Open);
TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
range.Load(fileStream, DataFormats.Rtf);
}
}
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
SaveFileDialog dlg = new SaveFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
if(dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Create);
TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
range.Save(fileStream, DataFormats.Rtf);
}
}
private void cmbFontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(cmbFontFamily.SelectedItem != null)
rtbEditor.Selection.ApplyPropertyValue(Inline.FontFamilyProperty, cmbFontFamily.SelectedItem);
}
private void cmbFontSize_TextChanged(object sender, TextChangedEventArgs e)
{
rtbEditor.Selection.ApplyPropertyValue(Inline.FontSizeProperty, cmbFontSize.Text);
}
}
}
Et voici une autre capture d'écran ou nous avons du texte sélectionné. Regardez comme les contrôles de la barre d'outils reflète l'état de la sélection :
En résumé
Comme vous venez de le voir, implémenter un éditeur de texte "riche" en WPF est plutôt simple, surtout grâce à excellent contrôle RichTextBox. Si vous le souhaitez, vous pouvez facilement compléter cet exemple avec des fonctionnalités comme l'alignement de texte, les couleurs, les listes et même les tableaux.
Veuillez noter que bien que l'exemple ci-dessus devrait fonctionner correctement, aucun gestionnaire d'exception ni vérification n'est présent pour garder le code essentiel minimum. Plusieurs endroits peuvent facilement lancer des exceptions, comme la liste déroulante des tailles de caractères qui peut en générer une si l'on saisie une valeur non numérique. Vous devriez vérifier et gérer toutes ces exception si vous souhaitiez développer cette application dans le cadre de votre travail.