I have a WPF form which has following controls -
Name - TextBox
Password - PasswordBox
Confirm Password - PasswordBox
Save - Button
What is want is - Enable save button only if all controls have valid value
Name should not be empty - To validate this i am using custom ValidationRule
Password should not be empty - To validate this i am using custom ValidationRule
Password and Confirm password should match.
I am using ValidationRule because it will not update source in case of error. [I cannot use IDataInfoError].
I am not able validate Password and Confirm Password match condition. Below is my XAML and Code behind -
<Window x:Class="WPFPasswordValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFPasswordValidation"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder/>
</Border>
</DockPanel>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="passwordBoxInError" TargetType="{x:Type PasswordBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TextBox Name="txtName" HorizontalAlignment="Left" Height="23" Margin="155,31,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Validation.Error="Validation_Error" Style="{StaticResource textBoxInError}" Validation.ErrorTemplate="{StaticResource validationTemplate}">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="Explicit" NotifyOnValidationError="true" Mode="TwoWay">
<Binding.ValidationRules>
<local:StringRangeValidationRule MinimumLength="1" MaximumLength="30"
ErrorMessage="Name is required and must be less than 30 letters."/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<PasswordBox HorizontalAlignment="Left" Margin="155,128,0,0" VerticalAlignment="Top" Width="120" Name="txtConfirmPass"/>
<TextBlock HorizontalAlignment="Left" Margin="106,35,0,0" TextWrapping="Wrap" VerticalAlignment="Top"><Run Text="Name"/></TextBlock>
<PasswordBox Name="txtPassword" HorizontalAlignment="Left" Margin="155,85,0,0" VerticalAlignment="Top" Width="120" PasswordChar="*" local:PasswordBoxAssistant.BindPassword="true" Validation.Error="Validation_Error" Style="{StaticResource passwordBoxInError}" Validation.ErrorTemplate="{StaticResource validationTemplate}">
<local:PasswordBoxAssistant.BoundPassword>
<Binding Path="Password" Mode="TwoWay" UpdateSourceTrigger="Explicit" NotifyOnValidationError="true">
<Binding.ValidationRules>
<local:StringRangeValidationRule MinimumLength="1" MaximumLength="30"
ErrorMessage="Password is required and must be less than 30 letters."/>
</Binding.ValidationRules>
</Binding>
</local:PasswordBoxAssistant.BoundPassword>
</PasswordBox>
<TextBlock HorizontalAlignment="Left" Margin="88,85,0,0" TextWrapping="Wrap" Text="Password" VerticalAlignment="Top"/>
<TextBlock HorizontalAlignment="Left" Margin="42,128,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="20"><Run Text="Confirm Password"/></TextBlock>
<Button Content="Save" HorizontalAlignment="Left" Margin="169,186,0,0" VerticalAlignment="Top" Width="75" Command="Save">
<Button.CommandBindings>
<CommandBinding Command="Save" Executed="Save_Executed" CanExecute="Save_CanExecute"/>
</Button.CommandBindings>
</Button>
</Grid>
Code behind C# -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Globalization;
namespace WPFPasswordValidation
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private int _noOfErrors = 0;
BindingExpression be = null;
public MainWindow()
{
InitializeComponent();
Person p = new Person();
p.Name = "abc";
p.Password = "1234";
this.DataContext = p;
}
private void Validation_Error(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added)
_noOfErrors++;
else
_noOfErrors--;
}
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
private void Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
be = this.txtName.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
be = this.txtPassword.GetBindingExpression(PasswordBoxAssistant.BoundPassword);
be.UpdateSource();
e.CanExecute = _noOfErrors == 0;
e.Handled = true;
}
}
public class StringRangeValidationRule : ValidationRule
{
private int _minimumLength = -1;
private int _maximumLength = -1;
private string _errorMessage;
public int MinimumLength
{
get { return _minimumLength; }
set { _minimumLength = value; }
}
public int MaximumLength
{
get { return _maximumLength; }
set { _maximumLength = value; }
}
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
ValidationResult result = new ValidationResult(true, null);
string inputString = (value ?? string.Empty).ToString();
if (inputString == null)
{
inputString = string.Empty;
}
if (inputString.Length < this.MinimumLength ||
(this.MaximumLength > 0 &&
inputString.Length > this.MaximumLength))
{
result = new ValidationResult(false, this.ErrorMessage);
}
return result;
}
}
public static class PasswordBoxAssistant
{
public static readonly DependencyProperty BoundPassword =
DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
"BindPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty UpdatingPassword =
DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false));
private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = d as PasswordBox;
// only handle this event when the property is attached to a PasswordBox
// and when the BindPassword attached property has been set to true
if (d == null || !GetBindPassword(d))
{
return;
}
// avoid recursive updating by ignoring the box's changed event
box.PasswordChanged -= HandlePasswordChanged;
string newPassword = (string)e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
PasswordBox box = dp as PasswordBox;
if (box == null)
{
return;
}
bool wasBound = (bool)(e.OldValue);
bool needToBind = (bool)(e.NewValue);
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
// set a flag to indicate that we're updating the password
SetUpdatingPassword(box, true);
// push the new password into the BoundPassword property
SetBoundPassword(box, box.Password);
SetUpdatingPassword(box, false);
}
public static void SetBindPassword(DependencyObject dp, bool value)
{
dp.SetValue(BindPassword, value);
}
public static bool GetBindPassword(DependencyObject dp)
{
return (bool)dp.GetValue(BindPassword);
}
public static string GetBoundPassword(DependencyObject dp)
{
return (string)dp.GetValue(BoundPassword);
}
public static void SetBoundPassword(DependencyObject dp, string value)
{
dp.SetValue(BoundPassword, value);
}
private static bool GetUpdatingPassword(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatingPassword);
}
private static void SetUpdatingPassword(DependencyObject dp, bool value)
{
dp.SetValue(UpdatingPassword, value);
}
}
public class Person
{
private string _name;
private string _password;
public string Password
{
get { return _password; }
set { _password = value; }
}
public string Name
{
get { return _name; }
set
{
_name = value;
}
}
}
}