UserControl 中的 PasswordBox - DataBinding 有效但框看起来是空的

PasswordBox in UserControl - DataBinding works but box looks empty

前期info/question: 下面发布的所有内容都有效(数据绑定 PasswordBox 字符串),除了我的 PasswordBox 看起来 是空的,即使它的内容是正确的。

详情: 我有一个带有 PasswordBox 和 DependencyProperty 的 UserControl。该 DP 可用于 set/retrieve 通过数据绑定来自该 PWBox 的 SecureString。

public partial class PasswordUserControl : UserControl
{       
    public SecureString Password
    {
        get { return (SecureString)GetValue(PasswordProperty); }
        set { SetValue(PasswordProperty, value); }
    }

    // https://msdn.microsoft.com/en-us/library/ms750428(v=vs.110).aspx
    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.Register("Password", typeof(SecureString), typeof(PasswordUserControl),
            new PropertyMetadata(default(SecureString)));

    public PasswordUserControl()
    {
        InitializeComponent();

        PasswordEntryBox.PasswordChanged += (sender, args) => {
            Password = ((PasswordBox)sender).SecurePassword; };
    }       
}

简单XAML:

<UserControl x:Class="WpfClient.PasswordUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:WpfClient"
         mc:Ignorable="d" 
         d:SizeToContent="true">

<StackPanel>
    <DockPanel>
        <Label Content="Password" Margin="0,0,5,0" DockPanel.Dock="Left"/>
        <PasswordBox Name="PasswordEntryBox" Width="200" DockPanel.Dock="Right" />
    </DockPanel>
</StackPanel>

在另一个位置简单使用该控件:

<local:PasswordUserControl Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

"Binding Password"感觉变成了一个SecureString:

public SecureString Password
    {
        get { return m_Password; }
        set
        {
            m_Password = value;
            RaisePropertyChanged("Password");
        }
    }

问题: 数据绑定似乎是双向的;我得到正确的用户输入(触发登录工作)。 PropertyChanged 也有效:对于添加的每个字符,我都会得到一个 属性 更新。

什么不起作用:实际的 PasswordBox 看起来是空的。如果我先在代码中设置密码并查看控件,它不会为已经输入的数据绑定文本显示一堆匿名圆圈字符(但字符串本身是正确的 - 调试器如此说并且实际登录工作!)。如果我手动将密码文本输入框中,我确实会为我的输入获取匿名字符,但我的输入不会添加到假定数据绑定的现有字符串中。它似乎擦除数据绑定字符串并从头开始创建一个新字符串。

我做错了什么?

为了能够在 PasswordBox 中看到任何密码字符,您需要将其 Password 属性 设置为 string。这意味着您需要从 SecureString 对象中提取纯密码字符串。

只要 Password 依赖项 属性 设置为新的 SecureString 值,您就应该这样做。

您可以将 PropertyChangedCallback 添加到依赖项 属性 并按如下方式实现 UserControl

public partial class PasswordUserControl : UserControl
{
    public SecureString Password
    {
        get { return (SecureString)GetValue(PasswordProperty); }
        set { SetValue(PasswordProperty, value); }
    }

    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.Register("Password", typeof(SecureString), typeof(PasswordUserControl),
            new PropertyMetadata(default(SecureString), new PropertyChangedCallback(OnPasswordChanged)));


    public PasswordUserControl()
    {
        InitializeComponent();
        PasswordEntryBox.PasswordChanged += PasswordEntryBox_PasswordChanged;
    }

    private bool _handleEvent = true;
    private void PasswordEntryBox_PasswordChanged(object sender, RoutedEventArgs e)
    {
       if(_handleEvent)
            Password = ((PasswordBox)sender).SecurePassword;
    }

    private static void OnPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        SecureString ss = e.NewValue as SecureString;
        if(ss != null)
        {
            PasswordUserControl control = d as PasswordUserControl;
            if(control != null)
            {
                control._handleEvent = false;
                control.PasswordEntryBox.Password = ConvertToUnsecureString(ss);
                control._handleEvent = true;
            }
        }
    }

    public static string ConvertToUnsecureString(SecureString securePassword)
    {
        if (securePassword == null)
            throw new ArgumentNullException("securePassword");

        IntPtr unmanagedString = IntPtr.Zero;
        try
        {
            unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
            return Marshal.PtrToStringUni(unmanagedString);
        }
        finally
        {
            Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
        }
    }
}

LoginPassword.Password = Globals.credentials == null ? "": new NetworkCredential("", Globals.credentials.SecurePassword).Password;

只需在xaml.cs文件中放一行即可。将密码框命名为 "LoginPassword".