R – WinForms databind to collection property (such as count)

data-bindingnetwinforms

I want to databind to a collection property, such as Count.

in general, when I data bind I specify data member for the object property in the collection, not for the actual properties collection itself exposes.

for example, I have a list of custom objects. I show them in datagridview. but I also want to show their total count using a separate label. is there a way to do this through databinding?

I imagine that somehow I need to force PropertyManager to be used, instead of CurrentyManager?

I get the following exception. Notice that DataSource is collection and has TotalValue property.

System.ArgumentException: Cannot bind to the property or column TotalValue on the DataSource.
Parameter name: dataMember
   at System.Windows.Forms.BindToObject.CheckBinding()
   at System.Windows.Forms.Binding.SetListManager(BindingManagerBase bindingManagerBase)
   at System.Windows.Forms.ListManagerBindingsCollection.AddCore(Binding dataBinding)
   at System.Windows.Forms.BindingsCollection.Add(Binding binding)
   at System.Windows.Forms.BindingContext.UpdateBinding(BindingContext newBindingContext, Binding binding)
   at System.Windows.Forms.Binding.SetBindableComponent(IBindableComponent value)
   at System.Windows.Forms.ControlBindingsCollection.AddCore(Binding dataBinding)
   at System.Windows.Forms.BindingsCollection.Add(Binding binding)

Best Answer

If your data source is implementing the IList interface, like the BindingList does, the binding is trying to bind to a property of the elements inside the list.

Imagine you have a list of string items, then there is no property called Count for a single element.

Wrap your list to a class which is delegatingt the Count property without implementing the IList interface and fire a PropertyChanged event when the BindingList fires a ListChanged event.

class CountWrapper : INotifyPropertyChanged
{
    private readonly IBindingList bindingList;

    public CountWrapper(IBindingList bindingList)
    {
        this.bindingList = bindingList;

        bindingList.ListChanged += BindingList_ListChanged;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public int Count
    {
        get { return bindingList.Count; }
    }

    private void BindingList_ListChanged(object sender, ListChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(string.Empty));
    }
}

Now you can bind the Count property like so:

myLabel.DataBindings.Add("Text", new CountWrapper(myObjectList), "Count");

This is possible for other properties also, just use a specific wrapper for your specific class. If your class implements INotifyPropertyChanged for its own properties, you only have to listen to that event and relay it also.

class MyListWrapper : INotifyPropertyChanged
{
    private readonly MyList bindingList;

    public MyListWrapper(MyList bindingList)
    {
        this.bindingList = bindingList;

        bindingList.ListChanged += BindingList_ListChanged;
        bindingList.PropertyChanged+= BindingList_PropertyChanged;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public int Count
    {
        get { return bindingList.Count; }
    }

    public int TotalCount
    {
        get { return bindingList.TotalCount; }
    }

    public string ReadWriteProperty
    {
        get { return bindingList.ReadWriteProperty; }
        set { bindingList.ReadWriteProperty = value; }
    }


    private void OnPropertyChanged(PropertyChangedEventArgs ea)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, ea);
    }

    private void BindingList_ListChanged(object sender, ListChangedEventArgs e)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(string.Empty));
    }

    private void BindingList_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        OnPropertyChanged(e);
    }
}