PasswordBox в UserControl - DataBinding работает, но поле выглядит пустым

Предварительная информация / вопрос: все, что размещено ниже, работает (привязка данных к строке PasswordBox), за исключением того, что мой PasswordBox выглядит пустым, даже если его содержимое корректно.

Детали: у меня есть UserControl с PasswordBox и DependencyProperty. Этот DP может использоваться для установки / извлечения SecureString из этого PWBox через привязку данных.

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}" />

"Пароль привязки" выглядит как SecureString:

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

Вопрос: привязка данных, кажется, работает в обоих направлениях; Я получаю правильный ввод пользователя (сработали входы в систему). PropertyChanged также работает: для каждого добавленного символа я получаю обновление свойства.

Что не работает: фактический PasswordBox выглядит пустым. Если я сначала установил пароль в коде и посмотрел на элемент управления, он не отображает кучу анонимных символов круга для уже введенного текста с привязкой к данным (но сама строка верна - так говорит отладчик, и действительные входы в систему работают!). Если я ввожу текст пароля в поле вручную, я получаю анонимные символы для своей записи, но мой ввод не добавляется к существующей строке, которая предположительно связана с данными. Это, по-видимому, стирает связанную с данными строку и создает новую с нуля.

Что я делаю неправильно?

2 ответа

Решение

Чтобы вы могли видеть любые символы пароля в 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".

Другие вопросы по тегам