Explicitly Updating And Validating Databindings In WPF

The other day I was working on something for a Codeproject article, where I needed to bind part of my UI to an underlying data object. I want to use all the good validation thing such as a Validation Style to use for my TextBox, and also the use of the new .NET 3.5 interface IDataErrorInfo.

So that was fine. But I also wanted to allow the user to either apply the changes or cancel them. When the user chooses to apply the changes, the changes should be made Explicitly to the underlying data object, and ONLY update database if the underlying data object is in a valid state.

So how do we do that. Well the first thing to do is make sure we have a data object that provides validation using the .NET 3.5 interface IDataErrorInfo.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.ComponentModel;
   6:  
   7:  namespace Binding.Explicit
   8:  {
   9:      class Person : IDataErrorInfo
  10:      {
  11:          #region Data
  12:          private StringBuilder combinedError
  13:              = new StringBuilder(2000);
  14:          #endregion
  15:  
  16:          #region Ctor
  17:          public Person ()
  18:          {
  19:  
  20:          }
  21:          #endregion
  22:  
  23:          #region Public Properties
  24:          public int Age { get; set; }
  25:          #endregion
  26:  
  27:          #region IDataErrorInfo Members
  28:  
  29:          /// <summary>
  30:          /// Return the full list of validation 
  31:          /// errors for this object
  32:          /// </summary>
  33:          public string Error
  34:          {
  35:              get
  36:              {
  37:                  return combinedError.ToString();
  38:              }
  39:          }
  40:  
  41:          /// <summary>
  42:          /// Validates a particular column, and returns a 
  43:          /// string representing the current error
  44:          /// </summary>
  45:          /// <param name="columnName">The property name to 
  46:          /// validate</param>
  47:          /// <returns>A string representing the 
  48:          /// current error</returns>
  49:          public string this[string columnName]
  50:          {
  51:              get
  52:              {
  53:                  string result = null;
  54:  
  55:                  //basically we need a case for each property you 
  56:                  //wish to validate
  57:                  switch (columnName)
  58:                  {
  59:                      case "Age":
  60:                          if (Age < 0)
  61:                          {
  62:                              result = "Age cant be < 0";
  63:                              combinedError.Append (result + "rn");
  64:                          }
  65:                          if (Age > 20)
  66:                          {
  67:                              result = "Age cant be > 20";
  68:                              combinedError.Append(result + "rn");
  69:                          }
  70:                          break;
  71:                  }
  72:                  return result;
  73:              }
  74:          }
  75:  
  76:          #endregion
  77:      }
  78:  }

