ListBox selected item refer to a variable - c#

Here what I've done.
I made my own class.
public class Node
{
public string name;
public string type;
public string vm_name;
public string vm_ip;
public string vm_hostname;
}
string[] nodes = new string[2];
Node vm1 = new Node();
Node vm2 = new Node();
I set Name property:
vm1.name = "name1";
vm2.name = "name2";
I put all variables from this type in a string
nodes[0] = vm1.name;
nodes[1] = vm2.name;
After that I added that array into the listbox items
nodeList.Items.AddRange(nodes);
Is there a way to access the variable by selecting the item from the list box ?
If there is a better way to do it I am open for suggestions.

Use DisplayMember and DataSource properties.And create an array of Nodes instead of strings,
var nodes = new []
{
new Node { name = "name1" },
new Node { name = "name2" }
}
nodeList.DisplayMember = "name";
nodeList.DataSource = nodes;
Then you can access your SelectedItem and cast it to Node like this:
private void listBox_SelectedIndexChanged(object sender, EventArgs e)
{
var selectedNode = nodeList.SelectedItem as Node;
if (selectedNode != null)
{
...
}
}

Bind the listbox directly to the Node instance instead of a string.
var node = (Node) nodeList.SelectItem;

Related

How to parse list of data dynamically and show the result in treeview

I have a list of objects with properties. The object class shown below:
public class ElementImpression
{
public int ElementId { get; private set; }
public string FamilyAndTypeName { get; private set; }
public string CategoryName { get; private set; }
public int CategoryNumber { get; private set; }
public string SystemAbbreviation { get; private set; }
public ElementImpression(Element e)
{
ElementId = e.Id.IntegerValue;
FamilyAndTypeName = e.get_Parameter(BuiltInParameter.ELEM_FAMILY_AND_TYPE_PARAM).AsValueString();
CategoryName = e.Category.Name;
CategoryNumber = e.Category.Id.IntegerValue;
SystemAbbreviation = e.get_Parameter(BuiltInParameter.RBS_DUCT_PIPE_SYSTEM_ABBREVIATION_PARAM).AsString();
}
}
The goal is to parse the list and create a structured, hierarchical presentation in a TreeView control. The number of levels in the hierarchy and which properties to use as nodes is defined at runtime by the user.
I have been succesful in creating the following treeview:
Treeview by using the following code:
private void UpdateTreeView(object sender, MyEventArgs e)
{
//Level 0: All
//Level 1: System Abbreviation
//Level 2: Category Name
//Level 3: Family and Type Name
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
//Payload is a container object holding the list to be parsed. It is cached as a property in the form.
//Payload.ElementsInSelection is the list of objects to parse.
var lv1Group = Payload.ElementsInSelection.GroupBy(x => x.SystemAbbreviation);
treeView1.Nodes.Add("All");
int i = -1;
foreach (IGrouping<string, ElementImpression> group1 in lv1Group)
{
treeView1.Nodes[0].Nodes.Add(group1.Key);
var lv2Group = group1.ToList().GroupBy(x => x.CategoryName);
i++;
int j = -1;
foreach (IGrouping<string,ElementImpression> group2 in lv2Group)
{
treeView1.Nodes[0].Nodes[i].Nodes.Add(group2.Key);
var lv3Group = group2.ToList();
j++;
int k = -1;
foreach (ElementImpression ei in lv3Group)
{
k++;
treeView1.Nodes[0].Nodes[i].Nodes[j].Nodes.Add(ei.FamilyAndTypeName);
treeView1.Nodes[0].Nodes[i].Nodes[j].Nodes[k].Nodes.Add(ei.ElementId.ToString());
treeView1.Nodes[0].Nodes[i].Nodes[j].Nodes[k].Nodes.Add(ei.CategoryNumber.ToString());
}
}
}
treeView1.EndUpdate();
}
Is it possible to rewrite the UpdateTreeView() method, so that it accepts some kind of object, which tells the method how many levels and what properties to use, and then parses the data and creates the treeview dynamically at runtime? Is it possible to do it using recursion?
Well, I managed to arrive at a solution. It is not recursive, but it is dynamic. Properties can be added or removed at runtime and the following code should parse it and populate the tree.
The general idea is to loop through all the objects which need to be organized to determine the FullPath of the deepest level node. Because I am using property values as nodes, each object already holds its' FullPath subparts. They just need to be combined in the correct order.
Then, starting from the root node at the top level, which is added manually, walk every step of the path checking if the node exists and if not creating it. This method successfully renders my data as needed in a treeview.
The FindTreeNodeByFullPath() method is from here.
public void PopulateTreeview()
{
//Manually add root node
treeView1.Nodes.Add("All");
//Loop all the objects
foreach (ElementImpression e in Elements)
{
//Declare array to hold the names of all nodes in path to the element
//PropertiesList is an object containing information about what properties to consider and how many.
string[] pathParts = new string[PropertiesList.Length + 2];
//The name of root node
pathParts[0] = "All";
//Populate the path parts with values from elements
for (int i = 0; i < PropertiesList.Length; i++)
{
pathParts[i + 1] = PropertiesList[i].getPropertyValue(e);
}
//Finish the list with the name of the element (id currently)
pathParts[pathParts.Length - 1] = e.Id.IntegerValue.ToString();
//Create an array of all full paths from root node to the element
string[] fullPaths = new string[PropertiesList.Length + 2];
for (int i = 0; i < fullPaths.Length; i++)
{
if (i == 0) fullPaths[i] = pathParts[i];
else fullPaths[i] = fullPaths[i - 1] + "." + pathParts[i];
}
//Iterate through the fullPaths to determine, if node exists, if not -> create it
TreeNode previousNode = null;
for (int i = 0; i < fullPaths.Length; i++)
{
TreeNode foundNode = treeView1.Nodes.FindTreeNodeByFullPath(fullPaths[i]);
if (foundNode == null)
{
if (previousNode != null) previousNode = previousNode.Nodes.Add(pathParts[i]);
}
else
{
previousNode = foundNode;
continue;
}
}
}
}

