1. Problem
You want to provide some default binding behavior for
data bound fields—such as formatting, null value replacements, or
fallback values in case of input validation failure.
2. Solution
Use the string format, null value replacement, and fallback value features of the Binding type.
3. How It Works
Silverlight 4 adds support for null value replacement, fallback values, and string formatting to data binding.
3.1. Null Value Replacement
Data fields or properties in backing business objects
often have differing connotations for what represents a null value
within that application's context. For example a null CLR string may not
be acceptable by a business logic layer whereas an empty string or the
string "null" may indicate that a database null value needs to be stored
in the bound data field. Another example could be a business data type
of nullable<int> bound to a TextBox, where an empty string entered
in the UI cannot be automatically converted to a nullable<int>.
Similarly, on the UI side of things, a null value of type such as
nullable<int> coming from the business logic layer may need a
specific visual representation, such as an empty string.
Typically, developers have employed value converters
to perform these types of conversions. The Binding element in
Silverlight 4 exposes a property named TargetNullValue (defined in the
BindingBase class) that allows this problem to be solved much more
elegantly. TargetNullValue is of type object, and when set to a specific
value does the following:
On transfer of data from the data source to
the target property, the binding displays the value specified in the
TargetNullValue, whenever the source data is a CLR null value.
On
transfer of data from the target to the data source, the binding
transfers a CLR null value whenever it encounters the value specified in
TargetNullValue.
As an example, look at this snippet:
<TextBox Text="{Binding Path=WeddingAnniversaryDate, TargetNullValue=``}"/>
If the WeddningAnniversaryDate source property was of
type nullable<DateTime> (or DateTime? In C# terms), then a CLR
null value from the property will displayed as an empty string in the
UI. Conversely, if an unmarried user applies an empty string in the UI,
the value transferred to the underlying business object's
WeddingAnniversaryDate property will be a CLR null.
3.2. Fallback value
There can be several reasons why an error might occur
in transferring a value from a source property to a target through a
binding: an invalid property path specification in the binding
declaration in XAML, errors in value conversion, a specific
ValueConverter throwing an exception, string formatting errors, etc.
To avoid an application exception during such
scenarios, it would be handy to be able to provide some sort of fallback
value that the UI can display in face of such exceptions without
causing the application to fail. Silverlight 4 introduces a
FallbackValue property (again through the BindingBase class) that allows
you to do exactly that. This snippet shows an example:
<TextBox Text="{Binding Path=Salary, FallbackValue=0}"/>
In this snippet, if there was any kind of error in
trying to transfer the value of the Salary data field to the bound Text
property of the TextBox, the value 0 specified in the FallbackValue
attribute will be displayed. Note that FallbackValue only applies to the
transfer of data from source to target and not vice versa.
3.3. String Formatting
Silverlight 4 also adds a StringFormat property to
the Binding type. Setting the StringFormat type to an appropriate format
allows you to apply a formatting to a value when displayed as text on
the UI. This snippet shows an example:
<TextBox Text="{Binding Path=PhoneNum, StringFormat=(###) ###-####}"/>
Let's assume that the PhoneNum field in the backing
class is a long value. With the applied StringFormat, a PhoneNum value
of 7325551212 will display as (732) 555-1212. Any valid string format as
allowed by the String.Format() method is an acceptable value. For the
various string formatting options, a good reference is the documentation
for the String.Format() method.
You can also use the standard parameter substitution
mechanism in the format. The snippet below shows an example where a
positioned parameter specifies the formatting of the phone number:
<TextBox Text="{Binding Path=PhoneNum,
StringFormat='Phone No: \{0:(###) ###-####\}' }"/>
In this case, a source value of 7325551212 will be
formatted as Phone No: (732) 555-1212. Note that since bindings in
Silverlight always bind a single value to a single property, using more
than one parameter substitution value (i.e. more than the 0th
placeholder in the above format) is an error. Also note that the "{"
and the "}" tokens are escaped with a "\" to prevent the XAML parser
from considering them as part of the binding expression rather than the
format string.
.4. The Code
Listing 1 shows the relevant changes to the data source classes in the dataclasses.cs file.
Listing 1. Changes to the data source classes
public class Employee : INotifyPropertyChanged
{
...
private long _PhoneNum = 9999999999;
public long PhoneNum
{
get { return _PhoneNum; }
set
{
long OldVal = _PhoneNum;
if (_PhoneNum.ToString().Trim().Length != 10)
throw new Exception("Phone Number has to be exactly 10 digits");
if (OldVal != value)
{
_PhoneNum = value;
RaisePropertyChanged(new PropertyChangedEventArgs("PhoneNum"));
}
}
}
...
}
public class Address : INotifyPropertyChanged
{
...
private string _ZipCode = null;
public string ZipCode
{
get
{
if (_ZipCode == null)
throw new Exception();
else
return _ZipCode;
}
set
{
string OldVal = _ZipCode;
//length needs to be 5 characters
if (value.Length != 5)
throw new Exception("Zipcode needs to be exactly 5 digits");
try
{
Convert.ToInt32(value);
}
catch
{
throw new Exception("Zipcode needs to be exactly 5 digits");
}
if (OldVal != value)
{
_ZipCode = value;
RaisePropertyChanged(new PropertyChangedEventArgs("ZipCode"));
}
}
}
}
|
You change the PhoneNum property to be of type long
and initialize it to a default value. You also change the ZipCode
property to of type string and throw an exception in the property getter
if the current value is null. We will explain the motivation behind the
changes as we examine the corresponding changes in the XAML in the next
listing. Listing 2 shows the relevant changes made to the bindings in MainPage.xaml highlighted in bold.
Listing 2. Changes to bindings in MainPage.xaml
<Grid x:Name="grid_EmployeeForm">
...
<TextBox Background="Transparent"
Grid.Column="3"
Margin="1,1,1,1"
Grid.Row="3"
Text="{Binding Address.State, Mode=TwoWay, TargetNullValue='NJ',
ValidatesOnExceptions=True,NotifyOnValidationError=True}"
x:Name="tbxState">
</TextBox>
<TextBox Background="Transparent"
Grid.Column="5"
Grid.Row="3"
Margin="1,1,1,1"
Text="{Binding Address.ZipCode, Mode=TwoWay ,FallbackValue='08820',
ValidatesOnExceptions=True,NotifyOnValidationError=True}"
x:Name="tbxZipCode" />
<TextBox Grid.Column="1"
Grid.Row="4"
Margin="1,1,1,1"
Text="{Binding PhoneNum, Mode=TwoWay ,StringFormat=(###) ###-####,
ValidatesOnExceptions=True,NotifyOnValidationError=True}"
x:Name="tbxPhoneNum" />
...
</Grid>
|
The first change to note in Listing 2
is the addition of the TargetNullValue attribute to the binding to the
Text property on the tbxState TextBox. Since the initial value of the
bound property State on the Address class is a CLR null, the binding
populates the Text property with the initial value of 'NJ' when the new
employee form is initially displayed.
The second change to note is the use of the
FallbackValue attribute on the binding for the tbxZipCode TextBox. If
you refer back to Listing 1,
you will note that you changed the bound property Address.ZipCode to
throw an exception in the property getter when the property value is
null. Since this is the case when a new instance of the data class is
initially created, this causes the binding to result in an error, and
consequently the FallbackValue to be used to populate the Text property.
The last change to note is the use of the
StringFormat attribute on the binding for the tbxPhoneNum property. You
may have noticed in Listing 1
that you changed the backing property PhoneNum to a long. The
application of the specified StringFormat causes a long value of
7325551212 to be displayed as (732) 555-1212 whereas without the format
applied, it would have been converted to the string 7325551212 and
displayed as is.
Figure 1 shows the New Employee form open with no data entered.