Udostępnij za pośrednictwem


Data binding for Windows Phone 8

[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]

Data binding provides a simple way for Windows Phone apps 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 uses simple code examples to illustrate data binding concepts. You can also accomplish many data binding tasks using Visual Studio.

This topic contains the following sections.

Connecting UI elements with data

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.

The target can also be a DependencyProperty of a DependencyObject in the following cases:

Starting with Windows Phone 8, 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:

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.

<TextBox x:Name="MyTextBox" Text="Text" Foreground="{Binding Brush1, Mode=OneWay}"/>
' Create an instance of the MyColors class 
' that implements INotifyPropertyChanged. 
Dim textcolor As 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
// 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 for Windows Phone 8.

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 data binding for Windows Phone.

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 app, 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.

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.

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.

Direction of the data flow

Each binding has a Mode property, which determines how and when the data flows. In Windows Phone you can use 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.

Change notification

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
    Implements INotifyPropertyChanged
    Private _Brush1 As SolidColorBrush

    ' Declare the PropertyChanged event. 
    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged

    ' Create the property that will be the source of the binding. 
    Public Property Brush1() As SolidColorBrush
        Get
            Return _Brush1
        End Get
        Set(ByVal value As SolidColorBrush)
            _Brush1 = value
            ' Call NotifyPropertyChanged when the source property 
            ' is updated. 
            NotifyPropertyChanged("Brush1")
        End Set
    End Property


    ' NotifyPropertyChanged will raise the PropertyChanged event, 
    ' passing the source property that is being updated. 
    Public Sub NotifyPropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, _
            New PropertyChangedEventArgs(propertyName))
    End Sub


End Class
// 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.

Updating the data source

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.

<TextBox x:Name="textBox1" Text="{Binding Test, Mode=TwoWay, UpdateSourceTrigger=Explicit}" />
<Button Content="Update" Click="Button_Click" />
Public Class TestData
    Private testValue As String
    Public Property Test() As String
        Get
            Return testValue
        End Get
        Set(ByVal value As String)
            testValue = value
        End Set
    End Property
End Class

Private data As TestData

Public Sub New()

    InitializeComponent()

    data = New TestData
    With data
        .Test = "one"
    End With

    textBox1.DataContext = data

End Sub

Private Sub Button_Click(ByVal sender As Object, _
    ByVal e As RoutedEventArgs)

    Dim expression As BindingExpression = _
        textBox1.GetBindingExpression(TextBox.TextProperty)
    MessageBox.Show("Before UpdateSource, Test = " & data.Test)
    expression.UpdateSource()
    MessageBox.Show("After UpdateSource, Test = " & data.Test)

End Sub
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);
}

Binding to collections

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.

<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 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>)>).

Data validation

Windows Phone supports simple data validation in TwoWay bindings for target-to-source updates.

Windows Phone 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.

Windows Phone provides visual feedback for validation errors in the following cases:

The visual feedback indicates the control that contains the error, and displays the error message nearby.

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 for Windows Phone 8.

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>

    <TextBlock 
        TextWrapping="Wrap"
        Text="To generate a validation error, input a negative number or a non-number and then tap the button."
    />

    <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 Content="Tap to Update"/>
</StackPanel>

The source object throws an exception in the set accessor if the value is negative.

Public Class Bills
    Private _Amount As Double
    Public Property Amount() As Double
        Get
            Return _Amount
        End Get
        Set(ByVal value As Double)
            If value < 0 Then
                Throw New Exception("Amount must be greater than zero.")
            End If
            _Amount = value
        End Set
    End Property

End Class
public class Bills
{
    private double _Amount;
    public double Amount
    {
        get { return _Amount; }
        set
        {
            if (value < 0)
                throw new Exception("Amount must be greater than zero.");
            _Amount = value;
        }
    }

}

The StackPanel implements a handler for the BindingValidationError event.

Private Sub StackPanel_BindingValidationError(ByVal sender As Object, _
    ByVal e As ValidationErrorEventArgs)

    If e.Action = ValidationErrorEventAction.Added Then

        MyTextBox.Background = New SolidColorBrush(Colors.Red)
    ElseIf e.Action = ValidationErrorEventAction.Removed Then
        MyTextBox.Background = New SolidColorBrush(Colors.White)
    End If

End Sub
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);
    }
}

In this code example, 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.

Data conversions

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.

You can format any String value for display by setting the StringFormat property.

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
    Implements IValueConverter

    ' Define the Convert method to change a DateTime object to
    ' a month string.
    Public Function Convert(ByVal value As Object, _
        ByVal targetType As Type, ByVal parameter As Object, _
        ByVal culture As System.Globalization.CultureInfo) As Object _
        Implements IValueConverter.Convert

        ' value is the data from the source object.
        Dim thisdate As DateTime = CType(value, DateTime)
        Dim monthnum As Integer = thisdate.Month
        Dim month As String
        Select Case (monthnum)
            Case 1
                month = "January"
            Case 2
                month = "February"
            Case Else
                month = "Month not found"
        End Select
        ' Return the value to pass to the target.
        Return month

    End Function

    ' ConvertBack is not implemented for a OneWay binding.
    Public Function ConvertBack(ByVal value As Object, _
        ByVal targetType As Type, ByVal parameter As Object, _
        ByVal culture As System.Globalization.CultureInfo) As Object _
        Implements IValueConverter.ConvertBack



        Throw New NotImplementedException

    End Function
End Class
// 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.

<phone:PhoneApplicationPage.Resources>
  <local:DateToStringConverter x:Key="Converter1"/>
</phone:PhoneApplicationPage.Resources>


...


<TextBlock Grid.Column="0" 
    Text="{Binding Month, Converter={StaticResource Converter1}}"/>

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.

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.

See Also

Other Resources

Quickstart: Data binding to controls for Windows Phone