在ListView的GridView中生成平面结构? (相同的集合已经绑定到树视图,这就是它在层次结构中的原因,并且已经有很多方法可以操作此结构中的数据,所以我宁愿保持原样)。
数据如下所示:
class Node
{
ObservableCollection<Node> Children;
...
}
在顶层,它全部包含在一个集合中:
ObservableCollection<Node> nodes;
现在我希望所有的孩子都在我的列表视图中的某个级别(但可能在许多分支中)...一种方式似乎是维护克隆副本但它看起来非常无效,我只想绑定到相同的集合。
答案 0 :(得分:3)
你在这里要做的很难。扁平化层次结构并不难 - 构建一个遍历T
个对象树并返回IEnumerable<T>
的方法非常容易。但是你想要的更难:你希望扁平列表与树保持同步。
可以这样做。原则上,您至少可以让层次结构中的每个节点都知道它在展平列表中的位置,然后将其子节点上的CollectionChanged
事件转换为扁平列表可以处理的内容。如果您只处理单项添加和删除操作,那可能会有效。
有一个很多更简单的方法。不要使用ListView
。在HeaderedItemsControl
中显示您的数据,并使用HierarchicalDataTemplate
,如this question的答案中所述。只是不要在ItemsPresenter
上设置左边距。这将在单个列中显示所有项目。你会知道有些项目是父母,有些是儿童,但用户不会。
如果需要柱状布局,请使用Grid
作为模板,并使用共享大小范围来控制列宽。
答案 1 :(得分:1)
维护所有节点在其ChildrenCollection更改时添加/删除的新集合似乎是最佳的。人们可以捕获Node的Children's CollectionChanged事件:
void ChildrenCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// ASSUMPTION: only one item is ever added/removed so will be at NewItems[0]/OldItems[0]
switch (e.Action)
{
case NotifyCollectionChangedAction.Add: nodes.AllChildren.Add(e.NewItems[0]);break;
case NotifyCollectionChangedAction.Remove: nodes.AllChildren.Remove(e.OldItems[0]); break;
case NotifyCollectionChangedAction.Replace:
{
int i = nodes.AllChildren.IndexOf(e.OldItems[0]);
nodes.AllChildren.RemoveAt(i);
nodes.AllChildren.Insert(i, e.NewItems[0]);
}
break;
case NotifyCollectionChangedAction.Reset:
{
nodes.AllChildren.Clear();
foreach (Node n in this.ChildrenCollection)
nodes.AllChildren.Add(n);
}
break;
// NOTE: dont't care if it moved
}
}
其中'nodes'是对顶级集合的引用。
然后,您可以将ListView.ItemsSource绑定到AllChildren,如果它是ObervableCollection将保持最新!
注意:如果节点中的属性发生变化,它们将不会反映在AllChildren集合中 - 它只是添加/删除和替换一个ChildrenCollection中的节点,它们将在AllChildren中复制自身集合。
注意II :你必须要小心,只需要替换树中的节点,从而为下面的整个分支进行操作,你现在必须先深度删除所有节点。那个分支所以“镜像”AllChildren集合也被更新了!
答案 2 :(得分:0)
编辑:为了有效展平,CompositeCollection
对我来说非常有用。
我会使用value converter,然后您的绑定源可以保持不变。
编辑:转换器可能看起来像这样(未经测试!):
[ValueConversion(typeof(ObservableCollection<Node>), typeof(List<Node>))]
public class ObservableCollectionToListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ObservableCollection<Node> input = (ObservableCollection<Node>)value;
int targetLevel = int.Parse(parameter as string);
List<Node> output = new List<Node>();
foreach (Node node in input)
{
List<Node> tempNodes = new List<Node>();
for (int level = 0; level < targetLevel; level++)
{
Node[] tempNodesArray = tempNodes.ToArray();
tempNodes.Clear();
foreach (Node subnode in tempNodesArray)
{
if (subnode.Children != null) tempNodes.AddRange(subnode.Children);
}
}
output.AddRange(tempNodes);
}
return output;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
你可以在Xaml中使用它:
<Window.Resources>
...
<local:ObservableCollectionToListConverter x:Key="ObservableCollectionToListConverter"/>
</Window.Resources>
...
<ListView ItemsSource="{Binding MyNodes, Converter={StaticResource ObservableCollectionToListConverter}, ConverterParameter=3}">
(ConverterParameter
指定级别)
答案 3 :(得分:0)
向DataContext
添加一个返回IEnumerable
并将其绑定到ListView
的方法。
在新方法中,返回分层数据集合的LINQ查询结果。只要您没有.ToList()
结果,您就不会拥有该集合的“影子副本”,只要您的可观察集合或INotifyCollectionChanged
,您就会获得最新结果事件得到妥善实施。
答案 4 :(得分:0)
您可以创建一个递归方法,该方法返回一个IEnumerable并创建一个返回该方法值的属性。不确定下面的例子是否有效但是这个想法是:
public Node MainNode { get; set;}
public IEnumerable<Node> AllNodes
{
get { return GetChildren(MainNode); }
}
public IEnumerable<Node> GetChildren(Node root)
{
foreach (Node node in root.Nodes)
{
if (node.Nodes.Count > 0)
yield return GetChildren(node) as Node;
yield return node;
}
}
围绕相同想法的另一个选择是AllNodes属性调用一个方法,该方法以递归方式加载平面列表中的所有节点,然后返回该列表(如果您不想使用yield return):
public ObservableCollection<Node> AllNodes
{
get
{
ObservableCollection<Node> allNodes = new ObservableCollection<Node>();
foreach(Node node in GetChildren(this.MainNode))
allNodes.Add(node);
return allNodes;
}
}