Benutzeroberfläche eines Texteditors mit WPF

Standard

In meinem letzten Artikel habe ich einen Überblick über den Layoutmechanismus in WPF gegeben. In diesem Artikel möchte ich zeigen, wie man eine Benutzeroberfläche für einen Texteditor mit WPF layouten kann.

Ziel

In diesem Artikel möchte ich zeigen, wie man mit dem WPF-Layoutmechanismus eine GUI für einen Notepad-ähnlichen Texteditor entwerfen kann. Das Ergebnis wird in etwa so aussehen:

Texteditor Ergebnis

Oft gibt es mehr als eine Möglichkeit, ein Ziel zu erreichen. So auch hier: in der ersten Variante verwende ich ein Grid und in der zweiten Variante ein DockPanel.

Variante mit Grid

Visual Studio erstellt ein Window immer mit einem Grid:

    <Window x:Class="TextEditor.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
     
        </Grid>
    </Window>

Zuerst müssen wir die Spalten und Zeilen definieren, die das Layoutgitter haben soll, dann können wir die Steuerelemente hinzufügen und einzelnen Gitterzellen zuweisen.

    <Window x:Class="TextEditor.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
     
            <Menu Grid.Column="0" Grid.Row="0" />
            <TextBox Grid.Column="0" Grid.Row="1" />
            <StatusBar Grid.Column="0" Grid.Row="2" />
        </Grid>
    </Window>

Wir definieren drei Zeilen und eine Spalte. Jedes Steuerelement kommt in eine eigene Zeile. Das Ergebnis sieht dann so aus:

Layout mit Grid

Wie wir sehen, sieht das Ergebnis noch gar nicht nach Texteditor aus. Drei gleich große Layoutgitterzeilen beinhalten das Menü, die TextBox und die Statuszeile.

Per Voreinstellung bekommt jede Zeile die Höhe “1*” zugewiesen, d.h. die Höhe wird im Verhältnis 1:1:1 aufgeteilt. Daher sind die drei Zeilen gleich hoch.

Wenn wir die Zeilendefinitionen so abändern, dass die erste Zeile (Menü) und die dritte Zeile (Statuszeile) eine fixe Höhe bekommen und die zweite Zeile (TextBox) den Rest bekommt, dann kommen wir dem Ziel etwas näher.

    ...
            <Grid.RowDefinitions>
                <RowDefinition Height="23"/>
                <RowDefinition Height="1*"/>
                <RowDefinition Height="26"/>
            </Grid.RowDefinitions>
    ...

Die erste Zeile setzen wir auf 23 Pixel und die letzte Zeile auf 26 Pixel. Die zweite Zeile bekommt den Rest. Die Höhenangabe für die zweite Zeile kann weggelassen werden, weil “1*” der voreingestellte Wert ist.

Dem Menü fehlen noch die Hauptmenüpunkte und die Textbox akzeptiert noch nicht die Eingabetaste. Mit folgenden Änderungen sieht unser Fenster beinahe wie ein richtiger Texteditor aus:

    <Window x:Class="TextEditor.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="23"/>
                <RowDefinition Height="1*"/>
                <RowDefinition Height="26"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
     
            <Menu Grid.Column="0" Grid.Row="0">
                <MenuItem Header="_Datei" />
                <MenuItem Header="_Bearbeiten" />
                <MenuItem Header="_Format"/>
                <MenuItem Header="_Ansicht"/>
                <MenuItem Header="_Hilfe"/>
            </Menu>
            <TextBox Grid.Column="0" Grid.Row="1" AcceptsReturn="True" />
            <StatusBar Grid.Column="0" Grid.Row="2" />
        </Grid>
    </Window>

Die WPF-TextBox zeigt mehrzeilige Texte immer mehrzeilig an, aber per Voreinstellung akzeptiert sie die Eingabetaste nicht. Mit der Eigenschaft AcceptsReturn kann dieses Verhalten geändert werden.

