TOC

This article has been localized into Czech by the community.

Ovládací prvek TreeView:

TreeView - výběr/rozbalení

V předchozích několika článcích o TreeView jsme použili datovou vazbu (data binding) k zobrazení vlastních objektů ve WPF TreeView. To funguje velmi dobře, ale zanechává to jeden problém: Protože každý uzel stromu je nyní reprezentován vaší vlastní třídou, například FamilyMember, jak jsme viděli v předchozím článku, už nemáte přímou kontrolu nad specifickou funkcionalitou uzlu TreeView, jako je stav výběru a rozbalení. V praxi to znamená, že nemůžete z kódu v pozadí vybrat nebo rozbalit/sbalit daný uzel.

Existuje mnoho řešení, jak se s tímto problémem vypořádat, od "hacků", kde používáte generátory položek TreeView k získání podkladového TreeViewItem, kde můžete ovládat vlastnosti IsExpanded a IsSelected, až po mnohem pokročilejší implementace inspirované MVVM. V tomto článku bych vám rád ukázal řešení, které leží někde uprostřed, což usnadňuje jeho implementaci a použití, zatímco stále to není úplný hack.

Řešení pro výběr/rozbalení TreeView

Základní princip spočívá v implementaci dvou dalších vlastností ve vaší datové třídě: IsExpanded a IsSelected. Tyto dvě vlastnosti jsou poté propojeny s TreeView, použitím několika stylů cílících na TreeViewItem, uvnitř ItemContainerStyle pro TreeView.

Tyto dvě vlastnosti můžete snadno implementovat na všechny vaše objekty, ale mnohem jednodušší je zdědit je od základního objektu. Pokud to pro vaše řešení není proveditelné, můžete pro to vytvořit rozhraní a poté toto implementovat, aby byl zajištěn společný základ. Pro tento příklad jsem si vybral metodu základní třídy, protože mi to umožňuje velmi snadno získat stejnou funkcionalitu pro mé další objekty. Zde je kód:

<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));
		}
	}
}

Omlouvám se za poměrně velké množství kódu na jednom místě. Ve skutečném řešení by bylo samozřejmě rozděleno do několika souborů a data pro strom by pravděpodobně pocházela z reálného zdroje dat, místo aby byla generována za běhu. Dovolte mi vysvětlit, co se v příkladu děje.

XAML část

Definoval jsem několik tlačítek, která mají být umístěna na spodku dialogu, pro použití dvou nových vlastností. Pak máme TreeView, pro které jsem definoval ItemTemplate (jak bylo ukázáno v předchozí kapitole) stejně jako ItemContainerStyle. Pokud jste ještě nečetli kapitoly o stylování, možná nebudete úplně rozumět této části, ale jedná se jednoduše o propojení vlastností naší vlastní uživatelské třídy s vlastnostmi IsSelected a IsExpanded na TreeViewItems, což je provedeno pomocí nastavovačů (setterů) stylů. O nich se můžete dozvědět více v jiné části tohoto tutoriálu.

Code-behind část

V kódu v pozadí jsem definoval třídu Person s několika vlastnostmi, která dědí naše extra vlastnosti od třídy TreeViewItemBase. Měli byste vědět, že třída TreeViewItemBase implementuje rozhraní INotifyPropertyChanged a používá jej k oznámení změn těchto dvou zásadních vlastností - bez toho změny výběru/rozbalení nebudou odráženy v uživatelském rozhraní. Koncept oznámení změn je vysvětlen v kapitolách o datové vazbě.

Ve třídě hlavního okna jednoduše vytvořím řadu osob a některým z nich přidám potomky. Osoby přidám do seznamu, který přiřadím jako ItemsSource TreeView, které s trochou pomoci od definované šablony je zobrazí způsobem, jakým jsou vidět na snímku obrazovky.

Nejzajímavější část nastane, když nastavím vlastnosti IsExpanded a IsSelected na objektu person2. To způsobí, že druhá osoba (Jane Doe) bude původně vybrána a rozbalena, jak je vidět na snímku obrazovky. Tyto dvě vlastnosti také používáme na objektech Person (zděděných od třídy TreeViewItemBase) v obslužných rutinách pro dvě testovací tlačítka (mějte prosím na paměti, že abychom kód udrželi co nejjednodušší a nejmenší, tlačítko výběru funguje pouze pro položky na nejvyšší úrovni).

Shrnutí

Vytvořením a implementací základní třídy pro objekty, které chcete používat a s nimi manipulovat v TreeView, a použitím získaných vlastností v ItemContainerStyle, usnadníte práci s výběry a stavy rozbalení. Existuje mnoho řešení, jak tento problém řešit, a i když by toto mělo fungovat, možná najdete řešení, které bude lépe vyhovovat vašim potřebám. Jak je to v programování obvyklé, jde o použití správného nástroje pro danou práci.


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!