WinForms Databinding, Part II: Format & Parse
Posted on November 14, 2005
In my last entry, I described the .NET 1.1 simple data binding mechanism: an object property is bound to a control property, and a change in either the object property value or the control property is reflected in the other. Today, I will describe what happens when these two properties are of different types.
For the following discussion, assume an Invoice class with an integer Amount property. The Amount property of an Invoice is bound to the Text property of a TextBox, as shown below.
public class Invoice { private int amt; public event EventHandler AmountChanged; private void OnAmountChanged() { if (AmountChanged != null) AmountChanged(this, EventArgs.Empty); } public int Amount { get { return amt; } set { amt = value; OnAmountChanged(); } } public Invoice() { } } public class DataBindingForm : Form { private Invoice iv; private TextBox textBox1; private Button button1; // ... protected override void OnLoad(EventArgs e) { base.OnLoad(e); iv = new Invoice(); iv.Amount = 500; } }
The runtime converts between the property types, so for example, a TextBox.Text string value of “500” can be converted to an integer value and store it in the Invoice.Amount property. What if the values are not convertible? Suppose that the TextBox contains the text “500”, and I replace it with “500.1”?
The first thing that happens is – nothing. At least not until I tab out of the TextBox. And then, it still appears that nothing is going on. Focus moves to the next control, but the value in the TextBox is still “500”. What happened?
When focus leaves the TextBox, the Validate event is raised on the TextBox, and then the Binding on TextBox.Text tries to convert the property value to an integer. Of course, “500.1” is not convertible to an integer, so Int32.Parse throws a FormatException. At this point, the Binding retrieves the current value from the Invoice.Amount property (500), and sets the TextBox text to that.
Let’s suppose that we want the user to be able to enter floating point values, and truncate any fractional part. To support this scenario, the Binding class raises the Parse event during control validation.
Binding.Parse
The Parse event is raised on the binding before the Binding class attempts to perform standard conversions through Convert.ChangeType(). Parse event handlers receive a ConvertEventArgs parameter. ConvertEventArgs offers a DesiredType property, indicating what type the data must be converted to, and Value, the proposed value to be converted.
In our example, the proposed value would be the string “500.1”, and the desired type is Int32. By adding an event handler to the TextBox binding for the Parse event, we can convert “500.1” to the Int32 value 500:
public class DataBindingForm : Form { // ... protected override void OnLoad(EventArgs e) { base.OnLoad(e); iv = new Invoice(); iv.Amount = 500; // Add a handler for Binding.Parse. Binding b = this.textBox1.DataBindings.Add("Text", iv, "Amount"); b.Parse += new ConvertEventHandler(binding_Parse); } private void binding_Parse(object sender, ConvertEventArgs e) { double val = 0; if (double.TryParse((string)e.Value, NumberStyles.Any, null, out val)) { e.Value = (int)val; } } }
Now if the user enters text that can be parsed as a double, the (truncated) integer part will be saved to the Invoice.Amount property.
Note that the Parse event is raised after the bound control property value changes and the Control.Validating event is raised. In the case above, that’s when the user changes the TextBox text and then tabs out of the control Parse is not raised when the bound object property value (Invoice.Amount) changes.
Binding.Format
Another interesting case is when we want the control property value to be displayed in a different form than the object property value. In the case of our invoice amount, we’d probably want to display it with a currency symbol.
When the binding pushes data to the control property, it raises the Format event. This allows your code to convert the raw data to a final form that will be applied to the control property. The code below adds a Format event handler to the binding to prepend the $ symbol to the data being assigned to the TextBox text:
protected override void OnLoad(EventArgs e) { base</span>.OnLoad(e); iv = new Invoice(); iv.Amount = 500; Binding b = this.textBox1.DataBindings.Add("Text", iv, "Amount"); b.Parse += new ConvertEventHandler(binding_Parse); b.Format += new ConvertEventHandler(binding_Format); } private void binding_Format(object sender, ConvertEventArgs e) { e.Value = ((int)e.Value).ToString("c"); }
Note that the Format handler is called not only when the object data property (Invoice amount) changes, but also when the control property is validated. In the latter case, the user is typically tabbing out of the control after making changes to the text. The Parse event is raised to convert the text to a form that can be assigned to the object data property, and then Format is raised to convert the data back to to the format desired for the control property.
Got something to say?