Wenn der Benutzer jetzt einen längeren Text in die TextBox eingibt oder einfügt, sollten Schiebeleisten erscheinen. Unser Fenster verhält sich leider noch nicht so. Wenn wir die TextBox in ein ScrollViewer-Element einbetten, erhalten wir das gewünschte Verhalten:

    <Window x:Class="TextEditor.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="23"/>
                <RowDefinition Height="1*"/>
                <RowDefinition Height="26"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
     
            <Menu Grid.Column="0" Grid.Row="0">
                <MenuItem Header="_Datei" />
                <MenuItem Header="_Bearbeiten" />
                <MenuItem Header="_Format"/>
                <MenuItem Header="_Ansicht"/>
                <MenuItem Header="_Hilfe"/>
            </Menu>
            <ScrollViewer Grid.Column="0" Grid.Row="1"
                          HorizontalScrollBarVisibility="Auto"
                          VerticalScrollBarVisibility="Auto">
                <TextBox AcceptsReturn="True" />
            </ScrollViewer>
            <StatusBar Grid.Column="0" Grid.Row="2" />
        </Grid>
    </Window>

Da jetzt ScrollViewer das Kindelement des Grids ist, muss die Zuweisung zu einer Layoutgitterzelle für das ScrollViewer-Element erfolgen. Die TextBox braucht dann keine Zuweisung mehr.

Mit den Eigenschaften HorizontalScrollBarVisibility und VerticalScrollBarVisibility können wir steuern, wann die Schiebeleisten erscheinen.

Variante mit DockPanel

Das Ziel kann auch mit einem DockPanel erreicht werden. Tauschen wir mal das Grid durch ein DockPanel aus und entfernen die Zeilen- und Spaltendefinitionen und die Zuordnung zu den Layoutgitterzellen:

    <Window x:Class="TextEditor.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <DockPanel>
            <Menu DockPanel.Dock="Top">
                <MenuItem Header="_Datei" />
                <MenuItem Header="_Bearbeiten" />
                <MenuItem Header="_Format"/>
                <MenuItem Header="_Ansicht"/>
                <MenuItem Header="_Hilfe"/>
            </Menu>
            <ScrollViewer HorizontalScrollBarVisibility="Auto"
                          VerticalScrollBarVisibility="Auto">
                <TextBox AcceptsReturn="True" />
            </ScrollViewer>
            <StatusBar DockPanel.Dock="Bottom" />
        </DockPanel>
    </Window>

Das Ergebnis sieht noch nicht so aus, wie wir wollen.

Layout mit DockPanel

Das Problem ist, dass bei einem DockPanel jenes Steuerelement, das den Rest der Fläche ausfüllen soll, als letztes angegeben werden muss. Das ist bei uns der ScrollViewer mit der TextBox. Außerdem gibt das DockPanel jedem Element gerade soviel Platz, wie es braucht, um den Inhalt darzustellen. Daher sollten wir etwas in der Statuszeile anzeigen, damit das DockPanel die Statuszeile überhaupt darstellt:

    <Window x:Class="TextEditor.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <DockPanel>
            <Menu DockPanel.Dock="Top">
                <MenuItem Header="_Datei" />
                <MenuItem Header="_Bearbeiten" />
                <MenuItem Header="_Format"/>
                <MenuItem Header="_Ansicht"/>
                <MenuItem Header="_Hilfe"/>
            </Menu>
            <StatusBar DockPanel.Dock="Bottom">
                <TextBlock Text="Col 1, Ln 1" />
            </StatusBar>
            <ScrollViewer HorizontalScrollBarVisibility="Auto"
                          VerticalScrollBarVisibility="Auto">
                <TextBox AcceptsReturn="True" />
            </ScrollViewer>
        </DockPanel>
    </Window>

Das sieht doch gleich besser aus.

Endergebnis

Zusammenfassung

Wir kamen sowohl mit dem Grid als auch mit dem DockPanel an unser Ziel. Beide Varianten haben Vor- und Nachteile.

Beim Grid mussten wir die Definitionen für die Zeilen und Spalten und an den Steuerelementen sowohl die Spalte als auch die Zeile angeben, obwohl unser Layout nur eine Spalte hatte.

Das DockPanel hatte den Nachteil, dass die Reihenfolge geändert und daher die Statuszeile im XAML-Code vor der TextBox angegeben werden musste. Außerdem mussten alle Steuerelemente einen Inhalt haben, damit sie überhaupt angezeigt wurden.

Leave a Reply

Your email address will not be published. Required fields are marked *