TOC

This article is currently in the process of being translated into French (~57% done).

The TreeView control:

TreeView - Selection/Expansion state

Dans les articles précédents à propos de la vue arborescente, nous avons utilisé le binding de données pour afficher des objets personnalisés dans une vue arborescente WPF. Cela marche très bien mais il reste un problème: comme chaque nœud est représenté par une classe personnalisée, par exemple FamilyMember comme nous l'avons vu dans l'article précédent, vous n'avez plus directement le contrôle sur les fonctionnalités spécifiques du nœud comme la sélection ou l'état étendu ou non. En pratique, cela signifie qu'on peut sélectionner ou développer/réduire un nœud choisi à partir du code-behind.

Beaucoup de solution existent pour gérer cela, depuis des "hacks" pour lesquels on utilise des générateurs d'items de la vue pour accéder à l'item sous-jacent où il est possible de contrôler les propriétés IsExpanded et IsSelected, jusqu'à des codages plus avancés inspirés du MVVM. Dans cet article, je voudrais vous montrer une solution qui se place vers le milieu, rendant l'implémentation et l'utilisation facile tout en étant pas totalement un hack.

Une solution pour sélection/étendre une vue arborescente

Le principe de base est d'implémenter 2 propriétés supplémentaires dans la classe: IsExpanded et IsSelected. C'est deux propriétés sont ensuite reliées à la vue en utilisant des styles ciblant le TreeViewItem, à l'intérieur du ItemContainerStyle pour la vue.

On pourrait simplement implémenter ces 2 propriétés pour chacun de nos objets, mais c'est plus simple de les hériter d'un objet de base. Si cela n'est pas faisable pour votre solution, vous pouvez créer une interface et ensuite l'implémenter à la place pour créer une base commune. Pour cet exemple, j'ai choisi la méthode de la classe de base car elle me permets d'obtenir les même fonctionnalités pour mes autres objets simplement. Voici le 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));
		}
	}
}

Je m'excuse pour la longueur de ce code à un seul endroit. Dans une vrai solution, ce code serait bien entendu découpé dans plusieurs fichier et les données pour l'arbre viendraient d'une vraie source de données au lieu d'être générées en direct. Permettez moi de vous expliquer ce qu'il se passe dans cet exemple.

Partie XAML

J'ai défini des boutons à placer à la fin de la fenêtre de dialogue pour utiliser les deux nouvelles propriétés. Ensuite on a la vue pour laquelle j'ai défini un ItemTemplate (comme vu précédemment) ainsi qu'un ItemContainerStyle. Si vous n'avez pas encore lu les chapitres sur le style, vous n'allez pas complètement comprendre cette partie, mais c'est seulement une question de liens entre les propriétés de notre classe personnalisée avec les propriétés IsSelected et IsExpanded de la vue, qui sont fait à l'aide de style setters. Vous pouvez en apprendre plus ailleurs dan ce tutoriel.

Code-behind part

In the code-behind, I have defined a Person class, with a couple of properties, which inherits our extra properties from the TreeViewItemBase class. You should be aware that the TreeViewItemBase class implements the INotifyPropertyChanged interface and uses it to notify of changes to these two essential properties - without this, selection/expansion changes won't be reflected in the UI. The concept of notification changes are explained in the Data binding chapters.

In the main Window class I simply create a range of persons, while adding children to some of them. I add the persons to a list, which I assign as the ItemsSource of the TreeView, which, with a bit of help from the defined template, renders them the way they are shown on the screenshot.

The most interesting part happens when I set the IsExpanded and IsSelected properties on the person2 object. This is what causes the second person (Jane Doe) to be initially selected and expanded, as shown on the screenshot. We also use these two properties on the Person objects (inherited from the TreeViewItemBase class) in the event handlers for the two test buttons (please bear in mind that, to keep the code as small and simple as possible, the selection button only works for the top level items).

Summary

By creating and implementing a base class for the objects that you wish to use and manipulate within a TreeView, and using the gained properties in the ItemContainerStyle, you make it a lot easier to work with selections and expansion states. There are many solutions to tackle this problem with, and while this should do the trick, you might be able to find a solution that fits your needs better. As always with programming, it's all about using the right tool for the job at hand.

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!