Skip Navigation LinksHome > View Post

Databinding and Nullable types in WinForms.NET

Scenario

  • You have an Entity Type with a Nullable property.
  • You have a TextBox which is bound to that property.
  • When the user clears the text in the TextBox, you want the value of your property to be set to null.

Problem

The result of the databinding will simply not succeed when clearing a TextBox which is bound to a Nullable type.

Solution

In this article I am going to show how you can do this by using an Extender Provider.

Simply add a Component class to your project which also implements the IExtenderProvider interface

[ProvideProperty("NullableBinding", typeof(TextBox))]
public partial class NullableExtender : Component, IExtenderProvider

The ProviderProperty attribute indicates that when adding the NullableExtender component to your Form or UserControl, all the TextBoxes will have an additional property named NullableBinding. It will look something like this in the VS.NET designer:

NullableBinding Property

Now to implement some functionality for this property:

private Dictionary<Control, Boolean> _nullables = new Dictionary<Control,bool>();

/// <summary>
/// This is the get part of the extender property.
/// It is actually a method because it takes the control.
/// </summary>
/// <param name="control"></param>
[DefaultValue(false),
Category("Data")]
public bool GetNullableBinding(Control control)
{
    bool nullableBinding = false;
    _nullables.TryGetValue(control, out nullableBinding);
    return nullableBinding;
}

/// <summary>
/// This is the set part of the extender property.
/// It is actually a method because it takes the control.
/// </summary>
/// <param name="control"></param>
/// <param name="nullable"></param>
public void SetNullableBinding(Control control, bool nullable)
{
    if (_nullables.ContainsKey(control))
        _nullables[control] = nullable;
    else
        _nullables.Add(control, nullable);
    if (nullable)
    {
                // Add a parse event handler.
        control.DataBindings["Text"].Parse += new ConvertEventHandler(NullableExtender_Parse);
    }
}

/// <summary>
/// When parsing, set the value to null if the value is empty.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void NullableExtender_Parse(object sender, ConvertEventArgs e)
{
    if (e.Value.ToString().Length == 0)
    {
        e.Value = null;
    }
}

The code that actually does the trick above is adding the event handler for the databinding Parse event. The event handler simply sets the value to null if the value is an empty string, and the databinding succeeds.

The default value of the property is set to false since most properties are not of a Nullable type, and I would suggest not setting the NullableBinding to true for these properties... ;)

UPDATE: You can now download the source code.

UPDATE: If you liked this post you should also read my post about DataBinding on SelectedIndexChanged.

Tags: WinForms.NET

 
Bruusi Post By Bruusi
2:50 PM
10 May 2006

» Next Post: Article mentioned at lhotka.net
« Previous Post: Correct link

Comments are closed for this post.

Posted by Rodney Richardson @ 15 May 2006 4:01 AM
Very neat. You're not, however, unhooking the event handler when setting Nullable to false. If you set it to true, then later set it to false it will still be hooked up, and still be parsing the field.

Posted by Bruusi @ 15 May 2006 10:46 PM
Hi Rodney,

I am glad that you enjoyed the post and understand your reasoning, but you have to remember that the code is actually generated at design time, and is included in the InitializeComponent() function in the designer generated file.

That means that the "Property" is only set once and the 'SetNullableBinding' method only called once when the Form or UserControl is initialised. I have included the example code below.

// Code generated in designer.
// SetNullableBinding only called once
this.nullableExtender1.SetNullableBinding(this.nullableIntTextBox, true);
// Other code generated by designer.
this.nullableIntTextBox.Size = new System.Drawing.Size(100, 20);
...

You could of course call the 'SetNullableBinding' function on the extender control twice yourself and then experience the problem you describe, but this would not be a very likely scenario.

Posted by Li @ 18 May 2006 12:07 PM
If you assign a null value to a textbox, does it stay that way after a postback if no changes are made?
Ex (in Page_Load):
if(!this.IsPostback)
{
mytextbox.Text = null;
}
else if(mytextbox.Text == null)
{
mylabel.Text = “stayed null”;
}
else
{
mylabel.Text = “changed to something else”;
}

Posted by Josh Einstein @ 09 Jun 2006 1:03 PM
I agree with Rodney. You can't make assumptions about the caller. The example should be amended to properly unhook it.

Good example otherwise though.

Posted by Jim @ 22 Jun 2006 5:59 PM
When set the "NullableBinding on nullableExtender1" property to 'true' I get the following:
"Property value is not valid."
Details are:
Object reference not set to an instance of an object.

