使用基础数据中不存在的结构中的项填充WPF TreeView

时间:2015-11-04 17:42:47

标签: c# wpf xaml treeview

我想在WPF TreeView中呈现一个不同类型的对象列表。一些对象应该是其他对象的子对象,但我不希望父对象必须维护自己的子对象列表。我试图通过实现一个属性来实现这一点,该属性为该组中的子项返回一个IEnumerator,并将其绑定为ItemsSource,但它似乎不起作用。我已经创建了以下内容来证明这个问题。

CS:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApplication3
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private Items MyItems;
        private int NextGroupId = 0;
        public MainWindow()
        {
            InitializeComponent();
            MyItems = new Items();
            tvMain.ItemsSource = MyItems.ItemList;
        }
        private void btnNewGroup_Click(object sender, RoutedEventArgs e)
        {
            MyItems.Add(new ItemGroup(MyItems)
            {
                Name = Guid.NewGuid().ToString(),
                GroupId = NextGroupId
            });
            NextGroupId++;
        }
        private void btnNewSubItem_Click(object sender, RoutedEventArgs e)
        {
            MyItems.Add(new SubItem(MyItems)
            {
                Name = Guid.NewGuid().ToString(),
                GroupId = (tvMain.SelectedItem as ItemGroup).GroupId
            });
        }

        private void tvMain_SelectedItemChanged(object sender,
            RoutedPropertyChangedEventArgs<object> e)
        {
            btnNewSubItem.IsEnabled = tvMain.SelectedItem is ItemGroup;
        }
    }
    public class Items
    {
        private ObservableCollection<BaseItem> _ItemList;
        public ObservableCollection<BaseItem> ItemList { get { return _ItemList; } }
        public Items()
        {
            _ItemList = new ObservableCollection<BaseItem>();
        }
        public void Add(BaseItem I)
        {
            _ItemList.Add(I);
        }
    }
    public class BaseItem
    {
        protected Items _List;
        public BaseItem(Items List)
        {
            _List = List;
        }
        public string Name { get; set; }
        public int GroupId { get; set; }
    }
    public class ItemGroup : BaseItem
    {
        public ItemGroup(Items _List)
            : base(_List) { }
        public IEnumerator Children
        {
            get
            {
                return _List.ItemList
                    .OfType<SubItem>()
                    .Where(SI => SI.GroupId == this.GroupId)
                    .GetEnumerator();
            }
        }
    }
    public class SubItem : BaseItem
    {
        public SubItem(Items _List)
            : base(_List) { }
    }
}

XAML:

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:self="clr-namespace:WpfApplication3"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TreeView x:Name="tvMain" HorizontalAlignment="Left" Height="273" Margin="50,25,0,0" 
                  VerticalAlignment="Top" Width="265" 
                  SelectedItemChanged="tvMain_SelectedItemChanged">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type self:ItemGroup}" 
                                          ItemsSource="{Binding Children}">
                    <TextBlock Text="{Binding Name}"/>
                </HierarchicalDataTemplate>
                <DataTemplate DataType="{x:Type self:SubItem}">
                    <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>
        <Button x:Name="btnNewGroup" Content="New Group" HorizontalAlignment="Left" 
                VerticalAlignment="Top" Width="100" Margin="336,142,0,0" 
                Click="btnNewGroup_Click"/>
        <Button x:Name="btnNewSubItem" Content="New SubItem" HorizontalAlignment="Left" 
                Margin="336,185,0,0" VerticalAlignment="Top" Width="100" 
                Click="btnNewSubItem_Click" IsEnabled="False"/>
    </Grid>
</Window>

要清楚,我希望SubItems显示为具有相同GroupId的ItemGroup的子项。请问最好的方法是什么?

2 个答案:

答案 0 :(得分:0)

