TOC

This article is currently in the process of being translated into Spanish (~99% done).

Controles de texto enriquecido:

How-to: Creating a Rich Text Editor

Este es otro artículo de instrucciones, inspirado en lo genial que es el control RichTextBox y lo fácil que es crear un pequeño editor de texto enriquecido muy potente - piense en Windows Wordpad! Si bien WPF lo hace realmente fácil para nosotros, habrá un poco más de código XAML y C# de lo habitual, pero está bien. Primero iremos a través de estas interesantes secciones, y luego al final, te mostraré la lista completa de códigos.

En este artículo usaremos un montón de controles y técnicas que ya han sido utilizadas en otras partes del tutorial, así que no entraremos en demasiados detalles. En caso de que necesites repasar partes del mismo, siempre puedes retroceder para obtener descripciones más detalladas.

Para empezar, echemos un vistazo a lo que buscamos. Este debería ser el resultado final:

Interfaz

La interfaz consiste de un control ToolBar con botones y cuadros combinados en el. Hay botones para cargar y guardar un documento, botones para controlar varias propiedades de estilo y grosor de fuente, y dos cuadros combinados para controlar el tipo de fuente y tamaño.

Debajo del toolbar está el control RichTextBox, donde se realizará toda la edición.

Comandos

Lo primero que podrás notar es el uso de Comandos de WPF, que ya hemos discutido con anterioridad en este artículo. Usamos los comandos Open y Save de la clase ApplicationCommands para cargar y guardar el documento, y usamos los comandos ToggleBold, ToggleItalic, y ToggleUnderline de la clase EditingCommands para los botones relacionados con el estilo.

La ventaja de usar Comandos es una vez más obvio, ya que el control RichTextBox tiene implementados los comandos ToggleBold, ToggleItalic, y ToggleUnderline. Esto significa que no tendremos que escribir ningún código para que funcionen. Sólo enlazalos al botón designado y funcionará:

<ToggleButton Command="EditingCommands.ToggleBold" Name="btnBold">
    <Image Source="/WpfTutorialSamples;component/Images/text_bold.png" Width="16" Height="16" />
</ToggleButton>

También obtenemos atajos de teclado de forma gratuita: presione Ctrl + B para activar ToggleBold, Ctrl + I para activar ToggleItalic y Ctrl + U para activar ToggleUnderline.

Tenga en cuenta que estoy usando un ToggleButton en lugar de un control normal Button. Pretendo que el botón sea verificable, si la selección está actualmente en negrita, y eso es compatible con la propiedad IsChecked del ToggleButton. Desafortunadamente, WPF no tiene forma de manejar esta parte para nosotros, por lo que necesitamos un poco de código para actualizar los diversos estados de botones y cuadros combinados. Hablaremos sobre eso más tarde.

Los comandos Abrir y Guardar no se pueden manejar automáticamente, por lo que tendremos que hacerlo como de costumbre, con un CommandBinding para la ventana y luego un controlador de eventos en Código subyacente:

<Window.CommandBindings>
    <CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" />
    <CommandBinding Command="ApplicationCommands.Save" Executed="Save_Executed" />
</Window.CommandBindings>

Te mostraré la implementación más adelante en este artículo.

Familia de fuentes y tamaño

Para mostrar y cambiar la familia de fuentes y el tamaño, tenemos un par de cuadros combinados. Se rellenan con las familias de fuentes del sistema, así como con una selección de posibles tamaños en el constructor de la ventana, de esta manera:

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 };
}

Una vez más, WPF nos facilita obtener una lista de posibles fuentes, utilizando la propiedad SystemFontFamilies. Como la lista de tamaños es más una sugerencia, hacemos que el control ComboBox sea editable, para que el usuario pueda ingresar un tamaño personalizado:

<ComboBox Name="cmbFontFamily" Width="150" SelectionChanged="cmbFontFamily_SelectionChanged" />
<ComboBox Name="cmbFontSize" Width="50" IsEditable="True" TextBoxBase.TextChanged="cmbFontSize_TextChanged" />

