Databinding in WinForms using IBindingList fails on empty list
I have a particular problem implementing my own collection which should support IBindingList
.
I have a collection class (DataCollection
) for a particular data class (DataItem
). The collection implements interfaces IBindingList
, IList
, IList<DataItem>
and the DataItem
implements INotifyPropertyChanged
(and has public properties for databinding).
When I try to bind the collection to a DataGridView
by setting the DataSource
property of the grid, it works correctly if the collection is not empty at the moment of binding. Otherwise, if the collection is empty, the grid notices when rows (i.e. DataItems
) are added or removed from the collection, but the cells stay empty. Related to the problem is that the grid fails to recognize the public members of the data class in case of AutoGenerateColumns=true
and it cannot generate the columns.
What I also tried, the bind my DataItems
using a BindingList<DataItem>
. In that case the grid works correctly even if the list is empty at the moment of setting DataSource
. On the other hand, if I use a BindingList<object>
(but the same DataItems
as content) the behavior is just as wrong as with my DataCollection
. I guess the problem is, if at the moment of binding the list is empty, the databinding fails to detect the DataItem
type properly, and it also cannot recover later, when finally items are added to the collection.
Important is that it works if the collection is not empty at binding time.
Notice, that the same error occurs when I specify the columns:
this.dataGridView.ReadOnly = true;
this.dataGridView.AutoGenerateColumns = false;
DataGridViewTextBoxColumn column;
column = new DataGridViewTextBoxColumn();
column.DataPropertyName = "Id";
column.HeaderText = "Id";
this.dataGridView.Columns.Add(column);
column = new DataGridViewTextBoxColumn();
column.DataPropertyName = "UserName";
column.HeaderText = "UserName";
this.dataGridView.Columns.Add(column);
this.dataGridView.DataSource = myList;
I also tried to return true
on the AllowNew
of my IBindingList
. That had no observable impact.
What also fails is the following:
var bindingSource = new BindingSource();
bindingSource.DataSource = myList;
this.dataGridView.DataSource = bindingSource;
The question is, how can I tell the binding mechanism to recognize my DataItems
?
(Thank you)
UPDATE 1:
I made a small test project which shows the issue:
public partial class Form1: Form {
public Form1() {
InitializeComponent();
}
class DataItem: INotifyPropertyChanged {
private int _id;
public int Id {
get {
return _id;
}
set {
if (value != _id) {
_id = value;
OnPropertyChanged("Id");
}
}
}
private string _userName;
public string UserName {
get {
return _userName;
}
set {
if (value != _userName) {
_userName = value;
OnPropertyChanged("UserName");
}
}
}
private void OnPropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
/// Make a list of type DataItem or object...
//BindingList<object> list = new BindingList<object>() {
BindingList<DataItem> list = new BindingList<DataItem>() {
//new DataItem() {
// Id = 1,
// UserName = "testuser"
//}
};
private void Form1_Load(object sender, EventArgs e) {
DataGridView dataGridView = new System.Windows.Forms.DataGridView();
dataGridView.Size = new Size(this.Width-20, this.Height-30);
dataGridView.AutoGenerateColumns = true;
DataGridViewTextBoxColumn column = new DataGridViewTextBoxColumn();
column.DataPropertyName = "Id";
column.HeaderText = "Id";
dataGridView.Columns.Add(column);
this.Controls.Add(dataGridView);
dataGridView.DataSource = list;
list.Add(
new DataItem() {
Id = 3,
UserName = "admin"
}
);
// Make some modifications on the data...
(new System.Threading.Thread( state => {
System.Threading.Thread.CurrentThread.IsBackground = true;
开发者_StackOverflow System.Threading.Thread.Sleep(2000);
this.Invoke( (Action)( () => {
list.Add(new DataItem() {
Id = 2,
UserName = "guest"
});
} ) );
System.Threading.Thread.Sleep(2000);
this.Invoke( (Action)( () => {
DataItem user = (list.First( obj => ((DataItem)obj).Id == 3 )) as DataItem;
user.UserName = "Administrator";
} ) );
})).Start();
}
}
If the type of the list is BindingList<DataItem>
it works correctly. If the type is BindingList<object>
it only works if the list is not empty when initializing the DataSource
.
Data binding will start by looking at the list items to try and get their properties, however for an empty list it will get all of its information from the Type
of the list items. The reason why data binding fails to discover any properties if you use an empty BindingList<object>
is that object
has no bindable properties.
To completely ensure that your DataCollection
class properly supports binding, even when empty, implement the ITypedList
interface. It includes the method GetItemProperties()
, which allows you to explicitly state which properties are bindable. Within this method, you can use the following to return the properties on DataItem
:
return TypeDescriptor.GetProperties(typeof(DataItem));
This way, even if the collection is empty, data binding will know which properties to show.
精彩评论