if I add the line:
this.nullableExtender1.SetNullableBinding(this.txtColorId, true);

to the designer file I get the following error in the designer.

NullableExtender.SetNullableBinding(Control control, Boolean nullable) in C:\WIP\VSS\VS2005\QSStudioMaster.root\QSStudioMaster\QSStudio\FrameworkUI\ FilterableList\NullableExtender.cs:line 55

The code Builds but when it runs I get "Object reference not set to an instance of an object" on line 55.

line 55 is:
control.DataBindings["Text"].Parse += new ConvertEventHandler(NullableExtender_Parse);

The value at this break is:
control.DataBindings["Text"] is 'null'

Am I missing other some other needed code to create these objects? Or is this possibly becuase I am because I am binding a usercontrol at runtime that contains the detail view of the item that seleceted in a datgridview?

Thanx for any help you may have.



Posted by Jim @ 22 Jun 2006 6:19 PM
I guess I didn't go far enough before the last post:
If I bind at design time to my business object this works fine. Is it possible for me to do this at runtime
using the utility method:

public static void BindField(Control control, string
propertyName, object dataSource, string dataMember)
{
Binding bd;
for (int index = control.DataBindings.Count - 1;
index >= 0; index--)
{
bd = control.DataBindings[index];
if (bd.PropertyName == propertyName)
control.DataBindings.Remove(bd);
}
control.DataBindings.Add(propertyName, DataSource,
DataMember);
}

Maybe I need to rearchitect how I am doing the Master-Detail view. Right now each time a row is changed I am rebinding the detail(usercontrol) to the selected row in the datagridview.

Posted by Bruusi @ 23 Jun 2006 6:07 AM
Hi Jim,

the bug you encountered is of course something I overlooked in the example code. The code should of course check to see if there is a binding available on the "Text" property of the textbox and not add the Parse event if there isn't.

Thanks for you comments and be sure to let me know how you get on!

Posted by Kris @ 02 Oct 2006 11:55 AM
Did anyone actually get this to work? I'm trying with a property of type int?, and the setter never get's called. It wasn't called before using the extender, and it isn't called now.
What am I doing wrong?

Posted by Kris @ 02 Oct 2006 12:44 PM
Ok, I found the solution (to previous post). Turns out that the formattingEnabled parameter in the binding needs to be true for data binding to work with Nullable<T> types.
The extender presented here then indeed allows setting null by erasing the field. I had to fix a few bugs though, so here's my code:

[ProvideProperty("NullableBinding", typeof(TextBox))]
class NullableExtender : Component, IExtenderProvider
{
public bool CanExtend(object extendee)
{
return extendee is TextBox;
}

private List<Control> nullables = new List<Control>();

[DefaultValue(false), Category("Data")]
public bool GetNullableBinding(Control control)
{
return nullables.Contains(control);
}

public void SetNullableBinding(Control control, bool nullable)
{
if (nullable)
{
if (!nullables.Contains(control))
{
nullables.Add(control);
control.DataBindings.CollectionChanged += new CollectionChangeEventHandler(DataBindings_CollectionChanged);
Binding binding = control.DataBindings["Text"];
if (binding != null)
{
binding.FormattingEnabled = true;
binding.Parse += new ConvertEventHandler(NullableExtender_Parse);
}
}
}
else
{
if (nullables.Contains(control))
{
nullables.Remove(control);
control.DataBindings.CollectionChanged -= new CollectionChangeEventHandler(DataBindings_CollectionChanged);
Binding binding = control.DataBindings["Text"];
if (binding != null)
{
binding.Parse -= new ConvertEventHandler(NullableExtender_Parse);
}
}
}
}

void DataBindings_CollectionChanged(object sender, CollectionChangeEventArgs e)
{
Binding binding = (sender as ControlBindingsCollection)["Text"];
if (binding != null)
{
binding.FormattingEnabled = true;
binding.Parse -= new ConvertEventHandler(NullableExtender_Parse);
binding.Parse += new ConvertEventHandler(NullableExtender_Parse);
}
}

private void NullableExtender_Parse(object sender, ConvertEventArgs e)
{
string value = e.Value as string;
if (value != null)
{
if (value.Length == 0)
{
e.Value = null;
}
}
}
}

Posted by Bruusi @ 04 Oct 2006 11:20 AM
Hi Kris,

I have not encountered your problem. Glad you found a solution to it, and thanks a lot for posting it.

B

Posted by Tudor Vlad @ 26 Nov 2007 3:13 AM
Although not clearly documented, .NET has support for data-binding to Nullable types.
When binding a datasource to a textbox, you can specify a parameter whose value will be interpreted as null.
In our case, we want String.Empty as null; So all we have to do is modify in the Designer.cs file the textbox databindings, setting the nullValue parameter like this:

this.txtBox.DataBindings.Add(new System.Windows.Forms.Binding("EditValue", this.bindingSource, "Property", true, System.Windows.Forms.DataSourceUpdateMode.OnValidation, String.Empty));

PS: in my case the Designer would modify "String.Empty" to some resource. The workaround was to put "" instead of String.Empty

Posted by John Walker @ 30 Nov 2007 11:27 PM
Tudor,

Thanks for the tip. I'm using it, although I hate going into the designer file and changing anything. I've been burned one too many times doing that, but it beats the alternative of having to do this in code.

The real question here is, why isn't this possible within the Advanced Databindings property grid for the control? I can add other values, but not an empty string. This seems so fundamental to me, it's just infuriating that it's not supported there.

Oh yeah...I'm using VS 2008, so it's still not there.

Posted by Blake @ 07 Dec 2007 11:46 AM
All of this is great for binding to the 'Text' property on controls because 'Text' always returns an empty string when there is no data in the control; it doesn't return a null reference. The 'SelectedValue' property on the ComboBox is another story, however. If you bind a Nullable type to the SelectedValue of a ComboBox, and then clear the contents of the ComboBox via the user interface, the associated Binding's Parse event is never raised! I believe (but don't know for sure) that the Parse event is not raised because the ComboBox's SelectedValue returns a null reference. Because the Parse event is never raised, we never get the opportunity convert a DBNull value to a null reference.

Anyone have any ideas on this? I've considered trapping the ComboBox's Validating event, and then manually updating the DataSource via ComboBox.DataBindings("SelectedValue").WriteValue(), but there's no guarantee that other Validating event-handlers have had their chances to validate the SelectedValue. In other words, I could be forcing an update to the DataSource before that update is validated.

Posted by Blake @ 07 Dec 2007 11:56 AM
Addendum to my post above:

I suppose I could manually perform the ComboBox.DataBindings("SelectedValue").WriteValue() in a Validated event-handler (instead of a Validating event-handler) to ensure that the value is validated before updating the data source. But even then, there's no guarantee that my event-handler will fire before any others, which means that other event-handlers can't count on the data source being updated yet.

Posted by david @ 09 Jan 2008 6:52 AM
It works for me simply adding :
txtField.DataBindings(0).NullValue = ""

where databindings(0) is the binding of text to a Bindingsource and a field of type Date.

Posted by Markus @ 07 Feb 2008 5:59 AM
Nice article, and very nice comment from david, thank you! Just remove all the Parse stuff from the code listing, and simply assign String.Empty to the NullValue of the binding. Much cleaner, and no reason to unhook an event anymore.

Posted by John Powell @ 22 Mar 2008 2:23 PM
You can achieve the same effect without code by configuring the following properties on the Binding:

FormattingEnabled = true
NullValue = string.Empty

For example:

textBox1.DataBindings.Add("Text", myClass, "MyTextProperty", true, DataSourceUpdateMode.OnPropertyChanged, string.Empty);

Posted by Kramer @ 22 Jul 2008 9:03 AM
Has anyone figured out how to bind a nullable to a datagrid column?

Posted by Grette @ 08 Mar 2009 2:42 PM
Hi all. He who labors diligently need never despair; for all things are accomplished by diligence and labor.
I am from Herzegovina and now teach English, give true I wrote the following sentence: "An overview on how to write a winning resume, including do and don for a successful first impression."

Thank :( Grette.

Posted by mallikn @ 05 Nov 2010 8:19 PM
just in case some one run into this issue with vs2010 not displaying the nullablebinding/extender property in properties window. go through this.

http://social.msdn.microsoft.com/Forums/en/winformsdesigner/thread/25423e00-b85b-4be1-a86b-0b01abbb4619

change the signatures in nullablextender.cs to:

[DefaultValue(false),
Category("Data")]
public bool GetNullableBinding(TextBox control)//(Control control)
{
..}
public void SetNullableBinding(TextBox control, bool nullable)//(Control control, bool nullable)
{
..}


© 2005 - 2014 Josh Twist - All Rights Reserved.