This article has been localized into Russian by the community.
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. Этим вы значительно облегчаете работу с выбором и состоянием расширения. Есть много решений для этой проблемы, и хотя этот пример работает, вы возможно захотите найти решение, которое лучше соответствует вашим потребностям. Как всегда в программировании, все дело в использовании правильного инструмента для работы.