DATABASE

Silverlight : Data Binding - Controlling Updates

1/28/2013 6:32:49 PM

1. Problem

You would like to have explicit programmatic control on when property updates happen in a TwoWay data binding scenario.

2. Solution

Set the UpdateSourceTrigger attribute to Explicit in the binding declaration and programmatically invoke BindingExpression.UpdateSource().

3. How It Works

The default behavior of most Silverlight controls is to send the updates occurring as a result of user edits directly to the bound property as soon as they occur. For instance, when you change the text in a TextBox, whose Text property is data bound in a TwoWay mode, the bound property is updated as soon as the user tabs out or focus is shifted somewhere else through some other means.

Often, it may be desirable to hold the updates and batch them at the end of an edit session through some explicit user-driven mechanism like a Save button. A multitude of reasons could drive a decision like that: computed fields that can only be calculated when multiple other fields are populated, validation logic that involves dependencies across multiple fields, some preprocessing of the edited data before updates are applied, and so on.

Silverlight offers you this control through the Binding.UpdateSourceTrigger property. Setting this property to UpdateSourceTrigger.Explicit causes the runtime to hold all property updates in the anticipation that you will perform the updates explicitly in code. The following code snippet shows a binding declaration for the Text property on a TextBox with the UpdateSourceTrigger attribute set to Explicit:

<TextBox HorizontalAlignment="Stretch"
 Margin="1,1,1,1"
 x:Name="tbxLName"
 VerticalAlignment="Stretch"
 Text=
"{Binding LastName, Mode=TwoWay,UpdateSourceTrigger=Explicit}"
 Grid.Row="1"
 Grid.Column="3"
 Width="Auto"
 Grid.RowSpan="1"
 Grid.ColumnSpan="3" />

To actually perform the updates, you need to access the BindingExpression instance supporting the binding in question and invoke the BindingExpression.UpdateSource() method on it. You can acquire the BindingExpression instance in question by using the FrameworkElement.GetBindingExpression() method and passing in the property whose related BindingExpression you may need. This code snippet shows an example:

BindingExpression beLastName = tbxLName.GetBindingExpression(TextBox.TextProperty);
beLastName.UpdateSource();

					  

Note that any validation logic that you have built into the property setters will execute only when UpdateSource() is invoked for that specific binding. So if you are batching the calls to UpdateSource(), it will cause all validation logic to be batched as well.

4. The Code

Figure 1 shows the state of the edit user interface before and after the Save button is clicked. Note that while you have potentially incorrect data in the state, zipcode and phone number fields, the validation check results only show up once the updates are attempted.

Figure 1. Employee edit user interface before and after batched update attempt

Listing 1 shows the XAML for the main page.

Listing 1. XAML for MainPage
<UserControl x:Class="Recipe4_7.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Recipe4_7"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:input="clr-
namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"
             Width="400"
             Height="450">
    <UserControl.Resources>

    <local:BoolToVisibilityConverter x:Key="REF_BoolToVisibilityConverter" />

    <DataTemplate x:Key="dtEmployee">
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

					  
<TextBlock Text="{Binding FirstName}" />
        <TextBlock Text="{Binding LastName}"
                   Grid.Column="1"
                   Grid.Row="0"
                   Margin="5,0,0,0" />
        <TextBlock Text=" -> Error!!"
                   Foreground="Red"
                   Visibility=
      "{Binding InError, Converter={StaticResource REF_BoolToVisibilityConverter}}"
                   Grid.Column="2" />

      </Grid>
    </DataTemplate>

  </UserControl.Resources>
    <Grid x:Name="LayoutRoot"
          Background="White"
          Margin="10,10,10,10">
        <Grid.RowDefinitions>
           <RowDefinition Height="*" />
           <RowDefinition Height="Auto" />
           <RowDefinition Height="Auto" />
           <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox Grid.Row="0"
                 x:Name="lbx_Employees"
                 ItemTemplate="{StaticResource dtEmployee}"
                 SelectionChanged="lbx_Employees_SelectionChanged" />
        <Grid x:Name="grid_NewButton"
              Margin="0,2,0,0"
              Grid.Row="1"
              HorizontalAlignment="Right">
            <Button  x:Name="btn_New"
                     Click="btn_New_Click"
                     Content="New Employee" />
        </Grid>
    <input:ValidationSummary Grid.Row="2"
                             Margin="0,10,0,5" />
    <Border Grid.Row="3"
                Visibility="Collapsed"
                x:Name="border_EmployeeForm"
                Margin="0,2,0,0"
                BorderBrush="Black"
                BorderThickness="1"
                Padding="1,1,1,1">

					  