How To Iterate Though Nodes Added To TreeView Using a Derives TreeNode Instance

I have a TreeView control in which I need to add some nodes passing a string to the Add() method, and add some nodes passing a derived TreeNode class instance to the Add() method.
I'm trying to see which leaf nodes are checked and I just can't figure out how to iterate through the nodes that are derived TreeNode class (EmojiNode) instances shown below.
TreeNode groupnode = EmojisTreeView.Nodes.Add("Group Name");
EmojiNode emojiNode = new EmojiNode(subgroupEmojiName, unicodeEndStr, emojiConvertResultStr);
int groupsubemojinode = groupsubnode.Nodes.Add(emojiNode);
foreach (var node in EmojisTreeView.Nodes.OfType<EmojiNode>())
{
// count is 0
}
public class EmojiNode : TreeNode
{
public string emojiName;
public string emojiValue;
public string emojiConvertedResultStr;
public EmojiNode(string emojiName, string emojiValue, string emojiConvertedResultStr)
{
this.emojiName = emojiName;
this.emojiValue = emojiValue;
this.emojiConvertedResultStr = emojiConvertedResultStr;
this.Text = this.emojiConvertedResultStr + " " + this.emojiName;
}
public EmojiNode(string emojiName)
{
this.emojiName = emojiName;
this.Text = this.emojiName;
}
public string getEmojiName()
{
return this.emojiName;
}
public string getEmojiValue()
{
return this.emojiValue;
}
public string getEmojiConvertedResultStr()
{
return this.emojiConvertedResultStr;
}
}

Binding to a treeview programatically not working in UWP

