How to insert a child node in a TreeView Control in WPF? - c#

I have a TreeView control that I have created in XAML in a WPF program
After adding a couple nodes at the root level, I have written code that loops through the tree structure like so:
ItemCollection items = treeView1.Items;
foreach (TreeViewItem n in items)
{
...
}
Once I find the place in this loop where I want to include a child node, how do I go about inserting a child?

This is a piece of very naive code that does it, you might want to make it more defensive if you actually use it.
var items = treeView1.Items;
var item = new TreeViewItem() { Header = "Interesting" };
items.Add(item);
var subitem = new TreeViewItem() {Header = "Sub Item"};
foreach (TreeViewItem n in items)
{
if (n.Header == "Interesting")
(n as TreeViewItem).Items.Add(subitem);
}

Related

Nesting a Foreach statement using element from the first foreach stement

I am trying to nest a foreach statement for a stacklayout, to get each item in it which are stacklayouts, and the stacklayouts also contains stacklayouts which I want to contain an entry.
`var Lab1 = TotalBodyStackLayout2.Children.OfType<StackLayout>();
foreach (StackLayout l in Lab1)
{
StackLayout newstacklayout = new StackLayout();
Label EDTL = new Label();
l.Children.RemoveAt(1);
var Labh = l.Children.OfType<ExpandableEditor>();
foreach (ExpandableEditor Item in Labh)
{
Label newlabel = new Label();
newlabel.Text = Item.Text;
l.Children.RemoveAt(0);
l.Children.Insert(0, newlabel);
}
newstacklayout.Children.Add(l.Children[0]);
MainstackLayout.Children.Add(newstacklayout);
}`
I keep getting an error at foreach (ExpandableEditor Item in Labh) which says
<System.InvalidOperationException: 'Collection was modified; enumeration
operation may not execute.'>
The exception occurred likely because this part:
// Labh is actually the same collection as l.Children
// It's just a special enumerator over l.Children that skips everything that's not an ExpandableEditor
var Labh = l.Children.OfType<ExpandableEditor>();
foreach (ExpandableEditor Item in Labh) // so here you are essentially looping over l.Children
{
Label newlabel = new Label();
newlabel.Text = Item.Text;
l.Children.RemoveAt(0); // while looping over it, you are removing...
l.Children.Insert(0, newlabel); // ...and adding stuff to l.Children
You can't add or remove stuff from the collection that you are looping over!
One way to solve this is to create a copy of l.Children before looping over it, and loop over the copy instead. This can be done by ToList:
var Labh = l.Children.OfType<ExpandableEditor>().ToList();
ForEach will open an Iterator/Enumerator on the Collection. The moment you try to:
l.Children.RemoveAt(0);
l.Children.Insert(0, newlabel);
It will cry foul (like it is). You can use a For loop instead.

Remove Child Nodes from Parent Node

Hello I currently have a TreeView with the following structure:
Root
Child
Root
Child
Root
Child
Child
RootN
ChildN
The TreeView structure can basically have NRootNodes - NChildren and the NRootNodes can have NRoots and NChildren so basically just like Windows Explorer Window.
My current issue that I have is that I have to get all the Parents or Root, in this case Roots / RootN and then I have to Remove all of their Child Nodes, in this case Child / ChildN. In the end I have to have only the Parent Nodes and then Clone them so I can move them to a different location within the TreeView.
RootNodes have a unique Tag - Folder and ChildNodes have another unique Tag - Calculations, as I have said earlier, I have to get rid of all Calculations in the Selected Node so only the Structure of that Selected Node will Remain.
Basically in the end I have to have something like this:
Root
Root
Root
Root
Root
I have a recursive method that "scans" the SelectedNode and gets all the Parents:
public List<TreeNode> CollectParentNodes(TreeNodeCollection parentCollection, List<TreeNode> collectedNodes)
{
foreach (TreeNode node in parentCollection)
{
if (!collectedNodes.Contains(node.Parent))
{
collectedNodes.Add(node.Parent);
parentNodeAdded = true;
}
if (node.Level != 0 && node.Tag.ToString() != Enumerations.NodeType.Calculation.ToString())
collectedNodes.Add(node);
if (node.Nodes.Count > 0)
CollectParentNodes(node.Nodes, collectedNodes);
}
parentNodeAdded = false;
return collectedNodes;
}
In the end I have a List that will hold all the Parents but the problem I'm facing is that that Parents also contain their descendents, in this case the Calculations
I have searched Google and StackOverFlow but I could not find anything of help, I appologize in advance if this has already been answered.
Thank you.
You can create an extension method GetAllNodes for TreeView that return List
Remember using using System.Linq; at top of your code
public static class Extensions
{
public static List<TreeNode> GetAllNodes(this TreeView tree)
{
var firstLevelNodes = tree.Nodes.Cast<TreeNode>();
return firstLevelNodes.SelectMany(x => GetNodes(x)).Concat(firstLevelNodes).ToList();
}
private static IEnumerable<TreeNode> GetNodes(TreeNode node)
{
var nodes = node.Nodes.Cast<TreeNode>();
return nodes.SelectMany(x => GetNodes(x)).Concat(nodes);
}
}
And the usage will be:
var result = this.treeView1.GetAllNodes().Where(x => x.Tag == "FOLDER").ToList();
Remember to add namespace of your extensions class at top of your code wherever you want to use it.
As an example you can set All nodes with tag of Folder to be in Red forecolor:
var result = this.treeView1.GetAllNodes().Where(x => (x.Tag as string) == "FOLDER").ToList();
result.ForEach(x => x.ForeColor = Color.Red);
And here is an Screenshot
This will create a new tree with the selected node as root and which child nodes consists only of nodes that are tagged "Folder".
You need to create a copy constructor (or extension method) to deep copy the node to prevent the manipulation on the node objects to impact your original tree source:
public TreeNode CollectFolderChildNodes(TreeNode selectedNode)
{
if (selectedNode.Tag == "Calculation")
return null;
// Get all the children that are tagged as folder
var childRootNodes = selectedNode.Children.Where((childNode) => childNode.Tag == "Folder";
// Clone root node using a copy constructor
var newRoot = new TreeNode(selectedNode);
newRoot.Children.Clear();
foreach (var childNode in childRootNodes)
{
// Iterate over all children and add them to the new tree
if (childNode.Children.Any())
{
// Repeat steps for the children of the current child.
// Recursion stops when the leaf is reached
newRoot.Children.Add(CollectFolderChildNodes(childNode));
}
else
{
// The current child item is leaf (no children)
newRoot.Children.Add(new TreeNode(childNode));
}
}
return newRoot;
}
I think this should do it, but I didn't tested it. But maybe at least the idea behind it is clear.
But as I mentioned before, maybe it's better to traverse the tree (using same ItemsSource) and set a property (e.g. IsHidingCalculations) to true so that only the folders will show up. You would need to implement an ItemsStyle and use a trigger that sets the items Visibility to Collapsed when your IsHidingCalculations evaluates to true.
To clone a node without its children you can create an extension method like this:
public static TreeNode CloneWithoutChildren(this TreeNode node)
{
return new TreeNode(node.Text, node.ImageIndex, node.SelectedImageIndex)
{
Name = node.Name,
ToolTipText = node.ToolTipText,
Tag = node.Tag,
Checked = node.Checked
}
}
and then:
collectedNodes.Add(node.CloneWithoutChildren());

How to convert the following foreach nested loop to LINQ?

Code:
foreach (var item in items.Children)
{
RadTreeViewItem parent1 = new RadTreeViewItem();
parent1.Header = NodeHeader(item.Path, item.Name, SelectedPath, ProjectData);
parent1.Tag = item;
foreach (var child in item.Children)
{
RadTreeViewItem children = new RadTreeViewItem();
children.Header = NodeHeader(child.Path, child.Name, SelectedPath, ProjectData);
children.Tag = child;
parent1.Items.Add(children);
}
Parent.Items.Add(parent1);
}
items.Children and item.Children are ObservableCollection<>
parent1.Header and children.Header are HeaderedItemsControl.Header
parent1.Tag and children.Tag are FrameworkElement.Tag
How to convert the above foreach nested loop to LINQ ?
LINQ... Language INtegrated (and here's the key bit) Query.
What you are doing is not a query. Leave it with the foreach loops; it is fine, it is clear, it is obvious.
In particular, the .Items.Add methods on the various collections is not really something you can trivially reproduce in LINQ. You can probably do it, but it will be ugly and hard to maintain. What you have is fine. If you want to change it for change's sake, maybe:
RadTreeViewItem parent1 = new RadTreeViewItem {
Header = NodeHeader(item.Path, item.Name, SelectedPath, ProjectData),
Tag = item
};
foreach (var child in item.Children)
{
parent1.Items.Add(new RadTreeViewItem {
Header = NodeHeader(child.Path, child.Name, SelectedPath, ProjectData),
Tag = child
});
}
Parent.Items.Add(parent1);
Not exactly an improvement, IMO.

Get all <td> title of a table column with coded ui

I need to check a filter function on a table.
This filter is only on the first cell of each row and I'm trying to figure out how to get all those values...
I tried with something like
public bool CheckSearchResults(HtmlControl GridTable, string FilterTxt)
{
List<string> Elements = new List<string>();
foreach (HtmlCell cell in GridTable.GetChildren())
{
Elements.Add(cell.FilterProperties["title"]);
}
List<string> Results = Elements.FindAll(l => l.Contains(FilterTxt));
return Results.Count == Elements.Count;
}
but I get stuck at the foreach loop...
maybe there's a simply way with linq, but i don't know it so much
edit:
all the cells i need have the same custom html tag.
with this code i should get them all, but i don't know how to iterate
HtmlDocument Document = this.UIPageWindow.UIPageDocument;
HtmlControl GridTable = this.UIPageWindow.UIPageDocument.UIPageGridTable;
HtmlCell Cells = new HtmlCell(GridTable);
Cells.FilterProperties["custom_control"] = "firstCellOfRow";
also because there's no GetEnumerator function or query models for HtmlCell objects, which are part of Microsoft.VisualStudio.TestTools.UITesting.HtmlControl library -.-
edit2:
i found this article and i tried this
public bool CheckSearchResults(string FilterTxt)
{
HtmlDocument Document = this.UIPageWindow.UIPageDocument;
HtmlControl GridTable = this.UIPageWindow.UIPageDocument.UIPageGridTable;
HtmlRow rows = new HtmlRow(GridTable);
rows.SearchProperties[HtmlRow.PropertyNames.Class] = "ui-widget-content jqgrow ui-row-ltr";
HtmlControl cells = new HtmlControl(rows);
cells.SearchProperties["custom_control"] = "firstCellOfRow";
UITestControlCollection collection = cells.FindMatchingControls();
List<string> Elements = new List<string>();
foreach (UITestControl elem in collection)
{
HtmlCell cell = (HtmlCell)elem;
Elements.Add(cell.GetProperty("Title").ToString());
}
List<string> Results = Elements.FindAll(l => l.Contains(FilterTxt));
return Results.Count == Elements.Count;
}
but i get an empty collection...
Try Cell.Title or Cell.GetProperty("Title"). SearchProperties and FilterProperties are only there for searching for a UI element. They either come from the UIMap or from code if you fill them out with hand. Otherwise your code should should work.
Or you can use a LINQ query (?) like:
var FilteredElements =
from Cell in UIMap...GridTable.GetChildren()
where Cell.GetProperty("Title").ToString().Contains(FilterTxt)
select Cell;
You could also try to record a cell, add it to the UIMap, set its search or filter properties to match your filtering, then call UIMap...Cell.FindMatchingControls() and it should return all matching cells.
The problem now is that you are limiting your search for one row of the table. HtmlControl cells = new HtmlControl(rows); here the constructor parameter sets a search limit container and not the direct parent of the control. It should be the GridTable if you want to search all cells in the table. Best solution would be to use the recorder to get a cell control then modify its search and filter properties in the UIMap to match all cells you are looking for. Tho in my opinion you should stick with a hand coded filtering. Something like:
foreach(var row in GridTable.GetChildren())
{
foreach(var cell in row.GetChildren())
{
//filter cell here
}
}
Check with AccExplorer or the recorder if the hierarchy is right. You should also use debug to be sure if the loops are getting the right controls and see the properties of the cells so you will know if the filter function is right.
I resolved scraping pages html by myself
static public List<string> GetTdTitles(string htmlCode, string TdSearchPattern)
{
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(htmlCode);
HtmlNodeCollection collection = doc.DocumentNode.SelectNodes("//td[#" + TdSearchPattern + "]");
List<string> Results = new List<string>();
foreach (HtmlNode node in collection)
{
Results.Add(node.InnerText);
}
return Results;
}
I'm freakin' hating those stupid coded ui test -.-
btw, thanks for the help

Bind DataTable to TreeView without Iterations

I'm able to Bind the Datatable Column to Gridview
Here I select the Distinct Values fron a column in a datatable and insert them to TreeView.
string[] menuGroup = ((from DataRow row1 in _ds.Tables["Rest_grdvitems"].Rows
orderby row1["Menu_Group"]
select row1["Menu_Group"].ToString()).Distinct()).ToArray();
TreeNode node = new TreeNode("All Items");
TV_Categories_List.Nodes.Add(node);
foreach (string menuitem in menuGroup)
{
TreeNode node1 = new TreeNode(menuitem);
TV_Categories_List.Nodes.Add(node1);
}
Since I have large no of rows to be inserted to TreeView, I need to avoid Iterations.
Can you hep me??
The TreeView (nor the ListView) can not be directly bound to a DataSource.
As #IamStalker states as a comment, there are iterations in the internal WinForms binding control mechanism: there is nothing wrong with iterating in your data to populate a TreeView.
If your main concern is the performance, then you should ensure you enclose the code that add nodes with .BeginUpdate() and .EndUpdate() methods: it will lock the TreeView display refresh during the populate operation. This is only applicable to the WinForms TreeView
To have only one iteration, you should change the LINQ as below (put var instead of string[] and remove .ToArray()). So the LINQ statement will returns a LINQ IEnumerable<string> instead of a string[]. This way, it will only be enumerated in the foreach loop that populates the TreeView.
var menuGroup = (from DataRow row1 in _ds.Tables["Rest_grdvitems"].Rows
orderby row1["Menu_Group"]
select row1["Menu_Group"].ToString()).Distinct();
TreeNode node = new TreeNode("All Items");
TV_Categories_List.BeginUpdate();
TV_Categories_List.Nodes.Add(node);
foreach (string menuitem in menuGroup)
{
TreeNode node1 = new TreeNode(menuitem);
TV_Categories_List.Nodes.Add(node1);
}
TV_Categories_List.EndUpdate();

Categories