<Grid x:Name="grid_EmployeeForm">

                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="0.142*" />
                    <ColumnDefinition Width="0.379*" />
                    <ColumnDefinition Width="0.1*" />
                    <ColumnDefinition Width="0.097*" />
                    <ColumnDefinition Width="0.082*" />
                    <ColumnDefinition Width="0.2*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="0.10*" />
                    <RowDefinition Height="0.15*" />
                    <RowDefinition Height="0.15*" />
                    <RowDefinition Height="0.15*" />
                    <RowDefinition Height="0.45*" />
                </Grid.RowDefinitions>
                <TextBox HorizontalAlignment="Stretch"
                         Margin="1,1,1,1"
                         x:Name="tbxFName"
                         VerticalAlignment="Stretch"
                         Text=
                  "{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
                         Grid.Row="1"
                         Width="Auto"
                         Grid.RowSpan="1"
                         Grid.ColumnSpan="2"
                         Grid.Column="1" />
                <TextBox HorizontalAlignment="Stretch"
                         Margin="1,1,1,1"
                         x:Name="tbxLName"
                         VerticalAlignment="Stretch"
                         Text=
                    "{Binding LastName, Mode=TwoWay,UpdateSourceTrigger=Explicit}"
                         Grid.Row="1"
                         Grid.Column="3"
                         Width="Auto"
                         Grid.RowSpan="1"
                         Grid.ColumnSpan="3" />
                <TextBlock HorizontalAlignment="Stretch"
                           Margin="1,1,1,1"
                           VerticalAlignment="Stretch"
                           Text="Last"
                           TextWrapping="Wrap"
                           Grid.RowSpan="1"

					  

Grid.Column="4"
                           Grid.ColumnSpan="2"
                           Height="Auto"
                           Width="Auto" />
                <TextBlock HorizontalAlignment="Center"
                           Margin="1,1,1,1"
                           VerticalAlignment="Center"
                           Text="First"
                           TextWrapping="Wrap"
                           Grid.RowSpan="1"
                           Grid.Column="1"
                           Width="Auto"
                           Height="Auto" />
                <TextBlock HorizontalAlignment="Center"
                           Margin="1,1,1,1"
                           VerticalAlignment="Stretch"
                           Text="Name"
                           TextWrapping="Wrap"
                           Grid.RowSpan="1"
                           Grid.Row="1"
                           Height="Auto"
                           Width="Auto" />
                <TextBlock HorizontalAlignment="Center"
                           Margin="1,1,1,1"
                           VerticalAlignment="Stretch"
                           Text="Street"
                           TextWrapping="Wrap"
                           Grid.Row="2"
                           Width="Auto" />
                <TextBox HorizontalAlignment="Stretch"
                         x:Name="tbxStreet"
                         VerticalAlignment="Stretch"
                         Text=
              "{Binding Address.Street, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
                         Grid.Row="2"
                         Margin="1,1,1,1"
                         Grid.Column="1"
                         Grid.ColumnSpan="5"
                         Width="Auto" />
                <TextBlock HorizontalAlignment="Center"
                           VerticalAlignment="Stretch"
                           Text="City"
                           TextWrapping="Wrap"
                           Margin="1,1,1,1"
                           Grid.Row="3" />

					  

