Link to home
Start Free TrialLog in
Avatar of Richard Payne
Richard PayneFlag for Bulgaria

asked on

C#, transfer or bind deserialized data into listbox within windowform

I have

 List<Configuration> config;     // This is List<T> 
config = DeserializeFromFile2<List<Configuration>>(filePath);

Open in new window


The code below worked in transferring to listbox
          
           listBox1.Items.Add(config[0].CompanyID);
           listBox1.Items.Add(config[0].CompanyString);
           listBox1.Items.Add(config[0].Test1);
            this.Show();

Open in new window


When I tried
listBox1.DataSource = ConfigData;

Open in new window


(1) It does not display data, just object name of some sort.... why?
(2) and how to make binding work correctly to list down data, so I can edit/modify.
(3) How to use addrange() for given List<Configuration> type, most of example I seen are int or string type.

For reference below is the class

    public class Configuration
    {
        public uint CompanyID { get; set; }
        public string CompanyString { get; set; }
        public uint Test1 { get; set; }
        public uint Test2 { get; set; }
        public uint Test3 { get; set; }

        public Configuration()
        {
            // Leave blank, do not install default data here.
        }

        public void ConfigurationDefault()
        {
            CompanyID = 10;
            CompanyString = "ZMDI";
            Test1 = 9678;
            Test2 = 34453;
            Test3 = 12981;
        }

    }

Open in new window

Avatar of zephyr_hex (Megan)
zephyr_hex (Megan)
Flag of United States of America image

Can you please show how you assign ConfigData ?
Avatar of it_saige
It's because the Datasource property expects an enumerable type (in other words something that can be enumerated over).  So you could select the config list as your datasource; e.g. - (Using my example configurations other question):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml.Serialization;

namespace EE_Q28741219
{
	public partial class Form1 : Form
	{
		List<ConfigurationX> configurations;

		public Form1()
		{
			InitializeComponent();
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			if (File.Exists("Configurations.xml"))
			{
				configurations = DeserializeFromFile<List<ConfigurationX>>("Configurations.xml");
			}
			else
			{
				if (configurations == null)
				{
					configurations = new List<ConfigurationX>();
					for (int i = 0; i < 10; i++)
						configurations.Add(new ConfigurationX() { CompanyID = (uint)i, CompanyString = string.Format("ZMDI{0}", i) });
				}
				SerializeToFile<List<ConfigurationX>>("Configurations.xml");
			}

			if (configurations != null)
				listBox1.DataSource = configurations;
		}

		T DeserializeFromFile<T>(string fileName) where T : class
		{
			if (typeof(T).IsSerializable)
			{
				using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
				{
					XmlSerializer serializer = new XmlSerializer(typeof(T));
					return (T)serializer.Deserialize(stream);
				}
			}
			return default(T);
		}

		void SerializeToFile<T>(string fileName) where T : class
		{
			if (typeof(T).IsSerializable)
			{
				using (var stream = new FileStream(fileName, FileMode.CreateNew, FileAccess.ReadWrite))
				{
					XmlSerializer serializer = new XmlSerializer(typeof(T));
					serializer.Serialize(stream, configurations);
				}
			}
		}
	}

	[Serializable]
	public class ConfigurationX
	{
		public uint CompanyID { get; set; }
		public string CompanyString { get; set; }
		public uint Test1 { get; set; }
		public uint Test2 { get; set; }
		public uint Test3 { get; set; }

		public ConfigurationX()
		{
			// Leave blank, do not install default data here.
		}

		public void ConfigurationXDefault()
		{
			CompanyID = 10;
			CompanyString = "ZMDI";
			Test1 = 9678;
			Test2 = 34453;
			Test3 = 12981;
		}

		public override string ToString()
		{
			return string.Format("ID: {0}; String: {1}; Test1: {2}; Test2: {3}; Test4: {4}", CompanyID, CompanyString, Test1, Test2, Test3);
		}
	}
}

Open in new window

Produces the following output -User generated image-saige-
If in your case, ConfigData is a list of some sort, then you might be seeing something like:User generated imageIf this is the case, then you are experiencing this because the ListBox will display the one of two things.  1.  The string representation of the object (meaning if you do not override the ToString() method, then it uses the default object.ToString() method which simply returns the name of the complex type).
2.  The set DisplayMember; e.g. -
listBox1.DisplayMember = "CompanyString"

