I built a code that takes data from an online database and adds them recursively through a while loop in a TreeView. Substantially the Add function is called when the user selects a new nation in the tree, for example, this is what is contained in the TreeView (Available countries):
<TreeView Name="nation_team" SelectedItemChanged="nation_team_SelectionChanged">
<TreeViewItem Header="Italy"/>
<TreeViewItem Header="Germany"/>
</TreeView>
when the user selects for example Italy, then the following function is set in motion:
private void nation_team_SelectionChanged(object sender, RoutedPropertyChangedEventArgs<Object> e)
{
soccerSeason.getChampionshipsForTeams();
}
the following function contains this content:
string stm = #" SELECT * FROM league
WHERE country = '" + MainWindow.AppWindow.nation_team.SelectedValue.ToString() + "'";
MySqlCommand cmd = new MySqlCommand(stm, database.Connection);
MySqlDataReader rdr = cmd.ExecuteReader();
TreeViewItem rootNode = new TreeViewItem() { Header = MainWindow.AppWindow.nation_team.SelectedValue.ToString() };
while (rdr.Read())
{
rootNode.Items.Add(getTreeViewChampionships(rdr.GetString(3)));
MainWindow.AppWindow.nation_team.Items.Add(rootNode);
}
rdr.Close();
As you can see I run a query to download all the championships for the nation selected, that is precisely the value taken from the TreeView (Italy or Germany).
Now I run the query and execute the reader "RDR".
Then I declare a rootNode that corresponds to the country selected by the user, in this country you have to add the championships returned from Query, if the user select "Italy", I'm waiting this result structure in TreeView:
Italy
Serie A //this two values was returned by query
Serie B
So far no problem query is successful and the values are read.
At this point it is activated while loop that loops through all the values, inside the while loop we find this method:
rootNode.Items.Add(getTreeViewChampionships(rdr.GetString(3)));
that would add "Serie A and Serie B" to Italy. The method getTreeViewChampionships, the method receives as a parameter the championships they are reading "RDR", in particular "Series A" and "Series B".
private TreeViewItem getTreeViewChampionships(string campionato)
{
TreeViewItem item = new TreeViewItem() { Header = campionato };
return item;
}
It would seem that there should be no problems and yet whenever I select a nation comes back the following error on this line:
MainWindow.AppWindow.nation_team.Items.Add(rootNode);
System.InvalidOperationException: For the item already exists a logical parent element. You must disassociate it from the old parent before it can bind to a new parent.
in MS.Internal.Controls.InnerItemCollectionView.AssertPristineModelChild (Object item)
in MS.Internal.Controls.InnerItemCollectionView.Add (Object item)
in System.Windows.Controls.ItemCollection.Add (Object newItem)
What am I doing wrong?
NB: The Nation available in the XAML is just an example, I add the Nations behind the code, like this way:
TreeViewItem rootNode = new TreeViewItem() { Header = rdr.GetStringOrNull(0) };
MainWindow.AppWindow.nation_team.Items.Add(rootNode);
First Answer
I managed to reproduce your problem with that minimal code :
private void nation_team_SelectionChanged(object sender, RoutedPropertyChangedEventArgs<Object> e)
{
TreeViewItem rootNode = new TreeViewItem() { Header = nation_team.SelectedValue.ToString() };
for (int i = 0; i < 3; ++i)
{
rootNode.Items.Add(new TreeViewItem() { Header = "some data" });
nation_team.Items.Add(rootNode);
}
}
This is how to resolve your problem :
private void nation_team_SelectionChanged(object sender, RoutedPropertyChangedEventArgs<Object> e)
{
TreeViewItem rootNode = (TreeViewItem)e.NewValue;
for (int i = 0; i < 3; ++i)
{
rootNode.Items.Add(new TreeViewItem() { Header = "some data" });
}
}
In that code, you won't create a new TreeViewItem with the same header as the selected item. You just get the actual selected item (i.e. e.NewValue) so you can directly add what you want.
Result on click :
Hope that helped.
Answer to comment
In order to dissociate root nodes (i.e. countries) and child nodes (i.e. series), I suggest something like that :
TreeViewItem rootNode = (TreeViewItem)e.NewValue;
if (rootNode.Parent is TreeView)
{
//Country
}
else
{
//Serie
}
If the parent type is TreeView, then the node is a root node and you can get the data from your database. If not, you are on a child node, and you can do other stuff.
Edit after context changed
I can't reproduce your issue. I used an empty TreeView in XAML code, then that code-behind :
public MainWindow()
{
InitializeComponent();
var rootNode1 = new TreeViewItem() { Header = "Italy" };
var rootNode2 = new TreeViewItem() { Header = "Germany" };
nation_team.Items.Add(rootNode1);
nation_team.Items.Add(rootNode2);
}
private void nation_team_SelectionChanged(object sender, RoutedPropertyChangedEventArgs<Object> e)
{
TreeViewItem rootNode = (TreeViewItem)e.NewValue;
for (int i = 0; i < 3; ++i)
{
rootNode.Items.Add(new TreeViewItem() { Header = "some data" });
}
}
All worked as excepted. e.NewValue is not null. Are you sure you are doing it the same way ?
Related
I get data from tow database, db1,db2, then Now I want to create treeView for the data in the resulting datagridview; in datagrid view there are: id, name, director,the first record is the prim director, that mean he has not up director(he is owner), each record has no other record or has more records(child), and each child has grandchild and so on, this scenario Just Like in the this page:
I want to create treeview (parent and child and grandchild and so on), depending on xml file
when i used this snippet after some :
void setTree()
{
{
foreach(DataGridViewRow dt in DataGridView1.Rows)
{
var per = this.DataGridView1.Rows.Cast<DataGridViewRow>().Select(n => new person
{
name = dt.Cells[0].Value.ToString(),
Sex = dt.Cells[1].Value.ToString(),
Status = dt.Cells[2].Value.ToString(),
child = dt.Cells[3].Value.ToString(),
id = dt.Cells[4].Value.ToString(),
father = dt.Cells[5].Value.ToString()
}).ToList();
var rootTreeNode = GetTree(per, "").First();.........(1)
treeView1.Nodes.Add(rootTreeNode);
}
}
}
private TreeNode[] GetTree(List<person> per, string parent)
{
return per.Where(p => p.father == parent).Select(p =>
{
var node = new TreeNode(p.name);
node.Tag = p.id;
node.Nodes.AddRange(GetTree(per, p.id));
return node;
}).ToArray();
}
Now, when I use this code, I get error at mark(1),it say:Additional information: Sequence contains no elements.
thank you
after several readings in internet and attempts to solve this small problem, I successed finally.
this is the solution:
{
.............
TreeNode tn = new TreeNode(this.DataGridView2.Rows[0].Cells[0].Value.ToString());//text
tn.Tag = this.DataGridView2.Rows[0].Cells[4].Value.ToString();// id
tn.Name = this.DataGridView2.Rows[0].Cells[5].Value.ToString();//directorid
treeView1.Nodes.Add(tn);
settree(tn);
}
public void settree(TreeNode ns)
{
foreach (DataGridViewRow dr in DataGridView2.Rows)
{
if (dr.Cells[5].Value.ToString() == ns.Tag.ToString())
{
TreeNode tsn = new TreeNode(dr.Cells[0].Value.ToString());
tsn.Tag = dr.Cells[4].Value.ToString();
tsn.Name = dr.Cells[5].Value.ToString();
ns.Nodes.Add(tsn);
settree(tsn);
}
}
}
i will be happy if you benefit from this code.
I wish my program worked like this:
1.It takes data form MS SQL
2.Makes tree based on this data(data rows)
3.I have few textBoxex and I want to fill them with data based on clicked node.
I`ve made class for SQL connection:
class Baza
{
private SqlConnection connection;
string dbdir = "Data Source=CS24\\SQLEXPRESS;user id=sa;password=alamakota;database=SHARP;connection timeout=3";
public Baza()
{
connection = new SqlConnection();
connection.ConnectionString = dbdir;
connection.Open();
}
public DataTable get_data(string q)
{
DataTable dt = new DataTable();
SqlDataReader dr ;
SqlCommand sqlc = new SqlCommand(q);
sqlc.Connection = this.connection;
dr = sqlc.ExecuteReader();
dt.Load(dr);
return dt;
}
}
And treeView creator:
private void Form1_Load(object sender, EventArgs e)
{
Baza baza = new Baza();
DataTable dt = new DataTable();
dt = baza.get_data("Select * from Users order by Id asc");
foreach (DataRow dr in dt.Rows)
{
TreeNode node = new TreeNode(dr["Name"].ToString() + " " + dr["Surname"].ToString());
treeView1.Nodes.Add(node);
}
}
I thought that node should have ID which I can use to make data filling but I dont really know how to do it. Can you help me with that?
I had the same trouble and i did this :
(I had hierarchical data in columns ParentID, IsGroup, OrderInGroup)
Create a class for the TreeViewItems
public class xTreeViewItem : INotifyPropertyChanged
{
public int id { get; set; }
public string Header { get; set; }
public int parent;
public List<xTreeViewItem > children { get; set; }
public bool isGroup = false;
public bool ParentResolved = false;
public float order {get;set;}
public void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Now make a list with the SQL Data
private void LoadTree()
{
ObservableCollection<xTreeViewItem> treeSource = new ObservableCollection<xTreeViewItem>();
List<xTreeViewItem> Buffer = new List<xTreeViewItem>();
foreach (DataRow row in table.Rows)
{
xTreeViewItem itm = new TreeViewItem();
// Load the required heirarchial data
Buffer.Add(itm);
}
for (int i = Buffer.Count - 1; i >= 0; i--)
{
if (Buffer[i].parentID != 0)
{
if (Buffer[i].ParentResolved)
Buffer.RemoveAt(i);
else
{
Buffer[i].ParentResolved = true;
while (Buffer.Any(c => (c.parentID == Buffer[i].menuID && !c.ParentResolved)))
{
Buffer[i].children.Add(FindChildren(Buffer[i]));
}
xTreeViewItem parent = Buffer.First(c => c.menuID == Buffer[i].parentID);
parent.children.Add(Buffer[i]);
Buffer[i].parent = parent;
Buffer.RemoveAt(i);
}
}
}
// remaining items acts as mainItems, so add it to the treeSource
foreach (xTreeViewItem x in Buffer)
{
treeSource.Add(x);
}
treeView.ItemsSource = treeSource;
}
xTreeViewItem FindChildren(xMenuItem x)
{
List<xTreeViewItem > subBuffer = Buffer.Where(c => (c.parentID == x.menuID && !c.ParentResolved)).ToList(); // if there is, get all of them to a list
subBuffer = subBuffer.OrderBy(c => c.order).ToList(); // << THIS IS CRAZY, BUT FIXES :P // order all of them by their 'order' property
int indx = Buffer.IndexOf(subBuffer[0]); // get the first item after ordering
Buffer[indx].ParentResolved = true;
Buffer[indx].parent = x; // changing parentresolved property to prevent infinte loops
while (Buffer.Any(c => (c.parentID == Buffer[indx].menuID && !c.ParentResolved))) // again checking if the current child got any other child.
{
Buffer[indx].children.Add(FindChildren(Buffer[indx])); // adding all the childs
}
return Buffer[indx]; // return the child
}
In XAML : put this as the TreeView's ItemTemplate :)
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding children}">
<ContentPresenter Content="{Binding Header}" Margin="2,0"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
This did the work for me :P
and to get the selected item, just use :
xTreeViewItem selectedItem = (xTreeViewItem)treeView.SelectedItem;
make your view update with this 'selectedItem'; .. :)
Hope it helped :)
** Sometimes the items may appear reversed. Simply give the List.Reverse(); in the point where it is so, i havent run the code myself, anyway this should work for all types, like loading MenuItems from Database and desplaying (useful if you have multiple languages) :)
** Im just 18 yrs old :P havent got a degree in CS, this is just a code that i came up with :) yea there might be flaws but it works for me :P
:)
I have a treeview and i want no postback on click any childnodes.And i will get selected childnode value.
I found a solution , when i set "node_.SelectAction = TreeNodeSelectAction.None;" i cant select any childnodes and no highlight on it.
Waiting your helps.Sorry about my en.
Aspx:
<asp:TreeView ID="TreeView1" runat="server"></asp:TreeView>
Cs:
TreeView1.Nodes.Clear();
TreeView1.TreeNodeExpanded += new TreeNodeEventHandler(TreeView1_TreeNodeExpanded);
DataTable dt = ImzaDll.Imza.KategorileriGetir(true);
foreach (DataRow row in dt.Rows)
{
TreeNode node_ = new TreeNode();
node_.Text = row["ACIKLAMA"].ToString();
node_.Value = row["KATEGORI"].ToString();
TreeView1.Nodes.Add(node_);
}
void TreeView1_TreeNodeExpanded(object sender, TreeNodeEventArgs e)
{
addChildNodes(e.Node);
}
private void addChildNodes(TreeNode node)
{
DataTable dt = ImzaDll.Imza.KutuphaneBasliklariGetir(true, node.Value.ToString());
foreach (DataRow row in dt.Rows)
{
TreeNode childNode = new TreeNode();
childNode.Text = row["BASLIK"].ToString();
childNode.Value = row["KUTUPHANE_ID"].ToString();
childNode.ToolTip = row["BASLIK"].ToString() + " kütüphanesini ekle";
childNode.Target = "_new";
node.ChildNodes.Add(childNode);
}
}
You can set CSSClass of treeview child nodes
like
<asp:TreeView LeafNodeStyle-CssClass="childnode" runat="server">....</asp:TreeView>
then using jquery you get get class and set return false like follow.
$(".childnode").click(function(){
return false;
})
...same way you can set RootNodeStyle-CssClass, ParentNodeStyle-CssClass class and use jquery to set them...
TreeNode tn = new TreeNode();
tn.SelectAction = TreeNodeSelectAction.None; OR tn.SelectAction = TreeNodeSelectAction.Expand;
Both of these will not cause postback.
you could remove the href of link('a') tag attribute to stop post back
$('#ctl00_ContentPlaceHolder1_tvHierarchyView table tr td>a').click(function () {
var treeViewData = window["<%=tvHierarchyView.ClientID%>" + "_Data"];
if (treeViewData.selectedNodeID.value != "") {
var selectedNode=document.getElementById(treeViewData.selectedNodeID.value);
var value = selectedNode.href.substring(selectedNode.href.indexOf(",") + 3, selectedNode.href.length - 2);
var text = selectedNode.innerHTML;
alert("Text: " + text + "\r\n" + "Value: " + value);
} else {
alert("No node selected.")
}
$(this).removeAttr("href");
/// ...................... rest of your code
}); /// End of click function
}); /// End of document ready function
Here steps explanation:
Get the dev id which contains the tree table by Using inspect element:
Get details from the selected child node.
After taken the details of child node , remove the attribute "href" to avoid post back.
Do whatever functionality you what do with selected node details (eg pass selected value using ajax)
If the logic within this method is run from an event handler such as Button_Click it works perfectly, but, when running this from a method such as below I get the error:
hostView.SelectedNode.Nodes.Add(newNode);
Object reference not set to an instance of an object.
Here is my code:
private void SetupHostTree()
{
// Set internal host names
using (var reader = File.OpenText("Configuration.ini"))
{
List<string> hostnames = ParseInternalHosts(reader).ToList();
foreach (string s in hostnames)
{
TreeNode newNode = new TreeNode(s);
hostView.SelectedNode.Nodes.Add(newNode);
string title = s;
TabPage myTabPage = new TabPage(title);
myTabPage.Name = s;
tabControl1.TabPages.Add(myTabPage);
}
}
}
Maybe there are no Selected Nodes :)
Probably because no node is currently selected in the hostView TreeView.
The documentation says that the TreeView.SelectedNode property will return null when no node is currently selected. And since you've combined it into an expression, the entire expression is failing because there is no Nodes collection on a null object!
Try this code:
private void SetupHostTree()
{
// Set internal host names
using (var reader = File.OpenText("Configuration.ini"))
{
List<string> hostnames = ParseInternalHosts(reader).ToList();
foreach (string s in hostnames)
{
// Ensure that a node is currently selected
TreeNode selectedNode = hostView.SelectedNode;
if (selectedNode != null)
{
TreeNode newNode = new TreeNode(s);
selectedNode.Nodes.Add(newNode);
}
else
{
// maybe do nothing, or maybe add the new node to the root
}
string title = s;
TabPage myTabPage = new TabPage(title);
myTabPage.Name = s;
tabControl1.TabPages.Add(myTabPage);
}
}
}
We have been given an example of using an IList which is used to fill a list box. I would like to use the same process to fill the list with objects but to fill a ListView.
The code from the object class looks like this:
public void ListClients(IList list)
{
list.Clear();
for (int i = 0; i < MAX_CLIENTS; ++i)
{
if (myClients[i] == null)
continue;
list.Add(myClients[i].FullName);
}
}
the code in the form, like this:
private void ListClientButton_Click(object sender, EventArgs e)
{
CDB.ListClients(ListClientsBox.Items);
}
I have tried numerous ways but I guess that I don't understand the IList concept well enough. Is it possible to fill a listview in Details view from the IList?
Does your ListView control have any columns set? If it doesn't, content won't be visible. Try:
private void ListClientButton_Click(object sender, EventArgs e)
{
ListClientsBox.Columns.Add("Full name");
CDB.ListClients(ListClientsBox.Items);
}
Edit:
I've run some tests, code you posted in comment seems to be fine. You might only need to adjust few ListView's properties, depending on display you aim for:
// perpare ListView beforehand
this.listView.Columns.Add("First name");
this.listView.Columns.Add("Email");
this.listView.Columns.Add("Country");
this.listView.View = View.Tile;
// if tile height is too small, some data might not be visible
this.listView.TileSize = new Size(180, 50);
// sample data
var people = new[]
{
new { FirstName = "John", Email = "john#domain.com", Country = "USA" },
new { FirstName = "Betty", Email = "betty72#mail.org", Country = "Canada" },
new { FirstName = "Steven", Email = "stv#domain.net", Country = "Brazil" },
};
foreach (var person in people)
{
ListViewItem item = new ListViewItem(person.FirstName);
item.SubItems.Add(person.Email);
item.SubItems.Add(person.Country);
this.listView.Items.Add(item);
}
And this is how ListView looks like, with both View.Tile and View.Details: