WPF – Hierarchical Binding và Hierarchical Data Template

Các dữ liệu dạng phân cấp có thể được hiển thị trên các control HeaderedItemsControl như TreeViewItem hay MenuItem. Và để sử dụng Data Template cho các các kiểu dữ liệu này, bạn cần sử dụng lớp HierarchicalDataTemplate.

Trong bài này tôi sẽ làm một ví dụ binding dữ liệu lên TreeView và thiết lập hiển thị bằng các HierarchicalDataTemplate cho từng kiểu dữ liệu.

Đầu tiên bạn một dự án với tên HierarchicalBinding và tạo dữ liệu nguồn trong code-behind như sau:

using System.Windows;
using System.Collections.Generic;

namespace HierarchicalBinding
{
    public partial class Window1 : Window
    {

        public Window1()
        {
            InitializeComponent();

            var categories = new List<Category>();

            var cat1 = new Category() { CategoryName = "Antivirus" };
            cat1.Products.Add(new Product() { ProductID = 1, ProductName = "Norton AV" });
            cat1.Products.Add(new Product() { ProductID = 2, ProductName = "Kaspersky" });
            cat1.Products.Add(new Product() { ProductID = 3, ProductName = "AVG" });

            var cat2 = new Category() { CategoryName = "Browser" };
            cat2.Products.Add(new Product() { ProductID = 4, ProductName = "Firefox" });
            cat2.Products.Add(new Product() { ProductID = 5, ProductName = "Chrome" });
            cat2.Products.Add(new Product() { ProductID = 6, ProductName = "Opera" });

            var cat3 = new Category() { CategoryName = "Game" };
            cat3.Products.Add(new Product() { ProductID = 7, ProductName = "FreeCell" });
            cat3.Products.Add(new Product() { ProductID = 8, ProductName = "Hearts" });
            cat3.Products.Add(new Product() { ProductID = 9, ProductName = "Minesweeper" });

            categories.Add(cat1);
            categories.Add(cat2);
            categories.Add(cat3);

            this.DataContext = categories;
        }
    }
    public class Product
    {
        public string ProductName { get; set; }
        public int ProductID { get; set; }
    }
    public class Category
    {
        public string CategoryName { get; set; }
        public List<Product> Products { get; set; }

        public Category()
        {
            Products = new List<Product>();
        }
    }
}

Trong Window1.xaml, ta binding dữ liệu vào TreeViewItem thông qua property ItemSource với header hiển thị là Categories. Đồng thời tạo một HierarchicalDataTemplate dùng cho kiểu dữ liệu Category trong Window.Resources. Bạn cần ItemsSource của HierarchicalDataTemplate đến collection của cấp dữ liệu tiếp theo, ở đây là Category.Products.

<Window x:Class="HierarchicalBinding.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:HierarchicalBinding"
        Title="Hierarchical Binding Demo" Height="250" Width="300">
    <Window.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:Category}"
      ItemsSource="{Binding Path=Products}">
            <TextBlock Background="LightBlue" Text="{Binding Path=CategoryName}" />
        </HierarchicalDataTemplate>

    </Window.Resources>
    <Grid>
        <TreeView>
            <TreeViewItem ItemsSource="{Binding}" Header="Categories"/>
        </TreeView>
    </Grid>
</Window>

Kết quả:

Như bạn thấy các node lá trong TreeView trên đại diện cho các đối tượng Product và chúng hiển thị theo tên kiểu. Để thiết lập lại kiểu hiển thị của các node này, bạn cần tạo thêm một HierarchicalDataTemplate cho kiểu Product. Bởi vì kiểu Product không chứa collection con nào, bạn không cần gán giá trị cho ItemsSource, hay có thể dùng cú pháp ItemsSource=”{Binding}”:

<Window x:Class="HierarchicalBinding.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:HierarchicalBinding"
        Title="Hierarchical Binding Demo" Height="250" Width="300">
    <Window.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:Category}"
      ItemsSource="{Binding Path=Products}">
            <TextBlock Background="LightBlue" Text="{Binding Path=CategoryName}" />
        </HierarchicalDataTemplate>

        <HierarchicalDataTemplate DataType="{x:Type local:Product}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=ProductID}" />
                <TextBlock Text=" - " />
                <TextBlock Text="{Binding Path=ProductName}" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid>
        <TreeView>
            <TreeViewItem ItemsSource="{Binding}" Header="Categories"/>
        </TreeView>
    </Grid>
</Window>

Kết quả:


https://yinyangit.wordpress.com

Related articles

Advertisements

6 thoughts on “WPF – Hierarchical Binding và Hierarchical Data Template

  1. private TreeViewItem SelectTreeViewItem(ItemCollection collection, string value)
            {
                if (collection == null) return null;
                foreach (TreeViewItem item in collection)//bị lỗi
                {
                    if (item.Header.Equals(value))
                    {
                        item.IsSelected = true;
                        return item;
                    }
    
                    if (item.Items != null)
                    {
                        var childItem = SelectTreeViewItem(item.Items, value);
                        if (childItem != null)
                        {
                            item.IsExpanded = true;
                            return childItem;
                        }
                    }
                }
                return null;
            }
    

    Mình áp dụng TreeView với kiểu dữ liệu string thì nó chạy đúng. Nhưng với kiểu dữ liệu là Category và Product thì chỗ foreach(TreeView item in collection) bị lỗi. Có cách nào tổng quát để duyệt toàn bộ TreeView mà không cần biết trước kiểu dữ liệu?

    Phản hồi
  2. Bài viết trên, mình tạo 1 nút button, và khi click, nó tìm phần tử trong TreeView, nếu phần tử nào có header là Firefox thì nó sẽ được chọn (TreeView.SelectedItem).
    Mình cũng có nghĩ ra cách duyệt toàn bộ cây, nhưng kết quả tìm được không phải là TreeViewItem mà là Product, nên dòng item.IsSelected = true;// bị lỗi

    private TreeViewItem SelectTreeViewItem<T>(List<T> collection, string value)
            {
                if (collection == null) return null;
                dynamic childCollection;
                for (var i = 0; i < collection.Count; i++)
                {
                    dynamic item = collection[i];
                    string header;
                    if (item is TreeViewItem)
                    {
                        header = item.Header;
                        childCollection = ((ItemCollection)item.Items).CollectionToList<Category>();
                    }
                    else
                    {
                        if (item is Category)
                        {
                            header = item.CategoryName;
                            childCollection = item.Products;
                        }
                        else
                        {
                            header = item.ProductName;
                            childCollection = null;
                        }
                    }
                    if (header.Equals(value))
                    {
                        item.IsSelected = true;// bị lỗi
                        return item;
                    }
    
                    if (childCollection != null)
                    {
                        var childItem = SelectTreeViewItem(childCollection, value);
                        if (childItem != null)
                        {
                            item.IsExpanded = true;
                            return childItem;
                        }
                    }
                }
                return null;
            }
    

    Class chuyển đổi từ ItemsCollection sang list

    public static class Helpers
        {
            public static List<T> CollectionToList<T>(this System.Collections.ICollection other)
            {
                var output = new List<T>(other.Count);
    
                output.AddRange(other.Cast<T>());
    
                return output;
            }
        }
    
    Phản hồi
  3. Bạn có thể VisualTreeHelper để giải quyết theo dạng sau, chú ý rằng cần override ToString() các class dữ liệu để so sánh Header:

    private TreeViewItem FindTreeViewItem(DependencyObject element,string value)
    {
        TreeViewItem item = element as TreeViewItem;
    
        if (item != null)
        {
            if (item.Header.ToString() == value)
            {
                return item;
            }                           
        }
        int count = VisualTreeHelper.GetChildrenCount(element);
    
        for (int i = 0; i < count; i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(element, i);
            TreeViewItem a = FindTreeViewItem(child,value);
            if (a != null)
                return a;
        }
        return null;
    }
    
    Phản hồi

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s