TreeView is randomly failing to show newly added nodes - c#

I have a TreeView in WinForm application, and I am using the add, reorder and delete methods to add new nodes, reorder existing nodes and delete old notes.
Sometimes when I add a new item it does net show immediately in the TreeView, but it does show correctly when I add the next node. It seems to happen randomly, so it's difficult to find the root cause.
Even when the node does not show correctly in the UI, the node count is correct.
TreeView1.BeginUpdate();
TreeView1.Nodes.Add("P1", "Parent");
foreach(User u in items)
{
if( condition)
{
node.Text =u.sNodeText;
node.Tag = u;
node.Text = u.sNodeText;
GetChildren(node);
TreeView1.Nodes["P1"].Nodes.Add((TreeNode)node.Clone());
}
}
TreeView1.ExpandAll();
TreeView1.EndUpdate();
TreeView1.Refresh();
Can anyone answer this question? I think the question is not meaningless.
Here is the GetChildren method.
private void GetChildren(TreeNode node)
{
TreeNode Node = null;
User nodeCat = (User)node.Tag;
foreach (User cat in items)
{
if (cat.sParentID == nodeCat.sID)
{
Node = node.Nodes.Add(cat.sNodeText);
Node.Tag = cat;
GetChildren(Node);
}
}

Have you tried Invalidate() versus Refresh()? Refresh only redraws the Client area, while Invalidate redraws the entire control. It's just a shot in the dark... I've never encountered this problem before.

First of all, after calling the GetChildren Method, why are you adding the node to the tree anyway? you should only add it to the tree in case its parentID is empty (or null or 0 depending on its type).
In Addition, add the EnsureVisible method to your newly added node, and remove the cloning:
...
if (u.sParentID==null)
{
TreeView1.Nodes["P1"].Nodes.Add(node);
node.EnsureVisible();
}
...
Hope this helps

If I am not mistaken isn't there a
TreeView1.BeginUpdate() method that you could use and at the end utilize the
TreeView1.EndUpdate();

I think this might be related to the use of Clone, which produces a shallow copy. The node count is update due to the use of the Add method, but the "new" node still has the reference from the one it was created from, so it's not a unique object. Try creating a deep copy instead and see how that goes.
e.g:
public TreeNode DeepNodeClone(TreeNode src)
{
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, src);
ms.Position = 0;
object obj = bf.Deserialize(ms);
ms.Close();
return (TreeNode)obj;
}
Then add this node as a child to your desired parent node.

Related

Duplicate Key error, but not recreating when run with debug points

I am implementing a function to add some data to a tree view - MVP (application consists of Word ribbon (VSTO) + C# code base). Below is my code.
private ElementTreeNode LoadElement(Element element, ElementTreeNode parent = null)
{
ElementTreeNode loadElement;
ElementTreeNode node;
//Add a node to the TreeView that represents the element.
loadElement = Add(text: GetElementNodeText(element.Name), parent: parent);
loadElement.Element = element;
//Add a node to the TreeView for conditions
if (element.TSS.Conditions.Count > 0)
{
Add(text: "Conditions", parent: loadElement).Conditions = element.TSS.Conditions;
}
//Add a node to the TreeView control for each image within the element.
foreach (Image image in element.FrameSets.Active.Images)
{
Add(text: GetImageNodeText, parent: loadElement).Image = image;
}
//Call this procedure recursively to add any nested elements.
foreach (Element objElement in element.FrameSets.Active.Elements)
{
LoadElement(element: objElement, parent: loadElement);
}
return loadElement;
}
Add method :
private ElementTreeNode Add(string text, string key = "", ElementTreeNode parent = null)
{
try
{
TreeNode node = null;
//Create a random key if a key is not specified.
if (key.Length == 0)
{
key = "ElementTreeNode_" + GetRandomNumber();
}
//Add a node to the TreeView control.
if (parent == null) //..............point 1
{
node = treeView.Nodes.Add(key: key, text: text);
}
else // ..............point 2
{
node = parent.Node.Nodes.Add(key: key, text: text);
}
//Add a new ElementTreeNode object to this collection. It will have the same key as objNode.
ElementTreeNode objNode = new ElementTreeNode(node);
elementTreeNodesCollection.Add(key: key, value: objNode);
return objNode;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + Environment.NewLine + "ElementTreeNodes.Add","MyApp,
MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
}
Create the random number :
public long GetRandomNumber()
{
Random rnd = new Random();
return rnd.Next();
}
The issue is, in my test run
this code first add the parent element to the treeview and added into the collection (Dictionary<string, ElementTreeNode> elementTreeNodesCollection). In the next step, a 'conditions' tree node will be added as a sub node under parent element to the tree node. When I try to add it to the elementTreeNodesCollection, it gives below error.
An item with same key has already been added
The biggest issue is, if I put debug points at point 1 or before (no other debug points in whole codebase) within Add method, I am not receiving any errors and items have been added to the tree view and collection well.
But if I add the debug point at or after point 2, I receive the error. Also when there are no break points, then also I got this error.
I tried setting the key as an incrementing number and then the code ran fine. But I need to use a random number here, as the LoadElement method involves with recursion.
Is this issue is related with the random number generating function? or other case?
I am really stuck in here.
Does anybody have an idea what I am doing wrong in here?
Thank you in advance.
I strongly suspect that the problem is your GetRandomNumber() method, which depends on the current time. If you call it multiple times in quick succession, you'll end up getting the same number out of it.
If you wait a bit between calls - e.g. due to being paused at a breakpoint - you'll get different random numbers.
I'd suggest that the simplest option would be to make the number not random at all - keep a counter that you increment each time you need to create a new key. Or you could use Guid.NewGuid() and convert that to a string, or something similar.
If you really want to use Random, create a single instance of Random that you use for all the calls - but be aware that Random isn't thread-safe; if you need to do work across multiple threads, it all becomes a lot more complicated.

SelectedNode is not working after refresh always showing null

Here i am trying to show the last selected node after reload of treeview but the selected node is always null. I am able to find the tree node from the treeview node collection but not able to assign it to tree view. Please suggeest:
public void LoadTreeViewData()
{
treeView.Nodes.Clear();// Clear any existing items
treeView.BeginUpdate(); // prevent overhead and flicker
LoadBaseNodes();//Populate all nodes
treeView.EndUpdate();
treeView.Refresh();
treeView.ExpandAll();
if (!string.IsNullOrEmpty(m_oSelectedNode_NAME))
{
TreeNode[] treeNodes = treeView.FlattenTree()
.Cast<TreeNode>()
.Where(r => Convert.ToDecimal(r.Tag) == Convert.ToDecimal(m_oSelectedNode_NAME))
.ToArray();
treeView.SelectedNode = treeNodes[0];
treeView.Focus();
}
}
This problem is happening because i am trying to Update the selected node inside the Tree view after label edit event. I put a timer inside After label edit to resolve the issue and set the Selected node it works for me.
var tnm = new Timer() { Enabled = true, Interval = 30 };
tnm.Tick += delegate {
SetSelectioNode();
tnm.Dispose();
};

Populating a treeview from a list

At the moment I have a hardcoded treeview in a winform set up with the following code:
private void Form1_Load_1(object sender, EventArgs e)
{
TreeNode _leftCameraNode = new TreeNode("LeftCamera");
TreeNode _rightCameraNode = new TreeNode("RightCamera");
TreeNode[] stereoCameraArray = new TreeNode[] { _leftCameraNode, _rightCameraNode };
TreeNode _screenNode = new TreeNode("Screen");
TreeNode _cameraNode = new TreeNode("Camera", stereoCameraArray);
TreeNode[] headNodeArray = new TreeNode[] { _cameraNode };
TreeNode _headNode = new TreeNode("HeadNode", headNodeArray);
TreeNode[] centreNodeArray = new TreeNode[] { _screenNode, _headNode };
TreeNode _centreNode = new TreeNode("CentreNode", centreNodeArray);
// root node
TreeNode[] inmoRootNodeArray = new TreeNode[] { _centreNode };
TreeNode treeNode = new TreeNode("Main Node", inmoRootNodeArray);
treeView1.Nodes.Add(treeNode);
}
This works fine, however it is very limiting in its functionality. What I really want this to do is be populated from a list of nodes that I've set up and filled out with information that I'm getting from a bunch of text boxes on my form.
For example, I create a new list of screens (child of my node class) like so:
public List<HV_Screen> _screenList = new List<HV_Screen>();
Then I fill out my list with the data entered from my text box like this:
if (_selectedNode > -1)
{
Node n = _nodeList[_nodeList.Count - 1];
n.Name = _screenName;
}
I've been looking around on the net and on stackoverflow but I can't find anything to help me out with dynamically creating my treeview, so I was wondering if anyone would be able to point me in the right direction and help me out in how to do this?
The reason I want to do this is because, say I have 3 different instances of my screen, I want my treeview to show this, Then if I change the name in screen1 to MYSCREEN, I want my treeview to show this. But, should I click screen2, the treeview will still show the default name. Then, when I click back on screen1, its new name MYSCREEN will still be there on display.
I hope that makes sence.
Edit
Following on from a comment here is a screenshot of my current GUI:
The tree structure there has all be hard coded in with the code above. This takes in no information from my class screen class.
Now, my HV_Screen class looks as follows:
private string WIDTH;
private string HEIGHT;
public string Width
{
get { return WIDTH; }
set { WIDTH = value; }
}
public string Height
{
get { return HEIGHT; }
set { HEIGHT = value; }
}
public override List<XmlElement> GenerateXML(XmlDocument _xmlDoc)
{
List<XmlElement> elementList = new List<XmlElement>();
XmlElement _screenName = _xmlDoc.CreateElement("ScreenName");
_screenName.SetAttribute("Name", name.ToString());
_screenName.InnerText = name.ToString();
elementList.Add(_screenName);
XmlElement _screenTag = _xmlDoc.CreateElement("ScreenTag");
_screenTag.SetAttribute("Tag", tag.ToString());
_screenTag.InnerText = tag.ToString();
elementList.Add(_screenTag);
XmlElement _localPosition = _xmlDoc.CreateElement("LocalPosition");
_localPosition.SetAttribute("X", XPOS.ToString());
_localPosition.SetAttribute("Y", YPOS.ToString());
_localPosition.SetAttribute("Z", ZPOS.ToString());
_localPosition.InnerText = WorldPos.ToString();
elementList.Add(_localPosition);
XmlElement _orientation = _xmlDoc.CreateElement("Orientation");
_orientation.SetAttribute("Yaw", YAW.ToString());
_orientation.SetAttribute("Pitch", PITCH.ToString());
_orientation.SetAttribute("Roll", ROLL.ToString());
_orientation.InnerText = Orientation.ToString();
elementList.Add(_orientation);
XmlElement _width = _xmlDoc.CreateElement("Width");
_width.SetAttribute("Width", WIDTH.ToString());
_width.InnerText = WIDTH.ToString();
elementList.Add(_width);
XmlElement _height = _xmlDoc.CreateElement("Height");
_height.SetAttribute("Height", HEIGHT.ToString());
_height.InnerText = HEIGHT.ToString();
elementList.Add(_height);
return elementList;
}
Nothing too major. This simply takes in the information and writes it to an xml file. I plan on doing this for each of my Node children. So, if I was to have a camera class, it would have its own generate XML function and write it out from there. My base class (node) simply has a bunch of getters and setters for things like name, tag etc.
With the way the code is set up at the moment, I can add in new nodes. First I must select the root node I want to branch from, then I fill out the information associated with it. So in that screen shot, if I was to select Main Node and then hit the Add button, Sample Name would now be a child of Main Node. However, if I was to select Sample and Name and change it, nothing would happen.
This way is also hard coded in to only relate to my screen class. In theory, I could simply turn a lot of different text boxes on and off but this would be really bad practise. And I'm pretty sure it will have an impact on my programs performance.
So again, what I want is one Name text box that will be in charge of filling out the name for every object that requires a name. Then, I wish to take the name I entered and have it fill out the treeview. Once I select it, I want my selected node to fill out the textboxes with all of the associated text I put in for it. But, should I change the name of my selected node, it will also change the name in the tree structure.
Again, sorry if that is confusing. I'm pretty beat at the moment.

how to clone a treeview c# and save it to recover later

i need to clone an entire treeview to implement Undo\Redo actions. i try in different ways but it appears that directly isn't possible. I mean copying the entire treeview, save that copy in a list. Then when the Undo action is requested decrement a specific counter of levels of undo and replace the actual treeview with the treeview that exist in the index positions of the list. if I do redo the same happens but lvls is incremented so i recover what come next. Every time that a modifications occurs in the treeview i need to save that copy and increment the lvl counter
I think you should consider going about this a different way.
You don't need to store the entire TreeView in order to keep track of undos and redos. You only need to keep track of the changes:
If you add a node, the undo is to remove it, so you only need a reference to that node.
If you remove it, the undo is to add it, so you need a reference to the added node and the node it was added to.
If you delete a whole branch, then you need to store the branch and the node it was deleted from.
And so forth. I think you'll have better luck doing it this way than trying to store the entire tree. And I've done it this way in applications where I've implemented undo/redo.
Instead of cloning the treeview itself you could simply clone the TreeNodes (see TreeNode.Clone()). It seems like the important bit is the relationship between the nodes, so this would probably work for you (I say "probably" because I haven't actually done this myself, so I can't tell you if there are any caveats to be wary of).
Consider implementing Command pattern (GoF):
Put your actions logic into classes
which implement common ICommand
{Do(); Undo();} interface.
On each user action you create object
of command requested and initialize
it with context parameters like new
and old filename.
Call Do(), put object into stack of
completed commands.
Each command is supplied with
context, so by calling Undo() it can
reverse changes.
Consider moving files into temporary
folder instead of removals.
Also, for a quick linear undo/redo, another option is the Memento pattern using zip of file as memento.
This is my shot at it, no big objects being moved in memory:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
namespace treeundoredo
{
static class Program
{
static TreeNode GetNode(string id, TreeNodeCollection root)
{
foreach (TreeNode node in root)
if (node.Text == id)
return node;
else
{
var subnode = GetNode(id, node.Nodes);
if (subnode != null)
return subnode;
}
return null;
}
[STAThread]
static void Main()
{
// list of (node, parent)
List<KeyValuePair<string, string>> undo = new List<KeyValuePair<string, string>>(), redo = new List<KeyValuePair<string, string>>();
Random rng = new Random();
TreeView Tree = new TreeView() { Size = new Size(200, 250) };
Tree.Nodes.Add("Root");
Tree.NodeMouseClick += (s, e) => { undo.Add(new KeyValuePair<string, string>(e.Node.Nodes.Add(rng.Next().ToString()).Text, e.Node.Text)); Tree.ExpandAll(); redo.Clear(); };
Button Undo = new Button() { Text = "Undo", Left = 205 };
Undo.Click += (s, e) => { if (undo.Count > 0) { var kvp = undo[undo.Count - 1]; GetNode(kvp.Key, Tree.Nodes).Remove(); redo.Add(kvp); undo.Remove(kvp); } };
Button Redo = new Button() { Text = "Redo", Left = 205, Top = 50 };
Redo.Click += (s, e) => { if (redo.Count > 0) { var kvp = redo[redo.Count - 1]; GetNode(kvp.Value, Tree.Nodes).Nodes.Add(kvp.Key); redo.Remove(kvp); undo.Add(kvp); Tree.ExpandAll(); } };
Form frm = new Form();
frm.Controls.AddRange(new Control[] { Tree, Undo, Redo });
Application.Run(frm);
}
}
}

TreeView child node populating problem

I need to construct a huge treeview from a composite database table with Grouping.
Grouping is, what we see in SQL Server Management Studio Express. After a Database node, some fixed folders are shown (like, Database Diagrams, Tables, Views, Synonyms, Programmability and Security) and children are grouped in those folders.
Up to this point I have used AfterSelect event and handler to achieve this.
But the problem with AfterSelect is, before selecting the node, the viewer is not able to know whether there is any child available. This is because, the expandable plus sign is not visible.
I want to use BeforeExpand. But the problem with BeforeExpand is, it works if the children are already populated. In that case, when I click groups, nothing happens.
How to solve this?
So codes/web-link will be appreciated.
What I usually do is to add a "dummy child node" wherever there may be children that should be loaded in a lazy manner. This will make the parent have the plus sign, and then you can add code to the AfterExpand event where you do the following:
Check if there are are exactly one child, and if that child is the dummy node (you can use the Tag property to identify the dummy node)
If the dummy node is found, launch a search to get the children and add them to the parent node, finish it off by removing the dummy node.
I typically give the dummy node a text like "Loading data. Please wait..." or so, so that the user gets some info on what is going on.
Update
I put together a simple example:
public class TreeViewSample : Form
{
private TreeView _treeView;
public TreeViewSample()
{
this._treeView = new System.Windows.Forms.TreeView();
this._treeView.Location = new System.Drawing.Point(12, 12);
this._treeView.Size = new System.Drawing.Size(200, 400);
this._treeView.AfterExpand +=
new TreeViewEventHandler(TreeView_AfterExpand);
this.ClientSize = new System.Drawing.Size(224, 424);
this.Controls.Add(this._treeView);
this.Text = "TreeView Lazy Load Sample";
InitializeTreeView();
}
void TreeView_AfterExpand(object sender, TreeViewEventArgs e)
{
if (e.Node.Nodes.Count == 1 && e.Node.Nodes[0].Tag == "dummy")
{
// this node has not yet been populated, launch a thread
// to get the data
ThreadPool.QueueUserWorkItem(state =>
{
IEnumerable<SomeClass> childItems = GetData();
// load the data into the tree view (on the UI thread)
_treeView.BeginInvoke((Action)delegate
{
PopulateChildren(e.Node, childItems);
});
});
}
}
private void PopulateChildren(TreeNode parent, IEnumerable<SomeClass> childItems)
{
TreeNode child;
TreeNode dummy;
TreeNode originalDummyItem = parent.Nodes[0];
foreach (var item in childItems)
{
child = new TreeNode(item.Text);
dummy = new TreeNode("Loading. Please wait...");
dummy.Tag = "dummy";
child.Nodes.Add(dummy);
parent.Nodes.Add(child);
}
originalDummyItem.Remove();
}
private IEnumerable<SomeClass> GetData()
{
// simulate that this takes some time
Thread.Sleep(500);
return new List<SomeClass>
{
new SomeClass{Text = "One"},
new SomeClass{Text = "Two"},
new SomeClass{Text = "Three"}
};
}
private void InitializeTreeView()
{
TreeNode rootNode = new TreeNode("Root");
TreeNode dummyNode = new TreeNode("Loading. Please wait...");
dummyNode.Tag = "dummy";
rootNode.Nodes.Add(dummyNode);
_treeView.Nodes.Add(rootNode);
}
}
public class SomeClass
{
public string Text { get; set; }
}
It's standard behaviour for a tree to show a "+" in front of every folder/group, and the plus dissapears when clicked on if it's found to have no children, this saves the expensive "do you have children" check.
Alternatively you can provide this information if you have a cheap way of determining if a node has children. This question provides more information.

Categories

Resources