-
TreeView控件:

TreeView 选中/展开 状态

在前几篇TreeView文章中,我们使用数据绑定在WPF TreeView中显示自定义对象。这非常有效,但它确实给您带来了一个问题:因为现在每个树节点都由您的自定义类表示,例如我们在上一篇文章中看到的FamilyMember,您不再可以直接控制TreeView节点特定的功能,例如选择和扩张状态。在praxis中,这意味着您无法从code-behind中选择或展开/折迭指定节点。

有许多解决方案可以解决这个问题,从“hacks”到使用TreeView的项目生成器来获取底层的TreeViewItem(可以控制IsExpanded和IsSelected属性),以及更高级的MVVM-inspired实现。在本文中,我想向您展示一个位于中间的解决方案,使其易于实现和使用,同时仍然不是一个完整的hack。

TreeView 选中/展开 解决方案

基本原则是在数据类上实现两个额外的属性:IsExpanded和IsSelected。然后将这两个属性连接到TreeView,使用几个针对TreeViewItem的样式,在TreeView的ItemContainerStyle中。

您可以在所有对象上轻松实现这两个属性,但从基础对象继承它们要容易得多。如果这对您的解决方案不可行,您可以为它创建一个接口,然后实现它,以建立一个共同点。对于这个例子,我选择了基类方法,因为它允许我很容易地为我的其他对象获得相同的功能。这是代码:

<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。如果你还没有阅读关于样式的章节,你可能不完全理解那个部分,但它只是将我们自己的自定义类上的属性与TreeViewItems上的IsSelectedIsExpanded属性结合在一起,这是使用Style完成的。 setter方法。您可以在本教程的其他地方了解更多相关信息。

后置代码部分

在后置代码中,我定义了一个Person类,它有几个属性,它们从TreeViewItemBase类继承了我们的额外属性。您应该知道TreeViewItemBase类实现了INotifyPropertyChanged接口,并使用它来通知这两个基本属性的更改 - 如果没有这个,选择/扩展更改将不会反映在UI中。通知更改的概念在数据绑定章节中进行了解释。

在主窗口类中,我只创建一系列人员,同时为其中一些人添加子项。我将这些人添加到一个列表中,我将其指定为TreeView的ItemsSource,它在定义的模板的帮助下,以截图显示的方式呈现它们。

当我在person2对象上设置IsExpanded和IsSelected属性时,会发生最有趣的部分。这是导致第二人(Jane Doe)最初被选中和扩展的原因,如屏幕截图所示。我们还在两个测试按钮的事件处理程序中对Person对象(继承自TreeViewItemBase类)使用这两个属性(请记住,为了使代码尽可能小而简单,选择按钮仅适用于顶级项目)。

小结

通过为您希望在TreeView中使用和操作的对象创建和实现基类,并使用ItemContainerStyle中获取的属性,您可以更轻松地使用选择和扩展状态。有许多解决方案可以解决这个问题,虽然这应该可以解决问题,但您可以找到更符合您需求的解决方案。与编程一样,所有关于使用正确的工具来完成手头的工作。