Esto también significa que manejaremos los cambios de manera diferente. Para la familia de fuentes ComboBox, podemos manejar el evento SelectionChanged, mientras nos conectamos al evento TextBoxBase.TextChanged del tamaño ComboBox, para manejar el hecho de que el usuario puede seleccionar e ingresar manualmente un tamaño.

WPF maneja la implementación de los comandos de negrita, cursiva y subrayado para nosotros, pero para la familia de fuentes y el tamaño, tendremos que cambiar manualmente estos valores. Afortunadamente, es bastante fácil de hacer, utilizando el método ApplyPropertyValue (). Los controladores de eventos mencionados anteriormente se ven así.

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);
}

No hay demasiado adorno aquí: simplemente pasamos el valor seleccionado / ingresado al método ApplyPropertyValue (), junto con la propiedad que deseamos cambiar.

Actualizando el estado

Como se mencionó anteriormente, WPF maneja los comandos Negrita, Cursiva y Subrayado para nosotros, pero tenemos que actualizar manualmente el estado de sus botones, ya que esto no es parte de la funcionalidad de Comandos. Sin embargo, está bien, ya que también tenemos que actualizar los dos cuadros combinados para reflejar la familia y el tamaño de la fuente actual.

Queremos actualizar el estado tan pronto como el cursor se mueva y / o la selección cambie, y para eso, el evento SelectionChanged del RichTextBox es perfecto. Así es como lo manejamos:

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();
}

Esto supone unas pocas líneas, pero el trabajo real solo requiere un par de líneas, solo las repetimos con una pequeña variación para actualizar cada uno de los tres botones, así como los dos cuadros combinados.

El principio es bastante simple. Para los botones, utilizamos el método GetPropertyValue () para obtener el valor actual de una propiedad de texto dada, popiedad texto, por ejemplo FontWeight, y luego actualizamos la propiedad IsChecked dependiendo de si el valor devuelto es el mismo que estamos buscando o no.

Para los cuadros combinados, hacemos lo mismo, pero en lugar de establecer una propiedad IsChecked, establecemos las propiedades SelectedItem o Text directamente con los valores devueltos.

Cargando y guardando un archivo

Cuando manejamos los comandos Abrir y Guardar, usamos un código muy similar:

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);
	}
}

Se utiliza un OpenFileDialog o SaveFileDialog para especificar la ubicación y el nombre de archivo, y luego el texto se carga o guarda mediante un objeto TextRange, que obtenemos directamente del RichTextBox, en combinación con un FileStream, que proporciona el acceso al archivo físico . El archivo se carga y guarda en el formato RTF, pero puede especificar uno de los otros tipos de formato si desea que su editor admita otros formatos, por ejemplo texto sin formato.

El ejemplo completo

Aquí está el código para toda la aplicación: primero el XAML y luego el código C# subyacente:

<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);
		}
	}
}

Y aquí hay otra captura de pantalla donde hemos seleccionado texto. Observe cómo los controles de la barra de herramientas reflejan el estado de la selección actual:

Resumen

Como puede ver, implementar un editor de texto enriquecido en WPF es muy simple, especialmente debido al excelente control RichTextBox. Si lo desea, puede ampliar fácilmente este ejemplo con elementos como alineación de texto, colores, listas e incluso tablas.

Por favor tenga en cuenta que, si bien el ejemplo anterior debería funcionar bien, no hay absolutamente ninguna excepción en el manejo o verificación, para mantener la cantidad de código al mínimo. Hay varios lugares que podrían generar fácilmente una excepción, como el cuadro combinado de tamaño de fuente, donde uno podría causar una excepción al ingresar un valor no numérico. Por supuesto, debe verificar todo esto y manejar posibles excepciones si desea ampliar este ejemplo para su trabajo.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!