Rebind BindingSource to another object

Rebind BindingSource to another object

Old forum URL: forums.lhotka.net/forums/t/4722.aspx


Kyle77 posted on Monday, April 21, 2008

Note: I posted this same question at the following location, but have also posted here because it references something from Rocky's book that doesn't seem to be working correctly for us:

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3214089&SiteID=1&mode=1

I've been running into a problem with data binding on a .NET Windows Forms application.  I've found a couple work-arounds, but neither one really feels right to me -- it just seems like there should be a better way.  So I guess I'm asking what is the best practice approach to solving the scenario I'll describe below?

First off, I have a business object, Customer, with several customer-related properties and an additional property to return a BindingList of another type of business object, we'll just call that property "MySubList".

Next, I have a UserControl, call it CustomerCtrl, that will display some of the various customer-related properties in Label controls and will display the BindingList property, "MySubList", in a DataGridView.  I plan to accomplish this via data binding using a BindingSource intermediary.  When I created the UserControl in the VS designer, I placed the Labels, DataGridView, and CustomerBindingSource on the control, so those controls are declared in the designer-generated code.

I intend to use the CustomerCtrl on the MainForm for the application.  It will always be visible.  There will be menu options to create a new customer, delete the current customer (displayed in the CustomerCtrl), and open a different customer (to replace the currently display customer in the CustomerCtrl).  From the MainForm or application logic, I need to be able to tell the CustomerCtrl to now use a different Customer business object for its data binding.  It is at that point where I run into problems.

The basics of what needs to happen for data binding the CustomerCtrl to a new Customer is something like the following:


private Customer _Customer = null;

public CustomerCtrl()
{
   InitializeComponent();
}

public void SetCustomer(Customer customer)
{
   _Customer = customer;
   CustomerBindingSource.DataSource = _Customer;
   MySubListGrid.DataSource = CustomerBindingSource;
   MySubListGrid.DataMember = "MySubList";
}

public void SetupDataBindings()
{
   // Setup the data bindings to the controls (most omitted for brevity)
   CustomerNameLbl.DataBindings.Add("Text", .....);
}

 

There are problems with that approach.  If you try to pass in a null customer, you get an exception, I think due to the DataMember not being found.  And even if you didn't get that, you can't set the data bindings on the controls multiple times.

You could somehow only call the SetupDataBindings() method once initially (or as necessary).  That would prevent the control data bindings from being setup multiple times.  But what about passing in a null customer?  This will happen, for instance, when the delete customer option is selected.  As part of our control data binding setup, we can establish a null string to be displayed, such as "No Customer", which is desirable.

I have seen a few pieces of advice / techniques that we have tried -- some that have worked, and some that haven't.  In Rockford Lhotka's "Expert C# 2005 Business Objects" (second edition), page 492, he says that you rebind the BindingSource object to the new business object by adding these lines of code immediately after your operation that results in a new business object:


this.rolesBindingSource.DataSource = null;
this.rolesBindingSource.DataSource = _roles;

He continues to say that you can't simply set the DataSource property to a new object.  You must first set the property to null, and then to the new object.  If you don't do this, the BindingSource will not bind to the new object and will silently remain bound to the old object, resulting in hard-to-debug problems in your application.

I tried this approach and it is essentially the same as when you pass in a null customer.  The point at which the CustomerBindingSource.DataSource is set to null, an exception is thrown, complaining about the DataMember not being found.  Is it necessary to somehow clear that MySubListGrid.DataMember prior to resetting the CustomerBindingSource.DataSource?

Also, I have tried removing all of the control data bindings prior to resetting the CustomerBindingSource and then setting them up again.  That seems ridiculous that one should have to go to all of that work.  It seems like there should be a much more elegant way of handling this, and I just don't know what it is.

I have also tried protecting against allowing a null customer.  For instance, if a null customer is passed into the SetCustomer() method, then I create a new Customer and set that to the CustomerBindingSource.DataSource.  Unfortunately, then I lose the control data binding null string text, such as the "No Customer".  Also, we really don't want a new Customer in that situation.


Any help / advice would be very much appreciated.

FrodeNilsen replied on Friday, April 25, 2008

First of all, I think you'd get more replies if  you condense your post a bit... To much to read! ;-)

The jist I'm getting is that you're unable to set the datasource to null - this should be no problem. Here is the code where you try to do that:

   _Customer = customer;
   CustomerBindingSource.DataSource = _Customer;
   MySubListGrid.DataSource = CustomerBindingSource;
   MySubListGrid.DataMember = "MySubList";

