What is wrong with my WPF PasswordBox?

695 Views Asked by At

I have a login view with a PasswordBox marked up as follows:

<PasswordBox x:Name="PasswordText" Width="Auto" Margin="5" PasswordChanged="PasswordTextOnPasswordChanged" />

The problem with it is that the caret remains at the extreme left of its text box; it doesn't move forward as you type, so the last character you type ends up the first character in the text box. E.g. when I type the password "123", the content of the PasswordBox, after conversion to a normal string, is "321". It is not the conversion code, for if I pre-populate the control with a password, i.e. no typing involved, its content converts to the correct plain string.

What is going on here? It seems buggy to me that the caret doesn't follow ones typing.

Behind the scenes (i.e. code-behind) I have:

private void PasswordTextOnPasswordChanged(object sender, RoutedEventArgs e)
{
    if (!_suppressPasswordChanged)
    {
        ((LoginFormViewModel)DataContext).Password = PasswordText.SecurePassword;
    }
}

_suppressPasswordChanged is just to avoid triggering the PasswordTextOnPasswordChanged handler when I explicitly set the password.

public void SetPassword(string password)
{
    try
    {
        _suppressPasswordChanged = true;
        PasswordText.Password = password;
    }
    finally
    {
        _suppressPasswordChanged = false;
    }
}
1

There are 1 best solutions below

0
On BEST ANSWER

A PasswordBox has no data binding, and thus requires code in the view to update the view-model when the user types in that box. I originally had this event handler:

private void PasswordTextOnPasswordChanged(object sender, RoutedEventArgs e)
{
    ((LoginFormViewModel)DataContext).Password = PasswordText.SecurePassword;
}

Then I wanted to be able to set the Password property in the view-model, and had to add this view method:

public void SetPassword(string password)
{
    PasswordText.Password = password;
}

Called from the setter:

set
{
    if (value == _password) return;
    _password = value;
    ((LoginFormPopup)View).SetPassword(_password.ToClearString());
    OnPropertyChanged();
}

This ended up with SetPassword triggering PasswordTextOnPasswordChanged, which called the setter again, as it should normally, but not when recursively triggered by code. My first and embarrassing attempt at preventing these methods calling each other was:

private bool suppressPasswordChanged;
private void PasswordTextOnPasswordChanged(object sender, RoutedEventArgs e)
{
    if (!suppressPasswordChanged)
    {
        ((LoginFormViewModel)DataContext).Password = PasswordText.SecurePassword;
    }
}

public void SetPassword(string password)
{
    try
    {
        suppressPasswordChanged = true;
        PasswordText.Password = password;
    }
    finally
    {
        suppressPasswordChanged = false;
    }
}

but because SetPassword always reset suppressPasswordChanged in its finally, it would still always get called from PasswordTextOnPasswordChanged.

My current, working, but maybe slightly smelly, solution is to move the responsibility of setting the 'do-not-call' flag to the event handler:

private bool _textChangedEventBusy;
private void PasswordTextOnPasswordChanged(object sender, RoutedEventArgs e)
{
    try
    {
        _textChangedEventBusy = true;
        ((LoginFormViewModel)DataContext).Password = PasswordText.SecurePassword;
    }
    finally
    {
        _textChangedEventBusy = false;
    }
}

public void SetPassword(string password)
{
    if (!_textChangedEventBusy)
    {
        PasswordText.Password = password;
    }
}

Now, SetPassword is only ever called from the setter in the view-model, and then setting the caret to the beginning of the text is ideal.