TOC

This article has been localized into Russian by the community.

Элемент управления TreeView:

TreeView - состояние выбора/раскрытия

В двух предыдущих статьях о TreeView мы использовали привязку данных для отображения пользовательских объектов в WPF TreeView.Это не плохо работает, но остается одна проблему: поскольку каждый узел дерева теперь представлен вашим пользовательским классом, например FamilyMember, из предыдущей статьи, у Вас больше нет прямого контроля над специфическими функциями узла TreeView, такими как выбор и состояние расширения. На практике это означает, что вы не можете выбрать или развернуть/свернуть данный узел из code-behind.

Существует множество решений этой проблемы : от приемов, в которых вы создаете элементы TreeView чтобы получить базовый TreeViewItem, где можно управлять свойствами IsExpanded и IsSelected, до гораздо более продвинутых реализаций, основанных на MVVM. В этой статье я хочу показать вам решение, которое находится где-то посередине, что облегчает его реализацию и использование, но при этом не является законченным приемом.

Решение для выделения и развертывания/свертывания в TreeView

Основной принцип заключается в реализации двух дополнительных свойств в вашем классе: IsExpanded и IsSelected. Затем эти два свойства подключаются к TreeView, используя стили внутри ItemContainerStyle для TreeView.

Вы можете легко реализовать эти два свойства в ваших объектах, но гораздо проще наследовать их от базового класса. Если это не приемлемо для вашего решения, вы можете создать для него интерфейс, а затем реализовать его. Для этого примера я выбрал метод базового класса, потому что это позволяет мне очень легко получить ту же функциональность для других моих объектов. Вот код:

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

Извините меня за довольно большое количество кода в одном месте. В реальности, очевидно, решение будет распределено по нескольким файлам, и данные для дерева, скорее всего, будут поступать из фактического источника данных, а не генерироваться на лету. Позвольте мне объяснить, что происходит в примере.

XAML часть

Я определил пару кнопок для размещения в нижней части окна, чтобы использовать два новых свойства. Также у нас есть TreeView, для которого я определил ItemTemplate (как показано в предыдущей главе), а также ItemContainerStyle. Если вы не читали главы по стилю, вы, возможно, не полностью понимаете эту часть кода, но это просто вопрос объединения свойств нашего пользовательского класса со свойствами IsSelected и IsExpanded в TreeViewItems, что делается с помощью сеттеров в стилях. Больше об этом Вы узнаете далее из этого урока.

Code-behind часть

В сode-behind я определил класс Person с парой свойств, который наследует наши дополнительные свойства от класса TreeViewItemBase. Вы должны знать, что класс TreeViewItemBase реализует интерфейс INotifyPropertyChanged и использует его для уведомления об изменениях этих двух важных свойств - без этого изменения выбора/расширения не будут отражены в пользовательском интерфейсе. Концепция изменения уведомлений объясняется в главах «Привязка данных».

В основном классе Window я просто создаю список людей, добавляя детей к некоторым из них. Я добавляю людей в список, который я назначаю в качестве ItemsSource TreeView, который с помощью определенного шаблона, отображает их так, как они показаны на скриншоте.

Самое интересное происходит, когда я устанавливаю свойства IsExpanded и IsSelected в trye для объекта person2. Это заставляет второго человека (Jane Doe) быть изначально выбранным и развернутым, как показано на скриншоте. Мы также используем эти два свойства в объектах Person (унаследованных от класса TreeViewItemBase) в обработчиках событий для двух тестовых кнопок (имейте в виду что для того, чтобы сделать код как можно меньше и проще, кнопка выбора работает только для верхнего уровня).

Итог

Создайте и реализуйте базовый класс для объектов, которые вы хотите использовать и манипулировать внутри TreeView, и используйте полученные свойства в ItemContainerStyle. Этим вы значительно облегчаете работу с выбором и состоянием расширения. Есть много решений для этой проблемы, и хотя этот пример работает, вы возможно захотите найти решение, которое лучше соответствует вашим потребностям. Как всегда в программировании, все дело в использовании правильного инструмента для работы.


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!