如何在WPF TreeView中显示类层次结构?

5

我在我的WPF项目中有一些简单的层次结构。

我有一个基类,称为Product。然后我从这个基类继承了两个类。接着我从它们两个类中继承了更多的类。

因此,我有Product类,然后是Book和Disk类,它们从Product类中继承,还有RecipeBook类、ProgramingBook类和AdventureBook类,它们从Book类中继承。

然后,我创建了一个集合并添加了一些书籍,例如:collection.Add(new RecipeBook() {bla bla bla});

最后,我有了一个包含不同类的集合。

所以,我只需要通过WPF TreeView显示所有这些东西。

它会像这样:

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

那我该如何做呢?
<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>

你已经尝试了什么? - Davide Piras
几乎什么都没有。我甚至不知道如何开始。我已经阅读了很多,但在谷歌上没有找到我需要的任何东西。 - Dmitriy
你能否向 WPF Treeview 添加节点?你创建了一个 WPF 应用程序吗?TreeView 的 XAML 是什么样子的? - Davide Piras
我尝试添加节点,但似乎需要创建自己的类,从TreeView或TreeViewItem继承它,然后才能将子项添加到树中。 - Dmitriy
有一个叫做ItemSource的属性,如果你将一个字符串集合分配给这个属性会发生什么? - Davide Piras
3个回答

3
我已经完成了大部分工作(例如,分组有效,但是所有组必须具有相同的级别)。
Product
    Book
       RecipeBook
          Book#1
          Book#2
        ProgramingBook
          Book#1
        ....
    Disk
        Disk
          Disk#1
          Disk#2
      .....

我正在使用你的样本数据作为我的输入,因此我的类结构如下:

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; }
}

填充如下:

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;

我们需要按类型对其进行分组,以便我们可以在CollectionViewSource PropertyGroupDescription上使用转换器。
这是我的转换器(我为不同级别的类型传递不同的值):
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();
    }
}

一旦我们拥有所需的数据,我们需要适当的模板(我的模板很简单,在最后的XAML中显示),但我们需要某种选择它们的方式,因此我使用以下模板选择器:

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);
    }
}

我的XAML代码如下:

<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>

这里的想法是,对于你的层次结构中的每种类型,我们创建一个新的组,实际对象是树的叶节点。您可以通过向转换器添加新部分来添加尽可能多的分组级别。您还可以通过更改转换器的返回值来重命名组。

如果您有任何问题,请随时问,因为这是一个相当长的答案 :)


给我几分钟整理一下并压缩它。大部分代码都在答案中。 - Leom Burke
是的,它可以工作!非常感谢!现在我需要了解它是如何工作的... :) 因为我生命中大部分都没见过这种东西 :) - Dmitriy
您可以在此处找到 ClassHierarchyTreeViewSample.zip(适用于 vs2010)。http://bit.ly/kJTUeu - Leom Burke
没问题 - 那是一种定制的分组方式,不是你每天都会使用的 :). - Leom Burke

1

谢谢您提供的链接,但是我的情况有些不同。在MSDN的示例中,每个类只有一个不同类别的列表。但在我的情况下,每个类都有几个列表。 - Dmitriy
我的意思是,对于产品列表,我需要在内部保留两个列表 - 书籍列表和CD列表。在书籍列表中,我需要保留3个列表。但是据我所知,每个模板只能设置一个ItemSource。 - Dmitriy
https://dev59.com/HkzSa4cB1Zd3GeqPjhgN - Bolu

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接