I have a list of objects called Items, that I want to display in a Treelistview in a hierarchy. The objects look like this:
public class ListItem
{
public int ID;
public string LabelText;
public int ParentID;
public bool Checked;
}
The list order ensures that parents are always defined first. Now I want to iterate over this list and build my hierarchy in the Treelistview. When I dectect a parent in the object I can search the already created nodes on the ListView and search for the ID, but I was wondering if it is possible to create the TreeNodes dynamically with the ID as the reference, like so (bit of pseudo code);
foreach (ListItem Item in Items)
{
TreeNode {Item.ID} = new TreeNode()
{
Checked = Item.Checked,
Text = Item.LabelText
};
if (Item.ParentID == null)
DropDown.Nodes.Add({Item.ID});
else
{Item.ParentID}.SubItems.Add({Item.ID});
}
To avoid having to recursively search the existing tree every time, I'd simply store each node in a temporary Dictionary<int,TreeNode>. Something like this:
var temp = new Dictionary<int,TreeNode>();
foreach( var item in Items )
{
var node = new TreeNode()
{
Checked = item.Checked,
Text = Item.LabelText
};
temp.Add( item.ID, node );
if( item.ParentID == 0 )
DropDown.Nodes.Add( node );
else
// TODO: make sure node.ParentId exists in temp here
temp[node.ParentID].SubItems.Add( node );
}
Also, depending on what you need, it might also be useful to create a custom TreeNode class that holds a reference to the ListItem.
Related
After removing one TreeNode I want the View to be at the node after the one I deleted (the same functionality should later also be implemented for editing a node), but currently it always shows the top of the list again and I need to scroll down manually, which can be quite irritating for the user.
I'm using the EnsureVisible() method but unfortunately it doesn't work (I'm testing it with a TreeView which contains about 30 nodes that don't have sub-nodes).
The code of the function (Only the first line and the last 4/5 lines are relevant, I think):
public override void Remove()
{
TreeNode moveToThisNode = treeControl1.SelectedNodes.Last().NextVisibleNode;
// first, group them by whether they are ObjectGroups or ObjectTypes
var grouping = from node in treeControl1.SelectedNodes
where !node.IsUngroupedNode()
let isGroup = node.IsGroupNode()
group node by isGroup into g
select new { IsGroupNode = g.Key, Items = g };
foreach (var grp in grouping)
{
foreach (var selectedNode in grp.Items)
{
// Only allow removal FIRSTLY of ObjectGroups and SECONDLY that are NOT the "ungrouped" group.
if (grp.IsGroupNode)
{
// Removes the Group
var cmd = (Commands.DataCommand<string>)mApplicationData.CommandFactory.Create(string.Concat(CommandPrefix, "Remove"));
cmd.Data = selectedNode.Text;
cmd.Execute();
}
else // ObjectType node --> just move to ungrouped
{
var t = (ObjectType)selectedNode.Tag;
var parentNode = selectedNode.Parent;
if (parentNode?.IsGroupNode() == true &&
parentNode?.IsUngroupedNode() == false) // No removal of ObjectTypes from within "ungrouped"
{
var group = (ObjectGroup)parentNode.Tag;
// Remove the ObjectType from the ObjectGroup but does not delete it -> becomes "ungrouped".
var cmd = (Commands.IGroupTypeCommand)mApplicationData.CommandFactory.Create(string.Concat(CommandPrefix, "TypeRemove"));
cmd.ObjectClass = t.Class;
cmd.ObjectTypeName = t.Name;
cmd.Data = group.Name;
cmd.Execute();
}
}
}
}
UpdateData();
if (moveToThisNode!=null)
{
moveToThisNode.EnsureVisible();
MessageBox.Show("Dummy test if moveToThisNode isn't null");
}
}
I figured it out!
The problem was that after the Remove() function my UpdateData() function is called which redraws all nodes. So calling EnsureVisible() before that is total nonsense.
So what I did was that in the Remove() I stored the name of the node I want to jump to in a member variable and at the end of UpdateData() I get the node with that name out of the TreeNodeCollection and (FINALLY) call EnsureVisible() for it.
My object looks something like this:
public class Transaction
{
long Id;
long Amount;
DateTime Date;
string ReferenceNumber;
string ParentReferenceNumber;
}
It is already coming in sorted by Date, but what I need to do is arrange the list of these transactions so that those having a ParentReferenceNumber that matches the ReferenceNumber on another Transaction appear in the order of "Parent then Child".
Here is what I tried. It produces the error "Collection was modified; enumeration operation may not execute." That's what I was afraid of, hence the question.
foreach (var p in Model.PaymentInfo)
{
var child = Model.PaymentInfo.SingleOrDefault(x => x.ParentReferenceNumber == p.ReferenceNumber);
if (child != null)
{
var parentIndex = Model.PaymentInfo.IndexOf(p);
var childIndex = Model.PaymentInfo.IndexOf(child);
Model.PaymentInfo.RemoveAt(childIndex);
Model.PaymentInfo.Insert(parentIndex + 1, child);
}
}
Start by adding a list of children to each node. For example:
public class WorkingTransaction
{
public Transaction Details;
public List<WorkingTransaction> Children;
public bool HasParent;
}
Then, create a dictionary of WorkingTransactions, keyed on Id. Here, transactions is your sorted list of transactions.
var workDict = transactions
.Select(t => new WorkingTransaction
{
Details = t,
Children = new List<WorkingTransaction,
HasParent = false
})
.ToDictionary(t => t.Details.Id, t);
Now, go through the dictionary and add those that have parent references to the parent's list of children, and update the HasParent flag. At the same time, any item that doesn't have a parent is added to the "root level" list.
List<WorkingTransaction> rootParents = new List<WorkingTransaction>();
foreach (var kvp in workDict)
{
WorkingTransaction parent;
if (workDict.TryGetValue(kvp.Value.Details.ParentReferenceNumber, out parent)
{
parent.Children.Add(kvp.Value);
kvp.Value.HasParent = true;
}
else
{
rootParents.Add(kvp.Value);
}
}
Now, you can go through each of the parents and process each of the children:
foreach (var t in rootParents)
{
processChildren(t.Children);
}
void ProcessChildren(WorkingTransaction t)
{
t.Children = t.Children.OrderBy(child => child.Details.Id).ThenBy(child => child.Details.Date);
// and recursively process the children of this transaction
foreach (var child in t.Children)
{
ProcessChildren(child);
}
}
You can use a similar recursive method to output the children in the proper order.
I had to create a new list in order to implement this in a simple way. I didn't want to change the client's existing object structure. Here's how I accomplished it:
var sortedList = new List<Transaction>();
foreach (var p in ListOfTransactions)
{
if (sortedList.Contains(p))
{
continue; // Don't add duplicates
}
sortedList.Add(p); // Add the transaction
var child = ListOfTransactions.SingleOrDefault(x => x.ParentReferenceNumber == p.ReferenceNumber);
if (child != null) // Add the child, if exists
{
sortedList.Add(child);
}
}
You are looking for information on an object sort. Look here for more information.
http://www.codeproject.com/Tips/761292/How-to-sort-an-object-list-in-Csharp
list.Sort(delegate(Member x, Member y)
{
// Sort by total in descending order
int a = y.Total.CompareTo(x.Total);
// Both Member has the same total.
// Sort by name in ascending order
a = x.Name.CompareTo(y.Name);
return a;
});
I have this list of 2000+ categories that need to be organized in a tree before being sent to the controller and the View so the javascript plugin can render them correctly.
I am already doing this but the performance is terrible. It is taking like 30 seconds to assemble the tree.
I can't see what is dropping performance here. Can you guys help me to improve this code?
var allCategories = dal.Listar();
List<Model.Entity.CategoriaCursoEADVO> nestedCategories = new List<Model.Entity.CategoriaCursoEADVO>();
foreach (Model.Entity.CategoriaCursoEAD item in allCategories)
{
if (item.IdCategoriaPai == null)
{
CategoriaCursoEADVO child = new CategoriaCursoEADVO();
child.id = item.Id;
child.text = item.Descricao;
nestedCategories.Add(child);
FillChild(allCategories, child, item.Id);
}
}
And here is the FillChild method:
public int FillChild(IEnumerable<CategoriaCursoEAD> categorias, CategoriaCursoEADVO parent, int IID)
{
var childCategories = categorias.Where(w => w.IdCategoriaPai.Equals(IID));
parent.children = new List<CategoriaCursoEADVO>();
if (childCategories.Count() > 0)
{
foreach (CategoriaCursoEAD cat in childCategories)
{
CategoriaCursoEADVO child = new CategoriaCursoEADVO();
child.id = cat.Id;
child.text = cat.Descricao;
parent.children.Add(child);
FillChild(categorias, child, cat.Id);
}
return 0;
}
else
{
return 0;
}
}
I think the problem is with the new instances and tried using Parallel loops with no satisfatory level of improvement.
This is a pretty good time to use a HashTable (Dictionary). Something like the below code should help.
// Convert the flat list into a hash table with the ID
// of the element as the key
var dict = allCategories.ToDictionary (i => i.Id);
// Group categories by the parent id
var parentGrouping = allCategories.Where(c => c.IdCategoriaPai != null).GroupBy(c => c.ParentId);
// Since we group the items by parent id, we can find
// the parent by id in the dictionary and add the children
// that have that particular id.
foreach(var groupItem in parentGrouping)
if(groupItem.Key != null)
dict[(int)groupItem.Key].children.AddRange(groupItem);
// Get the root elements.
var hierarchicalCategories = allCategories.Where(item => item.IdCategoriaPai == null);
// Do what you need to do here.
This code will create a tree of categories. hierarchicalCategories will contain direct references to the root elements (categories that do not have a parent), assuming that your data is structured that way.
I'm trying to query the following XML to build some objects which copy the folder hierarchy outlined in the XML.
<ShareList>
<Title>Documantis</Title>
<Url>/sites/dev/Documantis/Forms/AllItems.aspx</Url>
<Guid>fed8f456-efa9-4fe5-8b97-46734a3040b6</Guid>
<HasUniqueScopes>False</HasUniqueScopes>
<RootFolder>/sites/dev</RootFolder>
<Children>
<ShareListItem>
<Title>First</Title>
<Url>Documantis/First</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<PermissionMask>FullMask</PermissionMask>
<Children>
<ShareListItem>
<Title>Second</Title>
<Url>Documantis/First/Second</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<ParentGuid>22b2a7e9-a42e-497f-aad3-8caa85f6ac6d</ParentGuid>
</ShareListItem>
</Children>
</ShareListItem>
<ShareListItem>
<Title>Folda</Title>
<Url>Documantis/Folda</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<PermissionMask>FullMask</PermissionMask>
</ShareListItem>
</Children>
</ShareList>
I'm having trouble finding a way to return one level of the <ShareListItem> elements at a time, with my current code it returns all the ShareListItems in one List which doesn't represent the hierarchy accurately.
XmlDocument doc = new XmlDocument();
doc.LoadXml(sharepointXml);
XElement root;
using (XmlReader xr = new XmlNodeReader(doc)) { root = XElement.Load(xr); }
var result = from child in root.DescendantsAndSelf("ShareList") //.Elements("ShareList") // Descendants("ShareList")
select child;
foreach (XElement xml in result)
{
// Build ListItem from results
ShareList list = new ShareList()
{
Title = xml.Element("Title").Value,
Url = xml.Element("Url").Value,
Guid = xml.Element("Guid").Value,
HasUniqueScopes = Convert.ToBoolean(xml.Element("HasUniqueScopes").Value),
RootFolder = xml.Element("RootFolder").Value,
};
if (xml.Element("Children") != null)
{
var subResult = from child in xml.Element("Children").Descendants("ShareListItem")
select child;
foreach (XElement subXml in subResult)
{
// results here are flat and don't show depth of nodes
}
//list.Children =
}
I could recursively infer the hierarchy's structure from the URL Element, however I already have it represented in XML so I would rather learn how to return this through a query.
Edit:
Here's what I ended up using
public List<ShareList> HandleLists(XElement levelRoot)
{
List<ShareList> lists = new List<ShareList>();
var results = from list in levelRoot.DescendantsAndSelf("ShareList")
select list;
foreach (var list in results)
{
var children = list.Element("Children");
if (children == null)
return null;
ShareList shareList = new ShareList()
{
Title = list.Element("Title").Value,
Url = list.Element("Url").Value,
Guid = list.Element("Guid").Value,
HasUniqueScopes = Convert.ToBoolean(list.Element("HasUniqueScopes").Value),
RootFolder = list.Element("RootFolder").Value,
// Recursively find ListItem folders
Children = HandleSubfolders(list)
};
lists.Add(shareList);
}
return lists;
}
public List<ShareListItem> HandleSubfolders(XElement levelRoot)
{
List<ShareListItem> subfolders = new List<ShareListItem>();
// All nodes deeper than current
var children = levelRoot.Element("Children");
if (children == null)
return null;
// Subfolders
var items = children.Elements("ShareListItem");
foreach (var item in items)
{
ShareListItem listItem = new ShareListItem()
{
Title = item.Element("Title").Value,
Url = item.Element("Url").Value,
HasUniqueRole = Convert.ToBoolean(item.Element("HasUniqueRole").Value),
IsSubfolder = Convert.ToBoolean(item.Element("IsSubFolder").Value),
PermissionMask = item.Element("PermissionMask").Value,
PermissionMaskName = item.Element("PermissionMaskName").Value,
// Recursively find ListItem subfolders
Children = HandleSubfolders(item)
};
// Add subfolder to Children collection
subfolders.Add(listItem);
}
return subfolders;
}
You would want to use recursion here.
Create a method that handles one level of the hierarchy and calls itself with the next level.
public void HandleLevel(XElement levelRoot)
{
PerformAction(levelRoot);
var children = levelRoot.Element("Children");
if(children == null)
return;
var items = children.Elements("ShareListItem");
foreach(var item in item)
{
// Handle child's children:
HandleLevel(item);
}
}
PerformAction is the code that actually does, whatever you want to do for each document.
The way the code is currently structured, this action is also executed for the root document /sites/dev/Documantis/Forms/AllItems.aspx.
If you don't want this simply move PerformAction into the foreach loop and pass item instead of levelRoot.
BTW: Your initialization of the root element is very strange.
You can simply use this:
var root = XDocument.Parse(sharepointXml).Root;
The initial call to HandleLevel would simply look like this:
HandleLevel(root);
A good way of producing the results your after is by using XPath (here's a good primer if you need it).
Once you've got your XML into an XmlDocument you can return different bits of it by using an XPathNavigator, like this:
var xmlNavigator = xmlDocument.CreateNavigator();
var outerQuery = xmlNavigator.Select("ShareList/Children/ShareListItem");
while (outerQuery.MoveNext()) {
Console.WriteLine(outerQuery.Current.SelectSingleNode("Title").Value);
var innerQuery = outerQuery.Current.Select("Children/ShareListItem");
while (innerQuery.MoveNext()) {
Console.WriteLine(" - " + innerQuery.Current.SelectSingleNode("Title").Value);
}
}
In the code above, we query the XML for all ShareListItem nodes within Children nodes of the root ShareList node, and store the resultant XPathNodeIterator in the variable outerQuery. We then iterate over all the nodes found, and run an operation as well as another XPath query on each to retrieve child nodes to process. The code above produces the following output:
First
- Second
Folda
Which I think it what you're after. Obviously, you can use recursion if necessary if your XML can be nested more deeply than this.
One way of doing this is by creating Classes to represent the hierarchy like so:
public class ShareList {
...
public List<ShareList> Children { get; set; }
}
In your code, refactor the traversing part into a method that accepts a Sharelist node and traverse it, calling itself for each Children node:
private Sharelist RecurseHierarchy(XElement sharelistNode, ShareList parent)
{
// your traversing code goes here
// get your data and create a new Sharelist object
// if it has a children node, traverse it and call this same method on the child
// Sharelist nodes
parent.Title = sharelistNode.Element("Title").Value;
var children = sharelistNode.Element("Children");
if (children != null)
{
var items = children.Elements("ShareListItem");
foreach(var listItem in items)
{
ShareList childShareList = new ShareList();
parent.Children.Add(childShareList);
RecurseHierarchy(listItem, childShareList);
}
}
// Just in case we want to chain some method
return parent;
}
To call it initially, you will have to pass in the root node and a new ShareList object.
I want to organize a set of Object (MyPlugin) in a TreeView by Category.
Actually i'm adding them to the TreeView using the following method :
internal static TreeNode AddPluginNode(TreeView parent, AbstractEnvChecker plugin)
{
TreeNode created = new TreeNode(plugin.Name) { Tag = plugin };
parent.Nodes.Add(created);
return created;
}
// I'm using the Tag so i can conserve the MyPlugin Type for each Node
And this is the method i'm using to populate my TreeView from a List<MyPlugin> :
internal static void FillTreeViewWithPlugins(TreeView p_TreeView, Type p_Type, IList<AbstractEnvChecker> p_List)
{
TreeNode v_TreeNode;
if (p_TreeView != null)
{
p_TreeView.Nodes.Clear();
foreach (object p_Item in p_List)
{
if (p_Item.GetType().IsSubclassOf(p_Type))
{
v_TreeNode = null;
v_TreeNode = AddPluginNode(p_TreeView, p_Item as AbstractEnvChecker);
}
}
}
}
Everything works well, but the problem is that the previous method displays a simple TreeView that contains then list of MyPlugin. I want to classify them by a property called Category (String MyPlugin.Category).
So i should proceed like this :
TreeNode testNodeA = new TreeNode("A");
TreeNode testNodeB = new TreeNode("B");
TreeNode testNodeC = new TreeNode("C");
TreeNode[] array = new TreeNode[] { testNodeA, testNodeB, testNodeC };
TreeNode cat = new TreeNode("Categorie X", array);
treeView1.Nodes.Add(cat);
If i want to keep my previous code, i cannot know how many plugins will i add to each category so i cannot use an Array with fixed dimensions ...
I can't use List because the TreeNode constructor accepts only Array and the TreeView.Nodes.Add method accepts only TreeNode ...
How can i do it ?
Build a dictionary of categories on the fly while creating the nodes. Later, add only these nodes to the tree. The steps are as follows:
Declare a dictionary like that
Dictionary<string, TreeNode> categoryNodes = new ...;
The key is the category name, the value is the TreeNode for the category.
While iterating over all the MyPlugins in your list, check whether there's a category node in the dictionary and create one if there's not:
if (!categoryNodes.ContainsKey(p_Item.Category))
categoryNodes[p_Item.Category] = new TreeNode(p_Item.Category);
Add the new TreeNode for the plugin under the respective category node:
v_TreeNode = AddPluginNode(categoryNodes[p_Item.Category], p_Item as AbstractEnvChecker);
In the end, add all the values of the dictionary to the tree:
foreach (string key in categoryNodes.Keys)
{
p_TreeView.Nodes.Add(categoryNodes[key]);
}
Your code should look like this:
internal static void FillTreeViewWithPlugins(TreeView p_TreeView, Type p_Type, IList<AbstractEnvChecker> p_List)
{
Dictionary<string, TreeNode> categoryNodes = new Dictionary<string, TreeNode>();
TreeNode v_TreeNode;
if (p_TreeView != null)
{
p_TreeView.Nodes.Clear();
foreach (object p_Item in p_List)
{
if (p_Item.GetType().IsSubclassOf(p_Type))
{
if (!categoryNodes.ContainsKey(p_Item.Category))
categoryNodes[p_Item.Category] = new TreeNode(p_Item.Category);
v_TreeNode = null;
v_TreeNode = AddPluginNode(categoryNodes[p_Item.Category], p_Item as AbstractEnvChecker);
}
}
foreach (string cat in categoryNodes.Keys)
p_TreeView.Nodes.Add(categoryNodes[cat]);
}
}