Saturday, November 20, 2010

ForceBind

There are situations in a Windows.Form where the value of a control (such as the value in the .Text property of a TextBox) does not get propagated to its databound object. This can happen as the result of a MenuItem click or a ToolBar button click because these controls do not cause the current control (the ActiveControl, which is the TextBox in my example) to lose focus, thus not forcing the control's value into its databound object. You need something that will force this for every control you have. It can get complicated.

I have a method that I have named ForceBind() in most of my controls (those that have it implement an Interface I have created for this type of behavior). If the control is a container object that implements that Interface (such as a Panel, UserControl, etc.) it will call the ForceBind() method of *it's* ActiveControl (if *that* control implements the Interface).

The actual ForceBind() method is pretty complicated in my controls and relies on the fact that I have a DataBind() method on my controls that tell it which DataTable and DataColumn I am binding that control to. However, I've done a little experimenting and I see that we can find other ways of finding those fields.

I realize that nowadays, most people use a BindingSource rather than Binding directly to a DataTable or DataView. I have not updated my classes yet to differentiate between the various binding DataSources, but in the code I will show below, it is not all that difficult to add that functionality. I leave that as an exercise for the reader …

Here's the ForceBind() method for MyTextBox (along with the code for determining the m_BoundTable, m_BoundColumn and m_BoundProperty fields):

private DataTable m_BoundTable    = (DataTable)this.DataBindings[0].DataSource;
private string m_BoundColumn = this.DataBindings[0].DataBindings[0].BindingMemberInfo.BindingMember;
private CurrencyManager oCurrency = (CurrencyManager)this.BindingContext[m_BoundTable];

public virtual void ForceBind()
{
if (this.DataBindings.Count == 0)
return;

int nRow = -1;
if (this.m_BoundTable != null && this.m_BoundColumn != "")
{
if (this.oCurrency != null)
nRow = this.oCurrency.Position;

if (nRow >= 0)
{
object oValue = this.Text;

System.RuntimeTypeHandle handle = System.Type.GetTypeHandle(this);
System.Type eType = System.Type.GetTypeFromHandle(handle);

ConvertEventArgs e = new ConvertEventArgs(oValue, eType);
this.ParseHandler(this, e);

DataRow row = this.m_BoundTable.DefaultView[nRow].Row;
if (row[this.m_BoundColumn] != e.Value)
{
try
{
row[this.m_BoundColumn] = e.Value;
}
catch (Exception)
{ }

this.OnValidated(new EventArgs());
}
}
}
}

You would call this method whenever there's the possibility of an ActiveControl not losing its focus, say in your own base class for your ToolBar Buttons, for example.

2 comments:

  1. I am using the BindingSource approach and will need to do the exercise :-) but it is an interesting one. It is illuminating to see what plumming is in fact needed for a generic method to determine by itself what type of value an input needs to be converted to for assignment to a database field. It is good to learn for a pro.

    ReplyDelete
  2. Hi! Sorry for not replying sooner ... I guess I must have missed your comment. I'm glad you enjoyed my post.

    Have you tried doing this with a BindingSource yet?

    ReplyDelete