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 things 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: 30: 31: 32: 33: public string Error
34: {
35: get
36: {
37: return combinedError.ToString();
38: }
39: }
40:
41: 42: 43: 44: 45: 46: 47: 48: 49: public string this[string columnName]
50: {
51: get
52: {
53: string result = null;
54:
55:
56:
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>
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: 19: 20: 21: public partial class Window1 : Window
22: {
23: public Window1()
24: {
25: InitializeComponent();
26:
27: this.DataContext = new Person();
28: }
29:
30: 31: 32: 33: 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: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: private bool IsValid(string path, out string errorMessage)
67: {
68: errorMessage=((IDataErrorInfo)this.DataContext)[path];
69: return string.IsNullOrEmpty(errorMessage);
70: }
71:
72: 73: 74: 75: private void btnCancel_Click(object sender, RoutedEventArgs e)
76: {
77: this.Close();
78: }
79:
80: }
81: }
And that’s it, which gives us this sort of thing:
Here is a small demo project, should you wish to have the source code.