Open in new window

Which would produce the following output -User generated image-saige-
Avatar of Richard Payne

ASKER

Thank for prompt solution Saige, One more query before I closing this.

Is there alternative way to implement data transfer into listbox without using override tostring()?, for example for each loop and add or addrange?

Once again thanks for solution, it appreciated.
You mean besides using the DisplayMember property?

There are many different ways to populate the listbox, but any method that does not set the datasource to the collection of objects or assign the object as the item in the listbox collection, only adds a layer of complexity to the data access.

This is in part because you now have to match the selection from the listbox to it's parent object in the collection.

If this is confusing, I will be posting a few examples shortly.  These exampls will use the CompanyString property to display the items, and makes the assumption that the CompanyString is unique.

-saige-
Shall I accept Solution now or wait bit longer for your reply. Thx.
The Add method:
private void Form1_Load(object sender, EventArgs e)
{
	if (File.Exists("Configurations.xml"))
	{
		configurations = DeserializeFromFile<List<ConfigurationX>>("Configurations.xml");
	}
	else
	{
		if (configurations == null)
		{
			configurations = new List<ConfigurationX>();
			for (int i = 0; i < 10; i++)
				configurations.Add(new ConfigurationX() { CompanyID = (uint)i, CompanyString = string.Format("ZMDI{0}", i) });
		}
		SerializeToFile<List<ConfigurationX>>("Configurations.xml");
	}

	if (configurations != null)
	{
		for (int i = 0; i < configurations.Count; i++)
			listBox1.Items.Add(configurations[i].CompanyString);

		// You could also use a for each loop
		//foreach (var config in configurations)
		//     listBox1.Items.Add(config.CompanyString);
	}
}

Open in new window

Now if you wanted to retrieve the item, you would have to retrieve the selected item and match it to the item in the configurations collection; e.g. -
private void OnSelectedIndexChanged(object sender, EventArgs e)
{
	if (sender is ListBox)
	{
		ListBox lb = sender as ListBox;
		for (int i = 0; i < configurations.Count; i++)
		{
			if (configurations[i].CompanyString.Equals(lb.SelectedItem))
				MessageBox.Show(string.Format("You have selected - CompanyID: {0}", configurations[i].CompanyID));
		}

		// You could also use a For Each loop
		//foreach (var config in configurations)
		//{
		//     if (config.CompanyString.Equals(lb.SelectedItem))
		//          MessageBox.Show(string.Format("You have selected - CompanyID: {0}", configurations[i].CompanyID));
		//}

		// Or even linq
		//var selected = (from config in configurations where config.CompanyString.Equals(lb.SelectedItem) select config).FirstOrDefault();
		//if (selected != null)
		//     MessageBox.Show(string.Format("You have selected - CompanyID: {0}", configurations[i].CompanyID));
	}
}

Open in new window

Which produces the following output -User generated image-saige-
The AddRange method.  The AddRange method is similar to the add method except you are adding your entire selection at once.  This means you need either an array or an ObjectCollection, in the case of the ListBox.ObjectCollection.AddRange method definitions.  So without further ado -
private void Form1_Load(object sender, EventArgs e)
{
	if (File.Exists("Configurations.xml"))
	{
		configurations = DeserializeFromFile<List<ConfigurationX>>("Configurations.xml");
	}
	else
	{
		if (configurations == null)
		{
			configurations = new List<ConfigurationX>();
			for (int i = 0; i < 10; i++)
				configurations.Add(new ConfigurationX() { CompanyID = (uint)i, CompanyString = string.Format("ZMDI{0}", i) });
		}
		SerializeToFile<List<ConfigurationX>>("Configurations.xml");
	}

	if (configurations != null)
	{
		// Seed array for the addrange method.
		object[] items = new object[configurations.Count];

		for (int i = 0; i < configurations.Count; i++)
			items[i] = configurations[i].CompanyString;

		// You could also use a for each loop
		//int index = 0;
		//foreach (var config in configurations)
		//{
		//     items[index] = config.CompanyString;
		//     index++;
		//}

		// You could also use a linq statement
		// items = (from config in configurations select config.CompanyString).ToArray();

		listBox1.Items.AddRange(items);
	}
}