Then we need to create some items that will use these bindings (only “Age” in this simple case)

   1:  <Window x:Class="Binding.Explicit.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      Title="Window1" Height="300" Width="300">
   5:  
   6:      <Window.Resources>
   7:  
   8:          <Style x:Key="textStyleTextBox" TargetType="TextBox">
   9:              <Setter Property="Foreground" Value="#333333" />
  10:              <Style.Triggers>
  11:                  <Trigger Property="Validation.HasError" Value="true">
  12:                      <Setter Property="ToolTip"
  13:                          Value="{Binding 
  14:                          RelativeSource={RelativeSource Self},
  15:                          Path=(Validation.Errors)[0].ErrorContent}"/>
  16:                  </Trigger>
  17:              </Style.Triggers>
  18:          </Style>
  19:  
  20:      </Window.Resources>
  21:  
  22:      <StackPanel Orientation="Vertical">
  23:          <Label Content="Age" Width="auto" Height="auto"/>
  24:          <TextBox x:Name="txtAge" Width="auto" Height="auto"
  25:                   Style="{StaticResource textStyleTextBox}"
  26:                   Text="{Binding Path=Age, 
  27:                          UpdateSourceTrigger=Explicit,
  28:                          ValidatesOnDataErrors=True}"/>
  29:          <StackPanel Orientation="Horizontal">
  30:              <Button x:Name="btnUpdate" Content="Update Object"
  31:                      Width="auto" Height="auto" Click="btnUpdate_Click"/>
  32:              <Button x:Name="btnCancel" Content="Cancel"
  33:                      Width="auto" Height="auto" Click="btnCancel_Click"/>
  34:          </StackPanel>
  35:      </StackPanel>
  36:  </Window>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Also notice that within this XAML is a Style that is used by the bound TextBox. This Style creates a red rectangle around the bound TextBox and the appropriate tooltip, when the bound object is in an invalid state (basically when Validation.HasError is true.

Also notice that because part of my requirements, was to be able to choose to update the underlying object or cancel any changes, I am using the “UpdateSourceTrigger=Explicit” within the Binding expression.

So as you can probably imagine, the last part is to do the code behind, where we actually do the binding update Explicitly (manually). So let’s see that shall we

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Controls;
   7:  using System.Windows.Data;
   8:  using System.Windows.Documents;
   9:  using System.Windows.Input;
  10:  using System.Windows.Media;
  11:  using System.Windows.Media.Imaging;
  12:  using System.Windows.Navigation;
  13:  using System.Windows.Shapes;
  14:  using System.ComponentModel;
  15:  
  16:  namespace Binding.Explicit
  17:  {
  18:      /// <summary>
  19:      /// Interaction logic for Window1.xaml
  20:      /// </summary>
  21:      public partial class Window1 : Window
  22:      {
  23:          public Window1()
  24:          {
  25:              InitializeComponent();
  26:              //Create a single Person to be used as the DataContext
  27:              this.DataContext = new Person();
  28:          }
  29:  
  30:          /// <summary>
  31:          /// Manually update the Binding SOurce, and see if its in a valid state.
  32:          /// If its not need to mark bindind as Invalid
  33:          /// </summary>
  34:          private void btnUpdate_Click(object sender, RoutedEventArgs e)
  35:          {
  36:              BindingExpression expression =
  37:                  txtAge.GetBindingExpression(TextBox.TextProperty);
  38:              expression.UpdateSource();
  39:  
  40:              string errorMessage = string.Empty;
  41:              if (!IsValid("Age", out errorMessage))
  42:              {
  43:                  ValidationError error = new ValidationError(
  44:                      new ExceptionValidationRule(),
  45:                      expression, errorMessage, null);
  46:                  Validation.MarkInvalid(expression, error);
  47:              }
  48:              else
  49:              {
  50:                  MessageBox.Show("Success, we could update DB here",
  51:                      "Success", MessageBoxButton.OK,
  52:                      MessageBoxImage.Information);
  53:              }
  54:          }
  55:  
  56:          /// <summary>
  57:          /// Attempts to see if the underlying data objects
  58:          /// bound property is in a valid state. The 
  59:          /// errorMessage parameter is also filled in by the
  60:          /// underlying data object
  61:          /// </summary>
  62:          /// <param name="path">The property to validate</param>
  63:          /// <param name="errorMessage">The errorMessage that the
  64:          /// unlerlying bound data object will fill in</param>
  65:          /// <returns>True if the underlying bound object is valid</returns>
  66:          private bool IsValid(string path, out string errorMessage)
  67:          {
  68:              errorMessage=((IDataErrorInfo)this.DataContext)[path];
  69:              return string.IsNullOrEmpty(errorMessage);
  70:          }
  71:  
  72:          /// <summary>
  73:          /// Exit, you could do something else if you wanted to
  74:          /// </summary>
  75:          private void btnCancel_Click(object sender, RoutedEventArgs e)
  76:          {
  77:              this.Close();
  78:          }
  79:  
  80:      }
  81:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }And that’s it, which gives us this sort of thing

db

Here is a small demo project should you wish to have the source code : bindingexplicit.zip

Advertisements

11 thoughts on “Explicitly Updating And Validating Databindings In WPF

  1. Martin says:

    You say
    “Also notice that within this XAML is a Style that is used by the bound TextBox. This Style creates a red rectangle around the bound TextBox and the appropriate tooltip, when the bound object is in an invalid state (basically when Validation.HasError is true.”
    It’s obvius where you set the ToolTip, but where comes the border from?
    I don’t get this 😦

  2. sacha says:

    that would be the line

    <Setter Property=”Foreground” Value=”#333333″ />

  3. Martin says:

    But this line isn’t within the block..!? And #333333 is a GREY color and not a red one?
    I’m sorry if this sounds stupid, but I’m just starting to learn 🙂

    PS: I just tried it. It is indeed NOT the Border, it’s the Text color… Is RED just the standard error-color?

  4. sacha says:

    Ok, ah ha. I didnt realise but have just experimented with this, and it seems to be the default that WPF uses for ValidationErrors, where the adorned UIElement is adorned with a red border.

    You can change this, have a look at

    http://www.codeproject.com/KB/WPF/wpfErrorProvider.aspx

    or

    http://msdn2.microsoft.com/en-us/library/ms752347.aspx#data_validation

    Sorry I must have messed up here. But those are good resources

  5. Alex Simkin says:

    To Martin:

    TextBox know how to display error by itself.

    http://www.codegod.de/WebAppCodeGod/wpf-idataerrorinfo-and-databinding-AID416.aspx

  6. sacha says:

    Yeah I thought it was some sort of default

    Thanks Alex

  7. Martin says:

    Yeah thanks Alex!

  8. Robin Wheeler says:

    Any idea how to handle validation on a property that is a collection? For instance I’d like to make sure the a collection contains at least one item: person.Friends > 0

  9. sacha says:

    There would be nothing wriong with you using that sort of validation, you probably need to set a new property to contain the collection, and put that on a seperate DataContext or something like that.

    BUt in theory it should work

  10. steve says:

    Sacha, Hi. I have been trying to find a way to not show errors until say a submit button is pressed. It seems that with bindings that if the fields data doesn’t match the say no empty string rule for example then there is a red border, or what ever template you choose, as soon as the form loads. Is suppressing errors until, say a submit button is pressed possible? Thanks

  11. sacha says:

    Steve

    You only read have 3 choices

    -PropertyChanged (the situation you are explaining I think)

    -LostFocus which is better, but still causes validation when you loose focus

    -Explicit, you update the source yourself, but this is harder, but could do what you want to do.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: