Skip to main content

WPF: Visual Tree và TreeView

Khi bạn tìm hiểu về thiết kế giao diện bên WPF, có 2 thành phần quan trọng mà bạn cần chú ý, đó là Logical Tree và Visual Tree. Hôm nay mình bàn về Visual Tree
Gần như các thành phần được hiển thị trên window được gọi là visual tree. Bạn có thể nghĩ rằng visual tree được mở rộng từ logical tree, trong đó, ngoài các thành phần chính trong logical tree, nó còn có thành phần khác như border, scrollview... Dưới đây là hình ví dụ về Visual Tree, trong đó các node được tô đậm là Logical Tree.
anhso.net

Duyệt cây Visual Tree

        private void PrintVisualTree(int depth, DependencyObject obj)
        {
            Debug.WriteLine(new string(' ', depth) + obj);
            //txtKq.Text = txtKq.Text + "\n" + new string(' ', depth) + obj;
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));
            }
        }

Hạn chế của Visual Tree-Khắc phục

Visual Tree chỉ lấy ra được những object đã được wpf tạo ra sẵn (vì việc tạo hết các control sẽ tốn rất nhiều thời gian.
Ví dụ với TreeView, khi mới tạo, nó ở dạng Collapse, và bạn chỉ in được phần tử đầu tiên
        <TreeView Margin="0,0,466,0" Name="TreeView1" HorizontalAlignment="Right" VerticalAlignment="Top" Width="169" Height="200">
            <TreeViewItem Header="Cold Drinks">
                <TreeViewItem Header="Coke"></TreeViewItem>
                <TreeViewItem Header="Pepsi"></TreeViewItem>
                <TreeViewItem Header="Orange Juice"></TreeViewItem>
                <TreeViewItem Header="Milk"></TreeViewItem>
                <TreeViewItem Header="Iced Tea"></TreeViewItem>
                <TreeViewItem Header="Mango Shake"></TreeViewItem>
            </TreeViewItem>
        </TreeView>
        private void PrintValuesFromVisualTree(int depth, DependencyObject obj)
        {
            var item = obj as TreeViewItem;
            if (item != null)
            {
                lblKetqua.Content = lblKetqua.Content + new string(' ', depth) + item.Header.ToString() + "\n";
            }            
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                PrintValuesFromVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));
            }
        }
Các phần tử trong TreeView chưa được khởi tạo và VisualTreeHelper không thể tìm thấy được các children control của nó. Để tìm kiếm TreeViewItem trong TreeView, bạn phải tìm kiếm Item Containers trong ItemControl (vd như ListBoxItem trong ListBox) bởi vì các phần tử trong TreeViewItems lồng nhau.
Ví dụ: Để trả về ListBoxItem trong ListBox,bạn có thể gọi ListBox.ItemContainerGenerator.GetContainerFromItem. Nhưng nếu bạn áp dụng cho TreeViewItem, bạn chỉ nhận được các phần tử con trực tiếp. Vì vậy bạn cần phải đệ quy theo chiều sâu. Nếu các phần tử trong TreeView chưa được tạo (như lý do nêu trên), bạn cần phải thiết lập VirtualizingStackPanel.IsVirtualizing = true.
Tham khảo: http://msdn.microsoft.com/en-us/library/ff407130.aspx
/// <summary>
/// Recursively search for an item in this subtree.
/// </summary>
/// <param name="container">
/// The parent ItemsControl. This can be a TreeView or a TreeViewItem.
/// </param>
/// <param name="item">
/// The item to search for.
/// </param>
/// <returns>
/// The TreeViewItem that contains the specified item.
/// </returns>
private TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
    if (container != null)
    {
        if (container.DataContext == item)
        {
            return container as TreeViewItem;
        }

        // Expand the current container
        if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
        {
            container.SetValue(TreeViewItem.IsExpandedProperty, true);
        }

        // Try to generate the ItemsPresenter and the ItemsPanel.
        // by calling ApplyTemplate.  Note that in the 
        // virtualizing case even if the item is marked 
        // expanded we still need to do this step in order to 
        // regenerate the visuals because they may have been virtualized away.

        container.ApplyTemplate();
        ItemsPresenter itemsPresenter = 
            (ItemsPresenter)container.Template.FindName("ItemsHost", container);
        if (itemsPresenter != null)
        {
            itemsPresenter.ApplyTemplate();
        }
        else
        {
            // The Tree template has not named the ItemsPresenter, 
            // so walk the descendents and find the child.
            itemsPresenter = FindVisualChild<ItemsPresenter>(container);
            if (itemsPresenter == null)
            {
                container.UpdateLayout();

                itemsPresenter = FindVisualChild<ItemsPresenter>(container);
            }
        }

        Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);


        // Ensure that the generator for this panel has been created.
        UIElementCollection children = itemsHostPanel.Children; 

        MyVirtualizingStackPanel virtualizingPanel = 
            itemsHostPanel as MyVirtualizingStackPanel;

        for (int i = 0, count = container.Items.Count; i < count; i++)
        {
            TreeViewItem subContainer;
            if (virtualizingPanel != null)
            {
                // Bring the item into view so 
                // that the container will be generated.
                virtualizingPanel.BringIntoView(i);

                subContainer = 
                    (TreeViewItem)container.ItemContainerGenerator.
                    ContainerFromIndex(i);
            }
            else
            {
                subContainer = 
                    (TreeViewItem)container.ItemContainerGenerator.
                    ContainerFromIndex(i);

                // Bring the item into view to maintain the 
                // same behavior as with a virtualizing panel.
                subContainer.BringIntoView();
            }

            if (subContainer != null)
            {
                // Search the next level for the object.
                TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
                if (resultContainer != null)
                {
                    return resultContainer;
                }
                else
                {
                    // The object is not under this TreeViewItem
                    // so collapse it.
                    subContainer.IsExpanded = false;
                }
            }
        }
    }

    return null;
}

/// <summary>
/// Search for an element of a certain type in the visual tree.
/// </summary>
/// <typeparam name="T">The type of element to find.</typeparam>
/// <param name="visual">The parent element.</param>
/// <returns></returns>
private T FindVisualChild<T>(Visual visual) where T : Visual
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
    {
        Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
        if (child != null)
        {
            T correctlyTyped = child as T;
            if (correctlyTyped != null)
            {
                return correctlyTyped;
            }

            T descendent = FindVisualChild<T>(child);
            if (descendent != null)
            {
                return descendent;
            }
        }
    }

    return null;
}
public class MyVirtualizingStackPanel : VirtualizingStackPanel
{
    /// 
    /// Publically expose BringIndexIntoView.
    /// 
    public void BringIntoView(int index)
    {

        this.BringIndexIntoView(index);
    }
}
Download ví dụ: Visual Tree, TreeView

Comments

Popular posts from this blog

Lập trình đa luồng với Task

Bài viết được đăng trên Jou Lập trình
Trong phiên bản .NET framework 4.0, Microsoft đã bổ sung nhiều thư viện hỗ trợ việc xử lý đa luồng (multi-threading), nhằm đơn giản hóa việc lập trình lẫn hiệu suất của chương trình. Trong bài viết này, tôi xin hướng dẫn các bạn sử dụng lớp System.Threading.Task.

Tìm hiểu về IdentityServer 4

Trong bài viết này, mình sẽ hướng dẫn các bạn làm quen với thư viện Identity Server 4, và tích hợp các service In-Memory của Identity Server 4 vào Project Web API trong .NET Core.

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize
Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh.
Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.