Open in new window

But using an array is so archaic compared to using an enumerable [or a collection], i.e. - A list:
private void Form1_Load(object sender, EventArgs e)
{
	if (File.Exists("Configurations.xml"))
	{
		configurations = DeserializeFromFile<List<ConfigurationX>>("Configurations.xml");
	}
	else
	{
		if (configurations == null)
		{
			configurations = new List<ConfigurationX>();
			for (int i = 0; i < 10; i++)
				configurations.Add(new ConfigurationX() { CompanyID = (uint)i, CompanyString = string.Format("ZMDI{0}", i) });
		}
		SerializeToFile<List<ConfigurationX>>("Configurations.xml");
	}

	if (configurations != null)
	{
		// Intermediary collection for the addrange method.
		List<object> items = new List<object>();

		for (int i = 0; i < configurations.Count; i++)
			items.Add(configurations[i].CompanyString);

		// You could also use a for each loop
		//foreach (var config in configurations)
		//     items.Add(config.CompanyString);

		// You could also use a linq statement
		// items = (from config in configurations select config.CompanyString);

		listBox1.Items.AddRange(items.ToArray());
	}
}

Open in new window

But by far the simplest AddRange methodolgy recognizes when an intermediary is not needed and disposes of it all together:
private void Form1_Load(object sender, EventArgs e)
{
	if (File.Exists("Configurations.xml"))
	{
		configurations = DeserializeFromFile<List<ConfigurationX>>("Configurations.xml");
	}
	else
	{
		if (configurations == null)
		{
			configurations = new List<ConfigurationX>();
			for (int i = 0; i < 10; i++)
				configurations.Add(new ConfigurationX() { CompanyID = (uint)i, CompanyString = string.Format("ZMDI{0}", i) });
		}
		SerializeToFile<List<ConfigurationX>>("Configurations.xml");
	}

	if (configurations != null)
	{
		// We don't need to use an intermediary.
		listBox1.Items.AddRange((from config in configurations select config.CompanyString).ToArray());
	}
}

Open in new window

The output and retrieval is the same as above.

-saige-
But the most complex to implement but easiest to retrieve the correct data from is the use of a specialized Helper class.  Essentially this class is used in order to bind a displayed item with it's associated complex data object without needing to match it.  And it goes like this.

A simple, generic, ListItemData class implementation -
class ListItemData<T>
{
	public T ValueMember { get; private set; }
	public string DisplayMember { get; private set; }

	private ListItemData() { ;}

	public ListItemData(string displayMember, T valueMember)
	{
		DisplayMember = displayMember;
		ValueMember = valueMember;
	}

	public override string ToString()
	{
		return DisplayMember;
	}
}

Open in new window