I'm following this article to try and programmatically bind data to a treeview (I'm on 1903).
In a brand new UWP app, I have the following code behind:
public MainPage()
{
this.InitializeComponent();
var items = new List<Item>();
var rootItem = new Item();
rootItem.Name = "Root Item";
rootItem.Children.Add(new Item() { Name = "test child 1" });
items.Add(rootItem);
var treeView = new TreeView();
treeView.ItemsSource = items;
stackPanel.Children.Add(treeView);
}
Item looks like this:
public class Item
{
public string Name { get; set; }
public ObservableCollection<Item> Children { get; set; } = new ObservableCollection<Item>();
public override string ToString()
{
return Name;
}
}
This appears to be the exact structure outlined in the above article. However, when I run the application, I get this:
My guess is that I need to do, or set something that tells this treeview, or the collection that it has children - but I can't see what that might be.
You should create an ItemTemplate as explained in the docs.
You could use the XamlReader class to do this programmatically. Something like this:
const string Xaml = "<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><TreeViewItem ItemsSource=\"{Binding Children}\" Content=\"{Binding Name}\"/></DataTemplate>";
treeView.ItemTemplate = XamlReader.Load(Xaml) as DataTemplate;
If you use C# to build a TreeView, I recommend adding a TreeViewNode using traversal.
Due to the lack of instructions, TreeView does not automatically handle the Children of the Item. In the documentation you provide, the TreeView has a DataTemplate directive, so the children can render.
You can change code like this:
public MainPage()
{
this.InitializeComponent();
var items = new List<Item>();
var rootItem = new Item();
rootItem.Name = "Root Item";
rootItem.Children.Add(new Item() { Name = "test child 1" });
items.Add(rootItem);
var treeView = new TreeView();
foreach (var root in items)
{
var rootNode = new TreeViewNode() { Content = root.Name };
if (root.Children.Count > 0)
{
foreach (var child in root.Children)
{
rootNode.Children.Add(new TreeViewNode() { Content = child.Name });
}
}
treeView.RootNodes.Add(rootNode);
}
stackPanel.Children.Add(treeView);
}
Best regards.

Refreshing the listbox item after an element is changed

I have a ParameterItem class for adding some items to a listbox:
class ParameterItem
{
public string Name { get; set; }
public string Value { get; set; }
public ParameterItem(string name, string value)
{
Name = name;
Value = value;
}
public override string ToString()
{
return Name + " = " + Value;
}
public override bool Equals(object obj)
{
if (obj is ParameterItem)
return (Name == ((ParameterItem)obj).Name);
return false;
}
public override int GetHashCode()
{
return Name.ToLowerInvariant().GetHashCode();
}
}
And you can add items to the listbox using two textboxes (name and value). When you click on an item in the listbox, the textboxes get filled with the name and the value of the ParameterItem. I have the following code to change the contents of the selected ParameterItem in the listbox:
private void btnSaveParameter_Click(object sender, EventArgs e)
{
ParameterItem currentParameter = new ParameterItem(textParameterName.Text,
textParameterValue.Text);
// If we already have the parameter set then edit it.
if (lstbxSetParameters.Items.Contains(currentParameter))
{
((ParameterItem)lstbxSetParameters.SelectedItem).Value = currentParameter.Value;
lstbxSetParameters.;
}
// If it's not set yet then add it to the listbox.
else
{
lstbxSetParameters.Items.Add(currentParameter);
textParameterName.Text = String.Empty;
textParameterValue.Text = String.Empty;
}
}
The problem is, even though I can change the contents of the selected ParameterItem, in the listbox, it still looks like it is not changed.
For example I have a parameter in the list box:
TestParameter = 10
And I change the ParameterItem to
TestParameter = 5
but in the listbox it still looks like
TestParameter = 10
even though it's been changed.
How can I solve this problem? I think the listbox item should call the ToString() method of the ParameterItem again and refresh itself but how?
Or is there a better way to add key value pairs in the listbox?
You can change the selected item by remove it and insert it again.
// If we already have the parameter set then edit it.
if (lstbxSetParameters.Items.Contains(currentParameter))
{
var newItem = new ParameterItem((lstbxSetParameters.SelectedItem as ParameterItem).Name, currentParameter.Value);
var index = lstbxSetParameters.SelectedIndex;
lstbxSetParameters.Items.RemoveAt(index);
lstbxSetParameters.Items.Insert(index, newItem);
lstbxSetParameters.SelectedIndex = index;
}
My solution:
string[] nList = new string[lb.Items.Count];
nList = lb.Items.OfType<string>().ToArray();
nList[lb.SelectedIndex] = newValue;
lb.Items.Clear();
lb.Items.AddRange(nList);
This way instead of changing the selected item (with a lot of problem) I reloaded the Listbox with the item changed in the array.

C# grid DataSource polymorphism

