среда, 16 мая 2012 г.

Отображение странных иерархий в TreeView

Данный пример написан как ответ на вопрос заданный на формуме MSDN.
Под катом показано, как при помощи паттерна "декоратор" отобразить в виде дерева то, что первоначально назвать деревом можно с очень большой натяжкой.
Итак, есть некая структура классов вот такого вида:
public class Project
{
  public string Name { get; set; }
  public Types types { get; set; }
  public Instances instances { get; set; }
}

public class Types
{
  public string Name { get; set; }
  public List<ProjectType>; projectType { get; set; }
}

public class ProjectType
{
  public string Name { get; set; }
}

public class Instances
{
  public string Name { get; set; }
  public List<ProjectInstance> projectInstance { get; set; }
}

public class ProjectInstance
{
  public string Name { get; set; }
}
Исходнная вводная заключается в том, что структуру классов менять нельзя, а получить желательно вот такое дерево:
Несмотря на то, что WPF может для разных способов как показать дерево, для данного случая применить их напрямую затруднительно, т.к. в классе Project нет одной дочерней коллекции, а есть два класса из которых еще их коллекции надо извлечь.
Именно для таких случаев нам и может пригодиться паттерн "декоратор". Мы, рассширим функционал наших классов, но не за счет насследования, а за счет включения.
Для решения поставленной задачи воспользуемся вот таким классом "декоратором":

public class Wrapper
{
    public object Item { get; set; }

    public string Name
    {
        get
        {
            Type t = Item.GetType();
            PropertyInfo pi = t.GetProperty("Name");
            return pi.GetValue(Item, null).ToString();
        }
    }

    public List<Wrapper> Children
    {
        get
        {
            List<Wrapper> list = new List<Wrapper>();
            if (Item is Project)
            {
                list.Add(
                    new Wrapper() { Item = (Item as Project).types }
                    );
                list.Add(
                    new Wrapper() { Item = (Item as Project).instances }
                    );
            }
            if (Item is Types)
            {
                foreach (var item in (Item as Types).projectType)
                {
                    list.Add(
                    new Wrapper() { Item = item }
                    );
                }
            }
            if (Item is Instances)
            {
                foreach (var item in (Item as Instances).projectInstance)
                {
                    list.Add(
                    new Wrapper() { Item = item }
                    );
                }
            }
            return list;
        }
    }
}
На форму, кидаем TreeView, у которого прописываем тривиальный DataTemplate:

<Window x:Class="TreeViewExample.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="MainWindow" Height="350" Width="525">

    <Grid>

        <TreeView x:Name="tvProjects">

            <TreeView.ItemTemplate>

                <HierarchicalDataTemplate ItemsSource="{Binding Children}">

                    <TextBlock Text="{Binding Name}" />

                HierarchicalDataTemplate>

            TreeView.ItemTemplate>

        TreeView>

    Grid>

Window>
Готовим тестовые данные, подсовываем их в TreeView:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Loaded += new RoutedEventHandler(MainWindow_Loaded);
    } 

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        List<Project> projects = new List<Project>();
        for (int i = 0; i < 2; i++)
        {
            Project pt = new Project() { Name = "Project " + (i + 1) };
            pt.types = new Types() { Name = "Types", projectType = new List<ProjectType>() };
            for (int j = 0; j < 3; j++)
            {
                pt.types.projectType.Add(new ProjectType() { Name = "Type" + (j + 1) });
            }
            pt.instances = new Instances() { Name = "Instances", projectInstance = new List<ProjectInstance>() };
            for (int j = 0; j < 3; j++)
            {
                pt.instances.projectInstance.Add(new ProjectInstance() { Name = "Instances" + (j + 1) });
            }
            projects.Add(pt);
        }
        List<Wrapper> wrappers = new List<Wrapper>();
        foreach (var item in projects)
        {
            wrappers.Add(new Wrapper() { Item = item });
        }
        tvProjects.ItemsSource = wrappers;
    }
}
Смотрим на результат:
Как вам не знаю, а по мне так просто супер. И главное кроме "декоратора" вообще ничего сложного нет.

P.s. Пока писал извлечение Name в декораторе через рефлекшен, придумал как вообще обойтись без этого свойтсва. Кто скажет как?
P.p.s. Если в TreeView воспользоваться DataTemplateSelector-ом, котрый в зависимсоти от типа Item-а будет подсовывать разные DataTemplate, то можно и украшательством заняться.

Комментариев нет:

Отправить комментарий