I have a list of a class, the class also has a class which is used to display map in the tree view.
public class Option
{
public Guid Id;
public string Title;
public string Description;
public List<GotoOption> GotoOptions;
public bool IsEnd;
public string GotoValueParent;
public Option()
{
this.IsEnd = false;
this.GotoOptions = new List<GotoOption>();
}
}
public class GotoOption
{
public Guid GotoId;
public string Value;
}
So an Option can have many GotoOptions and these are mapped by the Guid, so if my tree view looked like:
Tree
1.1. Branch
1.2. Branch
1.3. Branch
There will be 4 options but the tree view will have 3 GotoOptions which link to the branches.
So my goal is to basically create a recursive loop so I don't have to manually create a loop, but I got no idea how to start it off.
Currently I have -
private void PopulateTreeView(Option option)
{
if (option != null)
{
TreeNode node = new TreeNode();
node.Text = option.Title;
node.Tag = option;
pages.Nodes.Add(node);
foreach (GotoOption op in option.GotoOptions)
{
Option ops = Options.FirstOrDefault(i => i.Id == op.GotoId);
TreeNode inner = new TreeNode();
inner.Text = ops.Title;
inner.Tag = ops;
node.Nodes.Add(inner);
foreach (GotoOption op2 in ops.GotoOptions)
{
Option opps = Options.FirstOrDefault(i => i.Id == op2.GotoId);
TreeNode inner2 = new TreeNode();
inner2.Text = opps.Title;
inner2.Tag = opps;
inner.Nodes.Add(inner2);
}
}
}
}
Which is looping for 3 layers only, but we could have 10-25 odd layers and that's a lot of manual code. I have been looking at how it works with files and folders http://www.dotnetperls.com/recursive-file-list but I can't seem to convert it from how it works there to getting it to work with my code. Any help would be great.
Managed to solve, I created an optional parameter passing in the created node, if the parameter is not passed in, it creates a new one.
private void PopulateTreeView(Option option, TreeNode existingNode = null)
{
if (option != null)
{
TreeNode newNode = new TreeNode();
newNode.Text = option.Title;
newNode.Tag = option;
if (existingNode == null)
{
pages.Nodes.Add(newNode);
}
else
{
existingNode.Nodes.Add(newNode);
}
foreach (GotoOption gotoOption in option.GotoOptions)
{
Option newOption = Options.FirstOrDefault(i => i.Id == gotoOption.GotoId);
PopulateTreeView(newOption, newNode);
}
}
}
private void CreateTreeView()
{
var roots = Options.Select(z => z.Id)
.Except(Options.SelectMany(z => z.GotoOptions.Select(x => x.GotoId)))
.Select(z => Options.Single(x => x.Id == z));
var treeNodes = roots.Select(GetNode);
foreach (var treeNode in treeNodes)
{
pages.Nodes.Add(treeNode);
}
}
private TreeNode GetNode(Option option)
{
var node = new TreeNode
{
Text = option.Title,
Tag = option
};
foreach (var child in option.GotoOptions.Select(z => Options.Single(x => x.Id == z.GotoId)))
{
node.Nodes.Add(GetNode(child));
}
return node;
}
Related
I have to create an indented navigation menu using below data from a .csv file:
ID;MenuName;ParentID;isHidden;LinkURL1;Company;NULL;False;/company2;About Us;1;False;/company/aboutus3;Mission;1;False;/company/mission4;Team;2;False;/company/aboutus/team5;Client 2;10;False;/references/client26;Client 1;10;False;/references/client17;Client 4;10;True;/references/client48;Client 5;10;True;/references/client510;References;NULL;False;/references
Using this data I have to develop an application that will parse the file and present the content in a console as the example below:
. Company.... About Us....... Team.... Mission. References.... Client 1.... Client 2
Menu items should be indented (depending on the parent), hidden items (isHidden==true) shouldn't be presented and items should be ordered alphabetically. So far I tried:
using (StreamReader sr = new StreamReader(#"file.csv"))
{
// Read the stream to a string, and write the string to the console.
string [] lines = sr.ReadToEnd().Split(/*';', */'\n');
for (int i = 1; i < lines.Length; i++)
{
Console.WriteLine($"String no {i} is : {lines[i-1]}");
}
}
With this i'm getting the lines but I'm stuck after that. I'm new in coding so any help will be appreciated :)
heres some code that should help you get off.
Working sample:
https://dotnetfiddle.net/L37Gjr
It first parses the data to a seperate object. This then gets used to build a m-ary tree, or a hierachical structure of connected nodes. (a node has a reference to 0 or more children).
https://en.wikipedia.org/wiki/M-ary_tree
Then tree traversal (use google if you need to know more) is used to insert and print the output, There is still something wrong however. it now uses level order traversal to print, this however comes up with an error:
Found root:1 - Company
Found root:10 - References
-------------------
1 - Company
2 - About Us
3 - Mission
4 - Team
10 - References
6 - Client 1
5 - Client 2
As you can see, it prints 4 - Team on the wrong level. I'll leave it to you to fix it (because i ran out of time), and if not i hope i gave you plenty ideas to go off and research on your own.
// sample for https://stackoverflow.com/questions/61395486/read-csv-file-and-return-indented-menu-c-sharp by sommmen
using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public class Node<T>
{
public T Data {get;set;}
public List<Node<T>> Children { get; set;}
public Node()
{
Children = new List<Node<T>>();
}
// Tree traversal in level order
public List<Node<T>> LevelOrder()
{
List<Node<T>> list = new List<Node<T>>();
Queue<Node<T>> queue = new Queue<Node<T>>();
queue.Enqueue(this);
while(queue.Count != 0)
{
Node<T> temp = queue.Dequeue();
foreach (Node<T> child in temp.Children)
queue.Enqueue(child);
list.Add(temp);
}
return list;
}
public List<Node<T>> PreOrder()
{
List<Node<T>> list = new List<Node<T>>();
list.Add(this);
foreach (Node<T> child in Children)
list.AddRange(child.PreOrder());
return list;
}
public List<Node<T>> PostOrder()
{
List<Node<T>> list = new List<Node<T>>();
foreach (Node<T> child in Children)
list.AddRange(child.PreOrder());
list.Add(this);
return list;
}
}
public class Entity
{
public int id {get;set;}
public string menuName {get;set;}
public int? parentID {get;set;}
public bool isHidden {get;set;}
public string linkURL {get;set;}
}
public static void Main()
{
var data = #"ID;MenuName;ParentID;isHidden;LinkURL
1;Company;NULL;False;/company
2;About Us;1;False;/company/aboutus
3;Mission;1;False;/company/mission
4;Team;2;False;/company/aboutus/team
5;Client 2;10;False;/references/client2
6;Client 1;10;False;/references/client1
7;Client 4;10;True;/references/client4
8;Client 5;10;True;/references/client5
10;References;NULL;False;/references";
var lines = data.Split('\n');
var rootNodes = new List<Node<Entity>>();
var childItems = new List<Entity>();
// Parse the data to entities
// Items without a parent are used as rootnodes to build a tree
foreach(var row in lines.Skip(1))
{
var columns = row.Split(';');
var id = Convert.ToInt32(columns[0]);
var menuName = columns[1];
var parentID = ToNullableInt(columns[2]);
var isHidden = Convert.ToBoolean(columns[3]);
var linkURL = columns[4];
var entity = new Entity()
{
id = id,
menuName = menuName,
parentID = parentID,
isHidden = isHidden,
linkURL = linkURL
};
if(parentID == null)
{
Console.WriteLine("Found root:" + entity.id + " - " + entity.menuName);
rootNodes.Add(new Node<Entity>()
{
Data = entity
});
}
else
{
childItems.Add(entity);
}
}
// Add the childElements to their appropriate rootnode
foreach(var rootNode in rootNodes)
{
foreach(var childItem in childItems.OrderBy(a=>a.parentID).ThenBy(b=>b.menuName))
{
var newNode = new Node<Entity>()
{
Data = childItem
};
Insert(rootNode, newNode);
}
}
Console.WriteLine("-------------------");
foreach(var rootNode in rootNodes)
{
var indent = 0;
var previous = rootNode;
foreach(var node in rootNode.LevelOrder())
{
if(node.Data.isHidden) continue;
if(previous.Data.parentID != node.Data.parentID)
indent++;
for(var i = 0; i < indent; i++)
Console.Write("\t");
Console.WriteLine(node.Data.id + " - " + node.Data.menuName);
previous = node;
}
}
}
public static void Insert(Node<Entity> rootNode, Node<Entity> targetNode)
{
foreach(var current in rootNode.LevelOrder())
{
if(current.Data.id == targetNode.Data.parentID)
{
current.Children.Add(targetNode);
return;
}
}
}
public static int? ToNullableInt(string s)
{
int i;
if (int.TryParse(s, out i)) return i;
return null;
}
}
I am trying to get all menus and children that satisfy those conditions using linq:
Menus should have either a link or children count > 0 to be displayed at the
website
For the menus that has children : they should have at least one child menu that has a link
This is the Menu class:
public class Menu
{
public string Name { get; set; }
public string Link { get; set; }
public List<Menu> Children { get; set; }
public Menu()
{
Children = new List<Menu>();
}
}
Suppose we have this data structure:
List<Menu> root = new List<Menu>();
Menu parent_1 = new Menu() { Name = "Parent 1", Link = null };
Menu parent_2 = new Menu() { Name = "Parent 2", Link = null };
//children for parent 1
Menu p1_child_1 = new Menu() { Name = "p1_child_1", Link = null };
Menu p1_child_2 = new Menu() { Name = "p1_child_2", Link = null };
//sub children of p1_child_2
Menu p1_child_1_1 = new Menu() { Name = "p1_child_1_1", Link = "l1-1" };
Menu p1_child_1_2 = new Menu() { Name = "p1_child_1_2", Link = null };
p1_child_1.Children.AddRange(new List<Menu> { p1_child_1_1 , p1_child_1_2 });
parent_1.Children.AddRange(new List<Menu> { p1_child_1, p1_child_2 });
Menu p2_child_1 = new Menu() { Name = "p2_child_1", Link = null };
Menu p2_child_2 = new Menu() { Name = "p2_child_2", Link = "l2-2" };
Menu p2_child_1_1 = new Menu() { Name = "p2_child_1_1", Link = null };
Menu p2_child_1_2 = new Menu() { Name = "p2_child_1_2", Link = null };
p2_child_1.Children.AddRange(new List<Menu> { p2_child_1_1, p2_child_1_2 });
parent_2.Children.AddRange(new List<Menu> { p2_child_1, p2_child_2 });
root.Add(parent_1);
root.Add(parent_2);
Result: The filtered list returned based on the conditions requested will be:
parent_1
p1_child_1
p1_child_1_1
parent_2
p2_child_2
How to achieve that using Linq or alternative approach taking into consideration the menu could have up to many levels?
Trying the solution as proposed in the comments, i added the extension method
public static IEnumerable<TResult> SelectHierarchy<TResult>(this IEnumerable<TResult> source, Func<TResult, IEnumerable<TResult>> collectionSelector, Func<TResult, bool> predicate)
{
if (source == null)
{
yield break;
}
foreach (var item in source)
{
if (predicate(item))
{
yield return item;
}
var childResults = SelectHierarchy(collectionSelector(item), collectionSelector, predicate);
foreach (var childItem in childResults)
{
yield return childItem;
}
}
Then called the method:
var result = root.SelectHierarchy(n => n.Children, n => n.Children.Count > 0 || n.Link != null).ToList();
However this is not what i want, I expect two menus which carry the subMenus that satisfy my condition, but i am getting 6 menus which i guess are flattened.
Although, p2_child_1 was returned since children count > 0, however it shouldn't cause its menus has no links. ( I placed the predicate as above, since i don't have other option.
This works for me:
public static class Ex
{
public static List<Menu> CloneWhere(this List<Menu> source, Func<Menu, bool> predicate)
{
return
source
.Where(predicate)
.Select(x => new Menu()
{
Name = x.Name,
Link = x.Link,
Children = x.Children.CloneWhere(predicate)
})
.Where(predicate)
.ToList();
}
}
The sample data looks like this:
...then I can apply this:
var result = root.CloneWhere(m => m.Children.Any() || m.Link != null);
...and I get this result:
Assuming the depth is not so big to cause stack overflow, you can use a simple recursive method or recursive lambda as follows:
Func<List<Menu>, List<Menu>> filter = null;
filter = items =>
(from item in items
let children = filter(item.Children)
where item.Link != null || children.Any()
select new Menu { Name = item.Name, Link = item.Link, Children = children }
).ToList();
var filtered = filter(root);
The essential part is to process the children first (post order traversal) before filtering the parent.
I have this code to generate a temp tree whose code is as follows
object regionSale = regionValue.GetValueAsString();
if (root.Children.Count > 0)
{
if ((tmpNode.Data.Level) == (levelNested - 1))
{
var newChild = new Node
{
Data = new NodeData
{
Level = levelNested,
RegionName = elemNested.GetValueAsString(),
RegionValue = NAValue.Equals(regionSale.ToString())
? null
: (double?)regionValue.GetValueAsFloat64()
},
Parent = tmpNode
};
tmpNode.Children.Add(newChild);
tmpNode = newChild;
}
else if (tmpNode.Data.Level == levelNested)
{
var node = tmpNode.Parent;
var newChild = new Node
{
Data = new NodeData
{
Level = levelNested,
RegionName = elemNested.GetValueAsString(),
RegionValue = NAValue.Equals(regionSale.ToString())
? null
: (double?)regionValue.GetValueAsFloat64()
},
Parent = node
};
node.Children.Add(newChild);
tmpNode = newChild;
}
else
{
var parentNode = tmpNode.Parent;
while ((parentNode.Data.Level) != (levelNested - 1))
{
parentNode = parentNode.Parent;
}
var newChild = new Node
{
Data = new NodeData
{
Level = levelNested,
RegionName = elemNested.GetValueAsString(),
RegionValue = NAValue.Equals(regionSale.ToString())
? null
: (double?)regionValue.GetValueAsFloat64()
},
Parent = parentNode
};
parentNode.Children.Add(newChild);
tmpNode = newChild;
}
}
else
{
var children = new Node();
children.Data = new NodeData
{
Level = levelNested,
RegionName = elemNested.GetValueAsString(),
RegionValue = NAValue.Equals(regionSale.ToString())
? null
: (double?)regionValue.GetValueAsFloat64()
};
children.Parent = root;
root.Children.Add(children);
tmpNode = children;
}
The data passed to this function is a root node like:
for (var nestedIndex = 0; nestedIndex < numofBulkValues; nestedIndex++)
{
var bulkElementNested = refBulkField.GetValueAsElement(nestedIndex);
var elemNested = bulkElementNested.GetElement(0);
var levelElement = bulkElementNested.GetElement(1);
var regionValue = bulkElementNested.GetElement(2);
var levelNested = levelElement.GetValueAsInt32();
tmpNode = GenerateTree(root, tmpNode, elemNested, regionValue, levelNested);
}
In this situation, the data i get is in the format
ADSK UW EQUITY
Europe, Middle East and Africa Level=1
The Americas Level=1
U.S Level=2
Other Americas Level=2
The Asia/Pacific Level=1
Other Asia/Pacific Level=2
Japan Level=2
Reconciliation Level=1
and there are multiple such equities. the problem is that this process is taking a long time almost 9 seconds to do but only takes 16 seconds to display the actual result. Yes, this is the core of the application and very important so it cannot be skipped. Is there any way to reduce the time to create this tree?
my node class is as follows:
public class Node
{
public Node()
{
}
public Node(Node node)
: this()
{
if (node == null)
return;
this.Data = new NodeData(node.Data);
if (node.Children != null)
this.Children = new List<Node>(node.Children);
this.Parent = new Node(node.Parent);
}
public NodeData Data;
public List<Node> Children = new List<Node>();
public Node Parent;
}
public class NodeData
{
public NodeData()
{
}
public NodeData(NodeData nodeData)
: this()
{
if (nodeData == null)
return;
this.RegionName = nodeData.RegionName;
this.RegionValue = nodeData.RegionValue;
this.Level = nodeData.Level;
}
public string RegionName;
public double? RegionValue;
public int Level;
}
If there is more i can provide please let me know. and thanks for any help
Okay,
So what i have done is that I have made changes in the Node.cs Class as follows:
public class Node
{
public Node()
{
}
public Node(Node node)
: this()
{
if (node == null)
return;
if (node.Children != null)
this.Children = new List<Node>(node.Children);
this.Parent = new Node(node.Parent);
this.RegionName = nodeData.RegionName;
this.RegionValue = nodeData.RegionValue;
this.Level = nodeData.Level;
}
public List<Node> Children = new List<Node>();
public Node Parent;
public string RegionName;
public double? RegionValue;
public int Level;
}
Also i have checked the functions where the log is being recorded, so small functions which get called a lot(for inside for..) i have removed those logs. This all has reduced the time from 4.30 minutes to about 1.30 minutes for 900 equities. But i wanted to ask if there is something more i can do to make it faster.
There is one other problem:
Only for 1 function out of many which pulls the data from the database(sqlite database), connection.Open() takes a lot of time. Can this problem be because the connection is Open for a long time?? or is there a possibility of another connection which is already open,so to close that and start this connection takes time?
I want to create a treeview in c# which will group file by prefix (here the prefix is a marked by the separator _). The following files should give this tree:
Files list :
p_a
p_a_test
p_LIG
p_p
p_p_c
p_p_c2
p_p_ccc
p_p_test
p_tres
TestLineGraph1
TestLineGrpah
Corresponding tree:
|--p_
|--p_a
|--p_a_test
|--p_LIG
|--p_p
|--p_p_
|--p_p_c
|--p_p_c2
|--p_p_ccc
|--p_p_test
|--p_tres
TestLineGraph1
TestLineGrpah
Here's my attempt of code:
private GraphUINode(List<string> subNodes, GraphUINode parent, string name, int lvl = 0)
: base(parent.m_viewDataSubControl)
{
parent.Nodes.Add(this);
this.Name = name;
this.Text = name;
string currentPrefix = "";
int pertinentSubNodes = 0;
while (pertinentSubNodes < subNodes.Count -1 && subNodes[pertinentSubNodes].Split('_').Length < 2+ lvl)
pertinentSubNodes++;
for (int i = 0; i <= lvl; i++)
{
currentPrefix += subNodes[pertinentSubNodes].Split('_')[i] + "_";
}
List<String> children = new List<string>();
foreach (string child in subNodes)
{
// The child is in the same group than the previous one
if (child.StartsWith(currentPrefix))
{
children.Add(child);
}
else
{
// Create a node only if needed
if (children.Count > 1)
{
// Create the new node
new GraphUINode(children, this, currentPrefix, lvl + 1);
children.Clear();
children.Add(child);
}
else
{
new GraphTemplateNode(this, m_viewDataSubControl, child);
}
currentPrefix = "";
for (int i = 0; i <= lvl; i++)
{
currentPrefix += child.Split('_')[i] + "_";
}
}
}
}
But I miss a few ones in the final result:
How can I get its back? Even when I debug step by step I can't find the logical way to do it.
So the first thing that we'll want to do here is take our strings and turn them into a tree. Once we have a tree then mapping those nodes to a TreeView is quite easy.
We'll start out with the definition for the tree itself:
public class Node<T>
{
public Node(T value, IEnumerable<Node<T>> children)
{
Value = value;
Children = children;
}
public T Value { get; private set; }
public IEnumerable<Node<T>> Children { get; private set; }
}
Nice and easy, each node is just a value and a collection of children.
Next we'll write a method to take a sequence of sequences, and build a tree from it. The idea here is that we'll group all of the items based on the first value in their sequence, build a node for each group, and then recursively call the method on the group to get the children for that node.
public static IList<Node<T>> GroupToTree<T>(this IEnumerable<IEnumerable<T>> source)
{
return GroupToTree(source.Select(sequence => sequence.GetEnumerator()));
}
private static IList<Node<T>> GroupToTree<T>(IEnumerable<IEnumerator<T>> source)
{
return source.WhereHasNext()
.GroupBy(iterator => iterator.Current)
.Select(group => new Node<T>(group.Key, GroupToTree(group)))
.ToList();
}
//This ensures that the iterators all get disposed
private static IEnumerable<IEnumerator<T>> WhereHasNext<T>(
this IEnumerable<IEnumerator<T>> source)
{
foreach (var iterator in source)
{
if (iterator.MoveNext())
yield return iterator;
else
iterator.Dispose();
}
}
Now we can take the raw data, split each of the strings into sequences of strings, and then map each of the nodes that we have here into UI-based nodes for presentation:
List<string> rawData = new List<string>();
//TODO populate raw data
Func<Node<string>, TreeNode> selector = null;
selector = node => new TreeNode(node.Value, node.Children.Select(selector).ToArray());
var nodes = rawData.Select(line => line.Split('_').AsEnumerable())
.GroupToTree()
.Select(selector);
suppose i have saved file path in database. now i want to show those file path through treeview. i found one sample that works fine but do not know when there will be huge data in database then treeview population will be hang or take too long. here i am giving the code. please check and tell which can be rectified as a result performance will be good when there will be huge data in db.
public static class MyDataBase
{
private static List<string> fields = new List<string>();
public static void AddField(string field)
{
fields.Add(field);
}
public static IList<string> FieldsInMyColumn()
{
return fields;
}
}
public void CreateTreeView()
{
foreach (string field in MyDataBase.FieldsInMyColumn())
{
string[] elements = field.Split('\\');
TreeNode parentNode = null;
for (int i = 0; i < elements.Length - 1; ++i)
{
if (parentNode == null)
{
bool exits = false;
foreach (TreeNode node in myTreeview.Nodes)
{
if (node.Text == elements[i])
{
exits = true;
parentNode = node;
}
}
if (!exits)
{
TreeNode childNode = new TreeNode(elements[i]);
myTreeview.Nodes.Add(childNode);
parentNode = childNode;
}
}
else
{
bool exits = false;
foreach (TreeNode node in parentNode.Nodes)
{
if (node.Text == elements[i])
{
exits = true;
parentNode = node;
}
}
if (!exits)
{
TreeNode childNode = new TreeNode(elements[i]);
parentNode.Nodes.Add(childNode);
parentNode = childNode;
}
}
}
if (parentNode != null)
{
parentNode.Nodes.Add(elements[elements.Length - 1]);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
MyDataBase.AddField(#"c:\jsmith\project1\hello.cs");
MyDataBase.AddField(#"c:\jsmith\project1\what.cs");
MyDataBase.AddField(#"c:\jsmith\project2\hello.cs");
MyDataBase.AddField(#"c:\jsmith\project1\tdp.cs");
MyDataBase.AddField(#"c:\jsmith\project2\ship.cs");
MyDataBase.AddField(#"d:\jsmith\project1\hello404.cs");
MyDataBase.AddField(#"c:\jsmith1\project2\ship.cs");
CreateTreeView();
}
thanks
Depending on your framework version, maybe you can try something like this :
public void ProcessPath(IEnumerable<String> path, TreeNodeCollection nodes)
{
if (!path.Any())
return;
var node = nodes.Cast<TreeNode>().FirstOrDefault(n => n.Text == path.First());
if (node == null)
{
node = new TreeNode(text: path.First());
nodes.Add(node);
}
ProcessPath(path.Skip(1),node.ChildNodes);
}
public void CreateTreeView()
{
foreach (string field in MyDataBase.FieldsInMyColumn())
ProcessPath(field.Split('\\'),myTreeView.Nodes);
}
If you really have a huge amount of rows, you should probably look for a solution where you only load the child nodes upon click on a node
The first that I could suggest to improve is to use while instead of foreach:
instead of this in both places:
bool exits = false;
foreach (TreeNode node in myTreeview.Nodes)
{
if (node.Text == elements[i])
{
exits = true;
parentNode = node;
}
}
you can use
bool exits = false;
int j = 0;
while (!exits && j<myTreeview.Nodes.Count)
{
if (myTreeview.Nodes[j].Text == elements[i])
{
exits = true;
parentNode = node;
}
j++;
}
This way you won't need to iterate through the whole Nodes collection and the loop will finish just after it finds the parent node. Off course, you will go through the whole collection in the cases there isn't Node titled elements[i].
P.S. I think you meant exists instead of exits