DataBinding to a UserControl with several nested, Bindable UserControls

S

Steve K.

I've created a couple of different UserControls to minimize duplicate
control and provide a consistent look for my data entry application. A
couple of examples are:

SimpleAddress
- [TextBox Controls] Address1, Address2, City, Zipcode
- [ComboBox Controls] State

InsurancePolicyDetails
- [TextBox Controls] CarrierName, PolicyNum, GroupNum, Relationship
- [MaskedTextBox Controls] CarrierPhone, CarrierFax
- [SimpleAddress Controls] CarrierAddress

You' notice that in the InsurancePolicyDetails UC there is a nested
SimpleAdress UC. This is but one example of the way I've structured my
various User Controls.

I'm struggling with how to handle the DataBinding for these User Controls.
I have the following Requirements and Concerns:
- Requirements -
1) SimpleAddress can be used in a Form or top level UserControl by iteself,
it won't always be nested in another custom UserControl and as such needs to
be bindable on it's own.
2) I need to set the State ComboBox's DataSource property (A Static IList
of states)
3) I need to bind a single entity (IE: Patient, Physician, etc) to the
SelectedValue of the SimpleAddress.State control
4) The CarrierName TextBox is a special, extended TextBox (
http://www.pmddirect.com/sklett/PMDTextBoxReview.html ) and needs to be
bound to it's DataSource via a UserControl level Bindable Property. What
I'm saying is that if I use the InsurancePolicyDetails UserControl on a form
I need to bind DIRECTLY to a property of InsurancePolicyDetails that
gets/sets the Text property of the InsuranceCarrierName TextBox.
5) Ability to DataBind to the SimpleAddress nested in the
InsurancePolicyDetails

- Problems I'm Having -
1) If I use Bindable Properties of my UserControls to expose the contained
Controls to DataBinding I could have many levels of "delegation" (I'm sure
there is a better term). For example, if I want to Bind an InsuranceCarrier
entity to the CarrierPhone control in my SimpleAddress which is contained in
my InsurancePolicyDetails Control I would need to add the following code:
<code>
// SimpleAddress.cs
[Bindable(BindableSupport.Yes)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string Phone
{
get { return maskedTextBox_Phone.Text; }
set { maskedTextBox_Phone.Text = value; }
}

// InsurancePolicyDetails.cs
[Bindable(BindableSupport.Yes)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string CarrierPhone
{
get { return simpleAddress1.Phone; }
set { simpleAddress1.Phone = value; }
}
</code>

Consider that I might some day want to nest InsurancePolicyDetails into a
"PatientFinancialData" UserControl and will need yet ANOTHER layer:

<code>
// PatientFinancialData.cs
[Bindable(BindableSupport.Yes)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string PatientPrimaryCarrierPhone
{
get { return insurancePolicyDetails_Primary.CarrierPhone; }
set { insurancePolicyDetails_Primary.CarrierPhone = value; }
}
</code>

So I would flag this whole mess as "Bad Design" - shame on me! :0) But
what are my alternatives?
a) Include BindingSource objects IN the UserControls adding properties to
container Controls to expose the BindingSource.
b) Bubble the whole damn UserControl out to the world via properties and
let the concerned object (Form, UserControl, etc) setup the BindingSource to
the exposes in Controls. That needs an explanation:
<code>
// InsurancePolicyDetails.cs
public string SimpleAddress
{
get { return simpleAddress1; }
set { simpleAddress1 = value; }
}

// PatientFinancialData.cs
public string InsurancePolicyDetails
{
get { return insurancePolicyDetails_Primary; }
set { insurancePolicyDetails_Primary = value; }
}

// My Form or UserControl that is displaying these controls.cs
PatientFinancialData pfd = new PatientFinancialData();
pfd.InsurancePolicyDetails.DataSource = _patient.PrimaryPolicy;
pfd.InsurancePolicyDetails.SimpleAddress.DataSource =
_patient.PrimaryPolicy.InsuranceCarrier.Address;

