This article has been localized into Vietnamese by the community.
TreeView - Selection/Expansion state
Trong vài bài viết trước của TreeView, chúng tôi đã sử dụng liên kết dữ liệu để hiển thị các đối tượng tùy chỉnh trong WPF TreeView. Điều này hoạt động rất tốt, nhưng nó khiến bạn gặp một vấn đề: Bởi vì mỗi nút cây(tree node) hiện được đại diện bởi lớp tùy chỉnh của bạn, ví dụ như FamilyMember như chúng ta đã thấy trong bài viết trước, bạn không còn có quyền kiểm soát trực tiếp chức năng cụ thể của nút(node) TreeView như lựa chọn và trạng thái mở rộng. Trong Praxis, điều này có nghĩa là bạn không thể chọn hoặc mở rộng/thu gọn một nút(node) đã cho từ code-behind.
Có rất nhiều giải pháp để xử lý vấn đề này, từ "hack" nơi bạn sử dụng các trình tạo item của TreeView để lấy TreeViewItem bên dưới, nơi bạn có thể kiểm soát các thuộc tính IsExpanded và IsSelected, cho đến các triển khai lấy cảm hứng MVVM tiên tiến hơn nhiều. Trong bài viết này tôi muốn chỉ cho bạn một giải pháp nằm ở đâu đó ở giữa, giúp dễ dàng thực hiện và sử dụng, trong khi vẫn không phải là một bản hack hoàn chỉnh.
Một giải pháp TreeView selection/expansion
Nguyên tắc cơ bản là triển khai hai thuộc tính bổ sung trên lớp dữ liệu của bạn: IsExpanded và IsSelected. Hai thuộc tính này sau đó được nối với TreeView, sử dụng một vài kiểu nhắm mục tiêu TreeViewItem, bên trong ItemContainerStyle cho TreeView.
Bạn có thể dễ dàng thực hiện hai thuộc tính này trên tất cả các đối tượng của mình, nhưng việc kế thừa chúng từ một đối tượng cơ sở sẽ dễ dàng hơn nhiều. Nếu điều này không khả thi cho giải pháp của bạn, bạn có thể tạo giao diện cho nó và sau đó thực hiện điều này thay vào đó, để thiết lập một điểm chung. Trong ví dụ này, tôi đã chọn phương thức lớp cơ sở, vì nó cho phép tôi rất dễ dàng có được chức năng tương tự cho các đối tượng khác của mình. Đây là code:
<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));
}
}
}
Tôi xin lỗi vì số lượng mã khá lớn ở một nơi. Trong một giải pháp trong thế giới thực, rõ ràng nó sẽ được trải ra trên nhiều tệp thay vào đó và dữ liệu cho tree có thể sẽ đến từ một nguồn dữ liệu thực tế, thay vì được tạo ra một cách nhanh chóng. Cho phép tôi giải thích những gì xảy ra trong ví dụ.
Phần XAML
Tôi đã xác định một vài button được đặt ở dưới cùng của hộp thoại, để sử dụng hai thuộc tính mới. Sau đó, chúng ta có TreeView, mà tôi đã định nghĩa một ItemTemplate (như đã trình bày trong chương trước) cũng như ItemContainerStyle. Nếu bạn chưa đọc các chương nói về phong cách nào, bạn có thể không hoàn toàn hiểu được phần nào đó, nhưng nó chỉ đơn giản là vấn đề buộc cùng các tài sản trên lớp tùy chỉnh riêng của chúng tôi với IsSelected và IsExpanded thuộc tính trên TreeViewItems, được thực hiện với Style setters. Bạn có thể tìm hiểu thêm về chúng ở nơi khác trong hướng dẫn này.
Phần Code-behind
Trong code-behind, tôi đã định nghĩa một lớp Person, với một vài thuộc tính, kế thừa các thuộc tính bổ sung của chúng ta từ lớp TreeViewItemBase. Bạn nên lưu ý rằng lớp TreeViewItemBase thực hiện giao diện INotifyPropertyChanged và sử dụng nó để thông báo về các thay đổi đối với hai thuộc tính thiết yếu này - nếu không có điều này, các thay đổi selection/expansion sẽ không được phản ánh trong UI. Khái niệm về thay đổi thông báo được giải thích trong các chương ràng buộc dữ liệu(Data binding).
Trong lớp Window chính, tôi chỉ cần tạo ra một loạt người, trong khi thêm trẻ em vào một số trong số họ. Tôi thêm những người vào một danh sách, mà tôi chỉ định là ItemSource của TreeView, với một chút trợ giúp từ mẫu được xác định, sẽ hiển thị chúng theo cách chúng được hiển thị trên ảnh chụp màn hình.
Phần thú vị nhất xảy ra khi tôi thiết lập các thuộc tính IsExpanded và IsSelected trên đối tượng person2. Đây là nguyên nhân khiến người thứ hai (Jane Doe) ban đầu được chọn và mở rộng, như thể hiện trên ảnh chụp màn hình. Chúng tôi cũng sử dụng hai thuộc tính này trên các đối tượng Person (được kế thừa từ lớp TreeViewItemBase) trong trình xử lý sự kiện cho hai nút kiểm tra (xin lưu ý rằng, để giữ code ít và đơn giản nhất có thể, nút chọn chỉ hoạt động cho các mục cấp cao nhất).
Tổng kết
Bằng cách tạo và triển khai lớp cơ sở cho các đối tượng mà bạn muốn sử dụng và thao tác trong TreeView và sử dụng các thuộc tính thu được trong ItemContainerStyle, bạn sẽ dễ dàng hơn khi làm việc với các lựa chọn và trạng thái mở rộng. Có nhiều giải pháp để giải quyết vấn đề này, và trong khi điều này nên thực hiện thủ thuật, bạn có thể tìm ra giải pháp phù hợp với nhu cầu của mình hơn. Như mọi khi với lập trình, tất cả là về việc sử dụng công cụ phù hợp cho công việc.