Now we can use any method that adds the object in the ListBox as a ListItemData object:
private void Form1_Load(object sender, EventArgs e)
{
	if (File.Exists("Configurations.xml"))
	{
		configurations = DeserializeFromFile<List<ConfigurationX>>("Configurations.xml");
	}
	else
	{
		if (configurations == null)
		{
			configurations = new List<ConfigurationX>();
			for (int i = 0; i < 10; i++)
				configurations.Add(new ConfigurationX() { CompanyID = (uint)i, CompanyString = string.Format("ZMDI{0}", i) });
		}
		SerializeToFile<List<ConfigurationX>>("Configurations.xml");
	}

	if (configurations != null)
	{
		// **************************************************  Using the Add Methods **************************************************
		//for (int i = 0; i < configurations.Count; i++)
		//     listBox1.Items.Add(new ListItemData<ConfigurationX>(configurations[i].CompanyString, configurations[i]));

		// You could also use a for each loop
		//foreach (var config in configurations)
		//     listBox1.Items.Add(new ListItemData<ConfigurationX>(config.CompanyString, config));
		// ****************************************************************************************************************************

		// ***************************************** Using the AddRange Methods with an Array *****************************************
		// Seed array for the addrange method.
		//object[] items = new object[configurations.Count];

		//for (int i = 0; i < configurations.Count; i++)
		//     items[i] = new ListItemData<ConfigurationX>(configurations[i].CompanyString, configurations[i]);

		// You could also use a for each loop
		//int index = 0;
		//foreach (var config in configurations)
		//{
		//     items[index] = new ListItemData<ConfigurationX>(config.CompanyString, config);
		//     index++;
		//}

		// You could also use a linq statement
		// items = (from config in configurations select new ListItemData<ConfigurationX>(config.CompanyString, config)).ToArray();
		// ****************************************************************************************************************************

		// ************************************* Using the AddRange Methods with an Intermediary **************************************
		// Intermediary collection for the addrange method.
		//List<ListItemData> items = new List<ListItemData>();

		//for (int i = 0; i < configurations.Count; i++)
		//     items.Add(new ListItemData<ConfigurationX>(configurations[i].CompanyString, configurations[i]));

		// You could also use a for each loop
		//foreach (var config in configurations)
		//     items.Add(new ListItemData<ConfigurationX>(config.CompanyString, config));

		// You could also use a linq statement
		// items = (from config in configurations select new ListItemData<ConfigurationX>(config.CompanyString, config));

		//listBox1.Items.AddRange(items.ToArray());
		// ****************************************************************************************************************************

		// But again we do not need to use an intermediary
		listBox1.Items.AddRange((from config in configurations select new ListItemData<ConfigurationX>(config.CompanyString, config)).ToArray());
	}
}

Open in new window

And our selection method becomes more simplified:
private void OnSelectedIndexChanged(object sender, EventArgs e)
{
	if (sender is ListBox)
	{
		ListBox lb = sender as ListBox;
		if (lb.SelectedItem is ListItemData<ConfigurationX>)
		{
			ListItemData<ConfigurationX> lid = lb.SelectedItem as ListItemData<ConfigurationX>;
			MessageBox.Show(string.Format("You have selected - CompanyID: {0}", lid.ValueMember.CompanyID));
		}
	}
}

Open in new window

Which again produces the same output as above.

-saige-
ASKER CERTIFIED SOLUTION
Avatar of it_saige
it_saige
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you so much, fantastic tutorial approach, I took example code structure bit by bit and test it out. Wow I learn a lot there.

There so much in .net framework.
Not a problem.  Glad I was able to help out.

-saige-
For some reason, it firing the OnSelectedIndexChanged (3 times), why it happen is not clear to me

So I modify the code to disable the event handler, this is quick fix.

What you think?

            if (config != null)
            {
                listBox1.SelectedIndexChanged -= OnSelectedIndexChanged;
                listBox1.DataSource = config;
                listBox1.DisplayMember = "CompanyString";
                listBox1.SelectedIndexChanged += OnSelectedIndexChanged;
            }
There could be a few reasons why the event fires multiple times.  Probably the best solution is to add a boolean value, e.g. - isLoading; and have the OnSelectedIndexChanged event check to see if this value is false.  If it is then the OnSelectedChangedEvent fires, otherwise, you disregard the event.

Something like:
private bool _isLoading = true;

private void Form1_Load(object sender, EventArgs e)
{
	if (File.Exists("Configurations.xml"))
	{
		configurations = DeserializeFromFile<List<ConfigurationX>>("Configurations.xml");
	}
	else
	{
		if (configurations == null)
		{
			configurations = new List<ConfigurationX>();
			for (int i = 0; i < 10; i++)
				configurations.Add(new ConfigurationX() { CompanyID = (uint)i, CompanyString = string.Format("ZMDI{0}", i) });
		}
		SerializeToFile<List<ConfigurationX>>("Configurations.xml");
	}

	if (configurations != null)
		listBox1.DataSource = configurations;
	listBox1.DisplayMember = "CompanyString";

	_isLoading = false;
}

private void OnSelectedIndexChanged(object sender, EventArgs e)
{
	if (!_isLoading && sender is ListBox)
	{
		ListBox lb = sender as ListBox;
		if (lb.SelectedItem is ConfigurationX)
		{
			ConfigurationX config = lb.SelectedItem as ConfigurationX;
			MessageBox.Show(string.Format("You have selected - CompanyID: {0}", config.CompanyID));
		}
	}
}

Open in new window