// YUCK! Barf!
</code>

I don't like this either... I can't say why yet, but it doesn't seem
*correct*.

So I could go on and on. What this all boils down to is that I really don't
know the correct way to accomplish what I'm need to. I've created these
UserControls (great) and they look nice (fine) but now that it has come time
to wire them up I've got a real mess on my hands.

I'd be very curious to hear how you pros think I should design this. I
don't mean I expect you to give me a full design or anything, just some
guidance or possibly suggestion that I haven't thought of. I'd really like
to wrap up this project this weekend and this darn
UserControl/DataBinding/Properties/DataSource issue has me blocked.

Hopefully I included enough information for things to make sense.

Any feedback/help greatly appreciated!

-Steve
 
M

Morten Wennevik [C# MVP]

Hi Steve,

I have good experience using a base usercontrol setting up the databinding
as well as keeping track of on which level in a BindingSource we are at.
This way, a usercontrol can be put at any level provided it can be bound to a
specific entity. It does not need to know of anything but this entity. The
parent control is responsible for pointing out where this entity is and all
the usercontrol does is bind to the lowest level property. For this to work
well, the business model needs to be closely tied to the usercontrols with a
separate class per usercontrol.

I've set up some sample code demonstrating this, in this sample a Winform
contains a textbox and a InsurancePolicyDetail control which in turn contains
an SimpleAddress control.

public partial class BaseControl : UserControl
{
public BaseControl()
{
InitializeComponent();
}

string _path;
BindingSource _source;

public virtual void SetDatabinding(BindingSource source, string path)
{
_path = path;
_source = source;

if (!_path.EndsWith("."))
_path += ".";
}

protected void BindProperty(string property, Control control, string
sourceProperty)
{
Binding binding = new Binding(property, _source, _path +
sourceProperty);
control.DataBindings.Add(binding);
}

protected void BindCombo(Control control, string sourceProperty,
string selectedProperty)
{
Binding binding = new Binding("DataSource", _source, _path +
sourceProperty);
control.DataBindings.Add(binding);
Binding binding2 = new Binding("SelectedItem", _source, _path +
selectedProperty);
control.DataBindings.Add(binding2);
}
}

public partial class SimpleAddress : BaseControl
{
public SimpleAddress()
{
InitializeComponent();
}

public override void SetDatabinding(BindingSource source, string path)
{
base.SetDatabinding(source, path);

BindProperty("Text", textBox1, "Address1");
BindProperty("Text", textBox2, "Address2");
BindProperty("Text", textBox3, "City");
BindProperty("Text", textBox4, "Zipcode");
BindCombo(comboBox1, "States", "State");
}
}

public partial class InsurancePolicyDetails : BaseControl
{
public InsurancePolicyDetails()
{
InitializeComponent();
}

public override void SetDatabinding(BindingSource source, string path)
{
base.SetDatabinding(source, path);

BindProperty("Text", textBox1, "CarrierName");
BindProperty("Text", textBox2, "PolicyNum");
BindProperty("Text", textBox3, "GroupNum");

simpleAddress1.SetDatabinding(source, path + ".CarrierAddress");
}
}

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

protected override void OnLoad(EventArgs e)
{
Carrier c = new Carrier();
c.CarrierID = "1";
c.CarrierInfo = new CarrierInfo();
c.CarrierInfo.CarrierName = "CName";
c.CarrierInfo.GroupNum = "2";
c.CarrierInfo.PolicyNum = "3";
c.CarrierInfo.CarrierAddress = new CarrierAddress();
c.CarrierInfo.CarrierAddress.Address1 = "Adre1";
c.CarrierInfo.CarrierAddress.Address2 = "Adre2";
c.CarrierInfo.CarrierAddress.City = "C";
c.CarrierInfo.CarrierAddress.Zipcode = "Zip";
c.CarrierInfo.CarrierAddress.States = new List<string>();
c.CarrierInfo.CarrierAddress.States.Add("Nebraska");
c.CarrierInfo.CarrierAddress.States.Add("Washington");

BindingSource source = new BindingSource(c, "");
textBox1.DataBindings.Add("Text", source, "CarrierID");

insurancePolicyDetails1.SetDatabinding(source, ".CarrierInfo");
}
}

