TOC

This article has been localized into German by the community.

Rich Text Steuerelemente:

Gewusst wie: Erstellen eines Rich-Text-Editors

Dies ist ein weiterer Gewusst-Wie-Artikel, inspiriert davon, wie cool das RichTextBox-Steuerelement ist und wie einfach es ist, einen kleinen, aber sehr leistungsfähigen Rich-Text-Editor zu erstellen - denken Sie an Windows Wordpad! WPF macht es uns zwar wirklich einfach, aber es wird etwas mehr XAML- und C#-Code als sonst geben, aber das ist OK. Wir gehen die interessanten Abschnitte nacheinander durch, und am Ende zeige ich Ihnen den gesamten Code..

In diesem Artikel werden wir viele Steuerelemente und Techniken verwenden, die wir in anderen Teilen des Tutorials verwendet haben, deshalb werden die Erklärungen nicht zu detailliert sein. Falls Sie Teile davon auffrischen möchten, können Sie jederzeit auf die ausführlichen Beschreibungen zurückgreifen.

Zu Beginn, lassen Sie uns ansehen, wo wir hin wollen. So soll das fertige Ergebnis aussehen:

Benutzeroberfläche

Das Interface besteht aus einem ToolBar-Control mit Schaltflächen und Comboboxen. Es gibt Schaltflächen zum Laden und Speichern eines Dokuments, Schaltflächen zum Steuern verschiedener Schriftdicke und Stileigenschaften sowie zwei Comboboxen zum Steuern der Schriftart und -größe.

Unter der Toolbar befindet sich das RichTextBox Steuerelement, in dem die Textbearbeitung stattfindet.

Commands

Das erste, was Ihnen vielleicht auffällt, ist die Verwendung von WPF-Commands, die wir bereits früher besprochen haben. Wir verwenden die Befehle Open (Öffnen) und Save (Speichern) aus der Klasse ApplicationCommands, um das Dokument zu laden und zu speichern, und wir verwenden die Befehle ToggleBold, ToggleItalic und ToggleUnderline aus der Klasse EditingCommands für unsere stilbezogenen Schaltflächen.

Der Vorteil der Verwendung von Commands liegt einmal mehr auf der Hand, da das RichTextBox-Control bereits die Befehle ToggleBold, ToggleItalic und ToggleUnderline implementiert. Das bedeutet, dass wir keinen Code schreiben müssen, damit sie funktionieren. Einfach an den dafür vorgesehenen Buton anschließen und es funktioniert:

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

Wir bekommen auch Tastenzuordnungen umsonst: Strg+B für Fett, Strg+I für Kursiv und Strg+U für Unterstrichen.

Beachten Sie, dass ich einen ToggleButton anstelle eines normalen Button-Controls verwende. Ich möchte, dass die Schaltfläche aktiviert dargestellt wird, wenn die Auswahl gerade fett ist, und das wird durch die IsChecked-Eigenschaft des ToggleButtons unterstützt. Leider hat WPF keine Möglichkeit, diesen Teil für uns zu handhaben, also brauchen wir ein wenig Code, um die verschiedenen Schaltflächen- und Combobox-Zustände zu aktualisieren. Dazu später mehr.

Die Befehle Öffnen und Speichern können nicht automatisch behandelt werden, also müssen wir das wie üblich mit einem CommandBinding für das Fenster und dann einem Ereignisbehandler im Code-behind machen:

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

Ich zeige die Implementierung später in diesem Artikel.

Schriftart und Schriftgröße

Um die Schriftart und -größe anzuzeigen und zu ändern, haben wir ein paar Comboboxen. Sie werden mit den Systemschriftarten sowie einer Auswahl möglicher Größen im Konstruktor für das Fenster bestückt:

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

Auch hier macht es uns WPF wieder leicht, eine Liste der möglichen Schriftarten zu erhalten, indem es die SystemFontFamilies-Eigenschaft verwendet. Da die Liste der Größen eher ein Vorschlag ist, machen wir dieses ComboBox-Steuerelement editierbar, so dass der Benutzer zusätzlich eine selbstdefinierte Größe eingeben kann:

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

