Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to show class hierachy in WPF TreeView?

I have some simple hierarchy in my WPF project.

I have base class, which calls Product. Then I inherit two classes from this base class. Then I inherit couple more classes from both of them.

So I have Product class, then Book and Disk classes, which inherit from Product class, and RecipeBook class, ProgramingBook class and AdventureBook class, which inherit from Book class.

Then I create collection and add some book there like collection.Add(new RecipeBook() {bla bla bla});

Finaly I have collection with diferent classes.

So all I need is to show all this stuff via WPF TreeView.

It's gonna be smth like that:

Product
    Book
       RecipeBook
          Book#1
          Book#2
        ProgramingBook
          Book#1
        ....
    Disk
      Disk#1
      Disk#2
      .....

So how can I do that?

<TreeView x:Name="treeView" Height="224" HorizontalAlignment="Left" Margin="10,10,0,0"  VerticalAlignment="Top" Width="337" >
      <TreeViewItem Header="Products" >
          <TreeViewItem Header="Books">
              <TreeViewItem Header="Recipe"></TreeViewItem>
              <TreeViewItem Header="Programing"></TreeViewItem>
              <TreeViewItem Header="Adventure"></TreeViewItem>
          </TreeViewItem>
          <TreeViewItem Header="CDs">             
          </TreeViewItem>
      </TreeViewItem>
</TreeView>
like image 968
Dmitriy Avatar asked Oct 11 '22 14:10

Dmitriy


2 Answers

I have this most of the way there (i.e. the groupings work but all groups must have the same level.)

Product
    Book
       RecipeBook
          Book#1
          Book#2
        ProgramingBook
          Book#1
        ....
    Disk
        Disk
          Disk#1
          Disk#2
      .....

I am using your sample data as my input so my class structure is as follows:

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

class Book : Product
{
    public string BookName { get; set; }
}

class RecipeBook : Book
{
    public int NumRecipes { get; set; }
}

class ProgrammingBook : Book
{
    public string LanguageCovered { get; set; }
}

class Disk : Product
{
    public int Size { get; set; }
}

populated as follows:

ObservableCollection<Product> products = new ObservableCollection<Product>();
        products.Add(new ProgrammingBook() { BookName = "P1", LanguageCovered = "C#" });
        products.Add(new ProgrammingBook() { BookName = "P2", LanguageCovered = "F#" });

        products.Add(new RecipeBook() { BookName = "P3", NumRecipes = 4 });
        products.Add(new RecipeBook() { BookName = "P4", NumRecipes = 6 });

        products.Add(new Disk() { Size = 512 });
        products.Add(new Disk() { Size = 1024 });

        this.DataContext = products;

We need a groupoing by type so we can use a converter on the CollectionViewSource PropertyGroupDescription.

This is my converter (I pass in different values for different levels of the hierarchy for the type:

public class GroupByType : IValueConverter
{
    public string type { get; set; }
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (type == "root")
        {
            if (value is Product)
            {
                return "Product";
            }

            return null;
        }

        if (type == "subs")
        {
            if (value is Book)
            {
                return "Book";
            }
            if (value is Disk)
            {
                return "Disk";
            }
        }

        if (type == "main")
        {
            if (value is ProgrammingBook)
            {
                return "PBook";
            }
            if (value is RecipeBook)
            {
                return "RBook";
            }
            if (value is Disk)
            {
                return "Disk";
            }
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

once we have the data we need appropriate templates (mine are simple and are shown in the xaml at the end) but we need some way of choosing them so I use the following template selector:

public class Selector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        string templateKey;
        if (item is CollectionViewGroup)
        {
            if ((item as CollectionViewGroup).Name == null)
            {
                return null;
            }
            templateKey = "GroupTemplate";
        }
        else if (item is ProgrammingBook)
        {
            templateKey = "pTemplate";
        }
        else if (item is RecipeBook)
        {
            templateKey = "rTemplate";
        }
        else if (item is Disk)
        {
            templateKey = "dTemplate";
        }
        else
        {
            return null;
        }
        return (DataTemplate)((FrameworkElement)container).FindResource(templateKey);
    }
}

My XAML looks like this:

<Window.Resources>
    <HierarchicalDataTemplate x:Key="GroupTemplate" ItemsSource="{Binding Path=Items}">
        <TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
    </HierarchicalDataTemplate>

    <DataTemplate x:Key="pTemplate">
        <TextBlock Text="{Binding Path=LanguageCovered }"/>
    </DataTemplate>

    <DataTemplate x:Key="rTemplate">
        <TextBlock Text="{Binding Path=NumRecipes}"/>
    </DataTemplate>
    <DataTemplate x:Key="dTemplate">
        <TextBlock Text="{Binding Path=Size}"/>
    </DataTemplate>
    <c:GroupByType x:Key="gt" />
    <c:Selector x:Key="s"/>
    <CollectionViewSource Source="{Binding}" x:Key="cvs">
                    <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription>
                <PropertyGroupDescription.Converter>
                    <c:GroupByType type="root"/>
                </PropertyGroupDescription.Converter>
            </PropertyGroupDescription>
            <PropertyGroupDescription>
                <PropertyGroupDescription.Converter>
                    <c:GroupByType type="subs"/>
                </PropertyGroupDescription.Converter>
            </PropertyGroupDescription>
            <PropertyGroupDescription>
                <PropertyGroupDescription.Converter>
                    <c:GroupByType type="main"/>
                </PropertyGroupDescription.Converter>
            </PropertyGroupDescription>
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>
</Window.Resources>
<Grid>
    <TreeView ItemsSource="{Binding Source={StaticResource cvs}, Path=Groups}" ItemTemplateSelector="{StaticResource s}"/>
</Grid>

The idea here is that for each type in your hierarchy we create a new group with the actual objects being the leaf nodes of the tree. You can add as many levels to the grouping as you need by adding new sections to the converter. You can also rename the groups by changing the return value of the converter.

If you have any questions then please ask as this is quite a long answer :).

like image 132
Leom Burke Avatar answered Nov 14 '22 23:11

Leom Burke


check: HierarchicalDataTemplate, there is a complete example for you to start: Displaying Hierarchical Data Sample

like image 38
Bolu Avatar answered Nov 15 '22 00:11

Bolu