public class Carrier
{
private string _id;

public string CarrierID
{
get { return _id; }
set { _id = value; }
}

private CarrierInfo _info;

public CarrierInfo CarrierInfo
{
get { return _info; }
set { _info = value; }
}
}

public class CarrierInfo
{
private string _name;

public string CarrierName
{
get { return _name; }
set { _name = value; }
}
private string _policy;

public string PolicyNum
{
get { return _policy; }
set { _policy = value; }
}
private string _group;

public string GroupNum
{
get { return _group; }
set { _group = value; }
}
private CarrierAddress _address;

public CarrierAddress CarrierAddress
{
get { return _address; }
set { _address = value; }
}
}

public class CarrierAddress
{
private string _adr1;

public string Address1
{
get { return _adr1; }
set { _adr1 = value; }
}
private string _adr2;

public string Address2
{
get { return _adr2; }
set { _adr2 = value; }
}
private string _city;

public string City
{
get { return _city; }
set { _city = value; }
}
private string _zip;

public string Zipcode
{
get { return _zip; }
set { _zip = value; }
}

private string _state;

public string State
{
get { return _state; }
set { _state = value; }
}

private IList<string> _states;

public IList<string> States
{
get { return _states; }
set { _states = value; }
}
}

--
Happy Coding!
Morten Wennevik [C# MVP]


Steve K. said:
I've created a couple of different UserControls to minimize duplicate
control and provide a consistent look for my data entry application. A
couple of examples are:

SimpleAddress
- [TextBox Controls] Address1, Address2, City, Zipcode
- [ComboBox Controls] State

InsurancePolicyDetails
- [TextBox Controls] CarrierName, PolicyNum, GroupNum, Relationship
- [MaskedTextBox Controls] CarrierPhone, CarrierFax
- [SimpleAddress Controls] CarrierAddress

You' notice that in the InsurancePolicyDetails UC there is a nested
SimpleAdress UC. This is but one example of the way I've structured my
various User Controls.

I'm struggling with how to handle the DataBinding for these User Controls.
I have the following Requirements and Concerns:
- Requirements -
1) SimpleAddress can be used in a Form or top level UserControl by iteself,
it won't always be nested in another custom UserControl and as such needs to
be bindable on it's own.
2) I need to set the State ComboBox's DataSource property (A Static IList
of states)
3) I need to bind a single entity (IE: Patient, Physician, etc) to the
SelectedValue of the SimpleAddress.State control
4) The CarrierName TextBox is a special, extended TextBox (
http://www.pmddirect.com/sklett/PMDTextBoxReview.html ) and needs to be
bound to it's DataSource via a UserControl level Bindable Property. What
I'm saying is that if I use the InsurancePolicyDetails UserControl on a form
I need to bind DIRECTLY to a property of InsurancePolicyDetails that
gets/sets the Text property of the InsuranceCarrierName TextBox.
5) Ability to DataBind to the SimpleAddress nested in the
InsurancePolicyDetails