I have two issues with that code:
1. I think you should use the designer to set the DataSource of MySubListGrid. The datasource of the GRID is never changing, it's the BINDINGSOURCE's datasource that changes. This way, line 3 and 4 is uneccessary.
2.  I would create a separate bindingsource for the sublist. That way, the DataMember property would be set on the new bindingsource in stead of on the grid directly. If you still want to initialize the bindingsources by code, your code above could be changed to something like this:

   _Customer = customer;
   CustomerBindingSource.DataSource = _Customer;
   MySubListBindingSource.DataSource = CustomerBindingSource;
   MySubListBindingSource.DataMember = "MySubList";
   MySubListGrid.DataSource = MySubListBindingSource;

..that said, if you use the GUI designer as suggested in (1), you will only need line 1,2 and 5 in the code above.

I just tested a similar case of the one you describe and I had no problems setting the datasource to null at runtime. I can upload the test project if you're interested.

Kyle77 replied on Friday, May 09, 2008

Thanks for your reply.  Sorry it took me so long to get back here...  And sorry about the lengthy post.  I guess I didn't want to put too little information, but probably did turn a few people away with so much to read.  :)  I'm going to be gone this weekend, but I'll give your suggestions a try next week and let you know how it goes.

In your second code listing, where you assign

_Customer = customer;

what happens if customer is null?  It seems like you're saying that by adding in an additional intermediary BindingSource between the CustomerBindingSource and the MySubListGrid, that is what makes it possible to 'use' a null customer.  Am I interpreting that correctly?  If so, could you elaborate on why that is the case?  I must be missing a key point...

Also, if you had an additional control on the form, like a TextBox bound to a property on the Customer object, such as FirstName, what would be the result of customer being null?  I think that should be fine, and you can even set the text to be displayed in the control if the bound object is null when you establish the binding relationship.

Thanks,
Kyle

jobyspencer replied on Saturday, May 10, 2008

I am also working on this issue of setting a BindingSource.DataSource = null.  My "simple" implementation throws an ArgumentException with message "Cannot bind to the property or column FirstName on the DataSource. Parameter name: dataMember" when I set the BindingSource.DataSource = null.  My small test app has a form with 2 textboxes (FirstNameText & LastNameText) that are bound to a Customer object with the FirstName & LastName properties.  There's a "Set Customer" button that creates a Customer object and sets it as the DataSource to a BindingSource that the textboxes are bound to.  There's also a "Null Customer" button that simply sets the BindingSource.DataSource = null.  See the code below.

        private void Form1_Load(object sender, EventArgs e)
        {
            /* Can not set data bindings here with the BindingSource.DataSource = null. You get an ArgumentException with msg:
            * "Cannot bind to the property or column FirstName on the DataSource. Parameter name: dataMember" when
             * you try to add the binding between the FirstNameText textbox and the FirstName property of the Customer object. */
            //this.SetupBindings();  // Throws ArgumentException.
        }

        private void SetupBindings()
        {
            this.FirstNameText.DataBindings.Add("Text", this.CustomerBindingSource, "FirstName", true, DataSourceUpdateMode.OnValidation);
            this.LastNameText.DataBindings.Add("Text", this.CustomerBindingSource, "LastName", true, DataSourceUpdateMode.OnValidation);
        }

        private void SetCustomerBtn_Click(object sender, EventArgs e)
        {
            Customer c = new Customer { FirstName = "Joby", LastName = "Spencer" };
            this.CustomerBindingSource.DataSource = c;
            this.SetupBindings();  // Setting-up bindings is ok here after DataSource is non-null.
        }

        private void NullCustomerBtn_Click(object sender, EventArgs e)
        {
            /* Setting the BindingSource.DataSource = null AFTER the bindings to the textboxes have been set-up throws an ArgumentException with msg:
             * "Cannot bind to the property or column FirstName on the DataSource. Parameter name: dataMember" */
            this.CustomerBindingSource.DataSource = null;  // Throws ArgumentException.
        }

Ultimately, the reason I want to set the DataSource to be a null Customer is that I need to clear-out all of the Customer's info on the form. ie: Have the textboxes be blank or empty when no customer is "opened" or "selected" by the user.

I have tried to work around this ArgumentException with a null customer by suspending the binding on the BindingSource by calling:
BindingSource.SuspendBinding();
BindingSource.RaiseListChangedEvents = false;
... and then resuming binding when a non-null customer is set as the DataSource.  Any advice or guidance on a better way to do this would be appreciated.  Thank you.  Joby

ajj3085 replied on Monday, May 12, 2008

Well, it's trying to bind and the object who's property it's trying to read is null.  So that can't work.

You can set the datasource to null if you turn off change events, but you'll need to set it again before you resume binding.

Alternately, you could set the DataSource = typeof( MyBusinessObject ).  That will also get you around the exception, but be sure to give it an actual object if you plan on letting the user continue editing a business object.

jobyspencer replied on Monday, May 12, 2008

Thanks for your response.  I was hoping the BindingSource could handle a null data source and would automatically not try to run the binding, but that is not the case.  I am using the "suspending binding" strategy when the data source is null, and then resuming binding when the data source is reset to non-null.  Thanks again.

Copyright (c) Marimer LLC