ComboBox DataBinding causes ArgumentException - c#

I several objects of class:
class Person
{
public string Name { get; set; }
public string Sex { get; set; }
public int Age { get; set; }
public override string ToString()
{
return Name + "; " + Sex + "; " + Age;
}
}
and a class that has a property of type Person:
class Cl
{
public Person Person { get; set; }
}
And I want to bind Cl.Person to combobox. When I try to do it like this:
Cl cl = new cl();
comboBox.DataSource = new List<Person> {new Person{Name = "1"}, new Person{Name = "2"}};
comboBox.DataBindings.Add("Item", cl, "Person");
I get an ArgumentException. How should I modify my binding to get the correct program behavior?
Thanks in advance!

Bind to "SelectedItem":
var persons = new List<Person> { new Person() { Name = "John Doe"}, new Person() { Name = "Scott Tiger" }};
comboBox1.DisplayMember = "Name";
comboBox1.DataSource = persons;
comboBox1.DataBindings.Add("SelectedItem", cl, "Person");

For simple databinding, this will work
cl.Person = new Person{ Name="Harold" };
comboBox.DataBindings.Add("Text",cl.Person, "Name");
But I don't think that's what you want. I think you want to bind to a list of items, then select one. To bind to a list of items and show the Name property, try this:
comboBox.DataSource = new List<Person> {new Person{Name = "1"}, new Person{Name = "2"}};
comboBox.DisplayMember = "Name";
Provided your Person class overrides Equals() such that, say, a Person is equal to another if they have the same Name, then binding to the SelectedItem property will work like so:
Cl cl = new Cl {Person = new Person {Name="2" }};
comboBox.DataBindings.Add("SelectedItem", cl, "Person");
If you can't override Equals(), then you just have to make sure you're referencing a Person instance from the DataSource list, so the code below works for your specific code:
Cl cl = new Cl();
cl.Person = ((List<Person>)comboBox1.DataSource)[1];
comboBox.DataBindings.Add("SelectedItem", cl, "Person");

Try
comboBox.DataBindings.Add("Text", cl, "Person.Name");
instead
You need to tell the combobox which property on it you want to bind to which property on your object (it's Text property, in my example which will show the Name property of the selected person).
*EDIT:* Actually scrap that, I was getting confused. You almost had it, only combobox doesn;t have a property called item, you want SelectedItem instead, like this:
Cl cl = new cl();
comboBox.DataSource = new List<Person> {new Person{Name = "1"}, new Person{Name = "2"}};
comboBox.DataBindings.Add("SelectedItem", cl, "Person");

if you are using Enums may be u have a class of enums you can a combo box like this
Specify the combo box datasourse eg
comboBoxname.DataSource = Enum.GetValues(typeof(your enum));
Now lets bind the combox box since we have the data source
comboBoxname.DataBindings.Add("SelectedItem",
object,
"field of type enum in the object");

Related

How to select certain properties in a list

Currently, I am working on a requirement that uses a List and retrieves the data. But there's change that I need to incorporate which I'm not getting.
Now I'm having a list of elements and on user selection, I would receive the column names(randomly) which are part of that list.
How do I implement because in run-time I don't know how many column names I would receive.
Below is the sample code:
class Program
{
static void Main(string[] args)
{
var students = new List<Student>()
{
new Student { Name = "Vijay", Age = 21 , Gender = "M" , Address = "Address_1"},
new Student { Name = "Ajay", Age = 26 , Gender = "M" , Address = "Address_2"},
new Student { Name = "John", Age = 21 , Gender = "M" , Address = "Address_3"},
new Student { Name = "Rob", Age = 42 , Gender = "M" , Address = "Address_4"},
new Student { Name = "Kohli", Age = 32 , Gender = "M" , Address = "Address_5"}
};
var result = students.Select(x => x.Name);
// I get result with Name column. That's fine.
}
}
public class Student
{
public string Name;
public int Age;
public string Gender;
public string Address;
}
The above code is just a sample of what I'm trying to explain. Since students.Select(x => x.Name); would give me the result which has a List of Names. But some times I might receive comma separated column names
like Name, Gender or Age, Gender or Name, Address or Address or all column names.
Expected results:
// When user sends Name & Gender as selected columns
Name, Gender
vijay 26
Ajay 21
Rob 42
...
// When user sends only Address as selected columns
Address
Address_1
Address_2
....
Can anyone please help me how do I build runtime select using the available list.
Thanks in advance
Dynamic Linq may help you.
using System.Linq.Dynamic.Core;
var values = new List<string> { "Age", "Gender" };
var columns = "new {" + string.Join(",", values) + "}";
var result = students.AsQueryable().Select(columns);
foreach (var x in result)
Console.WriteLine(x);
Output:
{ Age = 21, Gender = M }
{ Age = 26, Gender = M }
{ Age = 21, Gender = M }
{ Age = 42, Gender = M }
{ Age = 32, Gender = M }
Be aware that you are getting an anonymous type. And how you will work with it further is still a question.
I actually did this some time back for a web API. Basically, you can use Newtonsoft.Json to serialize your type while keeping only the fields\properties you want.
First, you'll need a custom ContractResolver:
public class SelectiveContractResolver : DefaultContractResolver
{
private readonly HashSet<string> properties;
public SelectiveContractResolver(HashSet<string> selectedProperties)
{
properties = selectedProperties;
}
protected override JsonProperty CreateProperty
(
MemberInfo member,
MemberSerialization memberSerialization
)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = _ =>
properties
.Contains(
$"{member.ReflectedType.FullName.ToString()}.{member.Name}"
.ToLower());
return property;
}
}
Here I specifically form my strings like "[full path goes here].Student.Name" or "[same here].Student.Gender" and check if property was selected for. This could be useful if you are going to serialize complex types and will need to feed selections for different types to your Resolver.
Second thing to do, and this is purely for optimization, create a factory that caches your resolvers, so you don't have to rebuild them every time:
public class SelectiveContractResolverFactory
{
private ConcurrentDictionary<HashSet<string>, IContractResolver>
cachedResolvers;
public SelectiveContractResolverFactory()
{
cachedResolvers =
new ConcurrentDictionary<HashSet<string>, IContractResolver>
(HashSet<string>.CreateSetComparer());
}
public IContractResolver GetResolver
(
IEnumerable<string> selectedProperties
)
{
var selectedSet = selectedProperties.ToHashSet();
if (cachedResolvers.ContainsKey(selectedSet))
return cachedResolvers[selectedSet];
var newResolver = new SelectiveContractResolver(selectedSet);
cachedResolvers[selectedSet] = newResolver;
return newResolver;
}
}
Finally, lets apply that thing to an object:
var student = new Student { Name = "Vijay", Age = 21 , Gender = "M" , Address = "Address_1"};
var selectedProps = new List<string> {"Student.Name","Student.Age"};
// Obviously, this should be injected as a singleton:
var resolverFactory = new SelectiveContractResolverFactory();
var resolver = resolverFactory.GetResolver(selectedProps);
var selectedResult = JsonConvert.SerializeObject
(
student,
Formatting.None,
new JsonSerializerSettings
{
ContractResolver = resolver
}
);
And just like that, you get a neat json object with the properties you wanted.
But, if you do have to get an actual C# object out of this, then you can deserialize into an ExpandoObject using ExpandoObjectConverter:
dynamic filteredStudent = JsonConvert
.DeserializeObject<ExpandoObject>
(
selectedResult,
new ExpandoObjectConverter()
);
Though, the way the question is phrased, I feel like you are going to be returning this data from an API of sorts, so json would already be perfect for this.

