Caliburn.Micro 支持密码框吗?
Caliburn.Micro support for PasswordBox?
http://caliburnmicro.com 的 Caliburn.Micro 主页提出了以下声明,但我无法使用我从该示例中想到的任何变体使 CM 与 PasswordBox 控件一起工作。无论如何,看不出这是如何工作的,因为名称大小写不同。有没有人有一个允许我获取 PasswordBox 值的 CM 示例?是否需要特定版本的 CM?我是 CM 的 运行 1.5.2 版。理想情况下 w/o 使用附加属性,但如果可以使用 CM,那么唯一的方法就可以了。请不要讲安全问题,因为这不是我的问题。
使用参数和保护方法自动在视图和视图模型之间应用方法
<StackPanel>
<TextBox x:Name="Username" />
<PasswordBox x:Name="Password" />
<Button x:Name="Login" Content="Log in" />
</StackPanel>
public bool CanLogin(string username, string password)
{
return !String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password);
}
public string Login(string username, string password)
{
...
}
我只能让它与依赖属性一起工作,有效地绕过了 Caliburn.Micro 提供的约定绑定优点。我知道这不是您的理想选择,但实际上这是我经常使用的解决方案。我相信当我历史上遇到这个障碍时,我在 Whosebug 上发现了 this post,它引导我朝这个方向前进。供您考虑:
public class BoundPasswordBox
{
private static bool _updating = false;
/// <summary>
/// BoundPassword Attached Dependency Property
/// </summary>
public static readonly DependencyProperty BoundPasswordProperty =
DependencyProperty.RegisterAttached("BoundPassword",
typeof(string),
typeof(BoundPasswordBox),
new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));
/// <summary>
/// Gets the BoundPassword property.
/// </summary>
public static string GetBoundPassword(DependencyObject d)
{
return (string)d.GetValue(BoundPasswordProperty);
}
/// <summary>
/// Sets the BoundPassword property.
/// </summary>
public static void SetBoundPassword(DependencyObject d, string value)
{
d.SetValue(BoundPasswordProperty, value);
}
/// <summary>
/// Handles changes to the BoundPassword property.
/// </summary>
private static void OnBoundPasswordChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
PasswordBox password = d as PasswordBox;
if (password != null)
{
// Disconnect the handler while we're updating.
password.PasswordChanged -= PasswordChanged;
}
if (e.NewValue != null)
{
if (!_updating)
{
password.Password = e.NewValue.ToString();
}
}
else
{
password.Password = string.Empty;
}
// Now, reconnect the handler.
password.PasswordChanged += PasswordChanged;
}
/// <summary>
/// Handles the password change event.
/// </summary>
static void PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox password = sender as PasswordBox;
_updating = true;
SetBoundPassword(password, password.Password);
_updating = false;
}
}
然后,在您的 XAML 中:
<PasswordBox pwbx:BoundPasswordBox.BoundPassword="{Binding UserPassword, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnValidationError=True,ValidatesOnDataErrors=True}" />
并且 pwbx 被发现为 Window 标签上的命名空间:
<Window x:Class="MyProject.Views.LoginView"
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"
mc:Ignorable="d"
xmlns:pwbx="clr-namespace:MyProject.Client.Controls">
视图模型:
using Caliburn.Micro;
using MyProject.Core;
using MyProject.Repositories;
using MyProject.Types;
using MyProject.ViewModels.Interfaces;
namespace MyProject.ViewModels
{
public class LoginViewModel : Screen, ILoginViewModel
{
private readonly IWindowManager _windowManager;
private readonly IUnitRepository _unitRepository;
public bool IsLoginValid { get; set; }
public Unit LoggedInUnit { get; set; }
private string _password;
public string UserPassword
{
get { return _password; }
set
{
_password = value;
NotifyOfPropertyChange(() => UserPassword);
NotifyOfPropertyChange(() => CanLogin);
}
}
private string _name;
public string Username
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange(() => Username);
NotifyOfPropertyChange(() => CanLogin);
}
}
public LoginViewModel(IWindowManager windowManager,IUnitRepository unitRepository)
{
_windowManager = windowManager;
_unitRepository = unitRepository;
DisplayName = "MyProject - Login";
Version = ApplicationVersionRepository.GetVersion();
}
public string Version { get; private set; }
public void Login()
{
// Login logic
var credentials = new UserCredentials { Username = Username, Password=UserPassword };
var resp = _unitRepository.AuthenticateUnit(credentials);
if (resp == null) return;
if (resp.IsValid)
{
IsLoginValid = true;
LoggedInUnit = resp.Unit;
TryClose();
}
else
{
var dialog = new MessageBoxViewModel(DialogType.Warning, DialogButton.Ok, "Login Failed", "Login Error: " + resp.InvalidReason);
_windowManager.ShowDialog(dialog);
}
}
public bool CanLogin
{
get
{
return !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(UserPassword);
}
}
}
}
这是一个更简化的示例,包括绑定约定,以便 PasswordBox
在 Caliburn.Micro Just Works™ 中绑定:
public static class PasswordBoxHelper
{
public static readonly DependencyProperty BoundPasswordProperty =
DependencyProperty.RegisterAttached("BoundPassword",
typeof(string),
typeof(PasswordBoxHelper),
new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static string GetBoundPassword(DependencyObject d)
{
var box = d as PasswordBox;
if (box != null)
{
// this funny little dance here ensures that we've hooked the
// PasswordChanged event once, and only once.
box.PasswordChanged -= PasswordChanged;
box.PasswordChanged += PasswordChanged;
}
return (string)d.GetValue(BoundPasswordProperty);
}
public static void SetBoundPassword(DependencyObject d, string value)
{
if (string.Equals(value, GetBoundPassword(d)))
return; // and this is how we prevent infinite recursion
d.SetValue(BoundPasswordProperty, value);
}
private static void OnBoundPasswordChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var box = d as PasswordBox;
if (box == null)
return;
box.Password = GetBoundPassword(d);
}
private static void PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox password = sender as PasswordBox;
SetBoundPassword(password, password.Password);
// set cursor past the last character in the password box
password.GetType().GetMethod("Select", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(password, new object[] { password.Password.Length, 0 });
}
}
然后,在您的引导程序中:
public sealed class Bootstrapper : BootstrapperBase
{
public Bootstrapper()
{
Initialize();
ConventionManager.AddElementConvention<PasswordBox>(
PasswordBoxHelper.BoundPasswordProperty,
"Password",
"PasswordChanged");
}
// other bootstrapper stuff here
}
此处提供的解决方案似乎不必要地复杂。
我们可以非常轻松地使用 Caliburn.Micro 操作将我们的密码发送到 ViewModel。
XAML:
<PasswordBox cal:Message.Attach="[Event PasswordChanged] = [Action OnPasswordChanged($source)]" />
视图模型:
public void OnPasswordChanged(PasswordBox source)
{
password = source.Password;
}
然后记得清除密码字段,这样它们就不会保留在内存中。
注意: 显然,此解决方案不允许您从 ViewModel 轻松更改密码,如果有必要,那么最好使用随附的 属性方法。
http://caliburnmicro.com 的 Caliburn.Micro 主页提出了以下声明,但我无法使用我从该示例中想到的任何变体使 CM 与 PasswordBox 控件一起工作。无论如何,看不出这是如何工作的,因为名称大小写不同。有没有人有一个允许我获取 PasswordBox 值的 CM 示例?是否需要特定版本的 CM?我是 CM 的 运行 1.5.2 版。理想情况下 w/o 使用附加属性,但如果可以使用 CM,那么唯一的方法就可以了。请不要讲安全问题,因为这不是我的问题。
使用参数和保护方法自动在视图和视图模型之间应用方法
<StackPanel>
<TextBox x:Name="Username" />
<PasswordBox x:Name="Password" />
<Button x:Name="Login" Content="Log in" />
</StackPanel>
public bool CanLogin(string username, string password)
{
return !String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password);
}
public string Login(string username, string password)
{
...
}
我只能让它与依赖属性一起工作,有效地绕过了 Caliburn.Micro 提供的约定绑定优点。我知道这不是您的理想选择,但实际上这是我经常使用的解决方案。我相信当我历史上遇到这个障碍时,我在 Whosebug 上发现了 this post,它引导我朝这个方向前进。供您考虑:
public class BoundPasswordBox
{
private static bool _updating = false;
/// <summary>
/// BoundPassword Attached Dependency Property
/// </summary>
public static readonly DependencyProperty BoundPasswordProperty =
DependencyProperty.RegisterAttached("BoundPassword",
typeof(string),
typeof(BoundPasswordBox),
new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));
/// <summary>
/// Gets the BoundPassword property.
/// </summary>
public static string GetBoundPassword(DependencyObject d)
{
return (string)d.GetValue(BoundPasswordProperty);
}
/// <summary>
/// Sets the BoundPassword property.
/// </summary>
public static void SetBoundPassword(DependencyObject d, string value)
{
d.SetValue(BoundPasswordProperty, value);
}
/// <summary>
/// Handles changes to the BoundPassword property.
/// </summary>
private static void OnBoundPasswordChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
PasswordBox password = d as PasswordBox;
if (password != null)
{
// Disconnect the handler while we're updating.
password.PasswordChanged -= PasswordChanged;
}
if (e.NewValue != null)
{
if (!_updating)
{
password.Password = e.NewValue.ToString();
}
}
else
{
password.Password = string.Empty;
}
// Now, reconnect the handler.
password.PasswordChanged += PasswordChanged;
}
/// <summary>
/// Handles the password change event.
/// </summary>
static void PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox password = sender as PasswordBox;
_updating = true;
SetBoundPassword(password, password.Password);
_updating = false;
}
}
然后,在您的 XAML 中:
<PasswordBox pwbx:BoundPasswordBox.BoundPassword="{Binding UserPassword, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnValidationError=True,ValidatesOnDataErrors=True}" />
并且 pwbx 被发现为 Window 标签上的命名空间:
<Window x:Class="MyProject.Views.LoginView"
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"
mc:Ignorable="d"
xmlns:pwbx="clr-namespace:MyProject.Client.Controls">
视图模型:
using Caliburn.Micro;
using MyProject.Core;
using MyProject.Repositories;
using MyProject.Types;
using MyProject.ViewModels.Interfaces;
namespace MyProject.ViewModels
{
public class LoginViewModel : Screen, ILoginViewModel
{
private readonly IWindowManager _windowManager;
private readonly IUnitRepository _unitRepository;
public bool IsLoginValid { get; set; }
public Unit LoggedInUnit { get; set; }
private string _password;
public string UserPassword
{
get { return _password; }
set
{
_password = value;
NotifyOfPropertyChange(() => UserPassword);
NotifyOfPropertyChange(() => CanLogin);
}
}
private string _name;
public string Username
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange(() => Username);
NotifyOfPropertyChange(() => CanLogin);
}
}
public LoginViewModel(IWindowManager windowManager,IUnitRepository unitRepository)
{
_windowManager = windowManager;
_unitRepository = unitRepository;
DisplayName = "MyProject - Login";
Version = ApplicationVersionRepository.GetVersion();
}
public string Version { get; private set; }
public void Login()
{
// Login logic
var credentials = new UserCredentials { Username = Username, Password=UserPassword };
var resp = _unitRepository.AuthenticateUnit(credentials);
if (resp == null) return;
if (resp.IsValid)
{
IsLoginValid = true;
LoggedInUnit = resp.Unit;
TryClose();
}
else
{
var dialog = new MessageBoxViewModel(DialogType.Warning, DialogButton.Ok, "Login Failed", "Login Error: " + resp.InvalidReason);
_windowManager.ShowDialog(dialog);
}
}
public bool CanLogin
{
get
{
return !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(UserPassword);
}
}
}
}
这是一个更简化的示例,包括绑定约定,以便 PasswordBox
在 Caliburn.Micro Just Works™ 中绑定:
public static class PasswordBoxHelper
{
public static readonly DependencyProperty BoundPasswordProperty =
DependencyProperty.RegisterAttached("BoundPassword",
typeof(string),
typeof(PasswordBoxHelper),
new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static string GetBoundPassword(DependencyObject d)
{
var box = d as PasswordBox;
if (box != null)
{
// this funny little dance here ensures that we've hooked the
// PasswordChanged event once, and only once.
box.PasswordChanged -= PasswordChanged;
box.PasswordChanged += PasswordChanged;
}
return (string)d.GetValue(BoundPasswordProperty);
}
public static void SetBoundPassword(DependencyObject d, string value)
{
if (string.Equals(value, GetBoundPassword(d)))
return; // and this is how we prevent infinite recursion
d.SetValue(BoundPasswordProperty, value);
}
private static void OnBoundPasswordChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var box = d as PasswordBox;
if (box == null)
return;
box.Password = GetBoundPassword(d);
}
private static void PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox password = sender as PasswordBox;
SetBoundPassword(password, password.Password);
// set cursor past the last character in the password box
password.GetType().GetMethod("Select", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(password, new object[] { password.Password.Length, 0 });
}
}
然后,在您的引导程序中:
public sealed class Bootstrapper : BootstrapperBase
{
public Bootstrapper()
{
Initialize();
ConventionManager.AddElementConvention<PasswordBox>(
PasswordBoxHelper.BoundPasswordProperty,
"Password",
"PasswordChanged");
}
// other bootstrapper stuff here
}
此处提供的解决方案似乎不必要地复杂。
我们可以非常轻松地使用 Caliburn.Micro 操作将我们的密码发送到 ViewModel。
XAML:
<PasswordBox cal:Message.Attach="[Event PasswordChanged] = [Action OnPasswordChanged($source)]" />
视图模型:
public void OnPasswordChanged(PasswordBox source)
{
password = source.Password;
}
然后记得清除密码字段,这样它们就不会保留在内存中。
注意: 显然,此解决方案不允许您从 ViewModel 轻松更改密码,如果有必要,那么最好使用随附的 属性方法。