I created a class for hierarchical data. From this class I generate custom treeview. Before sending data to treeview I need delete a branch that does not end with instance with HaveData = true
public class Data
{
public List<Data> Children
{
get;
set;
}
public bool HaveData
{
get;
set;
}
}
Treeview before:
1 First
1.1 Item
1.1.1 Item (HaveData = false)
1.2 Item
1.2.1 Item (HaveData = true)
...
I need:
1 First
1.2 Item
1.2.1 Item (HaveData = true)
...
How to go through all nodes and remove only those that ending HaveData = false?
Thank you.
Here is one recursive way to do it:
public void DeleteBranchWithNoData()
{
var toBeRemoved = new List<Data>();
foreach(var child in Children)
{
if(!child.HaveData && (child.Children == null || !child.Children.Any()))
{
toBeRemoved.Add(child);
}
else
{
child.DeleteBranchWithNoData();
}
}
Children.RemoveAll(d => toBeRemoved.Contains(d));
}
Call this method from the top node of the branch you want to delete.
You can see a live demo on rextester.
Make HaveData be true if the element has data OR any of its children have data.
Delete any nodes within the tree where HaveData is false.
???
Profit.
Related
I'm having a windows form with a tree view control. This tree view has a Root node and 2 child nodes. My requirement is i need to hide the first child node.
Is it possible to make visible false that particular child nod
Yes you could inherit from tree node and create your own behaviour. Like so.
public class RootNode : TreeNode
{
public List<ChildNode> ChildNodes { get; set; }
public RootNode()
{
ChildNodes = new List<ChildNode>();
}
public void PopulateChildren()
{
this.Nodes.Clear();
var visibleNodes =
ChildNodes
.Where(x => x.Visible)
.ToArray();
this.Nodes.AddRange(visibleNodes);
}
//you would use this instead of (Nodes.Add)
public void AddNode(ChildNode node)
{
if (!ChildNodes.Contains(node))
{
node.ParentNode = this;
ChildNodes.Add(node);
PopulateChildren();
}
}
//you would use this instead of (Nodes.Remove)
public void RemoveNode(ChildNode node)
{
if (ChildNodes.Contains(node))
{
node.ParentNode = null;
ChildNodes.Remove(node);
PopulateChildren();
}
}
}
public class ChildNode : TreeNode
{
public RootNode ParentNode { get; set; }
private bool visible;
public bool Visible { get { return visible; } set { visible = value;OnVisibleChanged(): } }
private void OnVisibleChanged()
{
if (ParentNode != null)
{
ParentNode.PopulateChildren();
}
}
}
No, there is no way to make node invisible. You should remove it instead of making invisible. And later you will have to add it back into its original position.
If you are loading a treeview with a sitemap file, then another approach is to do something like this. Here the user's credentials have been read from a DB and written to a cookie.
private void ManageTreeMenu()
{
var value = Utilities.Cookies.GetCookieValue("IsAdmin");
bool.TryParse(value, out var isAdmin);
var dir = Server.MapPath("~");
File.Delete(dir + "Web.sitemap");
if (isAdmin)
File.Copy(dir + "WebAdmin.sitemap", dir + "/Web.sitemap");
else
File.Copy(dir + "WebOper.sitemap", dir + "/Web.sitemap");
}
You'd have to do this again if the user's role was changed in the program. I have only verified this in Visual Studio, not in a deployed web application. Caveat emptor.
I have a node class that contains only value type properties, and one reference type: it's parent node. When performing tree searches, these nodes are created and destroyed hundreds of thousands of times in a very short time span.
public class Node
{
public Node Parent { get; set; }
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
}
The tree search looks something like this:
public static Node GetDepthFirstBest(this ITree tree, Node root)
{
Node bestNode = root;
float bestScore = tree.Evaluate(root);
var stack = new Stack<Node>();
stack.Push(root);
while(stack.Count > 0)
{
var current = stack.Pop();
float score = tree.Evaluate(current);
if (score > bestScore)
{
bestNode = current;
bestScore = score;
}
var children = tree.GetChildren(current);
foreach(var c in children) { stack.Push(c); }
}
return bestNode;
}
Because this is done in a Mono runtime that has a very old GC, I wanted to try and pool the node objects. However, I am at a loss on how to know when a node object is safe to return to the pool, since other nodes that are still in use might reference it as a parent. At the end of the search, the best node is returned and a list of nodes is formed by walking back through its ancestors. I have full control over how the nodes are created inside the tree, if that's useful.
What options could I try and implement?
So, fortunately, if you're doing a Depth-First-Search, which you appear to be, this is a bit easier. Any time you reach a leaf node, there are two possibilities: that leaf node is part of the current deepest tree, or it's not.
If it's not, that means it's safe to return this node to the pool. If it is, that means we can return any nodes in our old tree back to our pool that are not in our own ancestor chain.
Now, if we're not a leafnode, we don't know if we can be freed until after we've finished checking our children. then, once all our children are checked, we find out if any of our children said they were the current best. if so, we keep ourselves
this does mean we're doing quite a bit more checking.
Here's some sudo code:
List bestNodes;
bool evalNode(node, score)
{
if (childCount == 0)
{
if (score > bestScore)
{
bestScore = score;
bestNode = node;
bestNodes.Add(node);
return true;
}
else
{
freeNode(this);
return false;
}
}
else
{
bool inLongest = false;
foreach (child in children)
{
inLongest = evalNode(child, score + 1) || inLongest;
}
if (!inLongest)
{
freeNode(node);
}
else
{
free(bestNodes[score]);
bestNodes[score] = node;
}
return inLongest;
}
}
Try using the ref keyword if your node is a struct, this avoids copying the node every time you pass it through to a function.
Thus:
struct Node
{
object obj;
Node children;
}
public void DoStuffWithNode(ref Node pNode){...Logic...}
I'm converting a WPF client in Windows Forms and I got some problems trying to replicate the TreeView control structure.
In the first project I have a custom factory that builds a structure starting from an input string that is basically a XML.
The return type is a collection.
Custom TreeNode:
public class TreeViewNode
{
public TreeViewNode() { }
public DocumentKey DocKey { get; set; }
public string Text { get; set; }
public IList<TreeViewNode> Children { get; set; }
}
Factory:
public class TreeViewFactory
{
public IList<TreeViewNode> GetSctructure(DocumentKey docKey, string structure, bool loadAllParents)
{
XDocument xmlDocstructure = CommonXmlValueParser.GetXDocument(structure);
var parentsNodes = (from item in xmlDocstructure.Descendants("structure_item")
where (CommonXmlValueParser.GetAttribute(item, "level") == "1")
select new TreeViewNode
{
Text = GetNodeText(item),
DocKey = new DocumentKey()
{
Bank = docKey.Bank,
Ud = int.Parse(CommonXmlValueParser.GetElement(item.Element("ud"))),
Master = int.Parse(CommonXmlValueParser.GetElement(item.Element("master"))),
NVig = int.Parse(CommonXmlValueParser.GetElement(item.Element("nvig"))),
Subjects = docKey.Subjects
},
Children = GetChildrenNodes(item, 2, docKey.Bank)
}).ToList();
return parentsNodes;
}
private IList<TreeViewNode> GetChildrenNodes(XElement element, int level, int dataBank)
{
var childrenNodes = (from item in element.Descendants("structure_item")
where (CommonXmlValueParser.GetAttribute(item, "level") == level.ToString())
select new TreeViewNode
{
Text = GetNodeText(item),
DocKey = new DocumentKey()
{
Bank = dataBank,
Ud = int.Parse(CommonXmlValueParser.GetElement(item.Element("ud"))),
Master = int.Parse(CommonXmlValueParser.GetElement(item.Element("master"))),
NVig = int.Parse(CommonXmlValueParser.GetElement(item.Element("nvig"))),
},
Children = GetChildrenNodes(item, level + 1, dataBank)
}).ToList();
return childrenNodes;
}
}
Binding:
void CreateTree(object tree, EventArgs e)
{
//...
TreeViewFactory treeFactory = new TreeViewFactory();
var documentStructure = treeFactory.Structure(document.DocumentKey, document.XmlStructure, true);
this.tabMainControl.document.SetTreeViewStructureNodes(documentStructure);
}
public void SetTreeViewStructureNodes(IList<TreeViewNode> nodes)
{
this.treeView.ItemsSource = nodes;
}
Update:
I made the TreeViewNode derive from TreeNode and changed the method SetTreeViewStructureNodes in:
private TreeView SetTreeViewStructureNodes(IList<TreeViewNode> nodes)
{
TreeView treeView = new TreeView();
treeView.Nodes.AddRange(nodes.ToArray());
return treeView;
}
still that doesn't achieve my goal as it's still not rendered...
In Windows Forms as far as I know it's not possible to associate a sort of datasource that is a whatever type collection (implements IEnumerable).
Apart from using 3rd party components, how can I solve my problem. My experience on WinForms is pretty short and just when I learnt to manage much better WPF they decided to shift it :(
Appreciate all your help, regards.
Update2:
Piece of WinForms User Control where treeView is filled:
TreeView treeView = (TreeView)documentViewControl.Controls["treeViewStructure"];
TreeViewFactory treeFactory = new TreeViewFactory();
var documentStructure = treeFactory.GetStructure(document.DocumentKey, document.XmlStructure, true);
treeView = this.SetTreeViewStructureNodes(documentStructure);
Basically I'm moving from an UC to another. Both of them are part of 2 Tabs, children of a TabControl.
(Answered by the OP as a question edit. Converted to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
Actually I got that on my own. The idea is to mutuate the ricursive idea, creating one TreeNode from the collection (IList) of TreeViewNodes. Problem 1: recursion Problem 2: how to mantain the DocKey custom property
private TreeNode[] GetTreeViewNodes(IList<TreeViewNode> nodes)
{
IList<TreeNode> returnedNodes = new List<TreeNode>();
foreach (var item in nodes)
{
TreeNode node = new TreeNode(item.Text, this.GetTreeViewNodes(item.Children));
node.Tag = item.DocKey;
returnedNodes.Add(node);
}
return returnedNodes.ToArray();
}
And the code required for the treeview becomes this one:
this.treeView.Nodes.Clear();
this.treeView.Nodes.AddRange(this.GetTreeViewNodes(documentStructure));
public class TreeNode<T>
{
private List<TreeNode<T>> _children = new List<TreeNode<T>>();
public T Data { get; set; }
public TreeNode<T> Parent { get; private set; }
public ReadOnlyCollection<TreeNode<T>> Children {
get {
return new ReadOnlyCollection<TreeNode<T>>(_children);
}
}
public void AddChild(TreeNode<T> child)
{
_children.Add(child);
}
public ICollection<TreeNode<T>> GetAllNodes()
{
throw new NotImplementedException();
}
}
This kind of traversal is called Breadth-First Traversal:
public ICollection<TreeNode<T>> GetAllNodes()
{
var allNodes = new List<TreeNode<T>>();
var queue = new Queue<TreeNode<T>>();
queue.Enqueue(this); // will include root node
while (queue.Any())
{
var current = queue.Dequeue();
allNodes.Add(current);
foreach (var child in current._children)
queue.Enqueue(child);
}
return allNodes;
}
How it works: consider following tree
Lets see what queue [square brackets] will contain and what will be added to results (parentheses):
Before loop:
root is added to queue: [N0]
First loop:
first item removed from queue:
first item added to results: (N0)
all children of N0 are added into queue: [N1-1][N1-2]
Second loop:
first item N1-1 removed from queue: [N1-2]
first item added to results: (N0)(N1-1)
all children of N1-1 are added to queue: [N1-2][N2-1]
Third loop:
first item N1-2 removed from queue: [N2-1]
first item added to results: (N0)(N1-1)(N1-2)
all children of N1-2 are added to queue: [N2-1][N2-2][N2-3]
Fourth loop:
first item N2-1 removed from queue: [N2-2][N2-3]
first item added to results: (N0)(N1-1)(N1-2)(N2-1)
all children of N2-1 are added to queue: [N2-2][N2-3][N3-1]
All these items do not have children, thus further loops will only remove them one by one from queue and add to results.
I'm having a windows form with a tree view control. This tree view has a Root node and 2 child nodes. My requirement is i need to hide the first child node.
Is it possible to make visible false that particular child nod
Yes you could inherit from tree node and create your own behaviour. Like so.
public class RootNode : TreeNode
{
public List<ChildNode> ChildNodes { get; set; }
public RootNode()
{
ChildNodes = new List<ChildNode>();
}
public void PopulateChildren()
{
this.Nodes.Clear();
var visibleNodes =
ChildNodes
.Where(x => x.Visible)
.ToArray();
this.Nodes.AddRange(visibleNodes);
}
//you would use this instead of (Nodes.Add)
public void AddNode(ChildNode node)
{
if (!ChildNodes.Contains(node))
{
node.ParentNode = this;
ChildNodes.Add(node);
PopulateChildren();
}
}
//you would use this instead of (Nodes.Remove)
public void RemoveNode(ChildNode node)
{
if (ChildNodes.Contains(node))
{
node.ParentNode = null;
ChildNodes.Remove(node);
PopulateChildren();
}
}
}
public class ChildNode : TreeNode
{
public RootNode ParentNode { get; set; }
private bool visible;
public bool Visible { get { return visible; } set { visible = value;OnVisibleChanged(): } }
private void OnVisibleChanged()
{
if (ParentNode != null)
{
ParentNode.PopulateChildren();
}
}
}
No, there is no way to make node invisible. You should remove it instead of making invisible. And later you will have to add it back into its original position.
If you are loading a treeview with a sitemap file, then another approach is to do something like this. Here the user's credentials have been read from a DB and written to a cookie.
private void ManageTreeMenu()
{
var value = Utilities.Cookies.GetCookieValue("IsAdmin");
bool.TryParse(value, out var isAdmin);
var dir = Server.MapPath("~");
File.Delete(dir + "Web.sitemap");
if (isAdmin)
File.Copy(dir + "WebAdmin.sitemap", dir + "/Web.sitemap");
else
File.Copy(dir + "WebOper.sitemap", dir + "/Web.sitemap");
}
You'd have to do this again if the user's role was changed in the program. I have only verified this in Visual Studio, not in a deployed web application. Caveat emptor.