This article is currently in the process of being translated into Italian (~99% done).
How-to: Creating a Rich Text Editor
Questo è un altro articolo, sul controllo RichTextBox e da quanto sia facile creare un editor di testo semplice ma completo: pensate a Windows Wordpad! Mentre WPF ci rende tutto molto semplice. Esamineremo le sezioni interessanti una alla volta, quindi alla fine ci sarà il codice completo.
In questo articolo, utilizzeremo molti controlli e tecniche che abbiamo usato in altre parti del tutorial, quindi le spiegazioni non saranno troppo dettagliate. Nel caso in cui sia necessario rinfrescarsi su parti di esso, è sempre possibile tornare indietro per le descrizioni dettagliate.
Per cominciare, diamo un'occhiata a ciò che stiamo cercando di fare. Questo dovrebbe essere il risultato finale:
Interfaccia
L'interfaccia è costituita da un controllo ToolBar con pulsanti e caselle combinate. Sono disponibili pulsanti per il caricamento e il salvataggio di un documento, pulsanti per il controllo del font e delle proprietà dello stile e due caselle combinate per il controllo della famiglia e delle dimensioni del font.
Sotto la barra degli strumenti si trova il controllo RichTextBox, dove verranno eseguite tutte le modifiche.
I Comandi
La prima cosa che potresti notare è l'uso dei comandi WPF, di cui abbiamo già discusso in precedenza in questo articolo. Usiamo i comandi Apri e Salva della classe ApplicationCommands per caricare e salvare il documento e usiamo ToggleBold, ToggleItalic e ToggleUnderline comandi dalla classe EditingCommands per i nostri pulsanti relativi allo stile.
Il vantaggio dell'uso dei comandi è ancora una volta evidente, poiché il controllo RichTextBox implementa già i comandi ToggleBold, ToggleItalic e ToggleUnderline. Ciò significa che non dobbiamo scrivere alcun codice per farli funzionare. Basta collegarli al pulsante designato e funziona:
<ToggleButton Command="EditingCommands.ToggleBold" Name="btnBold">
<Image Source="/WpfTutorialSamples;component/Images/text_bold.png" Width="16" Height="16" />
</ToggleButton>
Abbiamo anche scorciatoie da tastiera: premi Ctrl + B per attivare ToggleBold, Ctrl + I per attivare ToggleItalic e Ctrl + U per attivare ToggleUnderline.
Nota che sto usando un ToggleButton invece di un normale controllo Pulsante . Voglio che il pulsante sia selezionabile, se la selezione è attualmente in grassetto ed è supportata dalla proprietà IsChecked di ToggleButton. Sfortunatamente, WPF non gestisce questa parte, quindi abbiamo bisogno di un po' di codice per aggiornare i vari stati dei pulsanti e delle caselle combinate. Ne parleremo più avanti.
I comandi Apri e Salva non possono essere gestiti automaticamente, quindi dovremo farlo come al solito, con un CommandBinding per la finestra e quindi un gestore di eventi in Code-behind:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" />
<CommandBinding Command="ApplicationCommands.Save" Executed="Save_Executed" />
</Window.CommandBindings>
Ti mostrerò l'implementazione più avanti in questo articolo.
Carattere e dimensione del testo
Per mostrare e modificare la famiglia e le dimensioni del carattere, abbiamo un paio di caselle combinate. Sono popolati con le famiglie di caratteri di sistema e con una selezione di possibili dimensioni nel costruttore per la finestra, in questo modo:
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 };
}
Per mostrare e modificare la famiglia e le dimensioni dei caratteri, ancora una volta WPF ci consente di ottenere un elenco di caratteri possibili utilizzando la proprietà SystemFontFamilies. Poiché l'elenco delle dimensioni è più un suggerimento, rendiamo modificabile il controllo ComboBox, in modo che l'utente possa inserire una dimensione personalizzata: abbiamo un paio di caselle combinate. Sono popolati con le famiglie di caratteri di sistema e con una selezione di possibili dimensioni nel costruttore per la finestra, in questo modo:
<ComboBox Name="cmbFontFamily" Width="150" SelectionChanged="cmbFontFamily_SelectionChanged" />
<ComboBox Name="cmbFontSize" Width="50" IsEditable="True" TextBoxBase.TextChanged="cmbFontSize_TextChanged" />
Questo significa anche che gestiremo le modifiche in modo diverso. Per la famiglia di caratteri ComboBox, possiamo semplicemente gestire l'evento SelectionChanged del TextBoxBase. Per gestire il fatto che l'utente può sia selezionare che inserire manualmente una dimensione si utilizza l'evento TextChanged del ComboBox.
WPF gestisce l'implementazione dei comandi Bold, Italic and Underline per noi, ma per la famiglia di caratteri e le dimensioni, dovremo modificare manualmente questi valori. Fortunatamente, è abbastanza facile da fare, usando il metodo ApplyPropertyValue().
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);
}
Qui non c'è niente di troppo elegante: passiamo semplicemente il valore selezionato / inserito al metodo ApplyPropertyValue (), insieme alla proprietà che desideriamo modificare.
Aggiornare lo stato
Come accennato in precedenza, WPF gestisce i comandi Bold, Italic and Underline per noi, ma dobbiamo aggiornare manualmente lo stato dei loro pulsanti, poiché non fa parte della funzionalità. Tuttavia, dal momento che dobbiamo anche aggiornare le due caselle combinate per riflettere la famiglia e le dimensioni del carattere corrente, questo ci va bene.
Vogliamo aggiornare lo stato non appena il cursore si sposta e / o la selezione cambia, e per questo, l'evento SelectionChanged del RichTextBox è perfetto. Ecco come lo gestiamo:
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();
}
Il lavoro effettivo richiede solo un paio di righe: le ripetiamo semplicemente con una piccola variazione per aggiornare ciascuno dei tre pulsanti e le due caselle combinate.
Il principio è abbastanza semplice. Per i pulsanti, utilizziamo il metodo GetPropertyValue () per ottenere il valore corrente per una determinata proprietà di testo, ad es. FontWeight e quindi aggiorniamo la proprietà IsChecked a seconda che il valore restituito sia uguale a quello che stiamo cercando o meno.
Per le caselle combinate, facciamo la stessa cosa, ma invece di impostare una proprietà IsChecked, impostiamo le proprietà SelectedItem o Text direttamente con i valori restituiti.
Aprire e salvare un file
Quando gestiamo i comandi Apri e Salva, utilizziamo un codice molto simile:
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);
}
}
Un OpenFileDialog o SaveFileDialog viene utilizzato per specificare la posizione e il nome del file, quindi il testo viene caricato o salvato utilizzando un oggetto TextRange, che otteniamo direttamente da RichTextBox, in combinazione con un FileStream, che fornisce l'accesso al file fisico . Il file viene caricato e salvato nel formato RTF, ma puoi specificare uno degli altri tipi di formato se desideri che il tuo editor supporti altri formati, ad es. testo semplice.
L'esempio completo
Qui appare l'intero codice dell'applicazione - iniziando dallo XAML, per poi presentare il codice in 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);
}
}
}
Ed ecco un altro screenshot in cui abbiamo selezionato del testo. Nota come i controlli della barra degli strumenti riflettono lo stato della selezione corrente:
Riassunto
Come puoi vedere, implementare un editor di testi complessi in WPF è molto semplice, soprattutto grazie all'eccellente controllo RichTextBox. Se lo desideri, puoi facilmente estendere questo esempio con elementi come allineamento del testo, colori, elenchi e persino tabelle.
Come puoi vedere, implementare un editor di testo RTF in WPF è molto semplice, soprattutto perché tieni presente che mentre l'esempio sopra dovrebbe funzionare perfettamente, non c'è assolutamente alcuna eccezione nella gestione o nel controllo, per mantenere la quantità di codice da un minimo. Esistono diversi punti che potrebbero facilmente generare un'eccezione, come la casella combinata della dimensione del carattere, in cui si potrebbe causare un'eccezione immettendo un valore non numerico. Ovviamente dovresti controllare tutto questo e gestire possibili eccezioni se desideri espandere questo esempio per il tuo controllo RichTextBox work.excellent. Se lo desideri, puoi facilmente estendere questo esempio con elementi come allineamento del testo, colori, elenchi e persino tabelle.