您必须对代码进行一些重大更改:

  1. 首先,Items.ItemList应仅包含ItemGroup类型的元素。现在它还包含SubItem类型的元素,它们在树视图中显示为父元素。这不是我们想要实现的目标。
  2. 但是,为了跟踪所有项目,我们需要另一个集合Items.AllItems
  3. ItemGroup.Children属性应修改为使用Items.AllItems集合而不是Items.ItemList
  4. 最后,我们需要在创建新子项时通知树视图。我们将使用INotifyPropertyChanged来执行此操作。
  5. 这是一个有效的代码。但请注意,它不是生产代码。我只改变了绝对必要的东西。它的目的只是为了向您展示一个想法。

    您应该也知道,与父对象拥有自己的子项列表的方法相比,这种解决方案可能会更慢。它取决于树视图中的项目数。

    public class Items
    {
        public List<BaseItem> AllItems { get; private set; }
        public ObservableCollection<BaseItem> ItemList { get; private set; }
    
        public Items()
        {
            ItemList = new ObservableCollection<BaseItem>();
            AllItems = new List<BaseItem>();
        }
    
        public void Add(BaseItem item)
        {
            AllItems.Add(item);
    
            if (item is ItemGroup)
                ItemList.Add(item);
            else
                AllItems.OfType<ItemGroup>().Single(i => i.GroupId == item.GroupId).Notify();
            }
        }
    
    public class ItemGroup : BaseItem, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public ItemGroup(Items _List): base(_List) { }
    
        public IEnumerator Children
        {
            get
            {
                return _List.AllItems
                    .OfType<SubItem>()
                    .Where(SI => SI.GroupId == this.GroupId)
                    .GetEnumerator();
            }
        }
    
        public void Notify()
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(nameof(Children)));
        }
    }
    

答案 1 :(得分:0)

我将接受Michal的回答,因为它向我展示了我做错了什么以及如何解决它。我实际上找到了一个我喜欢的不同解决方案,因为它不需要额外的收集:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication3
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private Items MyItems;
        private int NextGroupId = 0;
        public MainWindow()
        {
            InitializeComponent();
            MyItems = new Items();
            tvMain.ItemsSource = MyItems.Groups as IEnumerable;
        }
        private void btnNewGroup_Click(object sender, RoutedEventArgs e)
        {
            MyItems.Add(new ItemGroup(MyItems)
            {
                Name = Guid.NewGuid().ToString(),
                GroupId = NextGroupId
            });
            NextGroupId++;
        }
        private void btnNewSubItem_Click(object sender, RoutedEventArgs e)
        {
            MyItems.Add(new SubItem(MyItems)
            {
                Name = Guid.NewGuid().ToString(),
                GroupId = (tvMain.SelectedItem as ItemGroup).GroupId
            });
        }

        private void tvMain_SelectedItemChanged(object sender,
            RoutedPropertyChangedEventArgs<object> e)
        {
            btnNewSubItem.IsEnabled = tvMain.SelectedItem is ItemGroup;
        }
    }
    public class Items
    {
        public ObservableCollection<BaseItem> ItemList { get; private set; }
        public ICollectionView Groups { get; private set; }
        public Items()
        {
            ItemList = new ObservableCollection<BaseItem>();
            Groups = CollectionViewSource.GetDefaultView(ItemList);
            Groups.Filter = item => item is ItemGroup;
        }
        public void Add(BaseItem item)
        {
            ItemList.Add(item);
            if (item is SubItem)
                ItemList
                    .OfType<ItemGroup>()
                    .Where(g => g.GroupId == item.GroupId)
                    .Single()
                    .NotifyPropertyChanged("Children");
        }
    }
    public class BaseItem : INotifyPropertyChanged
    {
        protected Items _List;
        public BaseItem(Items List)
        {
            _List = List;
        }
        public string Name { get; set; }
        public int GroupId { get; set; }
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(string propName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
    public class ItemGroup : BaseItem
    {
        public ItemGroup(Items _List)
            : base(_List) { }
        public IEnumerator Children
        {
            get
            {
                return _List.ItemList
                    .OfType<SubItem>()
                    .Where(SI => SI.GroupId == this.GroupId)
                    .GetEnumerator();
            }
        }
    }
    public class SubItem : BaseItem
    {
        public SubItem(Items _List)
            : base(_List) { }
    }
}