views:

1315

answers:

4

I have a UserControl with multiple fields that I would like to have bound to a BindingSource. I would also like the UserControl to expose some BindingSource property so that it can be dropped on a Form and be bound to the BindingSource on the form. Is there an easy way to do this? I realize that I can rebind all of the controls of the UserControl in its BindSource setter. But this seems wrong. Is there some BindingSource Proxy that will let me link the BindingSource in the user control to the BindingSource in the form?

A: 

I've been struggling with the same problem all day. There must be a proper way to do this conceptually simple thing but I can't figure it out.

It's is pointless to write the bindings again in a separate DataBind method because you on't really use the designer that way. Seems stupid.

I thought of passing the BindingSource in the constructor so that it is in place before InitializeComponent but then again you need to mess-up with the designer generated code to bypass the local bindingSource field etc. Not so effective either.

I'm sure there is a simple way to do this. I'll keep trying.

rousso
+4  A: 

As per your question, I can hardly get what you intend to do. Thus I will try my best to provide you with, I hope, interesting information on that matter.

First, let's consider the following UserControl in a Customer management software project.

public partial class CustomerManagementUserControl : UserControl {
    public CustomerManagementUserControl() {
        InitializeComponent();
        _customerBindingSource = new BindingSource();
    }
    public IList<ICustomer> DataSource {
        set {
            _customerBindingSource.DataSource = value;
        }
    }
    private BindingSource _customerBindingSource;
}

Second, let's consider the following Form which should be your Customer management form.

public partial class CustomerManagementForm : Form {
    public CustomerManagementForm() {
        InitializeComponent();
        _customerUserControl = new CustomerManagementUserControl();
        _customerUserControl.Name = @"customerUserControl";
    }
    private void CustomerManagementForm_Load(object sender, EventArgs e) {
        // CustomersFacade is simply a static class providing customer management features and requirements.
        // Indeed, the GetCustomers() method shall return an IList<ICustomer>.
        // The IList type and typed IList<T> are both intended to be bindable as a DataSource for DataBinding.
        _customerUserControl.DataSource = CustomersFacade.GetCustomers();
        this.Controls.Add(_customerUserControl);
    }
    private CustomerManagementUserControl _customerUserControl;
}

If you're expecting to use CustomerManagementUserControl.DataSource property from within the Property window, please consider adding the following on top of your property definition.

[System.ComponentModel.DesignTimeVisible(true), System.ComponentModel.DesignerCategory("CustomerUserControl"), System.ComponentModel.Description("Sets the CustomerUserControl DataSource property")]

This is one way of doing what I guess you might want to do. On the other hand, if what you wish to do is to get the as most abstract as possible by setting a different type of object as your UserControl.BindingSource.DataSource property, then you will have to write a method which could detect the type of the object passed, then binding the properties accordingly. A nice way you could go, perhaps, is by Reflection, if you're comfortable working with it. In any possible way you may imagine working with such polymorphism features, you will have to write yourself an interface that all of your bindable objects will have to implement. This way, you will avoid unknown property names, and when will come the time to bind your UserControl's controls, you will be able to bind the correct property to the correct control and so forth.

Let's try the following:

public interface IEntity {
    double Id { get; set; }
    string Number { get; set; }
    string Firstname { get; set; }
    string Surname { get; set; }
    long PhoneNumber { get; set; }
}
public interface ICustomer : IEntity {
}
public interface ISupplier : IEntity {
    string Term { get; set; }
}
public sealed Customer : ICustomer {
    public Customer() {
    }
    public double Id { get; set; }
    public string Number { get; set; }
    public string Firstname { get; set; }
    public string Surname { get; set; }
    public long PhoneNumber { get; set; }    
}
public sealed Supplier : ISupplier {
    public Supplier() {
    }
    public double Id { get; set; }
    public string Number { get; set; }
    public string Firstname { get; set; }
    public string Surname { get; set; }
    public long PhoneNumber { get; set; }    
    public string Term { get; set; }
}

Considering the above code, you could use the DataSource property of your UserControl to bind with an IEntity, so your property could like like this.

[System.ComponentModel.DesignTimeVisible(true), System.ComponentModel.DesignerCategory("CustomerUserControl"), System.ComponentModel.Description("Sets the CustomerUserControl DataSource property")]
public IList<IEntity> DataSource {
    set {
        _customerBindingSource.DataSource = value;
    }
}

That said, if you wish to push even further, you could just expose your UserControl's controls DataBindings properties in order to set them on design-time. Considering this, you will want to expose your BindingSource as a public property either so that you may set it on design-time too, then choose your DataMember from this BindinSource.

I hope this helps you both a little or at least, give you some tracks for further searchings.

Will Marcouiller
A: 

If you wanted to do this all automatically you could look for the binding source from the parent form in the load event of your user control or something like that...

Dim components As Reflection.FieldInfo = typ.GetField("components", Reflection.BindingFlags.DeclaredOnly Or Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic)

Dim lstBindingSources As New List(Of BindingSource)
For Each obj As Object In components.Components
   Dim bindSource As BindingSource = TryCast(obj, BindingSource)
   If bindSource IsNot Nothing Then
      lstBindingSources.Add(bindSource)
   End If
Next
If lstBindingSources.Count = 1 Then
   MyBindingSource.DataSource = lstBindingSources(0).DataSource
End If
MikeG
A: 

I was just reading the answer of Will the Thrill and MikeG above and I have the following comments:

There are two things that one probably can do and the suggested solutions refer to both of them. a) You can change the bindingSource of the UserControl (in various ways) and b) you can change the DataSource of the BindingSource of the UserControl (in various ways).

The problem is that neither of these work. And that is where the probem is. The problem is that after changing the BindingSource.Datasource of the UserControl leaves you with TWO binding sources which then you will have to synchronize all the time to maintain Currency in both the form and the UserControl (keep track of the current record). If instead you change the BindingSource of the UserControl (by assigning the BindingSource of the form to it) you then have to re-create all the bindings to the new binding source. There are ways to solve both problems. But there is no elegant way to solve them. In both cases you need to write extra code in each UserControl that takes care of either finding all the Bindings and re-creating them using the newly assigned binding source, or synchronising the two binding sources so that the current record in the UserControl is related to the current record in the Form.

At the moment I decided that I must move on so I chose to have two binding sources (one in the form and one in the usercontrol) and just synchronising them. But I still can't believe that there is no simple way of doing this. There must be some trick that makes this possible without having to scrap the designer. Scrapping the designer ofcourse makes this very easy. But then you need to manually keep track of all the required bindings which requires much more coding and especially coding prone to errors since you have to use strings to refer to properties in bindings.

rousso
The BindingContext[BindingSource] is different from a form to another, but as for the BindingSource.DataSource itself, will it not be kept synchronized by the DataBinding features?
Will Marcouiller