I would like to create a method which collect custom childnode values from an xml file and rewrite whit datas from a form. I had an idea thet I collect the datas in an ArrayList and give it to the method. But I cant change it in a foreach, because it throws ArgumentOutOfRangeException( although the ArraList contains 8 elements and the incremental variable's value also 8). So I would ask for help.
Here is the Code:
public static void Search(ArrayList nodeIds, ArrayList values)
{
XDocument doc = XDocument.Load("Options.xml");
int i = 0;
foreach (XElement option in doc.Descendants("BasicOptions"))
{
foreach(string nodeId in nodeIds)
{
if (option.Attribute("id").Value == nodeId)
{
foreach (XElement prop in option.Nodes())
{
prop.Value = values[i].ToString();
i++;
}
}
}
}
doc.Save("Options.xml");
}
It seems to me that i will go out of range without question because it is declared externally to 3 foreach statements and used within the center foreach. You should rethink your approach.
I suggest, but without knowing your incoming values or why your calling this, to redclare your internal foreach as a for statement like the following:
public static void Search(ArrayList nodeIds, ArrayList values)
{
XDocument doc = XDocument.Load("Options.xml");
foreach (XElement option in doc.Descendants("BasicOptions"))
{
foreach (string nodeId in nodeIds)
{
if (option.Attribute("id").Value == nodeId)
{
var nodes = option.Nodes().ToList();
for (int i = 0; i < nodes.Count && i < values.Count; i++)
{
XElement node = (XElement)nodes[i];
node.Value = values[i].ToString();
}
}
}
}
doc.Save("Options.xml");
}
Related
I am using the follow Code to read an XML file:
XDocument doc = XDocument.Load(xmlPath);
foreach (var node in doc.Descendants("LogInfo"))
{
ListViewItem item = new ListViewItem(new string[]
{
node.Element("MailBox").Value,
node.Element("LastRun").Value,
});
listViewHome.Items.Add(item);
}
How can I change this Code to receive just the last "n" number of elements from that XML file?
Thanks in advance.
You could do this:
var nodes = doc.Descendants("LogInfo");
foreach (var node in nodes.Skip(Items.Count() - n))
{
...
}
Or this:
var nodes = doc.Descendants("LogInfo");
foreach (var node in nodes.Reverse().Take(n).Reverse())
{
...
}
If you're feeling adventurous, you could also write your own extension method which should be more efficient than either of these. Here's my quick and dirty solution:
public static IEnumerable<T> TakeFromEnd<T>(this IEnumerable<T> items, int n)
{
var arry = new T[n];
int i = 0;
foreach(var x in items)
{
arry[i++ % n] = x;
}
if (i < n)
{
n = i;
i = 0;
}
for (int j = 0; j < n; j++)
{
yield return arry[(i + j) % n];
}
}
var nodes = doc.Descendants("LogInfo");
foreach (var node in nodes.TakeFromEnd(n))
{
...
}
XDocument doc = XDocument.Load(xmlPath);
var logs = doc.Descendants("LogInfo");
var logsCount = logs.Count();
foreach (var node in logs.Skip(logsCount - n).Take(n))
{
ListViewItem item = new ListViewItem(new string[]
{
node.Element("MailBox").Value,
node.Element("LastRun").Value,
});
listViewHome.Items.Add(item);
}
And here is XPath solution, which will enumerate xml once
var xpath = String.Format("//LogInfo[position()>last()-{0}]", n);
foreach (var log in doc.XPathSelectElements(xpath))
{
ListViewItem item = new ListViewItem(new string[]
{
(string)log.Element("MailBox"),
(string)log.Element("LastRun")
});
listViewHome.Items.Add(item);
}
Like this:
TreeNode[] treeNodes = treeView.Nodes.Find(searchString, true);
but I want it to search in the text field instead of the name field.
I am not aware of any inbuilt method but you may use LINQ
TreeNode[] treeNodes = treeView.Nodes
.Cast<TreeNode>()
.Where(r => r.Text == "yourText")
.ToArray();
To search all tree nodes (not only the direct child nodes) you can use the extension method below
var nodes = treeView1.FlattenTree()
.Where(n => n.Text == "sometext")
.ToList();
--
public static class SOExtension
{
public static IEnumerable<TreeNode> FlattenTree(this TreeView tv)
{
return FlattenTree(tv.Nodes);
}
public static IEnumerable<TreeNode> FlattenTree(this TreeNodeCollection coll)
{
return coll.Cast<TreeNode>()
.Concat(coll.Cast<TreeNode>()
.SelectMany(x => FlattenTree(x.Nodes)));
}
}
If I understand you correctly (you last question was very confusing), you can write a find method yourself as follows
public static TreeNode[] Find(this TreeNode motherNode, string findNodeText)
{
List<TreeNode> nodeList = new List<TreeNode>();
foreach (TreeNode childNode in motherNode.Nodes)
if (childNode.Text.Equals(findNodeText, StringComparison.CurrentCulture))
nodeList.Add(childNode);
return nodeList.ToArray<TreeNode>();
}
This method can be used like
TreeView myTreeView = new TreeView();
foreach (TreeNode node in myTreeView.Nodes)
{
TreeNode[] childNodes = node.Find("Text");
// Do something...
}
I hope this helps.
The following code only shows the nodes which matches the search criteria.
Copy the following code in the search event
private void tbxSearch_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
trvMenu.BeginUpdate();
if (tbxSearch.Text.Length > 0)
{
for (int i = trvMenu.Nodes.Count; i > 0 ; i--)
{
NodeFiltering(trvMenu.Nodes[i - 1], tbxSearch.Text);
}
}
trvMenu.EndUpdate();
}
Then create the serch & filter function
private bool NodeFiltering(TreeNode Nodo,string Texto)
{
bool resultado = false;
if (Nodo.Nodes.Count == 0)
{
if (Nodo.Text.ToUpper().Contains(Texto.ToUpper()))
{
resultado = true;
}
else
{
Nodo.Remove();
}
}
else
{
for (int i = Nodo.Nodes.Count; i > 0; i--)
{
if (NodeFiltering(Nodo.Nodes[i - 1], Texto))
resultado = true;
}
if (!resultado)
Nodo.Remove();
}
return resultado;
}
This code is pretty nice for creating Treeview menus with many levels.
How would like to to compare values in this nested foreach.
I want to compare and if they match print YES for example.
Cheers
using System;
class Program
{
static void Main()
{
// Use a string array to loop over.
string[] ferns =
{
"apple",
"Equisetopsida",
"Marattiopsida",
"Polypodiopsida"
};
string[] fruits=
{
"apple",
"mango",
"Marattiopsida",
"Polypodiopsida"
};
// Loop with the foreach keyword.
foreach (string value in ferns)
{
Console.WriteLine(value);
foreach (string value in fruits)
{
Console.WriteLine(value);
}
//I would like to compare values here.
//Compare frens values against fruits values.
//How can i achieve this
}
}
}
foreach (string fern in ferns)
{
Console.WriteLine(fern);
foreach (string fruit in fruits)
{
Console.WriteLine(fruit);
if(fruit.Equals(fern))
Console.WriteLine("YES");
}
}
value is not a keyword here(through it is in some circumstances). All you have to do is pick whatever variable name you like
Do you want to compare them to see if they match in order? Or just if one list contains the other one at all?
If order counts, loop through both at same time with counter variable (just needs boundary checks):
for (int x = 0; x < ferns.Length; x++)
{
if (ferns[x] == fruits[x])
{
Console.WriteLine("YES!");
}
}
If it just needs to contain it:
foreach (string fern in ferns)
{
if (fruits.Contains(fern))
Console.WriteLine("YES!");
}
This would also be a good place to use an intersection. An intersection takes two lists and returns all the items that 'both' lists have in common.
IEnumerable<string> commonWords = ferns.Intersect(fruits);
Option A
foreach (string fernsvalue in ferns)
{
foreach (string fruitsvalue in fruits)
{
if (fernsvalue.Equals(fruitsvalue))
Console.WriteLine("They are equal");
}
}
Option B
List<string> fernsList = new List<string>(ferns.Length);
List<string> fruitsList = new List<string>(fruits.Length);
fernsList.AddRange(ferns);
fruitsList.AddRange(fruits);
List<string> Differences = fernsList.Except(fruitsList).ToList();
Option C
bool equal = ferns.SequenceEqual(fruits); //compares for exact equality
first of all, in each of yours foreach, current element has this same name, and you will not be able to reach botch of them in second foreach.
To compare two string, you can use String.Compare method. For example:
foreach (string fern in ferns)
{
foreach (string fruit in fruits)
{
if(String.Compare(fern,fruit,false)==0)
{
Console.WriteLine("YES");
}
}
}
Are you trying to see if the elements in the arrays fruits and ferns match at the same index? If so then a nested foreach loop isn't the best way to achieve this. It's much easier to use a for loop
for (int i = 0; i < ferns.Length && i < fruits.Length; i++) {
if (fruits[i] == ferns[i]) {
Console.WriteLine("{0} YES!!!", fruits[i]);
}
}
If instead you're looking to see if there is any match at all for an element in the ferns array in the fruits array then you could try the following
foreach (string fern in ferns) {
Console.Write("{0} ", fern);
bool isMatch = false;
foreach (string fruit in fruits) {
if (fruit == fern) {
isMatch = true;
break;
}
}
Console.WriteLine(isMatch ? "YES" : "NO");
}
I have data that looks like the below:
a.b.c.d.e.f.g
b.c.d.e.f.g.h.x
c.d.e.q.s.n.m.y
a.b.c
I need to take this data and turn each and every level into a node in a treeview. So the tree looks something like:
a
b
c
d
e
...
b
c
d
....
if for example at the same leve there is another a, elements under this should be added as nodes to that branch. I have thought of the following:
Parse each line that is qualified by the dot character for each element and create an ordered list.
For each item in the list add it as a node in the current location.
Before adding check to make sure another item at the same level does not exist with the same name.
Add the next element until all items in the list are done, next elements being child to the first added item of the list.
I hope I was clear and let me know if it needs further clarification.
You can change the Node class to have the checks, if you want a list of children nodes, etc, add that as a HashSet, so you can easily make the check for uniqueness. Add a method in the Node class to do the AddChild and do the check on the HashSet.
public class Main
{
public Main()
{
string treeStr = "";
string[] strArr = { "a.b.c.d.e.f.g", "b.c.d.e.f.g.h.x" };
List<Node> nodes = new List<Node>();
Node currentNode;
foreach (var str in strArr)
{
string[] split = str.Split('.');
currentNode = null;
for (int i = 0; i < split.Length; i++)
{
var newNode = new Node { Value = str };
if (currentNode != null)
{
currentNode.Child = newNode;
}
else
{
nodes.Add(newNode);
}
currentNode = newNode;
}
}
}
}
public class Node
{
public string Value { get; set; }
public Node Child { get; set; }
}
I'm assuming the existence of the methods CreateRootNode and AddChildNode.
void ParseToTreeview(IEnumerable<string> data) {
foreach (var line in data) {
var names = line.Split('.');
for (var i = 0; i < names.Length; i++) {
TreeNode node = null;
if (i == 0)
node = CreateRootNode(name:names[i]);
else
node = AddChildNode(name:names[i], parentNode:node);
}
}
}
A recursive method to add all of these is what you need. Here's a sample:
Use:
string[] yourListOfData = { "a.b.c.d.e.f.g", "b.c.d.e.f.g.h.x", "c.d.e.q.s.n.m.y", "a.b.c" };
foreach(string x in yourListOfData)
PopulateTreeView(x, myTreeView.Nodes[0]);
Sample Method:
public void PopulateTreeView(string values, TreeNode parentNode )
{
string nodeValue = values;
string additionalData = values.Substring(value.Length - (value.Length - 2));
try
{
if (!string.IsNullOrEmpty(nodeValue))
{
TreeNode myNode = new TreeNode(nodeValue);
parentNode.Nodes.Add(myNode);
PopulateTreeView(additionalData, myNode);
}
} catch ( UnauthorizedAccessException ) {
parentNode.Nodes.Add( "Access denied" );
} // end catch
}
NOTE: code above is not tested, might need tweaking
Does anyone know of an algorithm that will merge treenodes in the following way?
treeA
\ child a
\node(abc)
\ child b
\node(xyz)
+
treeB
\ child a
\node(qrs)
\ child b
\node(xyz)
\node(pdq)
\ child c
\node(pdq)
= // do merge
treeMerged
\ child a
\node(abc)
\node(qrs)
\ child b
\node(xyz)
\node(pdq)
\ child c
\node(pdq)
Any help would be greatly appreciated.
Well, once I actually took the time to think about it, the solution turns out to be far more simple than I anticipated. (I've posted the critical part of the code below)
private TreeNode DoMerge(TreeNode source, TreeNode target) {
if (source == null || target == null) return null;
foreach (TreeNode n in source.Nodes) {
// see if there is a match in target
var match = FindNode(n, target.Nodes); // match paths
if (match == null) { // no match was found so add n to the target
target.Nodes.Add(n);
} else {
// a match was found so add the children of match
DoMerge(n, match);
}
}
return target;
}
Still interested to know if someone has a better solution?
Ok, I'll admit, when I first started messing with this, I didn't think it would be too hard, so I figured I'll try to do it using LINQ. It came out to be nuts, but it works. I'm SURE there are more elegant and efficient algorithms, but here it is!
First, I have a ToEnumerable extension method on the TreeNodeCollection class:
public static class TreeNodeCollectionExtensions
{
public static IEnumerable<TreeNode> ToEnumerable(this TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
yield return node;
}
}
}
Then, I implement a custom comparer:
public class TreeNodeComparer : IEqualityComparer
{
public bool Equals(TreeNode x, TreeNode y)
{
return x.Text == y.Text;
}
public int GetHashCode(TreeNode obj)
{
return obj.Text.GetHashCode();
}
}
And finally, the crazyness:
private TreeView MergeTreeViews(TreeView tv1, TreeView tv2)
{
var result = new TreeView();
foreach (TreeNode node in tv2.Nodes)
{
result.Nodes.Add(node.Clone() as TreeNode);
}
foreach (TreeNode node in tv1.Nodes)
{
var nodeOnOtherSide = result.Nodes.ToEnumerable()
.SingleOrDefault(tr => tr.Text == node.Text);
if (nodeOnOtherSide == null)
{
TreeNode clone = node.Clone() as TreeNode;
result.Nodes.Add(clone);
}
else
{
var n = node.Nodes.ToEnumerable()
.Where(t => !(nodeOnOtherSide.Nodes.ToEnumerable()
.Contains(t, new TreeNodeComparer())));
foreach (TreeNode subNode in n)
{
TreeNode clone = subNode.Clone() as TreeNode;
nodeOnOtherSide.Nodes.Add(clone);
}
}
}
return result;
}
The way I coded it was that it returns a third "merged" treeView. You can change the code, so that it takes a third treeview as a parameter, so that you can pass in a treeView you may already have.
Again, I'm SURE there are better way to do this, but it SHOULD work.
One more thing I'd like to point out, this will only work for a TreeView that is two layers deep.
I came up with this recursive example, works perfect in C# (have been using it myself), note that you'll need to find a way to convert TreeNode.Nodes to an array:
public static TreeNode[] mergeTrees(TreeNode[] target, TreeNode[] source)
{
if (source == null || source.Length == 0)
{
return target;
}
if (target == null || target.Length == 0)
{
return source;
}
bool found;
foreach (TreeNode s in source)
{
found = false;
foreach (TreeNode t in target)
{
if (s.Text.CompareTo(t.Text) == 0)
{
found = true;
TreeNode[] updatedNodes = mergeTrees(Util.treeView2Array(t.Nodes), Util.treeView2Array(s.Nodes));
t.Nodes.Clear();
t.Nodes.AddRange(updatedNodes);
break;
}
}
if (!found)
{
TreeNode[] newNodes = new TreeNode[target.Length + 1];
Array.Copy(target, newNodes, target.Length);
newNodes[target.Length] = s;
target = newNodes;
}
}
return target;
}
If you are using the Node.Name attribute to set the actual path of the item, then a merge is somewhat simple.
First, create a TreeNodeCollection extension like so (this is needed to have a Case Sensitive Find() method for the TreeNodeCollection, which ensures that the Unique Path can be unique by Case as well) :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace TreeViewApp
{
public static class TreeNodeCollectionExtensions
{
public static TreeNode[] FindExact(this TreeNodeCollection coll, string keytofind)
{
TreeNode[] retval;
if (String.IsNullOrWhiteSpace(keytofind) || coll == null)
{
retval = new TreeNode[0];
}
else
{
TreeNode[] badfinds = coll.Find(keytofind, true);
List<TreeNode> goodfinds = new List<TreeNode>();
foreach (TreeNode bad in badfinds)
{
if (bad.Name == keytofind)
goodfinds.Add(bad);
}
retval = goodfinds.ToArray();
}
return retval;
}
}
}
Second, fill a treeview with your Source nodes...
Thrid, fill a treeview with your Target nodes...
Fourth, create an empty treeview.
and then, it's as simple as this:
private void btn_Merge_Click(object sender, EventArgs e)
{
//first merge
foreach (TreeNode sourceNode in this.treeview_Source.Nodes)
{
FindOrAdd(sourceNode, ref this.treeview_Merged);
}
//second merge
foreach (TreeNode targetNode in this.treeview_Target.Nodes)
{
FindOrAdd(targetNode, ref this.treeview_Merged);
}
}
private void FindOrAdd(TreeNode FindMe, ref TreeView InHere)
{
TreeNode[] found = InHere.Nodes.FindExact(FindMe.Name);
//if the node is not found, add it at the proper location.
if (found.Length == 0)
{
if (FindMe.Parent != null)
{
TreeNode[] foundParent = InHere.Nodes.FindExact(FindMe.Parent.Name);
if (foundParent.Length == 0)
InHere.Nodes.Add((TreeNode)FindMe.Clone());
else
foundParent[0].Nodes.Add((TreeNode)FindMe.Clone());
}
else
InHere.Nodes.Add((TreeNode)FindMe.Clone());
}
else
{
//if the item was found, check all children.
foreach (TreeNode child in FindMe.Nodes)
FindOrAdd(child, ref InHere);
}
}
Once again, this solution only works if you have unique paths... with the extension, it also accounts for uniqueness at the case level.
I posted this here in the hopes of helping someone who, like me, had to search for a solution for days on end without success and had to build my own.