使用基础数据中不存在的结构中的项目填充 WPF TreeView
Populate WPF TreeView with items in a structure that is not present in underlying data
我有一个不同类型的对象列表,我想在 WPF TreeView 中显示这些对象。有些对象应该是其他对象的子对象,但我不希望父对象必须维护自己的子对象列表。我试图通过为该组中的子项实现 属性 和 returns 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 的子项。请问最好的方法是什么?
您必须对代码进行一些重大更改:
- 首先,
Items.ItemList
应该只包含 ItemGroup
类型的元素。现在它还包含 SubItem
类型的元素,并且它们作为父项显示在树视图中。这不是我们想要达到的。
- 但是,为了跟踪所有项目,我们需要另一个集合
Items.AllItems
。
ItemGroup.Children
属性 应修改为使用 Items.AllItems
集合而不是 Items.ItemList
.
- 最后,我们需要在创建新子项时通知树视图。我们将使用
INotifyPropertyChanged
来这样做。
这是一个工作代码。但是,请注意,它不是生产代码。我只改变了绝对必要的东西。它的目的只是向您展示一个想法。
您还应该知道,与父对象有自己的子对象列表的方法相比,这种解决方案可能会更慢。这将取决于树视图中的项目数。
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)));
}
}
我将接受 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) { }
}
}
我有一个不同类型的对象列表,我想在 WPF TreeView 中显示这些对象。有些对象应该是其他对象的子对象,但我不希望父对象必须维护自己的子对象列表。我试图通过为该组中的子项实现 属性 和 returns 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 的子项。请问最好的方法是什么?
您必须对代码进行一些重大更改:
- 首先,
Items.ItemList
应该只包含ItemGroup
类型的元素。现在它还包含SubItem
类型的元素,并且它们作为父项显示在树视图中。这不是我们想要达到的。 - 但是,为了跟踪所有项目,我们需要另一个集合
Items.AllItems
。 ItemGroup.Children
属性 应修改为使用Items.AllItems
集合而不是Items.ItemList
.- 最后,我们需要在创建新子项时通知树视图。我们将使用
INotifyPropertyChanged
来这样做。
这是一个工作代码。但是,请注意,它不是生产代码。我只改变了绝对必要的东西。它的目的只是向您展示一个想法。
您还应该知道,与父对象有自己的子对象列表的方法相比,这种解决方案可能会更慢。这将取决于树视图中的项目数。
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)));
}
}
我将接受 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) { }
}
}