Viet TP's notepage

Welcome!! Viet is a Microsoft Student Partner in Vietnam

Một ngày vòng vèo với TreeView Databinding…

Databinding là một kĩ thuật then chốt ko thể ko biết tới khi lập trình trên .NET. Hầu như trong ứng dụng ko thể ko có Databind

Một điều đau đớn là kĩ thuật này làm mọi việc trở nên đơn giản, nhưng chính nó lại là một kĩ thuật cần phải nghiên cứu học tập thật là vất vả.

Hôm nay tôi đã thấy một ví dụ chứng minh sự khác biệt của việc tách mã nguồn và mã UI độc lập trong bộ VS2k8. Nó đòi hỏi 1 sự thay đổi trong cách nhìn nhận lúc code. Ví dụ đó chính là việc tôi thực hiện databind với TreeView

Mục đích của phần tôi làm ngày hôm nay là một TreeView thể hiện danh mục chủ đề (Category) bài viết (có phân cấp, chủ đề này có thể là con của chủ đề kia), và 1 ListBox để thể hiện những bài viết có trong chủ đề đó.

Tôi là sinh viên năm 2, đag muốn mà chưa đc làm MSP, vì thế nếu tôi có làm sai kĩ thuật hay “ngớ ngẩn” trong cách thiết kế thì tôi sẵn sàng nhận mọi ý kiến góp ý Wink

Mô tả qua cấu trúc TreeView. TreeView là một control có tính Hierarchical, tức là có dạng phân cấp như hình cây, thường thấy trong cây thư mục hoặc là cây chủ đề… Trong 1 TreeView có chứa các TreeViewItem. TreeViewItem có các thành phần Header (là tên thể hiện của nó trong cây), Items (là tập hợp các con của nó) và thuộc tính ItemsSource để bind vào 1 cấu trúc dữ liệu.

Kĩ năng còn kém, search ko đúng phần cần search nên tôi vòng vèo với mấy bài viết bày nhiều kiểu ko hợp lý. Dùng SiteMapDataSource hoặc là DataSource để Bind vào. Cả 2 kiểu này đều lấy trực tiếp dữ liệu từ cơ sở dữ liệu, sẵn có sự phân cấp hoặc có thể sort lại theo nhóm.

Nhưng cái tôi có từ WebService chỉ là mảng các Category. Hơn nữa cấu trúc của các kiểu dữ liệu đó giống như các bảng và có các relation trong database, ko giống cấu trúc trong TreeView, dùng binding ở đây có sự khập khiễng.

Tôi nghĩ tới việc Dynamic TreeView. Bởi nghĩ vậy nên thế nào tôi lại search lạc qua TreeView của Web, ko phải dành cho project WPF mà tôi đag làm. TreeView của Web ta có thể Add thêm Items…., nói chung là có thể Dynamic. Còn trong WPF thì ko, tìm mãi chẳng thấy cách nào để Add thêm cả!! I dont know Thinking Eye-rolling

Cuối cùng trời ko phụ lòng người!! (Ngồi cả buổi mò mẫm lận mà, vừa ăn trưa vừa nhìn màn hình máy tính)

Tôi có đc một kiến thức mới, đó là việc sử dụng ViewModel. Nghe đâu là ViewModel này đc nghiên cứu từ lâu và ứng dụng trong lúc xây dựng nên anh Blend nữa.

Cần phải nhận thức là trong VS2k8, WPF. Code nguồn và code UI đã đc tách nhau ra, đó là lý do tôi ko thể trực tiếp thêm TreeViewItem cho 1 TreeView từ code nguồn đc. Và còn phải thấy cầu nối giữa code nguồn và UI chính là databinding.

ViewModel đã hiện thực đc việc tạo 1 cấu trúc có 2 yếu tố: chứa nội dung của node, và chứa cấu trúc phân cấp của node, để có thể áp dụng cho TreeView.

Cho class CompletedCategory là cấu trúc dữ liệu chứa 1 Category đã chứa cả 1 số lượng xác định các articles mới nhất của nó

class CategoryViewModel có dạng sau:

