TOC

This article has been localized into Czech by the community.

Ovládací prvek Rich Text:

Jak vytvořit editor umožňující formátování textu

Toto je zas další "How to" článek na téma, jak cool RichTextBox je a jak jednoduché je vytvořit jednoduchý, ale schopný editor textu - jako Windows Wordpad! A i když nám to WPF dost usnadní, přesto budeme muset použít trochu více XAMLu a C# kódu, než obvykle, ale to je v pořádku. Projdeme si zajímavými částmi krok po kroku a nakonec vám ukážu kompletní zdrojový kód.

V tomto článku budeme používat mnoho ovládacích prvků a technik, které jsme používali v jiných částech tutoriálu, takže vysvětlování nebude tak podrobné. V případě, že si budete potřebovat některé části připomenout, vždycky se můžete vrátit k podrobnějším popisům v předešlých článcích.

Pro začátek se podívejme, o co budeme usilovat. Toto by měl být konečný výsledek.

Rozhraní

Rozhraní se skládá z ToolBaru, na kterém jsou tlačítka a combo boxy. Jsou zde tlačítka pro načítání a ukládání dokumentu, tlačítka pro různé typy a styly fontu a pak dva combo boxy měnící font samotný a jeho velikost

Pod panelem nástrojů je umístěn ovládací prvek RichTextBox, kde budeme provádět veškeré editace textu.

Příkazy

První věc, které jste si mohli všimnout je použití WPF příkazů, které jsme již probírali v minulém článku. Pro nahrání a uložení dokumentu používáme příkazy Open a Save třídy ApplicationCommands a tlačítka upravující styly využívají příkazů ToggleBold, ToggleItalic a ToggleUnderline ze třídy EditingCommands.

Výhoda použití příkazů je opět zřejmá, protože ovládací prvek RichTextBox již implementuje příkazy ToggleBold, ToggleItalic a ToggleUnderline. To znamená, že nemusíme psát žádný kód, abyste přímo mohli pracovat. Stačí je připojit k určenému tlačítku a hned to funguje:

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

Zdarma ihned máme také k dispozici klávesové zkratky – stisknutím Ctrl+B aktivujete ToggleBold pro tučný text, Ctrl+ I a pomocí ToggleItalic pro kurzívu a pomocí Ctrl+U, aktivujeme příkaz ToggleUnderline pro podtržení texu.

Všimněte si, že používám tlačítko ToggleButton místo běžného ovládacího prvku Button. Chci, aby tlačítko kontrolovalo stav textu pomocí vlastnosti IsChecked toggleButton, a pokud je výběr aktuálně tučný změní jej na normální text a opačně. Bohužel, WPF to přímo neumí, takže potřebujeme přidat trochu kódu a funkci tlačítka aktualizovat a stejně to platí i pro stav combo boxu. Více o tom později.

Příkazy Open a Save nelze zpracovat automaticky, takže to budeme muset jako obvykle zpracovat, s CommandBinding pro dané okno a potom napsat obslužnou rutinou události v Code-Behind:

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

Ukážu vám implementaci v tomto článku později.

Rodina a velikost písma

Chcete-li zobrazit a změnit rodinu písma a jeho velikost, máme tady pár combo boxů. Jsou v konstruktoru daného okna naplněny rodinami systémových písem, a možností vybrat jejich velikost, jak uvedeno zde:

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

Opět, WPF nám usnadňuje získat seznam možných písem, pomocí vlastnosti SystemFontFamilies. Vzhledem k tomu, že seznam velikostí je spíše jen návrh, vytváříme ovládací prvek ComboBox editovatelný, takže si uživatel může zadat také svou vlastní velikost:

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

To ale také znamená, že změny v ComboBoxech budeme zpracovávat odlišně. V případě ComboBoxu rodiny písem stačí zpracovat pouze událost SelectionChanged, zatímco v ComboBoxu volby velikosti písma máme drobný háček a musíme zpracovat událost TextBoxBase.TextChanged tak, aby ošetřil skutečnost, že uživatel může vybrat a ručně zadat vlastní velikost.

