This article has been localized into German by the community.
TreeView - Auswahl-/Expansionszustand
In den vorherigen TreeView-Artikeln haben wir die Datenbindung verwendet, um benutzerdefinierte Objekte in einem WPF-TreeView anzuzeigen. Das funktioniert wirklich gut, aber wir haben dabei ein kleines Problem: Da jeder Baumknoten jetzt durch seine benutzerdefinierte Klasse repräsentiert wird, zum Beispiel FamilyMember, wie im vorherigen Artikel, haben wir keine direkte Kontrolle über die knotenspezifischen Funktionen des TreeView, wie Auswahl und Expansionszustand. In der Praxis bedeutet dies, dass wir einen bestimmten Knoten nicht aus Code-Behind auswählen können.
Für diese Problemstellung existieren eine Menge von Lösungen. Angefangen von "Hacks", bei denen die Generatoren von TreeView Knoten verwendet werden, um das zugrundeliegende TreeViewItem zu erhalten, mit dem wir die IsExpanded- und IsSelected-Eigenschaften eines Knotens steuern können, bis hin zu wesentlich fortgeschritteneren MVVM-inspirierten Implementierungen. In diesem Artikel möchten wir Ihnen eine Lösung zeigen, die irgendwo in der Mitte liegt, so dass sie leicht zu implementieren und zu verwenden ist, während sie immer noch kein vollständiger Hack ist.
Eine Lösung für das Aus- und Einklappen eines Treeview.
Das Grundprinzip besteht darin, zwei zusätzliche Eigenschaften für Ihre Datenklasse zu implementieren: IsExpanded und IsSelected. Diese beiden Eigenschaften werden dann an das TreeView angehängt, wobei einige Stile für das TreeViewItem innerhalb des ItemContainerStyle für die TreeView verwendet werden.
Sie können diese beiden Eigenschaften problemlos für alle Ihre Objekte implementieren, aber es ist viel einfacher, sie von einem Basisobjekt zu übernehmen. Wenn dies für Ihre Lösung nicht durchführbar ist, könnten Sie eine Schnittstelle, ein sog. Interface, dafür erstellen und diese dann implementieren, um eine gemeinsame Grundlage zu schaffen. In diesem Beispiel habe ich die Basisklassenmethode gewählt, weil ich dadurch sehr einfach die gleiche Funktionalität für meine anderen Objekte erhalten kann. Hier ist der Code:
<Window x:Class="WpfTutorialSamples.TreeView_control.TreeViewSelectionExpansionSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TreeViewSelectionExpansionSample" Height="200" Width="300">
<DockPanel Margin="10">
<WrapPanel Margin="0,10,0,0" DockPanel.Dock="Bottom" HorizontalAlignment="Center">
<Button Name="btnSelectNext" Click="btnSelectNext_Click" Width="120">Select next</Button>
<Button Name="btnToggleExpansion" Click="btnToggleExpansion_Click" Width="120" Margin="10,0,0,0">Toggle expansion</Button>
</WrapPanel>
<TreeView Name="trvPersons">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="/WpfTutorialSamples;component/Images/user.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" Margin="0,0,4,0" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Controls;
namespace WpfTutorialSamples.TreeView_control
{
public partial class TreeViewSelectionExpansionSample : Window
{
public TreeViewSelectionExpansionSample()
{
InitializeComponent();
List<Person> persons = new List<Person>();
Person person1 = new Person() { Name = "John Doe", Age = 42 };
Person person2 = new Person() { Name = "Jane Doe", Age = 39 };
Person child1 = new Person() { Name = "Sammy Doe", Age = 13 };
person1.Children.Add(child1);
person2.Children.Add(child1);
person2.Children.Add(new Person() { Name = "Jenny Moe", Age = 17 });
Person person3 = new Person() { Name = "Becky Toe", Age = 25 };
persons.Add(person1);
persons.Add(person2);
persons.Add(person3);
person2.IsExpanded = true;
person2.IsSelected = true;
trvPersons.ItemsSource = persons;
}
private void btnSelectNext_Click(object sender, RoutedEventArgs e)
{
if(trvPersons.SelectedItem != null)
{
var list = (trvPersons.ItemsSource as List<Person>);
int curIndex = list.IndexOf(trvPersons.SelectedItem as Person);
if(curIndex >= 0)
curIndex++;
if(curIndex >= list.Count)
curIndex = 0;
if(curIndex >= 0)
list[curIndex].IsSelected = true;
}
}
private void btnToggleExpansion_Click(object sender, RoutedEventArgs e)
{
if(trvPersons.SelectedItem != null)
(trvPersons.SelectedItem as Person).IsExpanded = !(trvPersons.SelectedItem as Person).IsExpanded;
}
}
public class Person : TreeViewItemBase
{
public Person()
{
this.Children = new ObservableCollection<Person>();
}
public string Name { get; set; }
public int Age { get; set; }
public ObservableCollection<Person> Children { get; set; }
}
public class TreeViewItemBase : INotifyPropertyChanged
{
private bool isSelected;
public bool IsSelected
{
get { return this.isSelected; }
set
{
if(value != this.isSelected)
{
this.isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
private bool isExpanded;
public bool IsExpanded
{
get { return this.isExpanded; }
set
{
if(value != this.isExpanded)
{
this.isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Wir bitten um Entschuldigung für die ziemlich große Menge an Code an einem Ort. In einer realen Lösung würde er sich offensichtlich auf mehrere Dateien verteilen, und die Daten für den Baum würden wahrscheinlich aus einer tatsächlichen Datenquelle stammen, anstatt direkt generiert zu werden. Erlauben Sie uns, zu erklären, was im Beispiel passiert.
Der XAML Teil
Wir haben einige Schaltflächen definiert, die am unteren Rand des Dialogfelds platziert werden, um die zwei neuen Eigenschaften zu verwenden. Dann haben wir die TreeView, für die wir ein ItemTemplate definiert haben (wie in einem vorherigen Kapitel gezeigt), sowie einen ItemContainerStyle. Wenn Sie die Kapitel zum Thema Styling noch nicht gelesen haben, verstehen Sie diesen Teil möglicherweise nicht vollständig. Es geht jedoch einfach darum, die Eigenschaften in unserer eigenen benutzerdefinierten Klasse mit den IsSelected und IsExpanded Eigenschaften in den TreeViewItems zu verknüpfen, die mit Style-Settern erledigt werden. Sie können an anderer Stelle in diesem Lernprogramm mehr über sie erfahren.
Der Code-Behind Teil
Im Code-Behind haben wir eine Klasse Person mit einigen Eigenschaften definiert, die unsere zusätzlichen Eigenschaften von der TreeViewItemBase -Klasse erbt. Sie sollten sich darüber im Klaren sein, dass die TreeViewItemBase-Klasse die INotifyPropertyChanged-Schnittstelle implementiert und sie verwendet, um über Änderungen an diesen beiden wesentlichen Eigenschaften zu benachrichtigen - ohne dass Änderungen an der Auswahl / Erweiterung nicht auf der Benutzeroberfläche angezeigt werden. Das Konzept der Benachrichtigungsänderungen wird in den Kapiteln zu den Datenbindungen erläutert.
In der Hauptfensterklasse erstellen wir einfach eine Reihe von Personen, während wir einigen von ihnen Kinder hinzufügen. Wir fügen die Personen zu einer Liste hinzu, die wir als ItemsSource des TreeViews anwählen, die diese mit ein wenig Hilfe von der definierten Vorlage so darstellt, wie sie auf dem Screenshot angezeigt werden.
Der interessanteste Teil passiert, wenn wir die Eigenschaften IsExpanded und IsSelected für das Objekt person2 festlegen. Dies bewirkt, dass die zweite Person (Jane Doe) zunächst ausgewählt und erweitert wird, wie auf dem Screenshot gezeigt. Wir verwenden diese beiden Eigenschaften auch für die Person-Objekte (geerbt von der TreeViewItemBase-Klasse) in den Ereignisbehandlungsroutinen für die beiden Testschaltflächen (bitte beachten Sie, dass die Auswahltaste nur für die Top-Level-Elemente funktioniert, um den Code so klein und einfach wie möglich zu halten).
Zusammenfassung
Durch das Erstellen und Implementieren einer Basisklasse für die Objekte, die Sie in einem TreeView verwenden und bearbeiten möchten und durch die Verwendung der erzeugten Eigenschaften im ItemContainerStyle, ist es wesentlich einfacher, mit Selektionen und Erweiterungszuständen zu arbeiten. Es gibt viele Lösungen, um dieses Problem anzugehen. Während unsere Lösung für den Moment funktionieren sollte können Sie möglicherweise eine Lösung finden, die Ihren Anforderungen besser entspricht. Wie immer bei der Programmierung geht es darum, das richtige Werkzeug für die jeweilige Aufgabe zu verwenden.