I have a TreeView and a Buttton on a blank Form. I add three nodes to the TreeView with text "a", "b" and "c" respectively. The TreeView has a TreeViewNodeSorter as shown below which sorts based on node text.
When the button is clicked a new node with text "aa" is added to the TreeView. If Add is called to add the node, then the sort order of the nodes is now "a", "aa", "b", "c" - as I would expect.
If AddRange is called to add the node, the order is "a", "b", "aa", "c". What is the reason for this difference?
public partial class Form1 : Form
{
TreeView treeView = null;
public Form1()
{
InitializeComponent();
treeView = new TreeView();
treeView.TreeViewNodeSorter = new TreeNodeComparer();
treeView.Nodes.Add("a");
treeView.Nodes.Add("b");
treeView.Nodes.Add("c");
Controls.Add(treeView);
Button button = new Button();
button.Text = "Add";
button.Location = new Point(treeView.Location.X, treeView.Location.Y + treeView.Height + 10);
button.Click += button_Click;
Controls.Add(button);
}
void button_Click(object sender, EventArgs e)
{
TreeNode node = new TreeNode();
node.Text = "aa";
//treeView.Nodes.Add(node);
treeView.Nodes.AddRange(new TreeNode[] { node });
}
}
public class TreeNodeComparer : IComparer
{
public int Compare(object x, object y)
{
TreeNode xNode = x as TreeNode;
TreeNode yNode = y as TreeNode;
if (xNode == null || yNode == null)
{
return 0;
}
if (xNode == null)
{
return -1;
}
if (yNode == null)
{
return 1;
}
return xNode.Text.CompareTo(yNode.Text);
}
}
There is an article here that explains things pretty well:
http://geekswithblogs.net/sdorman/archive/2007/09/21/Add-vs.-AddRange.aspx
Basically .AddRange() is the bulk performance version and strives to make things quick. You can call .Sort() on the TreeView afterwards to sort the tree as it should be. (or change the .TreeViewNodeSorter property as mentioned in the article)
Related
I have an application that allows users to add new items into a TreeView control. When an item is selected the parent node reveals its child nodes, but it collapses when a new item (Button is clicked) is added to the Treeview control. I want it to stay expanded until the user interacts with it again.
//List<> collection is initialized
//Public class property is created to set it's member variables to the control values that the user enters
private void addButton_Click(object sender, EventArgs e)
{
if (ComboBoxOne.SelectedIndex != 0)
{
//Method that adds new item to a List<> Collection
AddToList();
//Method that goes through the List<> Collection, modifies the display of the item and adds it to the TreeView control
AddToTreeView();
}
}
//How I am adding the List<> objects to the treeview control
private void TravelTreeView()
{
TreeView1.Nodes.Clear();
//Items is the class I created and ObjectList is the List<>
foreach (Items obj in ObjectList)
{
TreeNode node = new TreeNode();
node.Text = obj.Name;
//Imagelist has 7 images
node.SelectedImageIndex = 0;
node.ImageIndex = obj.NameImage;
node.Nodes.Add(obj.AgeImage, "Age: " + obj.Age, 5);
node.Nodes.Add(obj.ZodiacImage, "Zodiac: " + obj.Zodiac, 6);
node.Nodes.Add(obj.JobImage, "Job: " + obj.Job, 7);
TreeView1.Nodes.Add(node);
}
}
I see that TreeView is cleared and then populated with list of nodes. As mentioned in the comment(s) you may check for IsExpanded property and then decide to call Expand on TreeNode.
Another way: As you are adding many new TreeNodes I would assume there is no previous state for these nodes to check for IsExpanded, in such a case, you may try something like below, the new Node is added in expanded state. Note Beginupdate/EndUpdate calls.
private void TravelTreeView()
{
// better to do this to avoid too many repaints
TreeView1.BeginUpdate();
TreeView1.Nodes.Clear();
//Items is the class I created and ObjectList is the List<>
foreach (Items obj in ObjectList)
{
TreeNode node = new TreeNode();
node.Text = obj.Name;
//Imagelist has 7 images
node.SelectedImageIndex = 0;
node.ImageIndex = obj.NameImage;
node.Nodes.Add(obj.AgeImage, "Age: " + obj.Age, 5);
node.Nodes.Add(obj.ZodiacImage, "Zodiac: " + obj.Zodiac, 6);
node.Nodes.Add(obj.JobImage, "Job: " + obj.Job, 7);
// add an expanded Node
node.Expand();
TreeView1.Nodes.Add(node);
}
// we are done with the updates to TreeView
TreeView1.EndUpdate();
}
WinForms does retain the state of the controls. The issue here seems to be the fact that you clean the items and then iterate your object and then reload the TreeView. In this case the items would be collapsed which is the default.
I would suggest the below approach,
private void TravelTreeView()
{
//Items is the class I created and ObjectList is the List<>
foreach (Items obj in ObjectList)
{
//Check to see if the item already exists
if (!TreeView1.Nodes.ContainsKey(obj.Name))
{
var node = new TreeNode();
node.Text = obj.Name;
//Imagelist has 7 images
node.SelectedImageIndex = 0;
node.ImageIndex = obj.NameImage;
node.Nodes.Add(obj.AgeImage, "Age: " + obj.Age, 5);
node.Nodes.Add(obj.ZodiacImage, "Zodiac: " + obj.Zodiac, 6);
node.Nodes.Add(obj.JobImage, "Job: " + obj.Job, 7);
TreeView1.Nodes.Add(node);
}
}
}
Information on the ContainsKey Method on TreeNodeCollection
I am using a TreeView to show a folderstructure. I dynamically create nodes in the NodeMouseClick event.
But even though the child nodes are populated, they are not visible in the treeview.
However, minimizing the window and maximizing it again solves this issue. Please let me know what I am doing wrong here. Please find below the code that I am using:
private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
if (treeView1.SelectedNode != null && e.Node.IsExpanded)
{
treeView1.BeginUpdate();
TreeNode node = e.Node;
while (node.Parent != null)
{
node = node.Parent;
}
VcDetailsEntity detailsEntity = connectedVCs.Where(c => c.VCName == node.Name).FirstOrDefault();
detailsEntity.VCBrowserPath = e.Node.Name;
FolderBrowser cosmosBrowser = new FolderBrowser();
List<FolderStreamEntity> folderStreams = folderBrowser.GetVcContentDetails(detailsEntity);
e.Node.Nodes.Clear();
foreach (var stream in folderStreams)
{
if (stream.IsDirectory)
{
TreeNode treeNode = new TreeNode();
treeNode.Name = stream.StreamName;
treeNode.Text = stream.QualifiedName;
treeNode.ToolTipText = stream.QualifiedName;
TreeNode dummyNode = new TreeNode();
treeNode.Nodes.Add((TreeNode)dummyNode.Clone());
TreeNode toUpdate = treeView1.Nodes.Find(e.Node.Name, true).FirstOrDefault();
toUpdate.Nodes.Add((TreeNode)treeNode.Clone());
}
}
treeView1.EndUpdate();
treeView1.Refresh();
}
}
I have tried the suggestions provided by Gnial0id, wpfnoop and LarsTech here below. But no luck. Temporarily I have resolved it by minimizing and maximizing the form programatically.
Well, it's hard to figure out anything from the provided code snippet because many parts are missing. Also I don't quite understand why TreeNode toUpdate = treeView1.Nodes.Find(e.Node.Name, true).FirstOrDefault(); is needed and then why you are cloning the node you just created etc. So I've prepared a sample test which is doing something similar and it does not experience the problem you are describing. Check it out and compare it to your code to find out what is wrong.
using System;
using System.Windows.Forms;
namespace Samples
{
static class Test
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var treeView = new TreeView { Dock = DockStyle.Fill, Parent = form };
for (int i = 1; i <= 10; i++)
{
var parent = new TreeNode { Text = "Parent#" + i };
treeView.Nodes.Add(parent);
for (int j = 1; j <= 10; j++)
{
var child = new TreeNode { Text = "Child#" + i };
var dummy = new TreeNode();
child.Nodes.Add(dummy);
parent.Nodes.Add(child);
}
}
var random = new Random();
int addCount = 0;
treeView.NodeMouseClick += (sender, e) =>
{
if (treeView.SelectedNode == e.Node && e.Node.IsExpanded)
{
treeView.BeginUpdate();
e.Node.Nodes.Clear();
int count = random.Next(20) + 1;
for (int i = 1; i <= count; i++)
{
var child = new TreeNode { Text = "AddChild#" + (++addCount) };
var dummy = new TreeNode();
child.Nodes.Add(dummy);
e.Node.Nodes.Add(child);
}
treeView.EndUpdate();
}
};
Application.Run(form);
}
}
}
Adding below code bloc immediately after the code to add a New Node did the magic for me.
treeView1.SelectedNode = NodeToUpdate;
Here the NodeToUpdate is the node where the new child nodes are added.
I have a treeview panel. In the panel, there are several child nodes. Some of them are only a header.
The way I create the treeview:
treeviewpaneL.Items.Add(art);
art.Items.Add(prt);
some if statement....
TreeViewItem cldhdr = new TreeViewItem() { Header = "ChildNodes:" };
prt.Items.Add(cldhdr);
TreeViewItem cld = new TreeViewItem() .......
........
.....
cldhdr.Items.Add(cld);
Treeview:
Node1
ChildNodes: (This is header only. It appears if child node exists)
Childnode1
Childnode2
childnode3
Node2
Node3
ChildNodes:
Childnode1
Childnode2
childnode3
Node4
Node5
In my treeview there are also images in front of all nodes. It's a code driven treeview. In the xaml part i have only:
<TreeView x:Name="treeviewpaneL" SelectedItemChanged="treeviewpaneL_SelectedItemChanged" >
</TreeView>
What I want to do is when I click on any of the treeview items, I need to get its index number.
My code is:
private void treeviewpaneL_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
int index = 0;
ItemsControl parent = ItemsControl.ItemsControlFromItemContainer(prt);
foreach (var _item in parent.Items)
{
if (_item == treeviewpaneL.SelectedItem)
{
selectedNodeIndex = index;
MessageBox.Show(selectedNodeIndex.ToString());
break;
}
index++;
}
}
With the code above, I can get the index of Node1,Node2,Node3, Node4 and Node5 as 0,1,2,3,4
What I want is to get the index numbers as:
Node1 = 0
Childnode1 = 1 (Skipping the header)
Childnode2 = 2
Childnode3 = 3
Node2 = 4
....
....
....
What am I missing?
Here is the solution, first of all your "MyTreeViewItem"
public class MyTreeViewItem :TreeViewItem
{
private int _index;
public int Index
{
get { return _index; }
set { _index = value; }
}
public MyTreeViewItem() : base()
{
}
}
and usage;
MyTreeViewItem art = new MyTreeViewItem();
art.Header = "Node1";
art.Index = 1;
MyTreeViewItem prt = new MyTreeViewItem();
prt.Header = "Child1";
prt.Index = 2;
art.Items.Add(prt);
treeviewpaneL.Items.Add(art);
and event;
private void treeviewpaneL_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
MyTreeViewItem selectedItem = e.NewValue as MyTreeViewItem;
if (selectedItem != null)
{
MessageBox.Show("" + selectedItem.Index);
}
}
To get the index of the currently selected item:
MyTreeView.Items.IndexOf(MyTreeView.SelectedItem);
I have a custom ASP.NET treeview control, which uses existing MS Treeview. I create and recreate the treeview upon postbacks (it is in UpdatePanel) from a stored IEnumerable.
Some items are added like this:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack && !Page.IsAsync)
{
DD.Items = null;
DD.Items.Add(new TreeviewItem("Choice 1", "4", "-1"));// = items;
DD.Items.Add(new TreeviewItem("something", "1", "-1"));
DD.Items.Add(new TreeviewItem("Europe", "2", "-1"));
DD.Items.Add(new TreeviewItem("pff", "3", "-1"));
}}
The control is initialized and loaded in it's OnLoad using BuildTreeFromItemCollection():
public void BuildTreeFromItemCollection()
{
BuildTreeFromItemCollection(this.Items, null);
}
public void BuildTreeFromItemCollection(IEnumerable<StoredItem> items, TreeNode parentNode)
{
IEnumerable<TreeviewItem> tvItems = items.Cast<TreeviewItem>();
var nodes = tvItems.Where(x => parentNode == null ? int.Parse(x.Parent) <= 0 : x.Parent == parentNode.Value);
TreeNode childNode;
foreach (var i in nodes)
{
childNode = new TreeNode(i.Name, i.Value)
{
PopulateOnDemand = this.PopulateOnDemand
};
if (parentNode == null)
{
TvHierarchy.Nodes.Add(childNode);
}
else
{
parentNode.ChildNodes.Add(childNode);
}
this.BuildTreeFromItemCollection(items, childNode);
}
}
TreeNodePopulate is handled like so:
void TvHierarchy_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
this.EnsureChildControls();
IEnumerable<StoredItem> childItems = NodePopulator(e.Node.Value);
foreach (StoredItem item in childItems)
{
TreeNode newNode = new TreeNode(item.Name, item.Value);
newNode.PopulateOnDemand = this.PopulateOnDemand;
e.Node.ChildNodes.Add(newNode);
}
this.Items.AddRange(childItems);
}
and this Func is temporarily attached to the NodePopulator:
private IEnumerable<StoredItem> ItemLoader(string val)
{
List<StoredItem> itemList = new List<StoredItem>();
Random r = new Random();
for (int i = 0; i <= 4; i++)
{
int rand = r.Next(10,100);
itemList.Add(new TreeviewItem("test " + rand.ToString(), rand.ToString(), val));
}
return itemList;
}
Unfortunately the BuildTreeFromItemCollection falls into infinite loop after a couple of node expansions, around 4th level, and i'm left with stack overflow.
The exact exception shows up on the line
var nodes = tvItems.Where(x => parentNode == null ? int.Parse(x.Parent) <= 0 : x.Parent == parentNode.Value);
but the Call stack looks already filled up. Where's the problem?
So it seems that the whole problem was in the code generating new nodes in this test function:
int rand = r.Next(10,100);
itemList.Add(new TreeviewItem("test " + rand.ToString(), rand.ToString(), val));
So when an ID was generated which already was earlier the code went into infinite loop. After expanding range to (10,10000) everything works fine.
I need to access the nodes of a TreeView as a plain list (as if all the nodes where expanded) to be able to do multiselection pressing the Shift key. Is there a way to accomplish this?
Thanks
Here is a method that will retrieve all the TreeViewItems in a TreeView. Please be aware that this is an extremely expensive method to run, as it will have to expand all TreeViewItems nodes and perform an updateLayout each time. As the TreeViewItems are only created when expanding the parent node, there is no other way to do that.
If you only need the list of the nodes that are already opened, you can remove the code that expand them, it will then be much cheaper.
Maybe you should try to find another way to manage multiselection. Having said that, here is the method :
public static List<TreeViewItem> FindTreeViewItems(this Visual #this)
{
if (#this == null)
return null;
var result = new List<TreeViewItem>();
var frameworkElement = #this as FrameworkElement;
if (frameworkElement != null)
{
frameworkElement.ApplyTemplate();
}
Visual child = null;
for (int i = 0, count = VisualTreeHelper.GetChildrenCount(#this); i < count; i++)
{
child = VisualTreeHelper.GetChild(#this, i) as Visual;
var treeViewItem = child as TreeViewItem;
if (treeViewItem != null)
{
result.Add(treeViewItem);
if (!treeViewItem.IsExpanded)
{
treeViewItem.IsExpanded = true;
treeViewItem.UpdateLayout();
}
}
foreach (var childTreeViewItem in FindTreeViewItems(child))
{
result.Add(childTreeViewItem);
}
}
return result;
}
Here is what you asked;
private static TreeViewItem[] getTreeViewItems(TreeView treeView)
{
List<TreeViewItem> returnItems = new List<TreeViewItem>();
for (int x = 0; x < treeView.Items.Count; x++)
{
returnItems.AddRange(getTreeViewItems((TreeViewItem)treeView.Items[x]));
}
return returnItems.ToArray();
}
private static TreeViewItem[] getTreeViewItems(TreeViewItem currentTreeViewItem)
{
List<TreeViewItem> returnItems = new List<TreeViewItem>();
returnItems.Add(currentTreeViewItem);
for (int x = 0; x < currentTreeViewItem.Items.Count; x++)
{
returnItems.AddRange(getTreeViewItems((TreeViewItem)currentTreeViewItem.Items[x]));
}
return returnItems.ToArray();
}
Call with your control as the first parameter e.g.;
getTreeViewItems(treeView1);