使用 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);
我遇到了如何在 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);