- Problems I'm Having -
1) If I use Bindable Properties of my UserControls to expose the contained
Controls to DataBinding I could have many levels of "delegation" (I'm sure
there is a better term). For example, if I want to Bind an InsuranceCarrier
entity to the CarrierPhone control in my SimpleAddress which is contained in
my InsurancePolicyDetails Control I would need to add the following code:
<code>
// SimpleAddress.cs
[Bindable(BindableSupport.Yes)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string Phone
{
get { return maskedTextBox_Phone.Text; }
set { maskedTextBox_Phone.Text = value; }
}

// InsurancePolicyDetails.cs
[Bindable(BindableSupport.Yes)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string CarrierPhone
{
get { return simpleAddress1.Phone; }
set { simpleAddress1.Phone = value; }
}
</code>

Consider that I might some day want to nest InsurancePolicyDetails into a
"PatientFinancialData" UserControl and will need yet ANOTHER layer:

<code>
// PatientFinancialData.cs
[Bindable(BindableSupport.Yes)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string PatientPrimaryCarrierPhone
{
get { return insurancePolicyDetails_Primary.CarrierPhone; }
set { insurancePolicyDetails_Primary.CarrierPhone = value; }
}
</code>

So I would flag this whole mess as "Bad Design" - shame on me! :0) But
what are my alternatives?
a) Include BindingSource objects IN the UserControls adding properties to
container Controls to expose the BindingSource.
b) Bubble the whole damn UserControl out to the world via properties and
let the concerned object (Form, UserControl, etc) setup the BindingSource to
the exposes in Controls. That needs an explanation:
<code>
// InsurancePolicyDetails.cs
public string SimpleAddress
{
get { return simpleAddress1; }
set { simpleAddress1 = value; }
}

// PatientFinancialData.cs
public string InsurancePolicyDetails
{
get { return insurancePolicyDetails_Primary; }
set { insurancePolicyDetails_Primary = value; }
}

// My Form or UserControl that is displaying these controls.cs
PatientFinancialData pfd = new PatientFinancialData();
pfd.InsurancePolicyDetails.DataSource = _patient.PrimaryPolicy;
pfd.InsurancePolicyDetails.SimpleAddress.DataSource =
_patient.PrimaryPolicy.InsuranceCarrier.Address;

// YUCK! Barf!
</code>

I don't like this either... I can't say why yet, but it doesn't seem
*correct*.

So I could go on and on. What this all boils down to is that I really don't
know the correct way to accomplish what I'm need to. I've created these
UserControls (great) and they look nice (fine) but now that it has come time
to wire them up I've got a real mess on my hands.

I'd be very curious to hear how you pros think I should design this. I
don't mean I expect you to give me a full design or anything, just some
guidance or possibly suggestion that I haven't thought of. I'd really like
to wrap up this project this weekend and this darn
UserControl/DataBinding/Properties/DataSource issue has me blocked.

Hopefully I included enough information for things to make sense.

Any feedback/help greatly appreciated!

-Steve
 
S

sklett

Hi Morten,

I didnt notice your response until just now! I'm heading home in a bit and
will review it when I'm there. Thanks for the example, I'm excited to check
it out.
I didn't want you to think I was ignoring your substantial reply...

-Steve

Morten Wennevik said:
Hi Steve,

I have good experience using a base usercontrol setting up the databinding
as well as keeping track of on which level in a BindingSource we are at.
This way, a usercontrol can be put at any level provided it can be bound
to a
specific entity. It does not need to know of anything but this entity.
The
parent control is responsible for pointing out where this entity is and
all
the usercontrol does is bind to the lowest level property. For this to
work
well, the business model needs to be closely tied to the usercontrols with
a
separate class per usercontrol.

I've set up some sample code demonstrating this, in this sample a Winform
contains a textbox and a InsurancePolicyDetail control which in turn
contains
an SimpleAddress control.

public partial class BaseControl : UserControl
{
public BaseControl()
{
InitializeComponent();
}

string _path;
BindingSource _source;

public virtual void SetDatabinding(BindingSource source, string
path)
{
_path = path;
_source = source;

if (!_path.EndsWith("."))
_path += ".";
}

protected void BindProperty(string property, Control control,
string
sourceProperty)
{
Binding binding = new Binding(property, _source, _path +
sourceProperty);
control.DataBindings.Add(binding);
}

protected void BindCombo(Control control, string sourceProperty,
string selectedProperty)
{
Binding binding = new Binding("DataSource", _source, _path +
sourceProperty);
control.DataBindings.Add(binding);
Binding binding2 = new Binding("SelectedItem", _source, _path +
selectedProperty);
control.DataBindings.Add(binding2);
}
}