Das bedeutet auch, dass wir Änderungen anders behandeln werden. Für die Schriftarten-ComboBox können wir nur das Ereignis SelectionChanged behandeln, während wir an das Ereignis TextBoxBase.TextChanged der ComboBox für die Größe anschließen, damit berücksichtigen wir, dass der User sowohl eine Größe aus der Liste auswählen als auch manuell eingeben kann.

WPF übernimmt für uns die Implementierung der Befehle Bold, Italic und Underline, aber für Schriftfamilie und -größe müssen wir diese Werte manuell ändern. Aber mit der Methode ApplyPropertyValue() geht das recht einfach. Die oben genannten Eventhandler sehen so aus:

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

Nichts Überraschendes hier - wir übergeben einfach den ausgewählten Wert an die ApplyPropertyValue() Methode, zusammen mit der Eigenschaft, die wir ändern möchten.

Aktualisieren des Status

Wie bereits erwähnt, behandelt WPF die Befehle Fett, Kursiv und Unterstrichen für uns, aber wir müssen den Status ihrer Schaltflächen manuell aktualisieren, da dies nicht Teil der Befehlsfunktionalität ist. Das macht aber nichts, da wir auch die beiden Comboboxen entsprechend der aktuellen Schriftfamilie und -größe aktualisieren müssen.

Wir wollen den Status aktualisieren, sobald sich der Cursor bewegt und/oder sich die Auswahl ändert, und dafür ist das SelectionChanged-Ereignis der RichTextBox perfekt. So gehen wir damit um:

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

Ziemlich viel Code, aber eigentlich sind es immer die gleichen paar Zeilen, die nur jeweils abgewandelt sind, um den jeweiligen Button und die Comboboxen zu aktualisieren.

Das Prinzip ist ganz einfach. Für die Schaltflächen verwenden wir die Methode GetPropertyValue(), um den aktuellen Wert für eine gegebene Texteigenschaft, z.B. FontWeight, abzurufen, und aktualisieren dann die Eigenschaft IsChecked, je nachdem, ob der zurückgegebene Wert mit dem gesuchten übereinstimmt oder nicht.

Für die Comboboxen machen wir dasselbe, aber anstatt eine IsChecked-Eigenschaft zu setzen, setzen wir die Eigenschaften SelectedItem oder Text direkt mit den zurückgegebenen Werten.

Laden und Speichern einer Datei

Zum Öffnen und Speichern benutzen wir sehr ähnlichen Code:

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

Ein OpenFileDialog oder SaveFileDialog wird verwendet, um den Speicherort und den Dateinamen anzugeben, und dann wird der Text entweder über ein TextRange-Objekt, das wir direkt aus der RichTextBox beziehen, in Kombination mit einem FileStream, der den Zugriff auf die physische Datei ermöglicht, geladen oder gespeichert. Die Datei wird im RTF-Format geladen und gespeichert, aber Sie können einen der anderen Formattypen angeben, wenn Ihr Editor andere Formate unterstützen soll, z.B. reinen Text.

Das vollständige Beispiel

Hier ist der Code für die gesamte Anwendung - zuerst das XAML und dann der 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);
		}
	}
}

Und hier ist ein weiterer Screenshot, wo wir einen Text ausgewählt haben. Beachten Sie, wie die Steuerelemente der Symbolleiste die Formatierung der aktuellen Auswahl wiedergeben:

Zusammenfassung

Wie Sie sehen können, ist die Implementierung eines Rich-Text-Editors in WPF sehr einfach, vor allem wegen des hervorragenden RichTextBox-Steuerelements. Wenn Sie möchten, können Sie dieses Beispiel einfach um Dinge wie Textausrichtung, Farben, Listen und sogar Tabellen erweitern.

Bitte beachten Sie, dass das obige Beispiel zwar gut funktioniert, es fehlt aber die Ausnahmebehandlung und jegliche Überprüfung, damit die Codemenge so gering wie möglich bleibt. Es gibt mehrere Stellen, an denen man leicht eine Ausnahme auslösen kann, wie z.B. die ComboBox Schriftgröße, wo man eine Ausnahme durch Eingabe eines nicht-numerischen Wertes auslösen kann. Sie sollten dies alles natürlich überprüfen und mögliche Ausnahmen behandeln, wenn Sie dieses Beispiel für Ihr eigenes Projekt benutzen möchten.


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!