<TextBlock Text="State"
                           Margin="1,1,1,1"
                           TextWrapping="Wrap"
                           Grid.Column="2"
                           Grid.Row="3"
                           HorizontalAlignment="Center" />
                <TextBlock Text="Zip"
                           Margin="1,1,1,1"
                           TextWrapping="Wrap"
                           Grid.Column="4"
                           Grid.Row="3"
                           HorizontalAlignment="Center" />
                <TextBox HorizontalAlignment="Stretch"
                         x:Name="tbxCity"
                         Margin="1,1,1,1"
                         VerticalAlignment="Stretch"
                         Text=
                "{Binding Address.City, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
                         Grid.Row="3"
                         Grid.Column="1" />

                <TextBox Background="Transparent"
                         Grid.Column="3"
                         Margin="1,1,1,1"
                         Grid.Row="3"
                         Text=
                "{Binding Address.State, Mode=TwoWay,UpdateSourceTrigger=Explicit,
                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, UpdateSourceTrigger=Explicit,
                  ValidatesOnExceptions=True,NotifyOnValidationError=True}"
                         x:Name="tbxZipCode" />

                <TextBlock HorizontalAlignment="Center"
                           VerticalAlignment="Stretch"
                           Text="Phone"
                           Margin="1,1,1,1"

					  

TextWrapping="Wrap"
                           Grid.Row="4" />
                <TextBox Grid.Column="1"
                         Grid.Row="4"
                         Margin="1,1,1,1"
                         Text=
                    "{Binding PhoneNum, Mode=TwoWay, UpdateSourceTrigger=Explicit,
          ValidatesOnExceptions=True,NotifyOnValidationError=True}"
                         x:Name="tbxPhoneNum" />
                <StackPanel Orientation="Horizontal"
                            Grid.Column="4"
                            Margin="1,1,1,1"
                            Grid.ColumnSpan="2"
                            Grid.Row="4">
                    <Button Height="30.911"
                            Margin="2,2,2,0"
                            VerticalAlignment="Top"
                            Content="Save"
                            x:Name="btnSave"
                            Click="btnSave_Click" />
                    <Button Height="30.911"
                            Margin="2,2,2,0"
                            VerticalAlignment="Top"
                            Content="Close"
                            x:Name="btnClose"
                            Click="btnClose_Click" />
                </StackPanel>

            </Grid>
        </Border>
    </Grid>
</UserControl>

					  

Note the changes in XAML to the Binding declarations to set the UpdateSourceTrigger to Explicit. Listing 2 shows the codebehind.

