Reputation: 11426
To produce a flat structure in the ListView's GridView? (the same collection is already bound to a treeview which is why it is in a Hierarchical Structure and there are already a lot of methods that manipulate the data in this structure so I would rather keep it as it is).
The data looks like this:
class Node
{
ObservableCollection<Node> Children;
...
}
At the top level it is all contained in a collection itself:
ObservableCollection<Node> nodes;
Now I want all the Children at a certain level (but could be in many branches) in my list view...One way seems to be maintaining a cloned copy but it looks terribly inefficant, I just want to bind to the same collection.
Upvotes: 1
Views: 3871
Reputation: 184466
Edit: For efficient flattening the CompositeCollection
has been quite useful to me.
I would use a value converter for that, then your binding source can stay the same.
Edit: The converter might look something like this (untested!):
[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();
}
}
You would use it like this in Xaml:
<Window.Resources>
...
<local:ObservableCollectionToListConverter x:Key="ObservableCollectionToListConverter"/>
</Window.Resources>
...
<ListView ItemsSource="{Binding MyNodes, Converter={StaticResource ObservableCollectionToListConverter}, ConverterParameter=3}">
(ConverterParameter
specifies the level)
Upvotes: 0
Reputation: 11426
Maintaining a new collection which all nodes add to/remove from when their ChildrenCollection changes seems the best. One can catch a Node's Children's CollectionChanged event:
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
}
}
Where 'nodes' is a reference to the top level collection.
You can then bind you ListView.ItemsSource to the AllChildren which if it is an ObervableCollection will stay up to date!
NOTE: Should Properties in a Node change they will not be reflected in the AllChildren collection - it is only the addition/removal and replacement of nodes in one the ChildrenCollection's that will replicate itself in the AllChildren collection.
NOTE II: You have to be careful where before you could just replace a node in the tree thereby forfieting the entire branch below, you now have to do a depth first removal of all nodes in the that branch so the "mirror" AllChildren collection is updated too!
Upvotes: 1
Reputation: 96712
What you're trying to do here is hard. Flattening a hierarchy's not hard - it's pretty easy to build a method that traverses a tree of T
objects and returns an IEnumerable<T>
. But what you want is much harder: you want the flattened list to be maintained in sync with the tree.
It's possible to do this. You can, in principle at least, have each node in the hierarchy know its position in the flattened list, and then translate CollectionChanged
events on its children into an something that the flattened list can deal with. That might work if you were only handling single-item add and remove operations.
There's a much easier way. Don't use a ListView
. Display your data in a HeaderedItemsControl
, and use a HierarchicalDataTemplate
, as described in the answer to this question. Only don't set a left margin on the ItemsPresenter
. This will present all of the items in a single column. You'll know that some of the items are parents and some are children, but the user won't.
If you want a columnar layout, use a Grid
as the template, and use shared size scopes to control the column widths.
Upvotes: 2
Reputation: 25959
You can create a recursive method that yield returns an IEnumerable and make a property that returns the value of that method. Not sure if the example below would work but it's the idea:
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;
}
}
Another option around the same idea would be that the AllNodes property calls a method that recursively loads all the Nodes in a flat list, then returns that list (if you don't want to use the 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;
}
}
Upvotes: 0
Reputation: 3461
Add a method to your DataContext
that returns a IEnumerable
and bind that to your ListView
.
Within the new method, return the result of a LINQ query of the hierarchical data collection. You won't have a "shadow copy" of the collection as long as you don't .ToList()
your results and you'll get up-to-date results as long as your observable collections or INotifyCollectionChanged
events are properly implemented.
Upvotes: 0