public partial class SimpleAddress : BaseControl
{
public SimpleAddress()
{
InitializeComponent();
}

public override void SetDatabinding(BindingSource source, string
path)
{
base.SetDatabinding(source, path);

BindProperty("Text", textBox1, "Address1");
BindProperty("Text", textBox2, "Address2");
BindProperty("Text", textBox3, "City");
BindProperty("Text", textBox4, "Zipcode");
BindCombo(comboBox1, "States", "State");
}
}

public partial class InsurancePolicyDetails : BaseControl
{
public InsurancePolicyDetails()
{
InitializeComponent();
}

public override void SetDatabinding(BindingSource source, string
path)
{
base.SetDatabinding(source, path);

BindProperty("Text", textBox1, "CarrierName");
BindProperty("Text", textBox2, "PolicyNum");
BindProperty("Text", textBox3, "GroupNum");

simpleAddress1.SetDatabinding(source, path +
".CarrierAddress");
}
}

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

protected override void OnLoad(EventArgs e)
{
Carrier c = new Carrier();
c.CarrierID = "1";
c.CarrierInfo = new CarrierInfo();
c.CarrierInfo.CarrierName = "CName";
c.CarrierInfo.GroupNum = "2";
c.CarrierInfo.PolicyNum = "3";
c.CarrierInfo.CarrierAddress = new CarrierAddress();
c.CarrierInfo.CarrierAddress.Address1 = "Adre1";
c.CarrierInfo.CarrierAddress.Address2 = "Adre2";
c.CarrierInfo.CarrierAddress.City = "C";
c.CarrierInfo.CarrierAddress.Zipcode = "Zip";
c.CarrierInfo.CarrierAddress.States = new List<string>();
c.CarrierInfo.CarrierAddress.States.Add("Nebraska");
c.CarrierInfo.CarrierAddress.States.Add("Washington");

BindingSource source = new BindingSource(c, "");
textBox1.DataBindings.Add("Text", source, "CarrierID");

insurancePolicyDetails1.SetDatabinding(source, ".CarrierInfo");
}
}

public class Carrier
{
private string _id;

public string CarrierID
{
get { return _id; }
set { _id = value; }
}

private CarrierInfo _info;

public CarrierInfo CarrierInfo
{
get { return _info; }
set { _info = value; }
}
}

public class CarrierInfo
{
private string _name;

public string CarrierName
{
get { return _name; }
set { _name = value; }
}
private string _policy;

public string PolicyNum
{
get { return _policy; }
set { _policy = value; }
}
private string _group;

public string GroupNum
{
get { return _group; }
set { _group = value; }
}
private CarrierAddress _address;

public CarrierAddress CarrierAddress
{
get { return _address; }
set { _address = value; }
}
}

public class CarrierAddress
{
private string _adr1;

public string Address1
{
get { return _adr1; }
set { _adr1 = value; }
}
private string _adr2;

public string Address2
{
get { return _adr2; }
set { _adr2 = value; }
}
private string _city;

public string City
{
get { return _city; }
set { _city = value; }
}
private string _zip;

public string Zipcode
{
get { return _zip; }
set { _zip = value; }
}

private string _state;

public string State
{
get { return _state; }
set { _state = value; }
}

private IList<string> _states;

public IList<string> States
{
get { return _states; }
set { _states = value; }
}
}