Listing 2. Codebehind to MainPage
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Recipe4_7
{

public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();

      //initialize the employee collection with some sample data
      ObservableCollection<Employee> empColl =
        new ObservableCollection<Employee>();

      empColl.Add(new Employee
      {
        FirstName = "Joe",
        LastName = "Duffin",
        PhoneNum = "2125551212",
        Address = new Address
        {
          Street = "2000 Mott Street",
          City = "New York",
          State = "NY",
          ZipCode = "10006"
        }
      });

      empColl.Add(new Employee
      {
        FirstName = "Alex",
        LastName = "Bleeker",
        PhoneNum = "7185551212",
        Address = new Address
        {
          Street = "11000 Clover Street",
          City = "New York",
          State = "NY",
          ZipCode = "10007"
        }
      });

empColl.Add(new Employee
      {
        FirstName = "Nelly",
        LastName = "Myers",
        PhoneNum = "7325551212",
        Address = new Address
        {

					  

Street = "12000 Fay Road",
          City = "New York",
          State = "NY",
          ZipCode = "10016"
        }
      });

      lbx_Employees.ItemsSource = empColl;

      this.BindingValidationError +=
        new System.EventHandler<ValidationErrorEventArgs>((s, e) =>
        {

          if (lbx_Employees.SelectedItem == null) return;
          //change the InError property of the currently selected Employee
          if (e.Action == ValidationErrorEventAction.Added)
            (lbx_Employees.SelectedItem as Employee).InError = true;
          else
            (lbx_Employees.SelectedItem as Employee).InError = false;

        });
    }

    private void btn_New_Click(object sender, RoutedEventArgs e)
    {
      //get the bound collection
      ObservableCollection<Employee> empColl =
        (ObservableCollection<Employee>)lbx_Employees.ItemsSource;
      //create and initialize a new Employee
      Employee newEmp = new Employee();
      newEmp.Address = new Address();
      //add it to the collection
      empColl.Add(newEmp);
//set the current selection to the newly added employee.
      //This will cause selection change to fire, and set the
      //datacontext for the form appropriately
      lbx_Employees.SelectedItem = newEmp;

    }

    private void lbx_Employees_SelectionChanged(object sender,
      SelectionChangedEventArgs e)
    {
      //set the datacontext of the form to the selected Employee
      grid_EmployeeForm.DataContext = (Employee)lbx_Employees.SelectedItem;

					  

//show the form
      border_EmployeeForm.Visibility = Visibility.Visible;
      grid_NewButton.Visibility = Visibility.Collapsed;
    }

    private void btnClose_Click(object sender, RoutedEventArgs e)
    {
      //hide the form
      border_EmployeeForm.Visibility = Visibility.Collapsed;
      grid_NewButton.Visibility = Visibility.Visible;
    }

    private void btnSave_Click(object sender, RoutedEventArgs e)
    {

      var bindingExpressions =
        grid_EmployeeForm.Children.OfType<TextBox>().
        Select((tbx)=>tbx.GetBindingExpression(TextBox.TextProperty));
      foreach (BindingExpression be in bindingExpressions) be.UpdateSource();
    }
  }
}

					  

Note the code in the Click event handler for btnSave. You perform a quick LINQ query on grid_EmployeeForm.Children collection to get access to the BindingExpressions for the Text property of all the children of type TextBox. You then proceed to iterate through the collection of BindingExpressions and call UpdateSource() on each of them.
Other  
  •  Silverlight : Data Binding - Validating Input for Bound Data
  •  Western Digital Black 4TB - One Of The Quickest Physical Drives
  •  Samsung 840 SSD 250GB - Most Of The Good Things
  •  SQL Server 2005 : Working with SQL Server Management Objects in Visual Studio (part 3) - Creating Backup-and-Restore Applications, Performing Programmatic DBCC Commands with SMO
  •  SQL Server 2005 : Working with SQL Server Management Objects in Visual Studio (part 2) - Retrieving Server Settings
  •  SQL Server 2005 : Working with SQL Server Management Objects in Visual Studio (part 1) - Iterating Through Available Servers
  •  SQL Server 2005 : Advanced OLAP - Roles
  •  SQL Server 2005 : Advanced OLAP - Translations
  •  SQL Server 2005 : Advanced OLAP - Perspectives
  •  Oracle Database 11g : Installing Oracle - Install the Oracle Software
  •  
    Video
    Top 10
    SG50 Ferrari F12berlinetta : Prancing Horse for Lion City's 50th
    The latest Audi TT : New angles for TT
    Era of million-dollar luxury cars
    Game Review : Hearthstone - Blackrock Mountain
    Game Review : Battlefield Hardline
    Google Chromecast
    Keyboards for Apple iPad Air 2 (part 3) - Logitech Ultrathin Keyboard Cover for iPad Air 2
    Keyboards for Apple iPad Air 2 (part 2) - Zagg Slim Book for iPad Air 2
    Keyboards for Apple iPad Air 2 (part 1) - Belkin Qode Ultimate Pro Keyboard Case for iPad Air 2
    Michael Kors Designs Stylish Tech Products for Women
    REVIEW
    - First look: Apple Watch

    - 3 Tips for Maintaining Your Cell Phone Battery (part 1)

    - 3 Tips for Maintaining Your Cell Phone Battery (part 2)
    Popular Tags
    Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Exchange Server Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe Photoshop CorelDRAW X5 CorelDraw 10 windows Phone 7 windows Phone 8 Iphone