Bind Object Model to UI in xamarin forms

I have an object model class ( desarlise json to object model ) , i want to use binding to attach the object model to the UI using xamarin forms.
P.S not natively nor XAML but through code.
Have search the internet but its getting more and more confusing
Any help will be anxiously helpful
Thanks
Your question is a bit unclear, but here's an example of data binding for Xamarin.Forms in code.
//INPC implementation removed in this sample
public class PersonViewModel
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
var person = new PersonViewModel {
FirstName = "John",
LastName = "Doe",
};
var first = new Label ();
first.SetBinding (Label.TextProperty, "FirstName");
var label = new Label ();
label.SetBinding (Label.TextProperty, "LastName");
var personView = new StackLayout {
Orientation = StackOrientation.Horizontal,
Children = {
first,
last
}
};
personView.BindingContext = person;

Items on ListBox show up as a class name

The problem is as follows: I connected the ListBox with a list of elements of some custom class (List<Person> persons = new List<Person>()) using DataSource property. Of course ValueMember and DisplayMember are both assigned to appropriate properties of this class. When I first load data, everything looks ok. However, when I click on some item (i.e. 7th position, counting from 1) and then rebuild the list AND the number of elements is LESS than 7, as a result I can't see the proper texts on the list. Instead, every item shows up as a class name, preceded by the namespace.
In other words, instead of the list:
John Doe
Jane Doe
Somebody Else
I see this:
MyNamespace.Person
MyNamespace.Person
MyNamespace.Person
It looks like it depends on last SelectedIndex. If there is no longer an item with that index (there are less items), the problem occurs.
I've tried different combinations of reassigning ValueMember and DisplayMember, as well as assigning null to the DataSource property of the list and reassign the list to this property, even tried to assign -1 to SelectedIndex before unbinding, but none of them helped.
[Edit]
I was asked to show some code. I'll paste the relevant fragments:
1. Class Person:
public class Person
{
private int id;
private string name;
public Person(int m_id, string m_name)
{
id = m_id;
name = m_name;
}
public int Id
{
get
{
return id;
}
}
public string Name
{
get
{
return name;
}
}
}`
2. In a constructor of the form:
List<Person> persons = new List<Person>();
3. In a method fired on buton1 click:
listBox1.DataSource = null; // this is optional. Commenting this line out doesn't help
persons.Add(new Person(1, "John Doe"));
persons.Add(new Person(2, "Jane Doe"));
persons.Add(new Person(3, "Somebody Else"));
listBox1.ValueMember = "Id";
listBox1.DisplayMember = "Name";
listBox1.DataSource = persons;
4. In a method fired on buton2 click:
listBox1.DataSource = null; // this is optional. Commenting this line out doesn't help
persons.Add(new Person(1, "Person One"));
persons.Add(new Person(2, "Person Two"));
listBox1.ValueMember = "Id";
listBox1.DisplayMember = "Name";
listBox1.DataSource = persons;
When I click button1, the listbox is filled and everything works fine. When I select last item ("Somebode Else") and then clisk button2, the listbox shows 2 identical items: "MyNamespace.Person".
[Edit 2 - complete code of form]
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace MyNamespace
{
public partial class Form1 : Form
{
private List<Person> persons = new List<Person>();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
persons.Clear();
persons.Add(new Person(1, "John Doe"));
persons.Add(new Person(2, "Jane Doe"));
persons.Add(new Person(1, "Somebody Else"));
listBox1.DataSource = null;
listBox1.ValueMember = "Id";
listBox1.DisplayMember = "Name";
listBox1.DataSource = persons;
}
private void button2_Click(object sender, EventArgs e)
{
persons.Clear();
persons.Add(new Person(1, "Person One"));
persons.Add(new Person(2, "Person Two"));
listBox1.DataSource = null;
listBox1.ValueMember = "Id";
listBox1.DisplayMember = "Name";
listBox1.DataSource = persons;
}
}
class Person
{
private int id;
private string name;
public Person(int m_id, string m_name)
{
id = m_id;
name = m_name;
}
public int Id
{
get
{
return id;
}
}
public string Name
{
get
{
return name;
}
}
public string ToString()
{
return id + ". " + name;
}
}
}
Steps to reproduce the problem:
Run the form
Click button1
Select last position on the list ("Somebody Else")
Click button2
If you select "John Doe" or "Jane Doe" on the list, everything works fine. It seems to "crash" when the selected index is not valid after rebuilding the list. I guess it's some bug.
When one sets the DataSource to null it clears the DisplayMember value. So to resolve, set it after you set a new DataSource and the problem disappears.
listBox1.DataSource = null; // this is optional. Commenting this line out doesn't help
persons.Add(new Person(1, "John Doe"));
persons.Add(new Person(2, "Jane Doe"));
persons.Add(new Person(3, "Somebody Else"));
listBox1.DataSource = persons;
listBox1.DisplayMember = "Name";
Otherwise in the Person class override the ToString method to ensure that the proper property will be shown if DataMember is empty:
public class Person
{
private int id;
private string name;
public Person(int m_id, string m_name)
{
id = m_id;
name = m_name;
}
public int Id
{
get
{
return id;
}
}
public string Name
{
get
{
return name;
}
}
public override string ToString()
{
return name;
}
}
With this whenever you set the listbox datasource to a List<Person> the listbox will automatically use the ToString method as the display. Using the selecteditem is simply a matter of casting it as Person, (Person)listBox1.SelectedItem.
Try setting the "ValueMember" and "DisplayMember" properties only once before any datasource binding. Can you set them in the designer? Then always before changing the datasource, run ListBox's "ClearSelected()"-method to clear any selections. Then unbind the datasource, edit the list, then set the edited list as datasource. Seems that this behavior is some kind of bug. Try if this helps.

Bind values from a list array to listbox

Could any body give a short example for binding a value from list array to listbox in c#.net
It depends on how your list array is.
Let's start from an easy sample:
List<string> listToBind = new List<string> { "AA", "BB", "CC" };
this.listBox1.DataSource = listToBind;
Here we have a list of strings, that will be shown as items in the listbox.
Otherwise, if your list items are more complex (e.g. custom classes) you can do in this way:
Having for example, MyClass defined as follows:
public class MyClass
{
public int Id { get; set; }
public string Text { get; set; }
public MyClass(int id, string text)
{
this.Id = id;
this.Text = text;
}
}
here's the binding part:
List<MyClass> listToBind = new List<MyClass> { new MyClass(1, "One"), new MyClass(2, "Two") };
this.listBox1.DisplayMember = "Text";
this.listBox1.ValueMember = "Id"; // optional depending on your needs
this.listBox1.DataSource = listToBind;
And you will get a list box showing only the text of your items.
Setting also ValueMember to a specific Property of your class will make listBox1.SelectedValue containing the selected Id value instead of the whole class instance.
N.B.
Letting DisplayMember unset, you will get the ToString() result of your list entries as display text of your ListBox items.

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