21.5 WPF-Listenelemente
Eine Reihe verschiedener WPF-Steuerelemente sind in der Lage, Listen anzuzeigen. Zu diesen Controls werden unter anderem die ListBox, die ComboBox, das TabControl, der ListView und das TreeView-Control gerechnet. Die von diesen Controls dargestellten Listenelemente lassen sich in zwei Gruppen kategorisieren:
- ItemControls
- HeaderedItemControls
Listensteuerelemente, die Listenelemente der Gruppe der ItemControls anzeigen, zeichnen sich durch die Eigenschaft Items aus. Diese Eigenschaft gewährleistet den Zugriff auf die einzelnen Elemente der Liste, der über den Index des Elements erfolgen kann. Typische Vertreter für Steuerelemente, die ItemControls aufnehmen, sind die ListBox und die ComboBox. Je nach Typ des Steuerelements werden die Listenelemente durch besondere Klassen beschrieben. Im Fall einer ListBox handelt es sich um ListBoxItem, bei der ComboBox sind es Elemente vom Typ ComboBoxItem.
Von der Klasse ItemControls ist die Klasse HeaderedItemControls abgeleitet. Es ist die Eigenschaft Header, die Elemente dieses Typs auszeichnet. Mit der Eigenschaft Header kann einem Element ein »Titel« zugewiesen werden, dem eine Spalte zugeordnet wird. Steuerelemente der Gruppe der HeaderedItemControls bilden somit keine einfache lineare, sondern eine hierarchische Struktur ab. Typische Vertreter dieser Gruppe sind die Klassen MenuItem, TreeView und auch ToolBar.
21.5.1 Das Steuerelement »ListBox«
Eine ListBox bietet eine Liste von möglichen Auswahlalternativen an, aus denen der Anwender eine oder auch mehrere wählen kann. Per Vorgabe gestattet eine ListBox nur die Einfachauswahl. Damit auch die Auswahl mehrerer Einträge möglich ist, müssen Sie die Eigenschaft SelectionMode auf Multiple oder Extended festlegen. Multiple gestattet die Auswahl durch einen einfachen Klick. Bei der Einstellung Extended muss der Anwender beim Anklicken des Listenelements die -Taste gedrückt halten.
Um eine klassische ListBox zu erzeugen, verwenden Sie für jedes Listenelement die Klasse ListBoxItem.
<ListBox Name="listBox1">
<ListBoxItem>Peter</ListBoxItem>
<ListBoxItem>Franz</ListBoxItem>
<ListBoxItem>Rolf</ListBoxItem>
<ListBoxItem>Hans-Günther</ListBoxItem>
</ListBox>
Listing 21.18 »ListBox« mit mehreren Listeneinträgen (XAML-Code)
Sie können der ListBox sowohl im XAML-Code als auch in der Code-Behind-Datei Listenelemente hinzufügen. Alle Listenelemente werden in der ListBox von einer Collection verwaltet, deren Referenz die Eigenschaft Items liefert. Durch Aufruf der Methode Add fügen Sie nach Bedarf Elemente hinzu:
listBox1.Items.Add("Beate");
listBox1.Items.Add("Gudrun");
Listing 21.19 Elemente mit C#-Code hinzufügen
Damit nicht genug: Anstatt eine Liste von ListBoxItems zu definieren, können Sie jeden Listeneintrag auch durch ein anderes Steuerelement beschreiben. Im folgenden Codefragment sind beispielsweise CheckBoxen hinzugefügt worden:
<ListBox Name="listBox1">
<CheckBox Name="chkBox1" Margin="3">Peter</CheckBox>
<CheckBox Name="chkBox2" Margin="3">Franz</CheckBox>
<CheckBox Name="chkBox3" Margin="3">Rolf</CheckBox>
<CheckBox Name="chkBox4" Margin="3">Hans-Günter</CheckBox>
</ListBox>
Listing 21.20 »ListBox«-Einträge dargestellt durch »CheckBoxen«
Abbildung 21.12 Ausgabe des XAML-Codes aus Listing 21.20
Das Hinzufügen von Listboxeinträgen in Form von CheckBoxen ist natürlich auch mit Programmcode möglich:
CheckBox chkBox4 = new CheckBox();
chkBox4.Content = "Beate";
chkBox4.Margin = new Thickness(3);
CheckBox chkbox5 = new CheckBox();
chkBox5.Content = "Gudrun";
chkBox5.Margin = new Thickness(3);
listBox1.Items.Add(chkBox4);
istBox1.Items.Add(chkBox5);
Listing 21.21 Hinzufügen von CheckBoxen mit C#-Code
Beachten Sie in diesem Codefragment, dass die Eigenschaft Margin durch den Typ Thickness beschrieben wird.
Zugriff auf das ausgewählte Element
Um die in einer ListBox ausgewählten Elemente im Programmcode für weitere Operationen nutzen zu können, stellt das Steuerelement die Eigenschaften SelectedIndex, SelectedItem und SelectedItems bereit, die es erlauben, die ausgewählten Listenelemente auszuwerten (siehe Tabelle 21.9).
Eigenschaft | Beschreibung |
Gibt den Index des ersten Elements in der aktuellen Auswahl zurück. Ist die Auswahl leer, ist der Rückgabewert -1. Mit dieser Eigenschaft kann auch ein Element vorselektiert werden. |
|
Gibt das erste Element in der aktuellen Auswahl zurück. Ist die Auswahl leer, ist der Rückgabewert null. |
|
Ruft alle ausgewählten Elemente ab. |
Möchten Sie, dass ein bestimmtes Listenelement beim Öffnen des Fensters vorselektiert ist, verwenden Sie die Eigenschaft SelectedIndex. Da die Liste der Elemente nullbasiert ist, genügt die Anweisung
listBox1.SelectedIndex = 0;
um das erste Element zu markieren.
Etwas schwieriger gestaltet es sich, den Inhalt eines ausgewählten Elements auszuwerten. Nehmen wir dazu den folgenden XAML-Code:
<ListBox Name="ListBox1">
<ListBoxItem>Frankreich</ListBoxItem>
<ListBoxItem>Italien</ListBoxItem>
<ListBoxItem>Polen</ListBoxItem>
<ListBoxItem>Dänemark</ListBoxItem>
</ListBox>
Um sich den Inhalt des ausgewählten Elements in einer MessageBox anzeigen zu lassen, ist der folgende Code notwendig:
MessageBox.Show(((ListBoxItem)ListBox1.SelectedItem).Content.ToString());
Zuerst lassen wir uns das erste ausgewählte Element mit der Methode SelectedItem zurückgeben, das wir in den Typ konvertieren, der die Listenelemente beschreibt. Hier handelt es sich um ListBoxItem. Dieser Typ verfügt über die Eigenschaft Content, die vom Typ Object ist. Da wir aber wissen, dass es sich um eine Zeichenfolge handelt, können wir diese mit ToString abrufen.
Auswahl mehrerer Elemente
Durch Einstellen der Eigenschaft SelectionMode=Multiple oder Extended kann der Anwender mehrere Listenelemente gleichzeitig auswählen. Um diese auszuwerten, eignet sich SelectedItems, die uns die Liste aller ausgewählten Elemente bereitstellt. Sie können diese Liste beispielsweise in einer foreach-Schleife durchlaufen:
private void btnShowItems_Click(object sender, RoutedEventArgs e) {
string items = "";
foreach (ListBoxItem item in ListBox1.SelectedItems)
items += item.Content +"\n";
MessageBox.Show(items);
}
Listing 21.22 Ausgabe der Liste aller ausgewählten Listenelemente
21.5.2 Die »ComboBox«
Die ComboBox ähnelt der eben behandelten ListBox. Der Unterschied zwischen diesen beiden Steuerelementen ist, dass die ComboBox immer nur ein Element anzeigt und somit auch nicht viel Platz in Anspruch nimmt.
Per Vorgabe kann der Anwender zur Laufzeit keine neuen Elemente in die ComboBox eintragen. Möchten Sie das zulassen, müssen Sie die Eigenschaft IsEditable=true setzen. Mit der Eigenschaft ReadOnly legen Sie fest, ob der Inhalt der ComboBox editiert werden kann. Die Kombination beider Eigenschaften entscheidet maßgeblich über die Handhabung des Steuerelements. Stellen Sie beispielsweise beide auf true ein, kann der Anwender kein Zeichen in die ComboBox eintragen. Ändern Sie allerdings IsEditable auf false, kann der Anwender bei fokussierter ComboBox ein Zeichen an der Tastatur eingeben. Befindet sich ein Element mit dem entsprechenden Anfangsbuchstaben in der Liste der Elemente, wird dieses ausgewählt.
Die einer ComboBox zugeordneten Elemente sind vom Typ ComboBoxItem:
<ComboBox Height="20" Name="comboBox1" Width="120">
<ComboBoxItem>Berlin</ComboBoxItem>
<ComboBoxItem>Hamburg</ComboBoxItem>
<ComboBoxItem>Bremen</ComboBoxItem>
<ComboBoxItem>Düsseldorf</ComboBoxItem>
<ComboBoxItem>Dresden</ComboBoxItem>
<ComboBoxItem>München</ComboBoxItem>
</ComboBox>
Listing 21.23 ComboBox mit Listenelementen im XAML-Code
Um per Programmcode ein Element hinzuzufügen, rufen Sie mit Items die ItemCollection der ComboBox ab. Deren Methode Add übergeben Sie einfach den gewünschten zusätzlichen Eintrag:
comboBox1.Items.Add("Stuttgart");
Das ausgewählte Element einer ComboBox können Sie mit der Eigenschaft Text abrufen. Mit dieser Eigenschaft lässt sich auch festlegen, welches Listenelement nach dem Laden des Fensters angezeigt werden soll. Gleichwertig können Sie mit SelectedIndex auch den Index des gewünschten Elements angeben.
Nur zwei Ereignisse sind für die ComboBox spezifisch: DropDownOpened und DropDownClosed. DropDownOpened wird beim Öffnen der Liste ausgelöst, DropDownClosed bei deren Schließen.
21.5.3 Das Steuerelement »ListView«
Das Steuerelement ListView ähnelt nicht nur der ListBox, es ist sogar aus ListBox abgeleitet. Im Gegensatz zur ListBox kann ein ListView-Steuerelement die Einträge unterschiedlich darstellen. Was sich im ersten Moment noch positiv anhört, relativiert sich aber auch wieder, denn derzeit ist das nur mit einem GridView-Element direkt möglich. GridView ist aus ViewBase abgeleitet. Sie können auch eigene Darstellungsansichten durch Ableiten von ViewBase ermöglichen, aber der Aufwand dafür ist nicht unerheblich.
Sehen wir uns zur Veranschaulichung den einfachen Einsatz des ListView-Controls als einspaltiges Listenfeld in einem Codefragment an (siehe auch Abbildung 21.13):
<Grid>
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" />
</GridView>
</ListView.View>
<ListViewItem>Peter Müller</ListViewItem>
<ListViewItem>Franz Goldschmidt</ListViewItem>
<ListViewItem>Rudi Ratlos</ListViewItem>
<ListViewItem>Conie Serna</ListViewItem>
</ListView>
</Grid>
Listing 21.24 XAML-Code eines einfachen »ListView«-Steuerelements
Abbildung 21.13 Einfacher Einsatz des ListView-Controls
Die Beschreibung der Kopfzeile wird innerhalb des GridView-Elements mit GridViewColumn vorgenommen. Das GridView-Element seinerseits ist der Eigenschaft View des ListView-Controls zugeordnet. Für einfache Einträge reicht das ListViewItem-Element vollkommen aus.
Mehrspaltiges Listenfeld
Um in einem ListView eine Tabelle darzustellen, ist die Bindung an eine Datenquelle erforderlich. Mit der Datenbindung werden wir uns in Kapitel 24 noch näher beschäftigen, daher sei an dieser Stelle nicht näher darauf eingegangen. Bei der Datenquelle muss es sich um ein Objekt handeln, das die Schnittstelle IEnumerable implementiert. Das Datenobjekt wird der Eigenschaft ItemSource des ListView zugewiesen.
Für jede Spalte ist ein GridViewColumn-Element zuständig. Wie in Listing 21.24 gezeigt, wird mit der Eigenschaft Header die Beschriftung der Kopfzeile festgelegt. Die Eigenschaft DisplayMemberBinding bestimmt, welche Objekteigenschaft in der Spalte angezeigt wird.
Das folgende Beispielprogramm soll die Vorgehensweise demonstrieren. Dazu stellen wir uns zuerst einmal eine Datenquelle zur Verfügung. Diese soll aus mehreren Person-Objekten bestehen, die auf der folgenden Klassendefinition basieren:
class Person {
public string Name { get; set; }
public int Alter { get; set; }
public string Wohnort { get; set; }
}
Listing 21.25 Klassendefinition für das folgende Beispielprogramm
Eine Methode erzeugt mehrere Person-Objekte und liefert als Rückgabewert ein Objekt vom Typ List<Person> an den Aufrufer. Das ist die Liste, die uns als Datenquelle dienen soll.
private List<Person> CreatePersonList() {
List<Person> liste = new List<Person>();
liste.Add(new Person { Name = "Meier", Wohnort = "Celle", Alter = 35 });
[...]
return liste;
}
Listing 21.26 Liste von Personen erzeugen
Für die Bindung der Datenquelle an die Eigenschaft ItemSource eignet sich der Konstruktor des Window-Objekts. Das ListView-Objekt soll den Namen lstView haben.
public MainWindow() {
InitializeComponent();
lstView.ItemsSource = CreatePersonList();
}
Listing 21.27 Datenquelle mit dem »ListView«-Steuerelement verbinden
Was uns nun nur noch bleibt, ist der XAML-Code. Die Ausgabe des Beispiels sehen Sie in Abbildung 21.14.
// Beispiel: ..\Kapitel 21\ListViewSample
<Grid>
<ListView Name="lstView">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Name" Width="100"
DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="Wohnort" Width="100"
DisplayMemberBinding="{Binding Path=Wohnort}" />
<GridViewColumn Header="Alter" Width="80"
DisplayMemberBinding="{Binding Path=Alter}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Grid>
Listing 21.28 »ListView« mit mehreren Spalten
Abbildung 21.14 Ausgabe des Beispielprogramms »ListViewSample«
21.5.4 Das Steuerelement »TreeView«
Mit dem TreeView-Steuerelement lassen sich Daten hierarchisch strukturiert darstellen. Sie kennen dieses Steuerelement, denn es wird auch im Windows-Explorer auf der linken Seite benutzt, um die Ordnerhierarchie darzustellen. Bei den Elementen eines TreeView-Controls handelt es sich nicht um eine lineare Liste, da jedes Element selbst wieder eine Liste untergeordneter Elemente haben kann.
Der folgende Code zeigt, wie die Elemente ineinander verschachtelt werden. In Abbildung 21.15 ist die Ausgabe des Codes zu sehen, wobei alle Knoten geöffnet sind.
<Grid>
<TreeView Name="treeView1">
<TreeViewItem Header="Asien">
<TreeViewItem Header="China" />
<TreeViewItem Header="Vietnam" />
<TreeViewItem Header="Philippinen" />
</TreeViewItem>
<TreeViewItem Header="Europa">
<TreeViewItem Header="Deutschland">
<TreeViewItem Header="NRW" />
<TreeViewItem Header="Hessen" />
</TreeViewItem>
<TreeViewItem Header="Italien" />
<TreeViewItem Header="Österreich" />
</TreeViewItem>
</TreeView>
</Grid>
Listing 21.29 »TreeView« im XAML-Code
Abbildung 21.15 Das »TreeView«-Steuerelement
Alle Elemente innerhalb des TreeView-Objekts sind vom Typ TreeViewItem beschrieben. Sowohl alle TreeViewItem-Objekte als auch der TreeView selbst haben eine Auflistung vom Typ ItemCollection, die die üblichen Interfaces IList, ICollection und IEnumerable implementiert. Der Zugriff auf diese Auflistung erfolgt über die Eigenschaft Items. Damit ist klar, dass man mit der Methode Add ein untergeordnetes Element hinzufügen kann, mit Remove ein Element löscht und mit Clear alle untergeordneten Elemente löschen kann. Eine sehr nützliche Eigenschaft der Klasse TreeViewItem ist Parent, mit der Sie die Referenz des direkt übergeordneten Elements abrufen können. Dabei müssen Sie aber aufpassen, von welchem Typ das übergeordnete Element ist, denn für alle Elemente der ersten Ebene handelt es sich dabei um das TreeView, ansonsten um ein TreeViewItem. Die Eigenschaft Header eines TreeViewItem-Objekts dient zur Anzeige des sichtbaren Eintrags, ist selbst aber vom Typ Object.
Um ein TreeViewItem-Element per Code zu selektieren, können Sie über die Eigenschaft Items unter Angabe des Index des TreeView das gewünschte Element per Code ansprechen und setzen dessen Eigenschaft IsSelected auf true, z. B.:
((TreeViewItem)treeView1.Items[0]).IsSelected = true;
Sehr ähnlich können Sie auch einen Knoten auf- und zuklappen. Dafür dient die Eigenschaft IsExpanded des TreeViewItem-Elements.
Der TreeView hat drei besonders erwähnenswerte Methoden: SelectedItem, SelectedValue und SelectedValuePath. Die Beschreibungen können Sie der folgenden Tabelle entnehmen.
Eigenschaft | Beschreibung |
Liefert das ausgewählte Element zurück. |
|
Ruft die Eigenschaft ab, die unter SelectedValuePath angegeben ist. |
|
Diese Eigenschaft gibt an, welche Eigenschaft beim Aufruf von SelectedValue zurückgeliefert werden soll. SelectedValuePath ist per Vorgabe leer und vom Datentyp string. |
Die Ereignisse im Umfeld des »TreeView«-Steuerelements
Mit zahlreichen Ereignissen lässt sich nicht nur das TreeView, sondern auch seine untergeordneten TreeViewItem-Elemente steuern. Alle im Einzelnen zu behandeln würde zusammen mit der schier endlosen Liste von Eigenschaften fast schon ein Buch für sich füllen. Aber lassen Sie mich an dieser Stelle ein wichtiges Ereignis des TreeView-Steuerelements erwähnen: SelectedItemChanged. Es wird immer dann ausgelöst, wenn der Anwender einen anderen Eintrag im TreeView selektiert.
Das Interessante an dem Ereignis sind zwei Eigenschaften im EventArgs-Parameter: OldValue und NewValue. Dabei liefern beide Eigenschaften ein Objekt zurück, das in den Typ TreeViewItem konvertiert werden muss. Darauf lässt sich anschließend mit der Eigenschaft Header die Beschriftung des Listeneintrags abrufen. Im folgenden Listing wird die Vorgehensweise gezeigt.
private void treeView1_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
TreeViewItem item = e.OldValue as TreeViewItem;
if(e.OldValue != null)
MessageBox.Show("Old:" + item.Header.ToString());
MessageBox.Show(("New:" + (e.NewValue as TreeViewItem).Header.ToString());
}
Listing 21.30 Das Ereignis »SelectedItemChanged« eines »TreeView«
Unter Umständen müssen Sie dabei berücksichtigen, dass im Moment der Ereignisauslösung noch kein Listenelement selektiert ist. Das würde dazu führen, dass e.OldValue den Inhalt null hat. Damit es zu keiner Ausnahme kommt, sollten Sie den Zustand der Eigenschaft OldValue vor der Auswertung dahingehend überprüfen.
Falls Sie in der TreeView-Klasse auch noch Ereignisse erwarten, die beim Auf- und Zuklappen eines Knotens ausgelöst werden, liegen Sie falsch. Solche Ereignisse gibt es, sie heißen Collapsed und Expanded und gehören zur Klasse TreeViewItem.
Beispielprogramm
Im folgenden Beispielprogramm werden einige der vorgestellten Eigenschaften und Methoden des TreeView-Steuerelements verwendet. Es enthält die in Listing 21.29 verwendeten Einträge und ergänzt diese. Im Fenster ist es möglich, neue Elemente zum TreeView hinzuzufügen oder das aktuell ausgewählte Element zu löschen. Bei einem Wechsel des selektierten Elements wird in einer TextBox das alte, in einer zweiten TextBox das neu ausgewählte Element angezeigt. Wird mit der rechten Maustaste auf einen Knoten geklickt, wird automatisch die diesem Element untergeordnete Struktur geöffnet.
Am schwierigsten gestaltet sich das Hinzufügen eines neuen Elements. Im Normalfall werden immer untergeordnete Elemente hinzugefügt. Allerdings muss auch dem Umstand Rechnung getragen werden, dass der TreeView möglicherweise leer ist oder ein Element zur ersten Hierarchieebene hinzugefügt werden soll. Das letztgenannte Problem wird mit einer CheckBox gelöst, das erste (TreeView enthält keine Elemente) durch Programmcode.
Die Benutzeroberfläche im komplett geöffneten Zustand sieht zur Laufzeit der Anwendung wie in Abbildung 21.16 gezeigt aus.
Abbildung 21.16 Ausgabe des Beispielprogramms »TreeViewSample«
// Beispiel: ..\Kapitel 21\TreeViewSample
<Window ...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TreeView Name="treeView1"
SelectedItemChanged="treeView1_SelectedItemChanged">
<TreeViewItem Header="Asien" MouseDown="TreeViewItem_MouseDown">
[...]
</TreeViewItem>
<TreeViewItem Header="Europa"
MouseDown="TreeViewItem_MouseDown">
[...]
</TreeViewItem>
</TreeView>
<Button Grid.Row="1" Grid.RowSpan="2" Name="btnDelete"
Click="btnDelete_Click" >Delete Item</Button>
<StackPanel Grid.Column="1">
<StackPanel Orientation="Horizontal">
<TextBox Name="txtNewItem" ></TextBox>
<Button Name="btnAdd" Click="btnAdd_Click">Add Item</Button>
</StackPanel>
<CheckBox Name="chkBox1" >
Element in der ersten Ebene
</CheckBox>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition/>
</Grid.RowDefinitions>
<Label Foreground="White">Vorheriges Element:</Label>
<TextBox Grid.Column="1" Name="txtOld"></TextBox>
<Label Foreground="White" Grid.Row="1" >Neues Element:</Label>
<TextBox Grid.Row="1" Grid.Column="1" Name="txtNew" />
</Grid>
</StackPanel>
</Grid>
</Window>
Dazu gehört der folgende C#-Code in der Code-Behind-Datei:
public MainWindow() {
InitializeComponent();
((TreeViewItem)treeView1.Items[0]).IsSelected = true;
}
private void treeView1_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e) {
TreeViewItem itemOld = e.OldValue as TreeViewItem;
TreeViewItem itemNew = e.NewValue as TreeViewItem;
if (e.OldValue != null) {
txtOld.Text = itemOld.Header.ToString();
txtNew.Text = itemNew.Header.ToString();
}
}
private void TreeViewItem_MouseDown(object sender, MouseButtonEventArgs e) {
TreeViewItem tvItem = sender as TreeViewItem;
if (tvItem != null) {
if (e.RightButton == MouseButtonState.Pressed) {
tvItem.IsExpanded = true;
Expand(tvItem);
}
}
}
private void Expand(TreeViewItem item) {
foreach (TreeViewItem node in item.Items) {
node.IsExpanded = true;
if (node.Items.Count > 0) Expand(node);
}
}
private void btnAdd_Click(object sender, RoutedEventArgs e) {
if (treeView1.Items.Count == 0 || chkBox1.IsChecked == true) {
treeView1.Items.Add(new TreeViewItem { Header = txtNewItem.Text });
return;
}
TreeViewItem selectedItem = (TreeViewItem)treeView1.SelectedItem;
if (selectedItem != null)
selectedItem.Items.Add(new TreeViewItem { Header = txtNewItem.Text });
}
private void btnDelete_Click(object sender, RoutedEventArgs e) {
TreeViewItem del = treeView1.SelectedItem as TreeViewItem;
if (del != null) {
TreeViewItem parent = del.Parent as TreeViewItem;
if (parent != null)
parent.Items.Remove(del);
else
treeView1.Items.Remove(del);
}
}
Listing 21.31 Der XAML- und C#-Code des Beispielprogramms »TreeViewSample«
21.5.5 Das Steuerelement »TabControl«
In vielen Anwendungen werden gruppierte Inhalte durch TabControl-Steuerelemente dargestellt. Das entsprechende WPF-Steuerelement ist erstaunlich einfach zu erstellen. Jede Registerkarte wird durch ein TabItem-Element beschrieben. Die Beschriftung wird mit der Eigenschaft Header festgelegt. Für die Darstellung des Inhalts dient die Eigenschaft Content, die genau ein Element aufnehmen kann. Wie Sie aber wissen, kann es sich dabei um ein Container-Steuerelement handeln, so dass der Gestaltung des Registerkarteninhalts kaum Grenzen gesetzt sind.
Oft sind die Registerkarten oben angeordnet. Mit der Eigenschaft TabStripPlacement ist es möglich, diese auch links, rechts oder unten anzuordnen. Soll die Beschriftung dabei auch noch entsprechend gedreht werden, bietet sich die Eigenschaft LayoutTransform an, der mit RotateTransform ein Element untergeordnet wird, das den Drehwinkel im Uhrzeigersinn beschreibt.
Das folgende Codefragment zeigt ein TabControl, dessen Tabs rechts angeordnet sind. Die Beschriftung der einzelnen Tabs ist um 90° gedreht.
<Grid>
<TabControl TabStripPlacement="Right">
<TabItem Header="Tab 1" Height="30">
<TabItem.LayoutTransform>
<RotateTransform Angle="90" />
</TabItem.LayoutTransform>
<TabItem.Content>
Hier steht der Inhalt der 1. Registerkarte.
</TabItem.Content>
</TabItem>
<TabItem Header="Tab 2">
<TabItem.LayoutTransform>
<RotateTransform Angle="90" />
</TabItem.LayoutTransform>
<TabItem.Content>
Hier steht der Inhalt der 2. Registerkarte.
</TabItem.Content>
</TabItem>
<TabItem Header="Tab 3">
<TabItem.LayoutTransform>
<RotateTransform Angle="90" />
</TabItem.LayoutTransform>
<TabItem.Content>
Hier steht der Inhalt der 3. Registerkarte.
</TabItem.Content>
</TabItem>
</TabControl>
</Grid>
Listing 21.32 Das »TabControl« im XAML-Code
Abbildung 21.17 Das Steuerelement »TabControl«
21.5.6 Die Menüleiste
Bisher haben wir uns mit den wichtigsten Steuerelementen beschäftigt, die mehr oder weniger einfach zu erstellen sind. Es ist nun an der Zeit, uns mit einem etwas komplexeren Steuerelement auseinanderzusetzen – mit der Menüleiste.
Die Menüleiste wird durch die Klasse Menu beschrieben, die untergeordneten Menüpunkte durch MenuItem. Trennstriche werden durch Separator beschrieben. Die Struktur eines Menüs sehen wir uns am besten an einem Beispiel an.
<DockPanel>
<Menu DockPanel.Dock="Top" Name="mnuMenu">
<MenuItem Header="_Datei">
<MenuItem Header="_Neu" />
<MenuItem Header="_Öffnen" />
<Separator />
<MenuItem Header="_Speichern" />
<MenuItem Header="Speichern _unter ..." />
<Separator />
<MenuItem Header="_Senden an">
<MenuItem Header="_Mail" />
<MenuItem Header="_Desktop" />
</MenuItem>
<MenuItem Header="_Beenden" />
</MenuItem>
<MenuItem Header="_Bearbeiten" />
<MenuItem Header="_Hilfe" />
</Menu>
<StackPanel>
</StackPanel>
</DockPanel>
Listing 21.33 Menüleiste mit Untermenüs im XAML-Code
Menüs werden meistens oben am Rand des Arbeitsbereichs des Windows verankert. Dazu bietet sich das DockPanel an. Im Menü selbst wird die Eigenschaft DockPanel.Dock auf Top festgelegt. Um den verbleibenden Bereich auszufüllen, ist im XAML-Code nach Menu noch ein StackPanel aufgeführt.
In der ersten dem Menu-Steuerelement untergeordneten Ebene sind alle Elemente des Hauptmenüs aufgeführt. Diese sind vom Typ MenuItem. Jedes MenuItem kann für sich wieder eine ihm selbst untergeordnete Ebene eröffnen. Eingeschlossen wird eine Ebene jeweils zwischen dem öffnenden und dem schließenden Tag von MenuItem. Die Beschriftung der Menüelemente erfolgt mit der Eigenschaft Header. Wie bei anderen Steuerelementen auch kann mit einem Unterstrich ein Access-Key festgelegt werden. In sehen Sie die Ausgabe des XAML-Codes.
Abbildung 21.18 Ein Menü mit Untermenü in einem »Window«
Zur Programmierung eines Menüelements ist nicht viel Neues zu sagen. Hier handelt es sich um das Ereignis Click, das ausgelöst wird, wenn ein Anwender auf das Menüelement klickt.
Weitere Möglichkeiten der Menüleiste
Die Klasse MenuItem stellt mit vielen Eigenschaften Möglichkeiten zur Verfügung, Einfluss auf das Layout auszuüben. Vier davon sollen an dieser Stelle vorgestellt werden.
Eigenschaft | Beschreibung |
Icon |
Legt das Symbol fest, das in einem MenuItem angezeigt wird. |
IsCheckable |
Gibt an, ob ein MenuItem aktiviert werden kann. |
IsChecked |
Gibt an, ob das MenuItem aktiviert ist. |
Beschreibt die Tastenkombination. |
Symbole anzeigen
Um einem Menüelement ein Symbol zuzuordnen, können Sie über die Eigenschaft Icon ein Bild zuordnen. Benutzen Sie dazu ein Image-Element, und geben Sie dessen Attribut Source die Position zu einer Bilddatei an. Im folgenden Codeabschnitt sehen Sie die Ergänzung der oben gezeigten Menüleiste um zwei Symbole:
<DockPanel>
<Menu DockPanel.Dock="Top" Name="mnuMenu">
<MenuItem Header="_Datei">
<MenuItem Header="_Neu" />
<MenuItem Header="_Öffnen">
<MenuItem.Icon>
<Image Source="Images/openHS.png" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="_Speichern">
<MenuItem.Icon>
<Image Source="Images/saveHS.png" />
</MenuItem.Icon>
</MenuItem>’
[...]
Listing 21.34 Menüelement mit Symbol
Tastenkürzel
Tastenkürzel mit der -Taste werden durch einen Unterstrich kenntlich gemacht. Um einen Shortcut zu verwenden, müssen Sie diese Angabe über die Eigenschaft InputGestureText zuweisen:
<MenuItem Header="_Öffnen" InputGestureText="Strg+O">
<MenuItem.Icon>
<Image Source="Images/openHS.png" />
</MenuItem.Icon>
</MenuItem>
Listing 21.35 Menüelement mit Shortcut
Abbildung 21.19 Untermenü mit Symbolen und Shortcuts
Aktivierbare Menüelemente
Manche Menüelemente sind Ein/Aus-Schaltern ähnlich. Sie signalisieren ihren augenblicklichen Zustand durch ein Häkchen. Damit ähneln Sie in gewisser Hinsicht einer CheckBox. Die Voraussetzung für dieses Verhalten wird in einem WPF-Menü durch die Eigenschaft IsCheckable geschaffen. Mit IsChecked können Sie darüber hinaus festlegen, ob die Option des Menüelements ausgewählt ist oder nicht.
<MenuItem Header="Schriftstil">
<MenuItem Header="Fett" IsCheckable="True" IsChecked="True" />
<MenuItem Header="Kursiv" IsCheckable="True" IsChecked="False" />
</MenuItem>
Listing 21.36 Menüelement mit CheckBox-Verhalten
Um ein Menüelement zu aktivieren bzw. zu deaktivieren, dient die Eigenschaft IsEnabled. Legen Sie diese auf False fest, um das Menüelement zu deaktivieren.
21.5.7 Das Kontextmenü
Kontextmenüs ähneln der eben vorgestellten Menüleiste. Im einfachsten Fall wird ein Kontextmenü direkt einem Steuerelement zugeordnet. Die Zuordnung erfolgt mit der Eigenschaft ContextMenu des betreffenden Steuerelements. Im folgenden Beispiel wird das Kontextmenü eines Buttons entwickelt:
<Button Name="Button1" Height="25" Content="Kontextdemo">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Name="cMenu1" Header="Kopieren" />
<MenuItem Name="cMenu2" Header="Ausschneiden" />
<MenuItem Name="cMenu3" Header="Einfügen" />
</ContextMenu>
</Button.ContextMenu>
</Button>
Listing 21.37 Bereitstellen eines Kontextmenüs
Jedes Menüelement wird, wie auch bei der Menüleiste, durch ein Objekt vom Typ MenuItem beschrieben. Mit der Eigenschaft Header wird die Beschriftung festgelegt.
Häufig wird ein Kontextmenü von mehreren Steuerelementen gleichermaßen benutzt. Sie sollten die Definition des Kontextmenüs dann an einem allgemeinen Ort beschreiben. Hier bietet sich der Resources-Abschnitt des Window oder der Application an.
In Kapitel 23 gehen wir noch detailliert auf den Ressourcen-Abschnitt eines Fenster bzw. des Application-Objekts ein. Nur so viel sei an dieser Stelle bereits verraten: Dieser Abschnitt bietet noch viel mehr Möglichkeiten als die hier gezeigten im Zusammenhang mit dem Kontextmenü.
Um ContextMenu eindeutig einem bestimmten Steuerelement zuordnen zu können, muss ein eindeutiger Identifizierer mit dem Attribut Key festgelegt werden, über den das Steuerelement sein zugeordnetes ContextMenu-Element identifizieren kann.
<Window ...>
<Window.Resources>
<ContextMenu x:Key="contextMenu1">
<MenuItem Header="_Ausschneiden">
<MenuItem.Icon>
<Image Source="Images/CutHS.png" Height="16" Width="16" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Kopieren">
<MenuItem.Icon>
<Image Source="Images/CopyHS.png" Height="16" Width="16" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Einfügen" Click="Event_EditPaste">
<MenuItem.Icon>
<Image Source="Images/PasteHS.png" Height="16" Width="16" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Window.Resources>
Listing 21.38 Kontextmenü im Abschnitt »Window.Resources«
Die Zuordnung des im Resources-Abschnitt definierten Kontextmenüs erfolgt durch Zuweisung als statische Ressource an die Eigenschaft ContextMenu des Steuerelements.
Das Kontextmenü kann mit Hilfe der Klasse ContextMenuService hinsichtlich Verhalten, Positionierung und Layout beeinflusst werden. Die Eigenschaft HasDropShadow legt beispielsweise fest, ob das Kontextmenü einen Schatteneffekt zeigen soll, und Placement, wo das Kontextmenü erscheinen soll. Mit HorizontalOffset und VerticalOffset können Sie festlegen, wo das Kontextmenü relativ zu seinem Steuerelement angezeigt werden soll.
Der folgende XAML-Code zeigt die Einbindung eines im Resources-Abschnitt definierten Kontextmenüs. Hier wird eine TextBox als Elternelement festgelegt und zudem demonstriert, wie ContextMenuService zur spezifischen Beschreibung eingesetzt wird.
<DockPanel>
<TextBox ContextMenu="{StaticResource contextMenu1}"
ContextMenuService.HasDropShadow="True"
ContextMenuService.Placement="Mouse" />
</DockPanel>
Listing 21.39 Einbindung der Ressource für das Kontextmenü
Beachten Sie, dass wir hier mit einer Markup-Erweiterung auf den Resources-Abschnitt zugreifen. Die Syntax der Einbindung mit StaticResource sollten Sie an dieser Stelle einfach nur zur Kenntnis nehmen. Auch darauf werden wir in Kapitel 23 eingehen.
21.5.8 Symbolleisten
Auch wenn es im ersten Moment den Anschein haben mag, dass Symbolleisten zu den komplexen WPF-Steuerelementen zu rechnen sind, ist dem nicht so. Eigentlich handelt es sich dabei nur um einen Container, der durch das Element ToolBar beschrieben wird und andere Controls beherbergt.
Eine ToolBar kann beliebige andere Steuerelemente aufnehmen und anzeigen, aber meistens handelt es sich dabei um Elemente vom Typ Button. Üblicherweise wird eine Symbolleiste unterhalb der Menüleiste angedockt. Als Container wird daher in der WPF ein DockPanel eingesetzt, dessen oberstes Element das Menu ist, gefolgt von der Symbolleiste.
<Window ...>
<DockPanel>
<Menu DockPanel.Dock="Top" Name="mnuMenu">
[...]
</Menu>
<ToolBar DockPanel.Dock="Top" Height="30">
<Button>
<Image Source="Images/openHS.png" />
</Button>
<Button>
<Image Source="Images/saveHS.png" />
</Button>
<Separator />
<ComboBox Width="80" SelectedIndex="0">
<ComboBoxItem>Arial</ComboBoxItem>
<ComboBoxItem>Courier</ComboBoxItem>
<ComboBoxItem>Windings</ComboBoxItem>
</ComboBox>
</ToolBar>
<StackPanel>
</StackPanel>
</DockPanel>
</Window>
Listing 21.40 Fenster mit »DockPanel«, »Menu« und »ToolBar«
Beim Verkleinern des Fensters könnte es passieren, dass die Fensterbreite nicht mehr ausreicht, um alle in einer ToolBar enthaltenen Elemente anzuzeigen. Es wird dann ein Überlaufbereich erzeugt, an dessen Ende eine Schaltfläche mit einem Pfeil angezeigt wird. Über diese Schaltfläche lässt sich ein Menü aufklappen, in dem die nicht mehr darstellbaren Elemente angezeigt werden.
Einzelnen Steuerelementen kann das Überlaufverhalten vorgeschrieben werden. Dazu wird der zugeordneten Eigenschaft OverflowMode ein Wert der gleichnamigen Enumeration übergeben.
Member | Beschreibung |
Always |
Das Steuerelement wird immer im Überlaufbereich angezeigt. |
AsNeeded |
Das Steuerelement wird bei Bedarf im Überlaufbereich angezeigt. |
Never |
Das Steuerelement wird nie im Überlaufbereich angezeigt. |
Der folgende Code beschreibt eine Symbolleiste mit drei ComboBoxen. Jeder ist eine andere Einstellung der Eigenschaft OverflowMode zugewiesen.
<ToolBar Height="30">
<Button>
<Image Source="Images/openHS.png" />
</Button>
<Button>
<Image Source="Images/saveHS.png" />
</Button>
<Separator />
<ComboBox Width="80" SelectedIndex="0"
ToolBar.OverflowMode="Always">
<ComboBoxItem>Arial</ComboBoxItem>
<ComboBoxItem>Courier</ComboBoxItem>
<ComboBoxItem>Windings</ComboBoxItem>
</ComboBox>
<ComboBox Width="80" SelectedIndex="0"
ToolBar.OverflowMode="AsNeeded">
<ComboBoxItem>Bonn</ComboBoxItem>
<ComboBoxItem>München</ComboBoxItem>
<ComboBoxItem>Nürnberg</ComboBoxItem>
</ComboBox>
<ComboBox Width="80" SelectedIndex="0"
ToolBar.OverflowMode="Never">
<ComboBoxItem>Test1</ComboBoxItem>
<ComboBoxItem>Test2</ComboBoxItem>
<ComboBoxItem>Test3</ComboBoxItem>
</ComboBox>
</ToolBar>
Listing 21.41 Die Einstellung »ToolBar.OverflowMode«
In Abbildung 21.20 sind die Auswirkungen deutlich zu erkennen. Das Kombinationslistenfeld mit der Einstellung OverflowMode=Always ist auch dann nur über die Dropdown-Schaltfläche in der Symbolleiste zu erreichen, wenn die Breite der Form eigentlich zur Darstellung ausreichen würde. Wird die Fensterbreite verringert, wird nur noch die ComboBox in der Symbolleiste angezeigt, deren Einstellung OverflowMode=Never lautet.
Abbildung 21.20 Der Einfluss der Eigenschaft »OverflowMode«
Positionieren mit der Komponente »ToolBarTray«
Möchten Sie mehrere ToolBars in einer Form anzeigen, bietet sich die Komponente ToolBarTray an. Dabei handelt es sich um einen Container, der das Positionieren aller darin enthaltenen ToolBars steuert. Mit einer ToolBarTray-Komponente wird es möglich, Symbolleisten hintereinander oder in mehreren Reihen anzuzeigen und per Drag & Drop zu verschieben.
Zu diesem Zweck stellt das ToolBar-Steuerelement mit Band und BandIndex zwei Eigenschaften zur Verfügung, die sich auf die Positionierung im ToolBarTray auswirken. Mit Band geben Sie an, in welcher Zeile die ToolBar erscheinen soll. Mit BandIndex legen Sie deren Position innerhalb der Zeile fest, wenn die Zeile von mehreren ToolBars in Anspruch genommen wird.
<ToolBarTray DockPanel.Dock="Top" IsLocked="False">
<ToolBar Height="30" Band="0" BandIndex="0">
[...]
</ToolBar>
<ToolBar Height="30" Band="0" BandIndex="1">
[...]
</ToolBar>
<ToolBar Height="30" Band="1" BandIndex="0">
[...]
</ToolBar>
<ToolBar Height="30" Band="1" BandIndex="1">
[...]
</ToolBar>
</ToolBarTray>
Listing 21.42 Die Eigenschaften »Band« und »BandIndex« einstellen
Die Einstellungen wirken sich auf die Darstellung der ToolBars nach dem Starten des Fensters aus. Zur Laufzeit kann der Anwender die Position nach Belieben per Drag & Drop verändern.
Abbildung 21.21 Zwei Symbolleisten in der Komponente »ToolBarTray«
21.5.9 Die Statusleiste
Die meist unten im Window angezeigten Statusleisten informieren den Anwender über den Zustand des laufenden Programms. WPF stellt Ihnen mit StatusBar eine Komponente zur Verfügung, mit der Sie das umsetzen können.
Sie können in die StatusBar beliebige Komponenten einfügen, z. B. TextBox oder Label. Besser ist es allerdings, stattdessen mit StatusBarItem-Elementen Bereiche zu definieren, in die die Komponenten eingebettet sind. Das ermöglicht es Ihnen, die Ausrichtung der Komponenten einfach zu gestalten. Dazu bietet sich die Eigenschaft HorizontalAlignment oder auch VerticalAlignment an.
<DockPanel>
<Menu DockPanel.Dock="Top" Name="mnuMenu">
[...]
</Menu>
<ToolBarTray DockPanel.Dock="Top" IsLocked="False">
<ToolBar Height="30" BandIndex="0" Band="0">
[...]
</ToolBar>
</ToolBarTray>
<StatusBar DockPanel.Dock="Bottom" Height="30">
<Button Width="80">Start</Button>
<Label>Suchen:</Label>
<StatusBarItem Width="100" HorizontalContentAlignment="Stretch">
<TextBox>Suchbegriff</TextBox>
</StatusBarItem>
<Separator />
<StatusBarItem HorizontalAlignment="Right">
Anzahl: 2</StatusBarItem>
</StatusBar>
<StackPanel>
</StackPanel>
</DockPanel>
Listing 21.43 Statusleiste in einem Fenster
Abbildung 21.22 Fenster mit Statusleiste
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.