是否可以为 'plain' 集合定义 TreeView 分层视图模板?

Is it possible to define a TreeView hierarchical view template for a 'plain' collection?

我的模型 类 看起来像这样:

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

class Item : Base { ... }

class Group : Base 
{
    public List<Base> GroupItems { get; private set; }
}

然后,我有一个带有 Items 集合的视图模型,我按照下面的 FillUp() 方法所示填充它:

class ViewModel : INotifyPropertyChanged
{
    public ObservableCollection<Base> Items { get; set; }

    private void FillUp()
    {
        Item item1 = new Item { Name = "Item1" };
        Item item2 = new Item { Name = "Item2" };
        Group group = new Group { Name = "Group" };
        group.GroupItems.Add(item1);
        this.Items.Add(item1);
        this.Items.Add(item2);
        this.Items.Add(group);
    }
}

因此,我的 Items 集合现在包含 2 个 Item 类型的对象和一个 Group 类型的对象,其中 "parent-child" 引用了 Item对象。

我想要实现的目标:我想在 TreeView 中显示此模型,以便属于某些 Group 的所有 Item 都应显示为子项Group 的元素,尽管它们在 "root" 集合中。

它应该看起来像这样:

- Item2
+ Group
   - Item1

我可以使用 HierarchicalDataTemplate 来显示父子元素,但这不允许我从属于某些组的根集合中过滤掉那些项目。

<TreeView>
  <TreeView.Resources>
    <HierarchicalDataTemplate DataType = "{x:Type loc:Group}" ItemsSource = "{Binding GroupItems}">
      <TextBlock Text="{Binding Name}"/>
    </HierarchicalDataTemplate>

    <DataTemplate DataType="{x:Type loc:Item}">
      <TextBlock Text="{Binding Name}"/>
    </DataTemplate>
  </TreeView.Resources>
  <TreeViewItem ItemsSource="{Binding Items}"/>
</TreeView>

根据这个视图,我得到了 "Item1" 两次:

- Item1
- Item2
+ Group
    - Item1

是否有任何方法可以仅通过视图 (XAML) 标记获得所需的结果?否则,我应该更改我的视图模型,以便 Item 仅添加到 Group 中,而不是添加到 "root" 集合中(但这是我在片刻)。

您可以在您的 ViewModel 中定义两个 Collections,一个用于所有项,一个用于树,看起来很简单。

class ViewModel
{
    public ObservableCollection<Base> AllItems { get; set; }
    public ObservableCollection<Base> RootLevelItems { get; set; }
}

如果您只有两个级别(例如,没有嵌套组),您可以将 ListView 与 GroupStyle 一起使用。

假设您有一些关于哪个项目属于组的指示,对于此示例,我在组和项目中添加了一个 Guid classes。

您可以通过两种方式解决此问题:

1) 创建复合包装器 class 。 并创建一个 Wrapper class 的 ItemsSource。如果有一个不属于组的单个项目,子项将保持为空。

public class Wrapper<T> where T : Base
{
    public List<T> Children { get; set; }
}

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

public class Item : Base
{
    public Guid GroupId { get; set; }
}

public class Group : Base 
{
    public Guid ID { get; set; }
    public List<Item> GroupItems { get; private set; }
}

您的第二个选择是使用 CollectionView 并过滤掉属于某个组的项目。

private ICollectionView _baseView;
public ICollectionView BaseView
{

    get
    {
        if (_baseView == null)
        {
            _baseView = new ListCollectionView((IList) items);
            _baseView.Filter = o => o is Group || o is Item && (o as Item).GroupId != Guid.Empty;                   
        }
        return _baseView;
    }
}

回复您的评论:

There are no duplicates in the collection by the way, as you can see in my code - it's just the view that displays the item twice.

您说您的代码中没有重复项,但显然有:

private void FillUp()
{
    Item item1 = new Item { Name = "Item1" };
    Item item2 = new Item { Name = "Item2" };
    Group group = new Group { Name = "Group" };
    group.GroupItems.Add(item1);  // Adding item1 into collection in Group 
    this.Items.Add(item1);  // Adding item1 into collection again
    this.Items.Add(item2);
    this.Items.Add(group);
}

因此,正如我所说,这是预期的行为,而 TreeView 只是向您显示您告诉它的内容。因此,要删除重复项,只需不要将其添加到您的 collection:

private void FillUp()
{
    Item item1 = new Item { Name = "Item1" };
    Item item2 = new Item { Name = "Item2" };
    Group group = new Group { Name = "Group" };
    group.GroupItems.Add(item1);  // Adding item1 into collection in Group 
    // this.Items.Add(item1);  // Don't add item1 into collection again
    this.Items.Add(item2);
    this.Items.Add(group);
}