0

I have a WPF application and am using MVVM pattern. On one of my User Controls, I have two PasswordBoxes to compare the user entered passwords. I am trying to implement a compare behavior whose result will determine if the submit button should be enabled or disabled in the ViewModel. I am kinda stuck.

EDIT: This is not a duplicate question as @Dbl mentioned in a comment. The duplicate question mentioned in his comment is about how to compare two SecureString data types. My question is totally different. It is about how to compare two object values - does not matter if they are SecureString or not - in a XAML UserControl without breaking the MVVM pattern where a behavior attached to one element needs to know about the value of another element inside the behavior. Also, this behavior needs to be able to access the underlying ViewModel of the the element and update an INPC property in the ViewModel.

Here is my XAML (removed quite a bit of elements for brevity):

<UserControl 
x:Class="DynaProPOS.WPF.Views.AppUser" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:prism="http://prismlibrary.com/" 
xmlns:syncfusion="http://schemas.syncfusion.com/wpf" 
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
xmlns:behavior="clr-namespace:DynaProPOS.WPF.Behaviors" 
xmlns:custProps="clr-namespace:DynaProPOS.WPF.CustomProperties"
prism:ViewModelLocator.AutoWireViewModel="True" 
Background="{DynamicResource BackgroundBrush}">
<Border Width="750" Height="260" BorderBrush="White" BorderThickness="2">
    <Grid x:Name="grid" KeyboardNavigation.TabNavigation="Cycle" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Width="Auto">
        <PasswordBox TabIndex="3" Grid.Row="3" Grid.Column="1" Margin="2" x:Name="Password1" HorizontalAlignment="Stretch" VerticalAlignment="Center">
            <i:Interaction.Behaviors>
                <behavior:PasswordBoxBindingBehavior Password="{Binding Password}" />
            </i:Interaction.Behaviors>
        </PasswordBox>
        <PasswordBox TabIndex="4" Grid.Row="4" Grid.Column="1" Margin="2,18,2,4" x:Name="Password2" HorizontalAlignment="Stretch" VerticalAlignment="Center">
            <i:Interaction.Behaviors>
                <behavior:ComparePasswordBehavior OriginalPassword="{Binding ElementName=Password1, Path=Password}"/>
            </i:Interaction.Behaviors>
        </PasswordBox>
        <Grid Grid.Column="3" Grid.RowSpan="5" VerticalAlignment="Stretch">
            <Grid.RowDefinitions>
                <RowDefinition Height="10*" />
                <RowDefinition Height="90*" />
            </Grid.RowDefinitions>
        </Grid>
        <syncfusion:ButtonAdv TabIndex="6" x:Name="RegisterButton" Grid.Row="5" Grid.Column="4" Margin="5" HorizontalAlignment="Right" Label="Submit" VerticalAlignment="Center" />
    </Grid>
</Border>

And Here is my ViewModel (again, remove lot of code for brevity).

public class AppUserViewModel : BindableBase
{
    private bool isEnabled;
    public AppUserViewModel(IAuthenticationService _authService)
    {
        authService = _authService;
        RegisterCommand = new DelegateCommand( async () => await RegisterUserAsync() );
    }

    public bool IsEnabled
    {
        get { return isEnabled; }
        set { SetProperty( ref isEnabled, value ); }
    }
}

And finally, here is my Behavior class.

public class ComparePasswordBehavior : Behavior<PasswordBox>
{
    protected override void OnAttached()
    {
        AssociatedObject.LostFocus += OnComparePasswordLostFocus;
        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        AssociatedObject.LostFocus -= OnComparePasswordLostFocus;
        base.OnDetaching();
    }

    public static readonly DependencyProperty OriginalPasswordProperty =
        DependencyProperty.Register("OriginalPassword", typeof(SecureString), typeof(ComparePasswordBehavior), new PropertyMetadata(null));

    private static void OnComparePasswordLostFocus( object sender, RoutedEventArgs e )
    {
        PasswordBox pswdBox = sender as PasswordBox;
        var behavior = Interaction.GetBehaviors(pswdBox).OfType<ComparePasswordBehavior>().FirstOrDefault();

        if (behavior != null)
        {
            var binding = BindingOperations.GetBindingExpression( behavior, OriginalPasswordProperty);
            PropertyInfo propInfo = binding.DataItem.GetType().GetProperty(binding.ParentBinding.Path.Path);
           // at this point I am stumped.  I don't seems to be able to
           // retrieve the value from the original password box element.
           // I am also not able to set the IsEnabled property of the ViewModel.
        }
    }