--
Happy Coding!
Morten Wennevik [C# MVP]


Steve K. said:
I've created a couple of different UserControls to minimize duplicate
control and provide a consistent look for my data entry application. A
couple of examples are:

SimpleAddress
- [TextBox Controls] Address1, Address2, City, Zipcode
- [ComboBox Controls] State

InsurancePolicyDetails
- [TextBox Controls] CarrierName, PolicyNum, GroupNum, Relationship
- [MaskedTextBox Controls] CarrierPhone, CarrierFax
- [SimpleAddress Controls] CarrierAddress

You' notice that in the InsurancePolicyDetails UC there is a nested
SimpleAdress UC. This is but one example of the way I've structured my
various User Controls.

I'm struggling with how to handle the DataBinding for these User
Controls.
I have the following Requirements and Concerns:
- Requirements -
1) SimpleAddress can be used in a Form or top level UserControl by
iteself,
it won't always be nested in another custom UserControl and as such needs
to
be bindable on it's own.
2) I need to set the State ComboBox's DataSource property (A Static
IList
of states)
3) I need to bind a single entity (IE: Patient, Physician, etc) to the
SelectedValue of the SimpleAddress.State control
4) The CarrierName TextBox is a special, extended TextBox (
http://www.pmddirect.com/sklett/PMDTextBoxReview.html ) and needs to be
bound to it's DataSource via a UserControl level Bindable Property. What
I'm saying is that if I use the InsurancePolicyDetails UserControl on a
form
I need to bind DIRECTLY to a property of InsurancePolicyDetails that
gets/sets the Text property of the InsuranceCarrierName TextBox.
5) Ability to DataBind to the SimpleAddress nested in the
InsurancePolicyDetails

- Problems I'm Having -
1) If I use Bindable Properties of my UserControls to expose the
contained
Controls to DataBinding I could have many levels of "delegation" (I'm
sure
there is a better term). For example, if I want to Bind an
InsuranceCarrier
entity to the CarrierPhone control in my SimpleAddress which is contained
in
my InsurancePolicyDetails Control I would need to add the following code:
<code>
// SimpleAddress.cs
[Bindable(BindableSupport.Yes)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string Phone
{
get { return maskedTextBox_Phone.Text; }
set { maskedTextBox_Phone.Text = value; }
}

// InsurancePolicyDetails.cs
[Bindable(BindableSupport.Yes)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string CarrierPhone
{
get { return simpleAddress1.Phone; }
set { simpleAddress1.Phone = value; }
}
</code>

Consider that I might some day want to nest InsurancePolicyDetails into a
"PatientFinancialData" UserControl and will need yet ANOTHER layer:

<code>
// PatientFinancialData.cs
[Bindable(BindableSupport.Yes)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public string PatientPrimaryCarrierPhone
{
get { return insurancePolicyDetails_Primary.CarrierPhone; }
set { insurancePolicyDetails_Primary.CarrierPhone = value; }
}
</code>

So I would flag this whole mess as "Bad Design" - shame on me! :0) But
what are my alternatives?
a) Include BindingSource objects IN the UserControls adding properties
to
container Controls to expose the BindingSource.
b) Bubble the whole damn UserControl out to the world via properties
and
let the concerned object (Form, UserControl, etc) setup the BindingSource
to
the exposes in Controls. That needs an explanation:
<code>
// InsurancePolicyDetails.cs
public string SimpleAddress
{
get { return simpleAddress1; }
set { simpleAddress1 = value; }
}

// PatientFinancialData.cs
public string InsurancePolicyDetails
{
get { return insurancePolicyDetails_Primary; }
set { insurancePolicyDetails_Primary = value; }
}

// My Form or UserControl that is displaying these controls.cs
PatientFinancialData pfd = new PatientFinancialData();
pfd.InsurancePolicyDetails.DataSource = _patient.PrimaryPolicy;
pfd.InsurancePolicyDetails.SimpleAddress.DataSource =
_patient.PrimaryPolicy.InsuranceCarrier.Address;

// YUCK! Barf!
</code>

I don't like this either... I can't say why yet, but it doesn't seem
*correct*.

So I could go on and on. What this all boils down to is that I really
don't
know the correct way to accomplish what I'm need to. I've created these
UserControls (great) and they look nice (fine) but now that it has come
time
to wire them up I've got a real mess on my hands.

I'd be very curious to hear how you pros think I should design this. I
don't mean I expect you to give me a full design or anything, just some
guidance or possibly suggestion that I haven't thought of. I'd really
like
to wrap up this project this weekend and this darn
UserControl/DataBinding/Properties/DataSource issue has me blocked.

Hopefully I included enough information for things to make sense.

Any feedback/help greatly appreciated!

-Steve
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top