I have a grid, and I'm setting the DataSource to a List<IListItem>. What I want is to have the list bind to the underlying type, and disply those properties, rather than the properties defined in IListItem. So:
public interface IListItem
{
string Id;
string Name;
}
public class User : IListItem
{
string Id { get; set; };
string Name { get; set; };
string UserSpecificField { get; set; };
}
public class Location : IListItem
{
string Id { get; set; };
string Name { get; set; };
string LocationSpecificField { get; set; };
}
How do I bind to a grid so that if my List<IListItem> contains users I will see the user-specific field? Edit: Note that any given list I want to bind to the Datagrid will be comprised of a single underlying type.
Data-binding to lists follows the following strategy:
does the data-source implement IListSource? if so, goto 2 with the result of GetList()
does the data-source implement IList? if not, throw an error; list expected
does the data-source implement ITypedList? if so use this for metadata (exit)
does the data-source have a non-object indexer, public Foo this[int index] (for some Foo)? if so, use typeof(Foo) for metadata
is there anything in the list? if so, use the first item (list[0]) for metadata
no metadata available
List<IListItem> falls into "4" above, since it has a typed indexer of type IListItem - and so it will get the metadata via TypeDescriptor.GetProperties(typeof(IListItem)).
So now, you have three options:
write a TypeDescriptionProvider that returns the properties for IListItem - I'm not sure this is feasible since you can't possibly know what the concrete type is given just IListItem
use the correctly typed list (List<User> etc) - simply as a simple way of getting an IList with a non-object indexer
write an ITypedList wrapper (lots of work)
use something like ArrayList (i.e. no public non-object indexer) - very hacky!
My preference is for using the correct type of List<>... here's an AutoCast method that does this for you without having to know the types (with sample usage);
Note that this only works for homogeneous data (i.e. all the objects are the same), and it requires at least one object in the list to infer the type...
// infers the correct list type from the contents
static IList AutoCast(this IList list) {
if (list == null) throw new ArgumentNullException("list");
if (list.Count == 0) throw new InvalidOperationException(
"Cannot AutoCast an empty list");
Type type = list[0].GetType();
IList result = (IList) Activator.CreateInstance(typeof(List<>)
.MakeGenericType(type), list.Count);
foreach (object obj in list) result.Add(obj);
return result;
}
// usage
[STAThread]
static void Main() {
Application.EnableVisualStyles();
List<IListItem> data = new List<IListItem> {
new User { Id = "1", Name = "abc", UserSpecificField = "def"},
new User { Id = "2", Name = "ghi", UserSpecificField = "jkl"},
};
ShowData(data, "Before change - no UserSpecifiedField");
ShowData(data.AutoCast(), "After change - has UserSpecifiedField");
}
static void ShowData(object dataSource, string caption) {
Application.Run(new Form {
Text = caption,
Controls = {
new DataGridView {
Dock = DockStyle.Fill,
DataSource = dataSource,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false
}
}
});
}
As long as you know for sure that the members of the List<IListItem> are all going to be of the same derived type, then here's how to do it, with the "Works on my machine" seal of approval.
First, download BindingListView, which will let you bind generic lists to your DataGridViews.
For this example, I just made a simple form with a DataGridView and randomly either called code to load a list of Users or Locations in Form1_Load().
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Equin.ApplicationFramework;
namespace DGVTest
{
public interface IListItem
{
string Id { get; }
string Name { get; }
}
public class User : IListItem
{
public string UserSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Location : IListItem
{
public string LocationSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void InitColumns(bool useUsers)
{
if (dataGridView1.ColumnCount > 0)
{
return;
}
DataGridViewCellStyle gridViewCellStyle = new DataGridViewCellStyle();
DataGridViewTextBoxColumn IDColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn NameColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn DerivedSpecificColumn = new DataGridViewTextBoxColumn();
IDColumn.DataPropertyName = "ID";
IDColumn.HeaderText = "ID";
IDColumn.Name = "IDColumn";
NameColumn.DataPropertyName = "Name";
NameColumn.HeaderText = "Name";
NameColumn.Name = "NameColumn";
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField";
DerivedSpecificColumn.HeaderText = "Derived Specific";
DerivedSpecificColumn.Name = "DerivedSpecificColumn";
dataGridView1.Columns.AddRange(
new DataGridViewColumn[]
{
IDColumn,
NameColumn,
DerivedSpecificColumn
});
gridViewCellStyle.SelectionBackColor = Color.LightGray;
gridViewCellStyle.SelectionForeColor = Color.Black;
dataGridView1.RowsDefaultCellStyle = gridViewCellStyle;
}
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = false;
Random rand = new Random();
bool useUsers = rand.Next(0, 2) == 0;
InitColumns(useUsers);
if(useUsers)
{
TestUsers();
}
else
{
TestLocations();
}
}
private void TestUsers()
{
List<IListItem> items =
new List<IListItem>
{
new User {Id = "1", Name = "User1", UserSpecificField = "Test User 1"},
new User {Id = "2", Name = "User2", UserSpecificField = "Test User 2"},
new User {Id = "3", Name = "User3", UserSpecificField = "Test User 3"},
new User {Id = "4", Name = "User4", UserSpecificField = "Test User 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
}
private void TestLocations()
{
List<IListItem> items =
new List<IListItem>
{
new Location {Id = "1", Name = "Location1", LocationSpecificField = "Test Location 1"},
new Location {Id = "2", Name = "Location2", LocationSpecificField = "Test Location 2"},
new Location {Id = "3", Name = "Location3", LocationSpecificField = "Test Location 3"},
new Location {Id = "4", Name = "Location4", LocationSpecificField = "Test Location 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
}
}
}
The important lines of code are these:
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField"; // obviously need to bind to the derived field
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
dataGridView1.AutoGenerateColumns = false; // Be specific about which columns to show
and the most important are these:
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
If all items in the list are known to be of the certain derived type, just call ConvertAll to cast them to that type.
You'll need to use a Grid template column for this. Inside the template field you'll need to check what the type of the object is and then get the correct property - I recommend creating a method in your code-behind which takes care of this. Thus:
<asp:TemplateField HeaderText="PolymorphicField">
<ItemTemplate>
<%#GetUserSpecificProperty(Container.DataItem)%>
</ItemTemplate>
</asp:TemplateField>
In your code-behind:
protected string GetUserSpecificProperty(IListItem obj) {
if (obj is User) {
return ((User) obj).UserSpecificField
} else if (obj is Location) {
return ((Location obj).LocationSpecificField;
} else {
return "";
}
}
I tried projections, and I tried using Convert.ChangeType to get a list of the underlying type, but the DataGrid wouldn't display the fields. I finally settled on creating static methods in each type to return the headers, instance methods to return the display fields (as a list of string) and put them together into a DataTable, and then bind to that. Reasonably clean, and it maintains the separation I wanted between the data types and the display.
Here's the code I use to create the table:
DataTable GetConflictTable()
{
Type type = _conflictEnumerator.Current[0].GetType();
List<string> headers = null;
foreach (var mi in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
if (mi.Name == "GetHeaders")
{
headers = mi.Invoke(null, null) as List<string>;
break;
}
}
var table = new DataTable();
if (headers != null)
{
foreach (var h in headers)
{
table.Columns.Add(h);
}
foreach (var c in _conflictEnumerator.Current)
{
table.Rows.Add(c.GetFieldsForDisplay());
}
}
return table;
}
When you use autogeneratecolumns it doesnt automatically do this for you?
My suggestion would be to dynamically create the columns in the grid for the extra properties and create either a function in IListItem that gives a list of available columns - or use object inspection to identify the columns available for the type.
The GUI would then be much more generic, and you would not have as much UI control over the extra columns - but they would be dynamic.
Non-checked/compiled 'psuedo code';
public interface IListItem
{
IList<string> ExtraProperties;
... your old code.
}
public class User : IListItem
{
.. your old code
public IList<string> ExtraProperties { return new List { "UserSpecificField" } }
}
and in form loading
foreach(string columnName in firstListItem.ExtraProperties)
{
dataGridView.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = columnName, HeaderText = columnName );
}
If you are willing to use a ListView based solution, the data-bindable version ObjectListView will let you do this. It reads the exposed properties of the DataSource and creates columns to show each property. You can combine it with BindingListView.
It also looks nicer than a grid :)

Categories