class CategoryViewModel : INotifyPropertyChanged
    {
        static CategoryViewModel DummyChild = new CategoryViewModel();
        public event PropertyChangedEventHandler PropertyChanged;

        public CompletedCategory Category { get; set; }
        public CategoryViewModel Parent { get; set; }
        public ObservableCollection<CategoryViewModel> Children { get; set; }

Category chính là thành phần chứa dữ liệu, Parent và Children chính là thành phần chứa thông tin về cấu trúc phân cấp của nó.

Ngoài ra, tôi muốn mỗi khi tôi Expand 1 TreeViewItem thì các con của nó mới đc sinh ra và hiển thị. Nên khi tạo 1 CategoryViewModel, tôi sẽ cho nó có 1 “con” là DummyChild (trong code ở trên), và thêm thuộc tính IsExpanded như sau:

        public bool HasDummyChild
        {
            get { return (this.Children.Count == 1 && this.Children[0] == DummyChild); }
        }

        private bool _isExpanded = false;
        public bool IsExpanded
        {
            get { return _isExpanded; }
            set
            {
                if (value != _isExpanded)
                    _isExpanded = value;

                if (_isExpanded && Parent != null)
                    Parent.IsExpanded = true;
                if (this.HasDummyChild)
                {
                    this.Children.Remove(DummyChild);
                    this.LoadChildren();
                }
            }
        }

 

Mỗi khi thuộc tính này đc gán bằng True thì nó sẽ Expand ngược lên các cha của nó, đồng thời nếu nó đag có DummyChild (nghĩa là chưa truy xuất các con) thì ta sẽ gọi this.LoadChildren(); để tìm con và add vào Collection Children của nó.

Cuối cùng tôi làm 1 class CategoryTreeViewModel (để bind vào TreeView) là cấu trúc chứa 1 list những CategoryViewModel. Lý do là để giống TreeView trong WPF có thể chứa nhiều node gốc (root node)

Như vậy ta đã có cấu trúc dữ liệu phân cấp giống như kiểu cấu trúc cho TreeView. Sau đây là cách để Bind vào TreeView:

 

<TreeView Margin="0,5.342,0,0" Grid.Row="1" x:Name="CategoryTree" ItemsSource="{Binding List}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
            <Setter Property="FontWeight" Value="Normal"/>
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="true">
                    <Setter Property="FontWeight" Value="Bold"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding Path=Category.Icon, Mode=OneWay}"/>
                <TextBlock Text="{Binding Path=Category.Name, Mode=OneWay}"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

Thuộc tính thì ta dùng ItemContainerStyle để Bind các thuộc tính của TreeViewItem vào với thuộc tính của CategoryViewModel (thuộc tính IsSelected ta làm tương tự như thuộc tính IsExpanded.

Về cấu trúc của cây thì ta dùng ItemTemplate để Bind với HierarchicalDataTemplate là một dạng DataTemplate phân cấp, ta bind cấu trúc này vào với thuộc tính Children (trong CategoryViewModel).

Vậy là xong!!

Vấn đề mà tôi rút ra được là cách làm việc code nguồn và code UI tách riêng đòi hỏi phải tư duy cho phù hợp với nó. Binding là cầu nối, và tôi phải xây dựng cấu trúc dữ liệu phù hợp với Control mà tôi muốn Bind. Những cách khác cũng làm đc, nhưng đây mới là cách làm phù hợp SmileIsland with a palm tree

References:

PS: Vấn đề còn lại ở đây là khi tôi mở Project bằng Expression Blend thì khi thao tác trong Blend (cụ thể là khi thiết lập việc Binding các control liên quan với TreeView thì … Blend stops working (tự văng Open-mouthed). Thứ gây lỗi trong quá trình hoạt động của Blend chỉ có heể là từ file .xaml, và sau khi mò mẫm thì biết đc là lỗi là do code ở phần TreeView của tôi. Cách viết code như vậy ko phù hợp với kiểu code của Blend sinh ra –> nó ko hiểu đc –> nó văng Open-mouthed

Mong mọi người góp ý lẹ lẹ Open-mouthed

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: