21.8 FlowDocuments
21.8.1 Allgemeine Beschreibung eines FlowDocuments
Mit den Steuerelementen Label und TextBlock haben Sie zwei Steuerelemente kennengelernt, die zur Laufzeit keine Textänderungen zulassen. Mit einer TextBox-Komponente ermöglichen Sie dem Anwender, eigene Einträge vorzunehmen. Der eingegebene Text ist allerdings unformatiert, das bedeutet, in einer TextBox kann der Inhalt nur in einer Schriftart dargestellt werden. Beispielsweise ist er entweder komplett fett oder kursiv und lässt auch keine unterschiedliche Farbwahl innerhalb des Elements zu. Verglichen mit den Möglichkeiten, die selbst HTML bietet, ist das geradezu erbärmlich, genügt aber den Anforderungen, die an ein einfaches Eingabesteuerelement gestellt werden.
Hinsichtlich der Verarbeitung und Darstellung von Text hat die WPF aber noch weitaus mehr zu bieten als die genannten einfachen Steuerelemente Label oder TextBox. Eines haben Sie bereits kennengelernt, nämlich den TextBlock. Ein TextBlock lässt bereits kurze, formatierte Texte zu. Sollen innerhalb eines TextBlocks darüber hinaus auch Listen, Tabellen oder Bilder angezeigt werden, ist das Steuerelement bereits an den Grenzen seiner Fähigkeiten angelangt.
Für solche komplexeren Anforderungen bietet Ihnen die WPF mit
- FlowDocumentReader
- FlowDocumentPageViewer
- FlowDocumentScrollViewer
drei weitere Steuerelemente an, mit denen beliebig strukturierte Dokumente in einem Fenster angezeigt werden können.
Ehe wir uns aber diesen drei Steuerelementen im Detail widmen, müssen wir uns mit dem Element FlowDocument beschäftigen. Dabei handelt es sich um ein Dokument, das in einem der drei genannten Steuerelemente angegeben wird, wie im folgenden Listing gezeigt wird:
<FlowDocumentReader>
<FlowDocument>
<!-- Inhalt, der durch Blöcke beschrieben wird -->
</FlowDocument>
</FlowDocumentReader>
Listing 21.62 »FlowDocument«, eingebettet im Element »FlowDocumentReader«
21.8.2 Eigenschaften eines »FlowDocuments«
Um ein FlowDocument zu beschreiben, stehen Ihnen viele Eigenschaften zur Verfügung. Einige sind Ihnen bereits geläufig, beispielsweise Padding, Margin, FontStyle usw. Auf einige spezifische Eigenschaften soll aber an dieser Stelle eingegangen werden. Sehen Sie sich dazu bitte die folgende Tabelle an.
Eigenschaft | Beschreibung |
Diese Eigenschaft gibt an, ob eine Silbentrennung durchgeführt werden soll. |
|
Wird diese Eigenschaft auf true gestellt, wird der Absatzumbruch optimiert. Das vermindert zu große Abstände zwischen den Wörtern. |
|
LineHeight |
Diese Eigenschaft gibt den Zeilenabstand innerhalb eines Absatzes an. |
Mit dieser Eigenschaft können Sie Formatierungen durch externe Styles beschreiben. Wir werden später in diesem Buch die Styles noch thematisieren. |
|
TextAlignment |
Diese Eigenschaft gibt die Textausrichtung an und kann auf Left, Right, Center und Justify eingestellt werden. |
Für eine umfassende Beschreibung aller WPF-Klassen und deren Fähigkeiten eines FlowDocument-Objekts reicht der Platz in diesem Buch einfach nicht aus. Deshalb sei auch an dieser Stelle auf die MSDN-Onlinehilfe verwiesen, die diesbezüglich natürlich deutlich mehr Informationen liefert.
21.8.3 Die Blöcke eines »FlowDocuments«
FlowDocuments beschreiben einen textuellen Inhalt. Die Darstellung erfolgt, wie nicht anders zu erwarten ist, in XML. Die Grundstruktur eines FlowDocuments setzt sich aus einem oder mehreren sogenannten Blöcken zusammen. Dabei stehen Ihnen fünf unterschiedliche Blöcke zur Gestaltung der Grundstruktur zur Verfügung. Diese können Sie Tabelle 21.15 entnehmen. Alle Blöcke werden durch entsprechende Klassen beschrieben und lassen sich auf die Basisklasse Block zurückführen, die zum Namespace System.Windows.Documents gehört.
Block | Beschreibung |
Paragraph |
Ein Paragraph-Element beschreibt Text. Dieses Element beschreibt also gewissermaßen das Fleisch des FlowDocuments. <Paragraph> |
Section |
Das Section-Element dient dazu, mehrere Blöcke zusammenzuführen, um eine einheitliche Formatierung aller enthaltenen Blöcke zu erreichen. <Section> |
List |
Das List-Element beschreibt eine Auflistung von ListItem-Elementen. Damit werden Listen innerhalb eines FlowDocuments beschrieben. Jedes ListItem-Element darf seinerseits einen beliebigen Block-Typ enthalten, natürlich auch wieder selbst ein List-Element, um eine verschachtelte Liste abzubilden. |
Table |
Das Table-Element bildet eine Tabelle ab. Jede Zelle der Tabelle darf einen beliebigen Block-Typ enthalten. |
BlockUIContainer |
Das BlockUIElement kann ein beliebiges UIElement-Element enthalten, beispielsweise einen Button oder eine TextBox. Damit lassen sich Dokumente erstellen, die programmierbar sind. <BlockUIContainer> |
Paragraph, Section und BlockUIContainer sind recht simpel und bedürfen keiner weiteren Beschreibung. List und Table hingegen sind etwas komplexer, so dass wir uns mit diesen beiden Blöcken weiter unten noch genauer beschäftigen werden.
Beispielprogramm
Das folgende Beispielprogramm FlowDocumentSample zeigt, wie ein einfaches FlowDocument durch XAML-Code ausgedrückt wird. Um das Grundprinzip zu verdeutlichen, wird in diesem Beispiel nur der Paragraph-Block verwendet.
// Beispiel: ..\Kapitel 21\FlowDocumentSample
<FlowDocumentReader>
<FlowDocument>
<Paragraph FontSize="20" FontFamily="Arial"
FontWeight="Bold" Foreground="Blue">
Kapitel 3: das Klassendesign
</Paragraph>
<Paragraph FontSize="16" FontFamily="Arial" FontWeight="Bold">
3.1 Einführung in die Objektorientierung
</Paragraph>
<Paragraph FontSize="12" FontFamily="Arial">
Die beiden wichtigsten ...
</Paragraph>
<Paragraph FontSize="12" FontFamily="Arial">
Stellen Sie sich ein Architekturbüro ...
</Paragraph>
<Paragraph FontSize="12" FontFamily="Arial">
Der Bauplan dient also ...
</Paragraph>
</FlowDocument>
</FlowDocumentReader>
Listing 21.63 Der XAML-Code des Beispielprogramms »FlowDocumentSample«
In Abbildung 21.36 sehen Sie die Ausgabe des Beispielprogramms.
Abbildung 21.36 Ausgabe des Beispielprogramms »FlowDocumentSample«
Wie das Beispiel sehr schön zeigt, werden Fließtexte innerhalb von Paragraph-Elementen beschrieben. Der Text weist im Beispiel keinerlei Formatierungen auf. Wie schon oben angedeutet, können Sie jedoch auch innerhalb eines Textes einzelne Wörter oder Passagen unterschiedlich formatieren. Dazu dienen Inline-Elemente, mit denen wir uns in Abschnitt 21.8.4 beschäftigen werden. Zuvor aber sollten wir uns noch zwei der in Tabelle 21.15 aufgeführten Blockelemente etwas detaillierter ansehen: List und Table.
Der »List«-Block
Dieses List-Element dient zum Erzeugen von Aufzählungen. Jeder Listeneintrag wird innerhalb von List durch ein ListItem-Element beschrieben. ListItem-Elemente beinhalten ihrerseits Blockelemente, so dass Sie den anzuzeigenden Text zum Beispiel in ein Paragraph-Element einschließen können.
Mit der Eigenschaft MarkerStyle legen Sie das Aufzählungszeichen fest. Es entstammt der Enumeration TextMarkerStyle und enthält zehn verschiedene Varianten – angefangen bei einfachen Symbolen bis hin zu Zahlen und Buchstaben. Verwenden Sie Zahlen, kann mit der Eigenschaft StartIndex der Index des ersten Elements festgelegt werden. Den Abstand zwischen den Aufzählungszeichen und dem Text des Listeneintrags können Sie bei Bedarf mit der Eigenschaft MarkerOffset definieren.
<Window ...>
<Grid>
<FlowDocumentReader>
<FlowDocument>
<List MarkerStyle="Decimal" FontFamily="Arial">
<ListItem>
<Paragraph>Erster Listeneintrag</Paragraph>
</ListItem>
<ListItem>
<Paragraph>Zweiter Listeneintrag</Paragraph>
</ListItem>
</List>
</FlowDocument>
</FlowDocumentReader>
</Grid>
</Window>
Listing 21.64 Das Blockelement »List« im XAML-Code
Dieser XAML-Code führt zu einer Ausgabe zur Laufzeit, wie sie in Abbildung 21.37 zu sehen ist.
Abbildung 21.37 Ausgabe des Listings 21.64
Der »Table«-Block
Ein Block vom Typ Table beschreibt eine Tabelle. Innerhalb des Table-Elements werden mit TableRowGroup die Zeilen innerhalb der Tabelle beschrieben. Ausgedrückt wird dabei jede Zeile durch ein TableRow-Element, jede Zelle der Datenzeile durch das Element TableCell. Ein Table-Element kann auch mehrere TableRowGroup-Elemente enthalten. Das ergibt einen Sinn, wenn mehrere Zeilen innerhalb der Tabelle unterschiedlich formatiert werden sollen.
Geben Sie innerhalb einer Tabellendefinition keine Spalten an, werden diese alle gleich breit dargestellt. Möchten Sie Spaltenbreiten individuell festlegen, sollten Sie im Table-Element auch einen Table.Columns-Abschnitt definieren, der als Container für die definierten TableColumn-Elemente dient. Geben Sie jedem TableColumn-Element die gewünschte Breite mit Width an. Dabei können Sie entweder die absolute Breite einstellen, z. B. mit Width=70, oder die relative Breite, beispielsweise mit Width=3*.
<Window ...>
<Grid>
<FlowDocumentReader>
<FlowDocument>
<Table CellSpacing="10" Padding="0" Background="Yellow"
FontFamily="Arial" FontSize="12">
<!-- Spaltendefinition -->
<Table.Columns>
<TableColumn Width="70" />
<TableColumn Width="120" />
<TableColumn Width="200"/>
</Table.Columns>
<!-- Zeilendefinition -->
<TableRowGroup Background="LightCyan">
<TableRow>
<TableCell BorderThickness="1" BorderBrush="Black">
<Paragraph>Zelle 0,0</Paragraph>
</TableCell>
<TableCell BorderThickness="1" BorderBrush="Black">
<Paragraph>Zelle 0,1</Paragraph>
</TableCell>
<TableCell BorderThickness="1" BorderBrush="Black">
<Paragraph>Zelle 0,2</Paragraph>
</TableCell>
</TableRow>
<TableRow>
<TableCell BorderThickness="1" BorderBrush="Black" >
<Paragraph>Zelle 1,0</Paragraph>
</TableCell>
<TableCell BorderThickness="1" BorderBrush="Black">
<Paragraph>Zelle 1,1</Paragraph>
</TableCell>
<TableCell BorderThickness="1" BorderBrush="Black">
<Paragraph>Zelle 1,2</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</FlowDocument>
</FlowDocumentReader>
</Grid>
</Window>
Listing 21.65 Ein »Table«-Block innerhalb eines »FlowDocuments«
In Abbildung 21.38 sehen Sie die Ausgabe der Tabelle zur Laufzeit.
Abbildung 21.38 Ausgabe des Listings 21.65
21.8.4 Inline-Elemente
Innerhalb eines Fließtextes können Formatierungen am Text vorgenommen werden. Dazu dienen Inline-Elemente, die intern von der abstrakten Klasse Inline abgeleitet sind.
- LineBreak: Mit diesem Element wird ein Zeilenumbruch durchgeführt.
- Bold, Italic und Underline: Diese Inline-Elemente formatieren den enthaltenen Text fett, kursiv oder unterstrichen.
Sie können Inline-Elemente grundsätzlich auch ineinander verschachteln, also Textpassagen
auch fett und gleichzeitig kursiv darstellen, wie im folgenden XAML-Code gezeigt wird:
<Paragraph>
Ein typisches Urlaubsland ist
<Italic><Bold>Österreich</Bold></Italic>.
</Paragraph> - Figure: Diese Elemente können Sie sich als ein FlowDocument vorstellen, das in einem übergeordneten Bereich eingebettet wird, beispielsweise dem Paragraph-Element. Der umgebende Inhalt umschließt das Figure-Element, wobei Sie mit den Eigenschaften HorizontalAnchor und VerticalAnchor sowie HorizontalOffset und VerticalOffsetdie Positionierung beliebig selbst festlegen dürfen. Height und Width legen die Abmessungen fest. Sollte der gewünschte Inhalt nicht in das Figure-Element passen, wird er abgeschnitten. Das Element eignet sich gut für Werbung, Zusatzinformationen oder auch Bilder.
- Floater: Dieses Element ist eine vereinfachte Form des Figure-Elements. Im Gegensatz zum Figure kann es nicht positioniert werden. Der Inhalt wird dort angezeigt, wo entsprechender Platz zur Verfügung steht. Für ein Floater-Element können Sie weder eine Offset- noch eine Anchor-Eigenschaft angeben. Im Allgemeinen eignen sich Floater eher zum Einfügen fortlaufender Inhalte.
- InlineUIContainer: Dieses Element verwenden Sie, um andere UIElement-Elemente in den Fließtext einzubetten, beispielsweise einen Button. Mit diesem Element können Sie zwar nur UI-Elemente direkt angeben, aber es steht Ihnen frei, dazu einen Container zu verwenden, der natürlich seinerseits mehrere Elemente aufnehmen kann. Im Großen und Ganzen ähnelt es dem Element BlockUIContainer.
- Span: Obwohl Sie mit den Elementen Bold, Underline und Italic Möglichkeiten haben, einzelne Passagen des Textes besonders darzustellen, reichen
diese oft nicht aus. Hier kommt das Span-Element ins Spiel, das wesentlich mehr Gestaltungsspielraum bietet. So lassen sich
einzelne Wörter oder Passagen mit FontFamily in einer anderen Schriftart darstellen, oder Sie können mit Background die Hintergrundfarbe anders festlegen als im restlichen Teil des Dokuments.
<Paragraph>Dies ist<Span Background="Yellow" Foreground="Blue"
FontFamily="Californian FB">Beispiel</Span> für das <Span
FontFamily="Courier New">Span</Span>-Element.
</Paragraph> - Hyperlink: Dieses Element stellt einen Hyperlink dar. Dem Attribut NavigateUri geben Sie einen URI an, der nach dem Klicken auf das Element angezeigt werden soll. Stellen Sie dem URI das Zeichen »#« voran, können Sie sogar zu einer benannten Stelle im Dokument wechseln. Mit dem Attribut TargetName legen Sie das Ziel der Navigation fest. Dabei kann es sich zum Beispiel um ein Fenster oder einen Frame handeln.
- Run: Das Element Run gibt einen unformatierten Text aus. Dieser kann durch umschließende Elemente wieder eine eigene Formatierung erhalten.
Das folgende Beispielprogramm demonstriert den Einsatz des Figure-Elements. Sicher ist das Layout des Windows durchaus deutlich verbesserungsbedürftig, aber dem Autor fehlt dazu einfach die erforderliche Begabung .
// Beispiel: ..\Kapitel 21\FigureElementSample
<FlowDocumentReader>
<FlowDocument>
<Paragraph FontSize="22" Background="LightBlue" FontStyle="Oblique"
FontFamily="Arial" TextAlignment="Center" Padding="10">
<TextBlock VerticalAlignment="Center">
<Bold>Seychellen-Infos</Bold>
</TextBlock>
</Paragraph>
<Paragraph FontFamily="Arial" FontSize="15">
Zu den vermutlich schönsten ...
<Figure Width="150" HorizontalAnchor="PageLeft"
VerticalAnchor="PageCenter"
HorizontalOffset="0"
VerticalOffset="40">
<BlockUIContainer>
<Image Source="1.jpg"></Image>
</BlockUIContainer>
</Figure>
<Figure Width="150" HorizontalAnchor="PageRight"
VerticalAnchor="PageCenter"
VerticalOffset="-5">
<BlockUIContainer>
<Image Source="2.jpg"></Image>
</BlockUIContainer>
</Figure>
</Paragraph>
</FlowDocument>
</FlowDocumentReader>
Listing 21.66 Das Beispielprogramm »FigureElementSample«
Abbildung 21.39 Die Ausgabe des Beispiels »FigureElementSample«
Alle hier behandelten Inline-Elemente wurden in den Codebeispielen in Paragraph-Elementen verwendet. Grundsätzlich unterstützt aber auch das TextBlock-Element alle Inlines.
21.8.5 »FlowDocuments« mit Code erzeugen
Nicht immer werden Sie Ihre FlowDocuments im XAML-Code von Visual Studio 2012 bereitstellen können. Hier bieten sich Ihnen zwei weitere Alternativen an:
- Sie beschreiben den Inhalt des FlowDocuments mit Programmcode.
- Sie greifen auf eine bereitgestellte XAML-Datei zurück.
In diesem Abschnitt wollen wir uns zunächst mit der Codierung beschäftigen, daran anschließend mit dem Einlesen aus einer Datei.
Das Schreiben des Programmcodes gestaltet sich zu einem mittleren Drama. Nicht die Komplexität ist dafür verantwortlich, sondern vielmehr der Gesamtumfang. Vielleicht sollten Sie sich daher zunächst einmal Abbildung 21.40 ansehen.
Abbildung 21.40 Ausgabe des Beispielprogramms »FlowDocumentWithCode«
Eigentlich wird nicht viel dargestellt, das FlowDocument enthält auch keine besonderen »Spezialitäten«. Doch schauen Sie sich jetzt den Beispielcode an, der dieser Ausgabe zugrunde liegt.
// Beispiel: ..\Kapitel 21\FlowDocumentWithCode
private void Window_Loaded(object sender, RoutedEventArgs e) {
FlowDocumentReader flowReader = new FlowDocumentReader();
FlowDocument document = new FlowDocument();
flowReader.Document = document;
this.Content = flowReader;
// Erster Block – Paragraph
Paragraph para1 = new Paragraph();
para1.FontSize = 22;
para1.Background = new SolidColorBrush(Colors.LightBlue);
para1.FontFamily = new FontFamily("Arial");
para1.TextAlignment = TextAlignment.Center;
para1.Padding = new Thickness(10);
TextBlock textBlock1 = new TextBlock();
textBlock1.VerticalAlignment = System.Windows.VerticalAlignment.Center;
textBlock1.Inlines.Add(new Bold(new Run("Wir planen ...")));
para1.Inlines.Add(textBlock1);
document.Blocks.Add(para1);
// Zweiter Block – Paragraph
Paragraph para2 = new Paragraph();
TextBlock textBlock2 = new TextBlock();
textBlock2.FontSize = 14;
textBlock2.TextWrapping = TextWrapping.Wrap;
textBlock2.FontFamily = new FontFamily("Arial");
textBlock2.Inlines.Add(new Run("Buchen Sie ..."));
para2.Inlines.Add(textBlock2);
document.Blocks.Add(para2);
// Dritter Block – Tabelle
Table table = new Table();
table.FontFamily = new FontFamily("Arial");
// 1. Spalte
TableColumn col1 = new TableColumn();
col1.Width = new GridLength(350);
table.Columns.Add(col1);
// 2. Spalte
TableColumn col2 = new TableColumn();
col2.Width = new GridLength(100);
table.Columns.Add(col2);
TableRowGroup rowGroup = new TableRowGroup();
TableRow row1 = new TableRow();
// Zelle 1, Reihe 1
TableCell cell00 = new TableCell(
new Paragraph(new Run("Australien - 3 wöchige Rundreise")));
cell00.BorderThickness = new Thickness(1);
cell00.BorderBrush = new SolidColorBrush(Colors.Black);
row1.Cells.Add(cell00);
// Zelle 2, Reihe 1
TableCell cell01 = new TableCell(new Paragraph(new Run("5570,-€")));
cell01.TextAlignment = TextAlignment.Right;
cell01.BorderThickness = new Thickness(1);
cell01.BorderBrush = new SolidColorBrush(Colors.Black);
row1.Cells.Add(cell01);
rowGroup.Rows.Add(row1);
table.RowGroups.Add(rowGroup);
document.Blocks.Add(table);
}
Listing 21.67 Das Beispielprogramm »FlowDocumentWithCode«
Erstellt wird das FlowDocument im Ereignis Loaded des Window. Als Viewer des Dokuments wird auch in diesem Fall ein FlowDocumentReader benutzt. Das FlowDocument beschreibt drei Blöcke, unter ihnen eine Tabelle. Um den Code nicht unnötig aufzublähen, ist in der Tabelle nur eine Zeile definiert. Das wird aber ausreichen, um den Ablauf zu erklären.
Sehen wir uns exemplarisch die erste Paragraph-Definition an. Ein Paragraph-Objekt verwaltet eine Collection von Inline-Objekten, die über die Eigenschaft Inlines veröffentlicht wird. Jedes zu einem Paragraph-Objekt gehörende Inline-Objekt muss dieser Collection hinzugefügt werden. In unserem Beispielprogramm handelt es sich um ein TextBlock-Objekt. Sehr ähnlich wird auch das zweite Paragraph-Objekt behandelt.
Ein Table-Block beschreibt mindestens ein TableGroup-Objekt, das seinerseits mehrere Zeilen beherbergt. Eine Zeile wiederum ist durch TableCell-Objekte beschrieben. Jedes TableCell-Objekt muss der Collection aller Zellen einer Tabellenzeile hinzugefügt werden, z. B.
row1.Cells.Add(cell00);
row1 ist hierbei die Referenz einer Tabellenzeile.
Mit
rowGroup.Rows.Add(row1);
wird die Zeile zum Mitglied des TableRowGroup-Objekts. Die TableRowGroup wird mit
table.RowGroups.Add(rowGroup);
zu einem Mitglied der Tabelle.
Ein FlowDocument beschreibt mit einer Collection vom Typ BlockCollection alle in ihm enthaltenen Blöcke. Natürlich müssen auch diese Blöcke erst dem Dokument bekannt gegeben werden. Im Fall unserer Tabelle ist das die Anweisung
document.Blocks.Add(table);
Das FlowDocument muss natürlich auch noch dem Viewer übergeben werden, der sich selbst auch noch dem Window-Objekt bekannt machen muss. Hierfür sind die beiden Anweisungen
flowReader.Document = document;
this.Content = flowReader;
zuständig.
21.8.6 Speichern und Laden eines »FlowDocuments«
Sie können ein FlowDocument als XAML-Datei speichern. Hierbei kommt die Klasse XamlWriter ins Spiel, deren Namespace System.Windows.Markup Sie mit using bekannt geben sollten. Mit der statischen Methode Save der Klasse wird das Dokument in die angegebene Datei geschrieben.
FileStream fs = new FileStream(@"C:\Test.xaml", FileMode.Create);
XamlWriter.Save(FlowDocument, fs);
fs.Close();
Listing 21.68 Speichern eines »FlowDocuments«
Zum Laden eines FlowDocument kommt die Klasse XamlWriter zum Einsatz. Hier ist es die statische Methode Load(), die das Laden initiiert.
File.OpenRead(@"C:\test.xaml");
DocumentViewer.DocumentProperty = XamlReader.Load(fs);
fs.Close();
Listing 21.69 Laden eines »FlowDocuments«
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.