1

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;                
        }
    }        
}

}

4

3 回答 3

2

Maybe you can validate those camp with event

<Grid>
    <TextBox Name="txtName" TextChanged="Content_TextChanged"/>
    <PasswordBox Name="txtPass" PasswordChar="*" PasswordChanged="Content_TextChanged"/>
    <PasswordBox Name="txtConfirmPass" PasswordChar="*" PasswordChanged="Content_TextChanged"/>
    <Button Name="btSave" Content="Save" IsEnabled="False"/>
</Grid>

Event:

private void Content_TextChanged(object sender, TextChangedEventArgs e)
{
    if (!txtName.Text.Equals(String.Empty) && txtPass.Password.Equals(txtConfirmPass.Password))
        btSave.IsEnabled = true;
    else
        btSave.IsEnabled = false;
}
于 2013-09-13T15:14:10.123 回答
1

As I'm not a fan of ValidationRules, I can't directly answer your question, but I just wanted to show you that there is a much easier way to do this.

private void Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    Person person = (Person)DataContext;
    e.CanExecute = !string.IsNullOrEmpty(person.Name) && 
        !string.IsNullOrEmpty(person.Password) && 
        !string.IsNullOrEmpty(txtConfirmPass.Text) && 
        txtConfirmPass.Text == person.Password;
}

If you really need red borders on your controls when there are errors as well, then you can implement the IDataErrorInfo interface on your data type classes, eg. Person.

于 2013-09-13T15:23:16.187 回答
1
 if (txtpassword1.Text == "" || txtpassword2.Text == "")
        {
            MessageBox.Show("Please enter values");
            txtpassword1.Focus();
            return;
        }
        else if (txtpassword1.Text != txtpassword2.Text)
        {
            MessageBox.Show("Password not matching");
            txtpassword2.Focus();
            return;
        }
于 2016-04-14T16:10:22.113 回答