Data Binding
Data binding provides a simple way for Silverlight-based applications to display and interact with data. The way data is displayed is separated from the management of the data. A connection, or binding, between the UI and a data object allows data to flow between the two. When a binding is established and the data changes, the UI elements that are bound to the data can reflect changes automatically. Similarly, changes made by the user in a UI element can be reflected in the data object. For example, if the user edits the value in a TextBox, the underlying data value is automatically updated to reflect that change.
Some common binding scenarios include binding a ListBox to a list of headlines, an input form's TextBox to a customer data object, or an Image to the current user's photo.
This topic contains the following sections:
This topic uses simple code examples to illustrate data binding concepts. For more complex examples, see the topics listed at the end.
You can also accomplish many data binding tasks using Visual Studio 2010. For more information, see Data Binding in the Silverlight Designer.
Every binding must specify a source and a target. The following illustration shows the basic concepts of a binding.
The source can be any CLR object, including the target element itself or other UI elements. If the target is in a data template, the source can be the UI element to which the template is applied.
The target can be any DependencyProperty of a FrameworkElement.
Starting with Silverlight 4, the target can also be a DependencyProperty of a DependencyObject in the following cases:
The DependencyObject is the value of a property of a FrameworkElement.
The DependencyObject is in a collection that is the value of a FrameworkElement property (for example, the Resources property).
The DependencyObject is in a DependencyObjectCollection(Of T).
Starting with Silverlight 5, the target can also be the Value property of a Setter within a Style. For an example, see the Style class overview.
The binding engine gets information from the Binding object about the following:
The source and target objects.
The direction of the data flow. You specify the direction by setting the Binding.Mode property.
The value converter, if one is present. You specify a value converter by setting the Binding.Converter property to an instance of a class that implements IValueConverter.
Other settings, such as StringFormat, FallbackValue, and TargetNullValue. See the Binding class for the full list of properties.
For example, the Foreground property of a TextBox can be bound to a SolidColorBrush so that the color of the text can change based on the data. In this scenario, the Foreground property is the target, and the SolidColorBrush object is the source for the binding.
The following example shows how to bind the Foreground color of a TextBox to a SolidColorBrush in code and XAML. The binding source is a property of the MyColors class, which is described later in this topic.
// Create an instance of the MyColors class // that implements INotifyPropertyChanged. MyColors textcolor = new MyColors(); // Brush1 is set to be a SolidColorBrush with the value Red. textcolor.Brush1 = new SolidColorBrush(Colors.Red); // Set the DataContext of the TextBox MyTextBox. MyTextBox.DataContext = textcolor;
Note: |
---|
This example uses the XAML attribute syntax for creating the binding. You could also use the object element syntax to create the binding in XAML. For more information, see XAML Overview and Property Path Syntax. |
The binding is created in XAML using the {Binding ...} syntax. The source is set in code by setting the DataContext property for the TextBox.
Data context is inherited. If you set the data context on a parent element, all its children will use the same data context. A child element can override this behavior by setting the Source property on its binding object or by setting its DataContext, which will then apply to all its children.
Setting the data context is useful when you want to have multiple bindings that all use the same source. To set the source for a single binding, set the Source property on the Binding object. For more information, see How to: Create a Binding.
You can also use the ElementName property or the RelativeSource property to specify the binding source. The ElementName property is useful when you are binding to other elements in your application, such as when you are using a slider to adjust the width of a button. The RelativeSource property is useful when the binding is specified in a ControlTemplate or a Style. For more information, see Binding Markup Extension and RelativeSource Markup Extension.
You can bind to a property of the source object by setting the Binding.Path property. The Path property supports a variety of syntax options for binding to nested properties, attached properties, string indexers. For more information, see Property Path Syntax.
Altogether, the preceding example causes the binding engine to create a binding, which is OneWay by default, connecting the Foreground property of the TextBox to the brush1 property of the TextColor object.
Each binding has a Mode property, which determines how and when the data flows. Silverlight enables three types of bindings:
OneTime bindings update the target with the source data when the binding is created.
OneWay bindings update the target with the source data when the binding is created and anytime the data changes. This is the default mode.
TwoWay bindings update both the target and the source when either changes. Alternately, you can disable automatic source updates and update the source only at times of your choosing.
In order for automatic target updates to occur, the source object must implement the INotifyPropertyChanged interface, as described in the next section.
In order for changes to the source object to propagate to the target, the source must implement the INotifyPropertyChanged interface. INotifyPropertyChanged has the PropertyChanged event, which tells the binding engine that the source has changed so that the binding engine can update the target value.
In the following example, the MyColors class implements the INotifyPropertyChanged interface for OneWay binding.
// Create a class that implements INotifyPropertyChanged. public class MyColors : INotifyPropertyChanged { private SolidColorBrush _Brush1; // Declare the PropertyChanged event. public event PropertyChangedEventHandler PropertyChanged; // Create the property that will be the source of the binding. public SolidColorBrush Brush1 { get { return _Brush1; } set { _Brush1 = value; // Call NotifyPropertyChanged when the source property // is updated. NotifyPropertyChanged("Brush1"); } } // NotifyPropertyChanged will raise the PropertyChanged event, // passing the source property that is being updated. public void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
To get change notification for collections bound to an ItemsControl, implement INotifyCollectionChanged in addition to INotifyPropertyChanged. If you implement INotifyCollectionChanged, changes to the collection, such as adding or removing an object, will propagate to the target. To get property change notification for objects in the collection, the objects must implement INotifyPropertyChanged.
Before implementing your own collection, consider using the ObservableCollection(Of T) class, which has a built-in implementation of INotifyCollectionChanged and INotifyPropertyChanged.
In TwoWay bindings, changes to the target automatically update the source, except when binding to the Text property of a TextBox. In this case, the update occurs when the TextBox loses focus.
You can disable automatic source updates and update the source at times of your choosing. For example, you can do this to validate user input from multiple controls before you update the bound data sources.
To disable automatic source updates, set the UpdateSourceTrigger property to Explicit. This setting affects all bindings that use the same Binding object (for example, when using an inherited data context). You must update the source for each binding individually, however. To update a binding, first call the FrameworkElement.GetBindingExpression method of a target element, passing in the target DependencyProperty. You can then use the return value to call the BindingExpression.UpdateSource method. The following example code demonstrates this process.
public class TestData { public String Test { get; set; } } TestData data; public MainPage() { InitializeComponent(); data = new TestData { Test = "one" }; textBox1.DataContext = data; } private void Button_Click(object sender, RoutedEventArgs e) { BindingExpression expression = textBox1.GetBindingExpression(TextBox.TextProperty); MessageBox.Show("Before UpdateSource, Test = " + data.Test); expression.UpdateSource(); MessageBox.Show("After UpdateSource, Test = " + data.Test); }
A binding source object can be treated either as a single object whose properties contain data or as a collection of objects. For example, you might want to display a list of items, such as monthly credit card bills. To do this, use an ItemsControl and use a DataTemplate to display each item in a collection. For more information on data templates, see How to: Customize Data Display with Data Templates.
<Grid.Resources> <DataTemplate x:Name="dataTemplate"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding Month, Converter={StaticResource Converter1}}"/> <TextBlock Grid.Column="1" Text="{Binding Total}"/> </Grid> </DataTemplate> </Grid.Resources> <ItemsControl x:Name="IC1" ItemsSource="{Binding}" ItemTemplate="{StaticResource dataTemplate}"/>
You can enumerate over any collection that implements IEnumerable. If you want the target to update the ItemsSource when the collection changes, implement INotifyCollectionChanged. For more information on change notification, see the Change Notification section earlier in this topic.
You can also bind to instances of the CollectionViewSource class, which provides sorting, grouping, filtering, and currency support for other data sources. The CollectionViewSource enables you to display multiple views of data that stay synchronized with user selection changes.
You can also use CollectionViewSource to bind multiple controls to hierarchically-related data. For more information, see How to: Bind to Hierarchical Data and Create a Master/Details View.
The PagedCollectionView class provides another way to apply sorting, grouping, and filtering to another data source. This class also includes paging and editing, and provides a data source for controls such as DataPager.
For information about encapsulating a generic list in an object so that you can bind a control to it, see the first code examples for List(Of T) and Dictionary(Of TKey, TValue).
Silverlight supports simple data validation in TwoWay bindings for target-to-source updates. Additionally, Silverlight 4 supports validation with IDataErrorInfo and INotifyDataErrorInfo regardless of the binding mode.
Silverlight reports a validation error whenever the Validation.Errors attached property of a binding contains errors. Errors are added to this collection in the following cases:
Exceptions are thrown from the binding engine's type converter.
Exceptions are thrown from the binding object's set accessor.
Exceptions are thrown from a validation attribute that is applied to a data object or member.
The binding object implements IDataErrorInfo and its Item property returns a value that is not null or Empty.
The binding object implements INotifyDataErrorInfo and its GetErrors method returns a value that is not null. The GetErrors return value can change as a result of the completion of asynchronous validation operations.
Silverlight provides visual feedback for validation errors in the following cases:
The ValidatesOnExceptions property value is true.
The ValidatesOnDataErrors property value is true. However, the ValidatesOnDataErrors property is ignored for a source property if the ValidatesOnExceptions property value is true and the source property setter throws an exception.
The ValidatesOnNotifyDataErrors property is true. This property can work in combination with ValidatesOnExceptions and ValidatesOnDataErrors.
The visual feedback indicates the control that contains the error, and displays the error message nearby, as shown in the following illustration.
You can customize the visual feedback for a control by modifying or replacing its default ControlTemplate. For more information, see Control Customization.
To receive notification that a validation error has occurred or has been resolved, you must set the NotifyOnValidationError property to true on the binding object. This tells the binding engine to raise the BindingValidationError event when a validation error is added to or removed from the Validation.Errors collection. For example, you can handle the error event to log the error or provide additional visual feedback.
To handle the BindingValidationError event, create an event handler on the target object or any of its parents. The BindingValidationError event is a routed event, so if you do not handle it on the element that raised the event, it will continue to bubble up until it is handled. For more information on routed events, see Events Overview for Silverlight.
The following example shows how to provide custom binding validation.
The binding is created in XAML.
<StackPanel BindingValidationError="StackPanel_BindingValidationError" > <StackPanel.Resources> <my:Bills x:Name="MyBills"/> </StackPanel.Resources> <TextBox x:Name="MyTextBox" Width="50" Margin="10"> <TextBox.Text> <Binding Mode="TwoWay" Source="{StaticResource MyBills}" Path="Amount" NotifyOnValidationError="true" ValidatesOnExceptions="true"/> </TextBox.Text> </TextBox> <Button Height="50" Width="150" Content="Click To Update Source"/> </StackPanel>
The source object throws an exception in the set accessor if the value is negative.
The StackPanel implements a handler for the BindingValidationError event.
private void StackPanel_BindingValidationError(object sender, ValidationErrorEventArgs e) { if (e.Action == ValidationErrorEventAction.Added) { MyTextBox.Background = new SolidColorBrush(Colors.Red); } else if (e.Action == ValidationErrorEventAction.Removed) { MyTextBox.Background = new SolidColorBrush(Colors.White); } }
After the sample starts, type in letters instead of numbers to get an error caused by the type converter. Type in a negative number to get an error from the source object's set accessor. Type in a positive number to resolve the validation error. TextBox target-to-source updates occur only when the TextBox loses focus. The button is provided to change the focus. If you prefer, you can update the source manually in response to the button click, as described earlier in the Updating the Data Source section.
For information about applying validation attributes to specify the allowed values for a property or object, see Using Data Annotations to Customize Data Classes. For information about providing more detailed validation reporting, see the ValidationSummary class overview.
You may need to display data in a format that differs from how it is stored. Some examples are the following:
Storing a color as an RGBA value but displaying it as a string name.
Storing a number as a floating-point value but displaying it as a currency value.
Storing a date as DateTime but displaying it in a calendar.
Storing a null value, but displaying a friendly default value.
In Silverlight 4, you can format any String value for display by setting the StringFormat property. For information on formatting codes, see Formatting Types.
In Silverlight 4, you can also display a friendly default value for null backing values by setting the TargetNullValue property.
You can also set a converter on any binding. The converter is customized for each scenario by creating a class and implementing the IValueConverter interface. The following example shows how to implement IValueConverter.
// Custom class implements the IValueConverter interface. public class DateToStringConverter : IValueConverter { #region IValueConverter Members // Define the Convert method to change a DateTime object to // a month string. public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // value is the data from the source object. DateTime thisdate = (DateTime)value; int monthnum = thisdate.Month; string month; switch (monthnum) { case 1: month = "January"; break; case 2: month = "February"; break; default: month = "Month not found"; break; } // Return the value to pass to the target. return month; } // ConvertBack is not implemented for a OneWay binding. public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion }
The binding engine calls the Convert and ConvertBack methods if the Converter parameter is defined for the binding. When data is passed from the source, the binding engine calls Convert and passes the returned data to the target. When data is passed from the target, the binding engine calls ConvertBack and passes the returned data to the source. The following example shows how to set the Converter parameter.
The converter also has optional parameters: ConverterCulture, which allows specifying the culture to be used in the conversion, and ConverterParameter, which allows passing a parameter for the conversion logic. For an example using these parameters, see IValueConverter.
If there is an error in the conversion, do not throw an exception. Instead, return DependencyProperty.UnsetValue, which will stop the data transfer.
In Silverlight 4, to display a default value that appears whenever the binding source cannot be resolved, set the FallbackValue property. This is useful to handle conversion and formatting errors. It is also useful to bind to source properties that might not exist on all objects in a bound collection.
Starting with Silverlight 5, you can debug data bindings by setting breakpoints on bindings in XAML. This helps you track down issues that might otherwise be hard to isolate. For example, when a binding behaves incorrectly, the issue could be anything from an incorrect property path to an exception thrown during value conversion.
By default, your program will break on a binding breakpoint whenever the binding system transfers a value between the data source and target. This can occur because of user input or programmatic manipulation of the binding or the bound objects. When a breakpoint is hit, you can use the Locals window to examine every aspect of the current binding state.
The binding state is represented in the Locals window by a run time property called BindingState, which is of type BindingDebugState. This property provides access to information such as the current Binding, BindingExpression, and data source objects, the value at each completed stage of binding resolution, and any errors that have occurred.
You can also set breakpoint conditions in order to break only when the binding is in a particular state. To use the BindingState property in the condition statement, you must cast it to a BindingDebugState instance. You must also fully qualify all type names, as shown in the following example code.
((System.Windows.Data.Debugging.BindingDebugState)BindingState).Error != null
Data binding debugging imposes a performance penalty while debugging. For this reason, it is useful to disable this feature after you have finished debugging your bindings. You can do this by setting the static Binding.IsDebuggingEnabled field to false in the constructor of your application class.