    public SecureString OriginalPassword
    {
        get { return ( SecureString )GetValue( OriginalPasswordProperty ); }
        set { SetValue( OriginalPasswordProperty, ( SecureString )value ); }
    }
}

I have a dependency property defined in my behavior to hold the password value from the original password box. In the lostfocus event of my behavior, I need to compare the two passwords and set the IsEnabled Property of my ViewModel accordingly.

I need to do two things here. I need to retrieve the Password value from Password1 PasswordBox Element I also need to set the IsEnabled Property of my ViewModel based on the password comparison result. Can somebody please help? I have been stuck here for a day now. Thanks.

Babu Mannavalappil
  • 447
  • 2
  • 9
  • 27
  • Possible duplicate of [C# - compare two SecureStrings for equality](https://stackoverflow.com/questions/4502676/c-sharp-compare-two-securestrings-for-equality) – Dbl Nov 03 '17 at 19:12
  • don't delete your question though - in a way i expect this question to hit more visits from search engines – Dbl Nov 03 '17 at 19:13

1 Answers1

1

The instance of ComparePasswordBehavior doesn't know anything about the instance of PasswordBoxBindingBehavior and vice versa. Besides, it is the resposibility of the view model to compare the passwords and set the IsEnabled property.

The behaviour should just transfer the password from the PasswordBox to the view model. You should store the SecureStrings in the view model and do the comparison in there.

Please refer to the following sample code.

Behavior:

public class PasswordBehavior : Behavior<PasswordBox>
{
    protected override void OnAttached()
    {
        AssociatedObject.LostFocus += OnComparePasswordLostFocus;
        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        AssociatedObject.LostFocus -= OnComparePasswordLostFocus;
        base.OnDetaching();
    }

    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.Register("Password", typeof(SecureString), typeof(PasswordBehavior), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });


    public SecureString Password
    {
        get { return (SecureString)GetValue(PasswordProperty); }
        set { SetValue(PasswordProperty, value); }
    }

    private static void OnComparePasswordLostFocus(object sender, RoutedEventArgs e)
    {
        PasswordBox pswdBox = sender as PasswordBox;
        PasswordBehavior behavior = Interaction.GetBehaviors(pswdBox).OfType<PasswordBehavior>().FirstOrDefault();
        if (behavior != null)
        {
            behavior.Password = pswdBox.SecurePassword;
        }
    }
}

View Model:

public class AppUserViewModel : BindableBase
{
    private bool isEnabled;
    public bool IsEnabled
    {
        get { return isEnabled; }
        set { SetProperty(ref isEnabled, value); }
    }

    private SecureString _password1;
    public SecureString Password1
    {
        get { return _password1; }
        set
        {
            if (SetProperty(ref _password1, value))
                ComparePasswords();
        }
    }

    private SecureString _password2;
    public SecureString Password2
    {
        get { return _password2; }
        set
        {
            if (SetProperty(ref _password2, value))
                ComparePasswords();
        }
    }

    private void ComparePasswords()
    {
        IsEnabled = (_password1 != null || _password2 != null) 
            && SecureStringToString(_password1) == SecureStringToString(_password2);
    }

    private string SecureStringToString(SecureString value)
    {
        IntPtr valuePtr = IntPtr.Zero;
        try
        {
            valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
            return Marshal.PtrToStringUni(valuePtr);
        }
        finally
        {
            Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
        }
    }
}

View:

<PasswordBox>
    <i:Interaction.Behaviors>
        <behavior:PasswordBehavior Password="{Binding Password1}" />
    </i:Interaction.Behaviors>
</PasswordBox>
<PasswordBox>
    <i:Interaction.Behaviors>
        <behavior:PasswordBehavior Password="{Binding Password2}"/>
    </i:Interaction.Behaviors>
</PasswordBox>
mm8
  • 163,881
  • 10
  • 57
  • 88