WPF pro nás automaticky zpracovává implementaci příkazů Bold, Kurzíva a Podtržení, ale pro rodinu písma a jeho velikost, budeme muset tyto hodnoty změnit ručně. Naštěstí je to docela snadné a to pomocí metody ApplyPropertyValue(). Výše uvedené obslužné rutiny událostí pak vypadají takto.

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

Tady nepotřebujeme příliš mnoho fantazie – jen zde prostě předáváme vybrané / zadané hodnoty metodě ApplyPropertyValue() spolu s vlastností, kterou chceme změnit.

Aktualizace stavu

Jak již bylo zmíněno, WPF automaticky zpracovává příkazy Bold, Italic and Underline, ale musíme ručně aktualizovat stav jejich tlačítek, protože to není součástí příkazu funkce. To je však v pořádku, protože také musíme aktualizovat dvě pole se seznamem tak, aby odrážely aktuální rodinu a velikost písma.

Stav chceme aktualizovat v okamžiku, jakmile se kurzor přesune a/nebo se změní výběr. K tomu se výtečně hodí událost SelectionChanged ovládacího prvku RichTextBox. Zde je návod, jak to provést:

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

Je to docela dost řádků, ale skutečná práce vyžaduje pouze pár řádků - jen je opakujeme s malými úpravami pro aktualizaci každého ze tří tlačítek, a také dvou combo boxů.

Princip je poměrně jednoduchý. Pro tlačítka používáme Metodu GetPropertyValue() abychom získali aktuální hodnoty pro danou vlastnost textu, například FontWeight, a pak aktualizujeme vlastnost IsChecked v závislosti na tom, zda je vrácená hodnota stejná jako ta, kterou hledáme nebo ne.

U combo boxu děláme totéž, ale místo nastavení vlastnosti IsChecked, jsme nastavili vlastnost SelectedItem nebo Text a to přímo s vrácenými hodnotami.

Načítání a ukládání souboru

Při zpracování příkazů Otevřít a Uložit používáme velmi podobný kód:

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

OpenFileDialog nebo SaveFileDialog se používá k určení umístění a názvu souboru a pak je text načten nebo uložen pomocí objektu TextRange, který získáme přímo z RichTextuBoxu v kombinaci s FileStream, který poskytuje přístup k objektu fyzického souboru. Soubor je načten a uložen ve formátu RTF, ale můžete zvolit libovolný z dalších typů formátů, pokud chcete, aby váš editor tento podporoval, např. prostý text.

Kompletní příklad

Zde je kód pro celou aplikaci – nejprve v XAML a pak v C# Code-behind:

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

A tady je další snímek obrazovky, kde jsme vybrali nějaký text. Všimněte si, jak ovládací prvky panelu nástrojů odráží stav aktuálního výběru:

Shrnutí

Jak můžete vidět, implementace editoru formátovaného textu je ve WPF velmi jednoduchá, zejména díky vynikajícímu ovládacímu prvku RichTextBox. Pokud chcete, můžete tento příklad snadno rozšířit o další funkčnosti, jako je zarovnání textu, barvy, seznamy nebo tabulky.

Mějte prosím na paměti, že zatímco výše uvedený příklady by měly fungovat v pohodě, neobsahují absolutně žádné zpracování výjimek ani vůbec žádnou jinou kontrolu z důvodu, aby ukázkové množství kódu bylo co nejmenší. Existuje zde však několik míst, která by mohla snadno výjimku vyvolat, jako je například ComboBox velikosti písma, kde by výjimku mohlo způsobit zadáním nečíselné hodnoty. Pokud byste tedy chtěli tento kód případně rozšířit pro vlastní využití, měli byste samozřejmě vše důkladně zkontrolovat a vznik možných výjimek ošetřit.


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!