在前几篇TreeView文章中,我们使用数据绑定在WPF TreeView中显示自定义对象。这非常有效,但它确实给您带来了一个问题:因为现在每个树节点都由您的自定义类表示,例如我们在上一篇文章中看到的FamilyMember,您不再可以直接控制TreeView节点特定的功能,例如选择和扩张状态。在praxis中,这意味着您无法从code-behind中选择或展开/折迭指定节点。
有许多解决方案可以解决这个问题,从“hacks”到使用TreeView的项目生成器来获取底层的TreeViewItem(可以控制IsExpanded和IsSelected属性),以及更高级的MVVM-inspired实现。在本文中,我想向您展示一个位于中间的解决方案,使其易于实现和使用,同时仍然不是一个完整的hack。
基本原则是在数据类上实现两个额外的属性: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));
}
}
}
我很抱歉在一个地方有相当多的代码。在现实世界的解决方案中,它显然会分散在多个文件上,而树的数据可能来自实际的数据源,而不是即时生成。请允许我解释一下示例中发生的情况。
我已经定义了几个按钮放在对话框的底部,以使用这两个新属性。然后我们有了TreeView,我已经为它定义了一个ItemTemplate(如前一章所示)以及一个ItemContainerStyle。如果你还没有阅读关于样式的章节,你可能不完全理解那个部分,但它只是将我们自己的自定义类上的属性与TreeViewItems上的IsSelected和IsExpanded属性结合在一起,这是使用Style完成的。 setter方法。您可以在本教程的其他地方了解更多相关信息。
在后置代码中,我定义了一个Person类,它有几个属性,它们从TreeViewItemBase类继承了我们的额外属性。您应该知道TreeViewItemBase类实现了INotifyPropertyChanged接口,并使用它来通知这两个基本属性的更改 - 如果没有这个,选择/扩展更改将不会反映在UI中。通知更改的概念在数据绑定章节中进行了解释。
在主窗口类中,我只创建一系列人员,同时为其中一些人添加子项。我将这些人添加到一个列表中,我将其指定为TreeView的ItemsSource,它在定义的模板的帮助下,以截图显示的方式呈现它们。
当我在person2对象上设置IsExpanded和IsSelected属性时,会发生最有趣的部分。这是导致第二人(Jane Doe)最初被选中和扩展的原因,如屏幕截图所示。我们还在两个测试按钮的事件处理程序中对Person对象(继承自TreeViewItemBase类)使用这两个属性(请记住,为了使代码尽可能小而简单,选择按钮仅适用于顶级项目)。
通过为您希望在TreeView中使用和操作的对象创建和实现基类,并使用ItemContainerStyle中获取的属性,您可以更轻松地使用选择和扩展状态。有许多解决方案可以解决这个问题,虽然这应该可以解决问题,但您可以找到更符合您需求的解决方案。与编程一样,所有关于使用正确的工具来完成手头的工作。