使用 ReactiveUI 与 PasswordBox 的两种绑定方式

Two way binding with PasswordBox using ReactiveUI

我遇到了如何在 PasswordBox 中正确绑定密码的问题。我当前的解决方案几乎可以工作。问题是打字时光标停留在开头,而不是结尾处的文本。输入 12345,我的视图模型收到值 54321

我正在使用 WFP 和 XAML。视图后面的代码如下所示:

Observable.FromEventPattern(SecretKey, nameof(SecretKey.PasswordChanged))
    .Subscribe(evt => ViewModel.SecretKey = SecretKey.Password)
    .DisposeWith(disposableRegistration);

this.OneWayBind(ViewModel, viewModel => viewModel.SecretKey, view => view.SecretKey.Password)
    .DisposeWith(disposableRegistration);

当其中一个构造被注释时,游标行为正确,所以问题在于两种方式的绑定,我不知道如何解决它。因为你现在 PasswordBox.Password 无法从视图绑定到视图模型,所以我需要使用事件。

我认为没有 'offical' 的方法,我不确定你是否应该这样做。
但是你可以在设置值后手动设置光标位置来解决这个问题(source)。

private void SetCursor(PasswordBox passwordBox, int index)
{
    passwordBox.GetType().GetMethod("Select", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(passwordBox, new object[] { index, 0 });
}

'hack'-绑定:

this.WhenAnyValue(x => x.ViewModel.SecretKey)
    .Where(pw => pw != null)
    .Do(pw =>
    {
        secretKey.Password = pw;
        SetCursor(secretKey, pw.Length);
    })
    .Subscribe()
    .DisposeWith(disposableRegistration);

PasswordBox 通常由附加属性处理。我进行了研究,ReactiveUi 绑定无法处理带有附加 属性 的双向绑定。另一方面,当您使用 ReactiveUi 时,XAML 中的绑定没有危险信号 - 不鼓励,但不禁止。请记住,纯 ReactiveUi 无法实现附加属性,我决定在绑定 PasswordBox 时例外,我认为这是合理和正确的。

我使用了 PasswordHelper,它实现了 wpftutorial.net

教程中的附加属性
public static class PasswordHelper
{
    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.RegisterAttached("Password",
        typeof(string), typeof(PasswordHelper),
        new FrameworkPropertyMetadata(string.Empty, OnPasswordPropertyChanged));
 
    public static readonly DependencyProperty AttachProperty =
        DependencyProperty.RegisterAttached("Attach",
        typeof(bool), typeof(PasswordHelper), new PropertyMetadata(false, Attach));
 
    private static readonly DependencyProperty IsUpdatingProperty =
       DependencyProperty.RegisterAttached("IsUpdating", typeof(bool), 
       typeof(PasswordHelper));
 
 
    public static void SetAttach(DependencyObject dp, bool value)
    {
        dp.SetValue(AttachProperty, value);
    }
 
    public static bool GetAttach(DependencyObject dp)
    {
        return (bool)dp.GetValue(AttachProperty);
    }
 
    public static string GetPassword(DependencyObject dp)
    {
        return (string)dp.GetValue(PasswordProperty);
    }
 
    public static void SetPassword(DependencyObject dp, string value)
    {
        dp.SetValue(PasswordProperty, value);
    }
 
    private static bool GetIsUpdating(DependencyObject dp)
    {
        return (bool)dp.GetValue(IsUpdatingProperty);
    }
 
    private static void SetIsUpdating(DependencyObject dp, bool value)
    {
        dp.SetValue(IsUpdatingProperty, value);
    }
 
    private static void OnPasswordPropertyChanged(DependencyObject sender,
        DependencyPropertyChangedEventArgs e)
    {
        PasswordBox passwordBox = sender as PasswordBox;
        passwordBox.PasswordChanged -= PasswordChanged;
 
        if (!(bool)GetIsUpdating(passwordBox))
        {
            passwordBox.Password = (string)e.NewValue;
        }
        passwordBox.PasswordChanged += PasswordChanged;
    }
 
    private static void Attach(DependencyObject sender,
        DependencyPropertyChangedEventArgs e)
    {
        PasswordBox passwordBox = sender as PasswordBox;
 
        if (passwordBox == null)
            return;
 
        if ((bool)e.OldValue)
        {
            passwordBox.PasswordChanged -= PasswordChanged;
        }
 
        if ((bool)e.NewValue)
        {
            passwordBox.PasswordChanged += PasswordChanged;
        }
    }
 
    private static void PasswordChanged(object sender, RoutedEventArgs e)
    {
        PasswordBox passwordBox = sender as PasswordBox;
        SetIsUpdating(passwordBox, true);
        SetPassword(passwordBox, passwordBox.Password);
        SetIsUpdating(passwordBox, false);
    }
}

在 XAML 中,我使用带有附加属性的 PasswordBox 的标准绑定:

<PasswordBox Name="PasswordBox"
             local:PasswordHelper.Attach="True"
             local:PasswordHelper.Password="{Binding Password, Mode=TwoWay}"/>

在后面的代码中,我将 ViewModel 绑定到 DataContext

this.WhenActivated(d =>
{
    this.WhenAnyValue(x => x.ViewModel).BindTo(this, x => x.DataContext).DisposeWith(d);
});

您可以在 GitHub repository 中找到示例应用程序。

我最近遇到了类似的问题,下一个解决方案对我来说效果很好。可能会对其他人有所帮助。

ViewModel.WhenAnyValue(x => x.Password)
    .Where(password => string.Equals(password, PasswordBox.Password, StringComparison.CurrentCulture) is false)
    .ObserveOn(RxApp.MainThreadScheduler)
    .Subscribe(password => PasswordBox.Password = password)
    .DisposeWith(disposableRegistration);

Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(
        h => PasswordBox.PasswordChanged += h,
        h => PasswordBox.PasswordChanged -= h)
    .Select(_ => PasswordBox.Password)
    .Where(password => string.Equals(password, ViewModel.Password, StringComparison.CurrentCulture) is false)
    .ObserveOn(RxApp.MainThreadScheduler)
    .Subscribe(password => ViewModel.Password = password)
    .DisposeWith(disposableRegistration);