wpfui icon indicating copy to clipboard operation
wpfui copied to clipboard

ui:PasswordBox Binding issues

Open JeremyWu917 opened this issue 2 years ago • 8 comments

Describe the bug
Here are two bugs if I binding Text as password(mvvm two-way)

  1. The password&text change double when IsPasswordRevealed is false
  2. The text value is * when IsPasswordRevealed is false, if I post the text to db the value saved in db is *

To Reproduce
Just binding a property

<ui:PasswordBox
        Grid.Column="1"
        MaxLength="64"
        PasswordChar="*"
        Text="{Binding ViewModel.Config.Passcode}" />

Expected behavior
Actually I just wanna find a property to bing two-way

Screenshots
1 2

Desktop:

  • OS: [Windows11 21H2 22000.856]
  • .NET: [net6.0]
  • Version: [2.0.2]

Additional context
More info refer #373

Bugs may fire here

JeremyWu917 avatar Sep 06 '22 02:09 JeremyWu917

@pomianowski Please help to check and advise, thanks

JeremyWu917 avatar Sep 09 '22 03:09 JeremyWu917

@JeremyWu917 binding doesn't work for me, but the TextChanged event does.

                <ui:PasswordBox
                    Margin="0,8,0,0"
                    Icon="Password24"
                    MaxLength="64"
                    PasswordChar="*" TextChanged="PasswordBox_TextChanged"
                    PlaceholderText="Password" />
    private void PasswordBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
    {
        if (e.Source is PasswordBox box)
        {
            string password = box.Password;
        }
    }

chucker avatar Sep 17 '22 07:09 chucker

@JeremyWu917 binding doesn't work for me, but the TextChanged event does.

                <ui:PasswordBox
                    Margin="0,8,0,0"
                    Icon="Password24"
                    MaxLength="64"
                    PasswordChar="*" TextChanged="PasswordBox_TextChanged"
                    PlaceholderText="Password" />
    private void PasswordBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
    {
        if (e.Source is PasswordBox box)
        {
            string password = box.Password;
        }
    }

@chucker Thanks for your comment ☕ Yes you got it, but, you know what, if you wanna using MVVM to binding field TEXT to control the view, it still doesn't work, cause it will show double values when you perform some change in ViewModel. So, could you please have a further check? Thanks

@pomianowski Could you please have a look? Thanks

JeremyWu917 avatar Sep 17 '22 12:09 JeremyWu917

I don't think WPFUI's PasswordBox properly supports binding to the password at this point.

Instead, I use use WPFUI's control (but do use WPFUI's styling).

First, a utility class (I didn't write this; I think it originated at http://blog.functionalfun.net/2008/06/wpf-passwordbox-and-data-binding.html):

using System.Windows;
using System.Windows.Controls;

namespace Chucker.WpfUtils
{
    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

            if (dp is not PasswordBox box)
            {
                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);
        }
    }
}

Then I can do:

 <PasswordBox wpfutils:PasswordBoxAssistant.BindPassword="True"
              wpfutils:PasswordBoxAssistant.BoundPassword="{Binding MyPassword, Mode=TwoWay}"

Now you just have a regular string INPC property in your view model, e.g.

        private string _MyPassword = "";
        public string MyPassword
        {
            get => _MyPassword;
            set => SetProperty(ref _MyPassword, value);
        }

chucker avatar Sep 17 '22 13:09 chucker

@chucker Thanks bro. Yes you are right, I know the alt way to make the PosswordBox works just like you posted. But I still wanna the WPF-UI have this feature just like this in convenience.

JeremyWu917 avatar Sep 17 '22 15:09 JeremyWu917

Hey @JeremyWu917, the Password property of the PasswordBox, should allow two-way binding in the current development branch.

pomianowski avatar Sep 18 '22 09:09 pomianowski

@pomianowski Noted, thanks ☕

JeremyWu917 avatar Sep 18 '22 10:09 JeremyWu917

Hey @JeremyWu917, the Password property of the PasswordBox, should allow two-way binding in the current development branch.

@pomianowski

Sorry for the late response, after review and check, here is NEW issue about PlaceholderText as below gif.

  1. PlaceholderText can not disappear when IsPasswordRevealed is true
  2. PlaceholderText disappeared for ever when you clear the value

Thanks

2

JeremyWu917 avatar Sep 21 '22 02:09 JeremyWu917