我遇到了绑定到PasswordBox的问题。这似乎是一个安全风险,但我正在使用MVVM模式,所以我希望绕过这个。我在这里找到了一些有趣的代码(有没有人用过这个或类似的东西?)
http://www.wpftutorial.net/PasswordBox.html
它在技术上看起来很棒,但我不确定如何检索密码。
我基本上在LoginViewModel
和Username
的Password
中有属性。 Username
很好,正在工作,因为它是一个TextBox
。
我按照说明使用了上面的代码并输入了这个
<PasswordBox ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>
当我把PasswordBox
作为TextBox
和Binding Path=Password
时,我的LoginViewModel
中的属性被更新了。
我的代码非常简单,基本上我有Command
为我的Button
。当我按下它时,调用CanLogin
,如果它返回true,则调用Login
。
你可以看到我检查我的物业Username
这里工作得很好。
在Login
我发送给我的服务Username
和Password
,Username
包含我的View
的数据,但Password
是Null|Empty
private DelegateCommand loginCommand;
public string Username { get; set; }
public string Password { get; set; }
public ICommand LoginCommand
{
get
{
if (loginCommand == null)
{
loginCommand = new DelegateCommand(
Login, CanLogin );
}
return loginCommand;
}
}
private bool CanLogin()
{
return !string.IsNullOrEmpty(Username);
}
private void Login()
{
bool result = securityService.IsValidLogin(Username, Password);
if (result) { }
else { }
}
这就是我在做的事情
<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}"
MinWidth="180" />
<PasswordBox ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>
我有我的TextBox
,这没问题,但在我的ViewModel
中,Password
是空的。
我做错了什么或错过了一步?
我放了一个断点,确定代码进入静态助手类但它永远不会在我的Password
中更新我的ViewModel
。
虽然我同意避免将密码存储在任何地方很重要,但我仍然需要能够在没有视图的情况下实例化视图模型并执行我的测试。
对我有用的解决方案是使用视图模型注册PasswordBox.Password函数,并让视图模型在执行登录代码时调用它。
这确实意味着视图的代码隐藏中的一行代码。
所以,在我的Login.xaml中,我有
<PasswordBox x:Name="PasswordBox"/>
在Login.xaml.cs我有
LoginViewModel.PasswordHandler = () => PasswordBox.Password;
然后在LoginViewModel.cs中我定义了PasswordHandler
public Func<string> PasswordHandler { get; set; }
当登录需要发生时,代码调用处理程序从视图中获取密码......
bool loginResult = Login(Username, PasswordHandler());
这样,当我想测试viewmodel时,我可以简单地将PasswordHandler设置为匿名方法,该方法允许我提供我想在测试中使用的任何密码。
我花了很多时间研究各种解决方案。我不喜欢装饰者的想法,行为搞砸了验证用户界面,代码背后......真的吗?
最好的方法是坚持自定义附加属性并绑定到视图模型中的SecureString
属性。尽可能长时间保持在那里。每当您需要快速访问普通密码时,请使用以下代码暂时将其转换为不安全的字符串:
namespace Namespace.Extensions
{
using System;
using System.Runtime.InteropServices;
using System.Security;
/// <summary>
/// Provides unsafe temporary operations on secured strings.
/// </summary>
[SuppressUnmanagedCodeSecurity]
public static class SecureStringExtensions
{
/// <summary>
/// Converts a secured string to an unsecured string.
/// </summary>
public static string ToUnsecuredString(this SecureString secureString)
{
// copy&paste from the internal System.Net.UnsafeNclNativeMethods
IntPtr bstrPtr = IntPtr.Zero;
if (secureString != null)
{
if (secureString.Length != 0)
{
try
{
bstrPtr = Marshal.SecureStringToBSTR(secureString);
return Marshal.PtrToStringBSTR(bstrPtr);
}
finally
{
if (bstrPtr != IntPtr.Zero)
Marshal.ZeroFreeBSTR(bstrPtr);
}
}
}
return string.Empty;
}
/// <summary>
/// Copies the existing instance of a secure string into the destination, clearing the destination beforehand.
/// </summary>
public static void CopyInto(this SecureString source, SecureString destination)
{
destination.Clear();
foreach (var chr in source.ToUnsecuredString())
{
destination.AppendChar(chr);
}
}
/// <summary>
/// Converts an unsecured string to a secured string.
/// </summary>
public static SecureString ToSecuredString(this string plainString)
{
if (string.IsNullOrEmpty(plainString))
{
return new SecureString();
}
SecureString secure = new SecureString();
foreach (char c in plainString)
{
secure.AppendChar(c);
}
return secure;
}
}
}
确保您允许GC收集您的UI元素,因此不要在PasswordChanged
上使用PasswordBox
事件的静态事件处理程序。我还发现了一个异常,当使用SecurePassword
属性进行设置时,控件没有更新UI,这就是我将密码复制到Password
的原因。
namespace Namespace.Controls
{
using System.Security;
using System.Windows;
using System.Windows.Controls;
using Namespace.Extensions;
/// <summary>
/// Creates a bindable attached property for the <see cref="PasswordBox.SecurePassword"/> property.
/// </summary>
public static class PasswordBoxHelper
{
// an attached behavior won't work due to view model validation not picking up the right control to adorn
public static readonly DependencyProperty SecurePasswordBindingProperty = DependencyProperty.RegisterAttached(
"SecurePassword",
typeof(SecureString),
typeof(PasswordBoxHelper),
new FrameworkPropertyMetadata(new SecureString(),FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, AttachedPropertyValueChanged)
);
private static readonly DependencyProperty _passwordBindingMarshallerProperty = DependencyProperty.RegisterAttached(
"PasswordBindingMarshaller",
typeof(PasswordBindingMarshaller),
typeof(PasswordBoxHelper),
new PropertyMetadata()
);
public static void SetSecurePassword(PasswordBox element, SecureString secureString)
{
element.SetValue(SecurePasswordBindingProperty, secureString);
}
public static SecureString GetSecurePassword(PasswordBox element)
{
return element.GetValue(SecurePasswordBindingProperty) as SecureString;
}
private static void AttachedPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// we'll need to hook up to one of the element's events
// in order to allow the GC to collect the control, we'll wrap the event handler inside an object living in an attached property
// don't be tempted to use the Unloaded event as that will be fired even when the control is still alive and well (e.g. switching tabs in a tab control)
var passwordBox = (PasswordBox)d;
var bindingMarshaller = passwordBox.GetValue(_passwordBindingMarshallerProperty) as PasswordBindingMarshaller;
if (bindingMarshaller == null)
{
bindingMarshaller = new PasswordBindingMarshaller(passwordBox);
passwordBox.SetValue(_passwordBindingMarshallerProperty, bindingMarshaller);
}
bindingMarshaller.UpdatePasswordBox(e.NewValue as SecureString);
}
/// <summary>
/// Encapsulated event logic
/// </summary>
private class PasswordBindingMarshaller
{
private readonly PasswordBox _passwordBox;
private bool _isMarshalling;
public PasswordBindingMarshaller(PasswordBox passwordBox)
{
_passwordBox = passwordBox;
_passwordBox.PasswordChanged += this.PasswordBoxPasswordChanged;
}
public void UpdatePasswordBox(SecureString newPassword)
{
if (_isMarshalling)
{
return;
}
_isMarshalling = true;
try
{
// setting up the SecuredPassword won't trigger a visual update so we'll have to use the Password property
_passwordBox.Password = newPassword.ToUnsecuredString();
// you may try the statement below, however the benefits are minimal security wise (you still have to extract the unsecured password for copying)
//newPassword.CopyInto(_passwordBox.SecurePassword);
}
finally
{
_isMarshalling = false;
}
}
private void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
{
// copy the password into the attached property
if (_isMarshalling)
{
return;
}
_isMarshalling = true;
try
{
SetSecurePassword(_passwordBox, _passwordBox.SecurePassword.Copy());
}
finally
{
_isMarshalling = false;
}
}
}
}
}
和XAML用法:
<PasswordBox controls:PasswordBoxHelper.SecurePassword="{Binding LogonPassword, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
我在视图模型中的属性如下所示:
[RequiredSecureString]
public SecureString LogonPassword
{
get
{
return _logonPassword;
}
set
{
_logonPassword = value;
NotifyPropertyChanged(nameof(LogonPassword));
}
}
RequiredSecureString
只是一个简单的自定义验证器,具有以下逻辑:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class RequiredSecureStringAttribute:ValidationAttribute
{
public RequiredSecureStringAttribute()
:base("Field is required")
{
}
public override bool IsValid(object value)
{
return (value as SecureString)?.Length > 0;
}
}
在这里你有它。完整且经过测试的纯MVVM解决方案。
我使用了这个方法并传递了密码框,虽然这确实违反了MVVM,但这对我来说是必不可少的,因为我在我的shell中使用了一个带有数据模板的内容控件来登录我的shell,这是一个复杂的shell环境。所以访问shell后面的代码本来就是垃圾。
据我所知,传递密码箱我认为与从后面的代码访问控制相同。我同意密码,不要留在内存中等。在这个实现中,我没有视图模型中的密码属性。
按钮命令
Command="{Binding Path=DataContext.LoginCommand, ElementName=MyShell}" CommandParameter="{Binding ElementName=PasswordBox}"
视图模型
private void Login(object parameter)
{
System.Windows.Controls.PasswordBox p = (System.Windows.Controls.PasswordBox)parameter;
MessageBox.Show(p.Password);
}
我想我会把我的解决方案放在混合中,因为这是一个常见的问题......而且有很多选择总是一件好事。
我只是将一个PasswordBox
包裹在UserControl
中并实施了DependencyProperty
以便能够绑定。我正在尽我所能避免在内存中存储任何明文,所以一切都是通过SecureString
和PasswordBox.Password
属性完成的。在foreach
循环期间,每个角色都会暴露出来,但它非常简短。老实说,如果您担心自己的WPF应用程序会因此次曝光而受到损害,那么您应该处理更大的安全问题。
这样做的好处在于你没有违反任何MVVM规则,即使是“纯粹的”规则,因为这是一个UserControl
,所以它允许有代码隐藏。当你使用它时,你可以在View
和ViewModel
之间进行纯粹的交流,而你的VideModel
不知道View
的任何部分或密码的来源。只要确保你在你的SecureString
绑定ViewModel
。
可绑定PasswordBox.xaml
<UserControl x:Class="BK.WPF.CustomControls.BindanblePasswordBox"
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" d:DesignHeight="22" d:DesignWidth="150">
<PasswordBox x:Name="PswdBox"/>
</UserControl>
BindablePasswordBox.xaml.cs(版本1 - 没有双向绑定支持。)
using System.ComponentModel;
using System.Security;
using System.Windows;
using System.Windows.Controls;
namespace BK.WPF.CustomControls
{
public partial class BindanblePasswordBox : UserControl
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(SecureString), typeof(BindanblePasswordBox));
public SecureString Password
{
get { return (SecureString)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public BindanblePasswordBox()
{
InitializeComponent();
PswdBox.PasswordChanged += PswdBox_PasswordChanged;
}
private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
{
var secure = new SecureString();
foreach (var c in PswdBox.Password)
{
secure.AppendChar(c);
}
Password = secure;
}
}
}
版本1的用法:
<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
VerticalAlignment="Center"
Password="{Binding Password, Mode=OneWayToSource}"/>
BindablePasswordBox.xaml.cs(版本2 - 具有双向绑定支持。)
public partial class BindablePasswordBox : UserControl
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(SecureString), typeof(BindablePasswordBox),
new PropertyMetadata(PasswordChanged));
public SecureString Password
{
get { return (SecureString)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public BindablePasswordBox()
{
InitializeComponent();
PswdBox.PasswordChanged += PswdBox_PasswordChanged;
}
private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
{
var secure = new SecureString();
foreach (var c in PswdBox.Password)
{
secure.AppendChar(c);
}
if (Password != secure)
{
Password = secure;
}
}
private static void PasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var pswdBox = d as BindablePasswordBox;
if (pswdBox != null && e.NewValue != e.OldValue)
{
var newValue = e.NewValue as SecureString;
if (newValue == null)
{
return;
}
var unmanagedString = IntPtr.Zero;
string newString;
try
{
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(newValue);
newString = Marshal.PtrToStringUni(unmanagedString);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
}
var currentValue = pswdBox.PswdBox.Password;
if (currentValue != newString)
{
pswdBox.PswdBox.Password = newString;
}
}
}
}
第2版的用法:
<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
VerticalAlignment="Center"
Password="{Binding Password, Mode=TwoWay}"/>
正如你所看到我绑定到密码,但也许它绑定到静态类..
这是一个attached property。这种属性可以应用于任何类型的DependencyObject
,而不仅仅是声明它的类型。因此,即使它在PasswordHelper
静态类中声明,它也会应用于您使用它的PasswordBox
。
要使用此附加属性,只需将其绑定到ViewModel中的Password
属性:
<PasswordBox w:PasswordHelper.Attach="True"
w:PasswordHelper.Password="{Binding Password}"/>
你可以用附属物做,看看.. PasswordBox with MVVM
对于像我这样的完整新手,这里是Konamiman
上面提到的完整工作样本。谢谢Konamiman
。
<PasswordBox x:Name="textBoxPassword"/>
<Button x:Name="buttonLogin" Content="Login"
Command="{Binding PasswordCommand}"
CommandParameter="{Binding ElementName=textBoxPassword}"/>
public class YourViewModel : ViewModelBase
{
private ICommand _passwordCommand;
public ICommand PasswordCommand
{
get {
if (_passwordCommand == null) {
_passwordCommand = new RelayCommand<object>(PasswordClick);
}
return _passwordCommand;
}
}
public YourViewModel()
{
}
private void PasswordClick(object p)
{
var password = p as PasswordBox;
Console.WriteLine("Password is: {0}", password.Password);
}
}
我这样做了:
XAML:
<PasswordBox x:Name="NewPassword" PasswordChanged="NewPassword_PasswordChanged"/>
<!--change tablenameViewSource: yours!-->
<Grid DataContext="{StaticResource tablenameViewSource}" Visibility="Hidden">
<TextBox x:Name="Password" Text="{Binding password, Mode=TwoWay}"/>
</Grid>
C#:
private void NewPassword_PasswordChanged(object sender, RoutedEventArgs e)
{
try
{
//change tablenameDataTable: yours! and tablenameViewSource: yours!
tablenameDataTable.Rows[tablenameViewSource.View.CurrentPosition]["password"] = NewPassword.Password;
}
catch
{
this.Password.Text = this.NewPassword.Password;
}
}
这个对我有用!
如前所述,VM应该不知道View,但传递整个PasswordBox看起来就像最简单的方法。因此,可能不是将传递的参数强制转换为PasswordBox,而是使用Reflection从中提取Password属性。在这种情况下,VM期望某种密码容器具有属性密码(我正在使用来自MVMM Light-Toolkit的RelayCommands):
public RelayCommand<object> SignIn
{
get
{
if (this.signIn == null)
{
this.signIn = new RelayCommand<object>((passwordContainer) =>
{
var password = passwordContainer.GetType().GetProperty("Password").GetValue(passwordContainer) as string;
this.authenticationService.Authenticate(this.Login, password);
});
}
return this.signIn;
}
}
它可以使用匿名类轻松测试:
var passwordContainer = new
{
Password = "password"
};
对我来说,这两件事都错了:
PasswordBox
作为命令参数发送到ViewModel按照Steve in CO的描述传输SecurePassword(SecureString实例)似乎是可以接受的。我更喜欢Behaviors
代码,我还有额外的要求,可以从viewmodel重置密码。
Xaml(Password
是ViewModel属性):
<PasswordBox>
<i:Interaction.Behaviors>
<behaviors:PasswordBinding BoundPassword="{Binding Password, Mode=TwoWay}" />
</i:Interaction.Behaviors>
</PasswordBox>
行为:
using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace Evidence.OutlookIntegration.AddinLogic.Behaviors
{
/// <summary>
/// Intermediate class that handles password box binding (which is not possible directly).
/// </summary>
public class PasswordBoxBindingBehavior : Behavior<PasswordBox>
{
// BoundPassword
public SecureString BoundPassword { get { return (SecureString)GetValue(BoundPasswordProperty); } set { SetValue(BoundPasswordProperty, value); } }
public static readonly DependencyProperty BoundPasswordProperty = DependencyProperty.Register("BoundPassword", typeof(SecureString), typeof(PasswordBoxBindingBehavior), new FrameworkPropertyMetadata(OnBoundPasswordChanged));
protected override void OnAttached()
{
this.AssociatedObject.PasswordChanged += AssociatedObjectOnPasswordChanged;
base.OnAttached();
}
/// <summary>
/// Link up the intermediate SecureString (BoundPassword) to the UI instance
/// </summary>
private void AssociatedObjectOnPasswordChanged(object s, RoutedEventArgs e)
{
this.BoundPassword = this.AssociatedObject.SecurePassword;
}
/// <summary>
/// Reacts to password reset on viewmodel (ViewModel.Password = new SecureString())
/// </summary>
private static void OnBoundPasswordChanged(object s, DependencyPropertyChangedEventArgs e)
{
var box = ((PasswordBoxBindingBehavior)s).AssociatedObject;
if (box != null)
{
if (((SecureString)e.NewValue).Length == 0)
box.Password = string.Empty;
}
}
}
}
在Windows通用应用程序中
您可以将此代码与属性“Password”一起使用并与modelView绑定
<PasswordBox x:Uid="PasswordBox" Password="{Binding Waiter.Password, Mode=TwoWay}" Name="txtPassword" HorizontalAlignment="Stretch" Margin="50,200,50,0" VerticalAlignment="Top"/>
它非常简单。为密码创建另一个属性并使用TextBox绑定它
但是所有输入操作都使用实际的密码属性
私有字符串_Password;
public string PasswordChar
{
get
{
string szChar = "";
foreach(char szCahr in _Password)
{
szChar = szChar + "*";
}
return szChar;
}
set
{
_PasswordChar = value; NotifyPropertyChanged();
}
}
public string Password {get {return _Password; }
set
{
_Password = value; NotifyPropertyChanged();
PasswordChar = _Password;
}
}
对于了解此实现所带来的风险的任何人,要将密码同步到ViewModel,只需添加Mode = OneWayToSource即可。
XAML
<PasswordBox
ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password, Mode=OneWayToSource}" />
您可以在WPF Application Framework (WAF)项目的ViewModel示例应用程序中找到PasswordBox的解决方案。
但是,贾斯汀是对的。不要在View和ViewModel之间以纯文本形式传递密码。请改用SecureString(请参阅MSDN PasswordBox)。
我使用了一个身份验证检查,然后是一个由调解器类调用的子到View(它还实现了一个身份验证检查)来将密码写入数据类。
这不是一个完美的解决方案;但是,它解决了我无法移动密码的问题。
我正在使用尚未提及的简洁的MVVM友好解决方案。首先,我在XAML中命名PasswordBox:
<PasswordBox x:Name="Password" />
然后我在视图构造函数中添加一个方法调用:
public LoginWindow()
{
InitializeComponent();
ExposeControl<LoginViewModel>.Expose(this, view => view.Password,
(model, box) => model.SetPasswordBox(box));
}
就是这样。视图模型在通过DataContext附加到视图时会收到通知,并在分离时通知另一个通知。此通知的内容可通过lambdas进行配置,但通常只是对视图模型进行setter或方法调用,将有问题的控件作为参数传递。
通过使视图公开接口而不是子控件,可以非常容易地使它成为MVVM友好的。
上面的代码依赖于我博客上发布的helper class。
我花了很多年时间试图让这个工作。最后,我放弃了,只是使用了DevExpress的PasswordBoxEdit。
它是有史以来最简单的解决方案,因为它允许绑定而不会产生任何可怕的技巧。
Solution on DevExpress website
为了记录,我不以任何方式与DevExpress有关联。
<UserControl x:Class="Elections.Server.Handler.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"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
Height="531" Width="1096">
<ContentControl>
<ContentControl.Background>
<ImageBrush/>
</ContentControl.Background>
<Grid >
<Border BorderBrush="#FFABADB3" BorderThickness="1" HorizontalAlignment="Left" Height="23" Margin="900,100,0,0" VerticalAlignment="Top" Width="160">
<TextBox TextWrapping="Wrap"/>
</Border>
<Border BorderBrush="#FFABADB3" BorderThickness="1" HorizontalAlignment="Left" Height="23" Margin="900,150,0,0" VerticalAlignment="Top" Width="160">
<PasswordBox x:Name="PasswordBox"/>
</Border>
<Button Content="Login" HorizontalAlignment="Left" Margin="985,200,0,0" VerticalAlignment="Top" Width="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="Login">
<cal:Parameter Value="{Binding ElementName=PasswordBox}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</ContentControl>
</UserControl>
using System;
using System.Windows;
using System.Windows.Controls;
using Caliburn.Micro;
namespace Elections.Server.Handler.ViewModels
{
public class LoginViewModel : PropertyChangedBase
{
MainViewModel _mainViewModel;
public void SetMain(MainViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
}
public void Login(Object password)
{
var pass = (PasswordBox) password;
MessageBox.Show(pass.Password);
//_mainViewModel.ScreenView = _mainViewModel.ControlPanelView;
//_mainViewModel.TitleWindow = "Panel de Control";
//HandlerBootstrapper.Title(_mainViewModel.TitleWindow);
}
}
}
;) 简单!
我的答案在MVVM模式中更简单
在类视图模型中
public string password;
PasswordChangedCommand = new DelegateCommand<RoutedEventArgs>(PasswordChanged);
Private void PasswordChanged(RoutedEventArgs obj)
{
var e = (WatermarkPasswordBox)obj.OriginalSource;
//or depending or what are you using
var e = (PasswordBox)obj.OriginalSource;
password =e.Password;
}
获胜的PasswordBox的密码属性或XCeedtoolkit提供的WatermarkPasswordBox生成RoutedEventArgs,以便您可以绑定它。
现在在xml视图中
<Xceed:WatermarkPasswordBox Watermark="Input your Password" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="PasswordChanged">
<prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Xceed:WatermarkPasswordBox>
要么
<PasswordBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="PasswordChanged">
<prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</PasswordBox>
如果你想要它只在一个控件和一个命令中组合它
<PasswordBox Name="PasswordBoxPin" PasswordChar="*">
<PasswordBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding AuthentifyEmpCommand}" CommandParameter="{Binding ElementName=PasswordBoxPin}"/>
</PasswordBox.InputBindings>
</PasswordBox>
在你的Vm上(像Konamiman所示)
public void AuthentifyEmp(object obj)
{
var passwordBox = obj as PasswordBox;
var password = passwordBox.Password;
}
private RelayCommand _authentifyEmpCommand;
public RelayCommand AuthentifyEmpCommand => _authentifyEmpCommand ?? (_authentifyEmpCommand = new RelayCommand(AuthentifyEmp, null));
这是我的看法:
XAML
<PasswordBox x:Name="pbPassword" />
<Button Content="Login" Command="{Binding LoginCommand}" x:Name="btnLogin"/>
代码背后 - 使用代码隐藏不一定违反MVVM。只要你不在其中放置任何业务逻辑。
btnLogin.CommandParameter = new Func<string>(()=>pbPassword.Password);
视图模型
LoginCommand = new RelayCommand<Func<string>>(getpwd=> { service.Login(username, getpwd()); });
你可以使用这个XAML:
<PasswordBox Name="PasswordBox">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PasswordChanged">
<i:InvokeCommandAction Command="{Binding PasswordChangedCommand}" CommandParameter="{Binding ElementName=PasswordBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</PasswordBox>
而这个命令执行方法:
private void ExecutePasswordChangedCommand(PasswordBox obj)
{
if (obj != null)
Password = obj.Password;
}
这对我来说很好。
<Button Command="{Binding Connect}"
CommandParameter="{Binding ElementName=MyPasswordBox}"/>
不违反MVVM模式的简单解决方案是在ViewModel中引入一个收集密码的事件(或委托)。
在ViewModel中:
public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;
使用这些EventArgs:
class HarvestPasswordEventArgs : EventArgs
{
public string Password;
}
在View中,订阅创建ViewModel的事件并填写密码值。
_viewModel.HarvestPassword += (sender, args) =>
args.Password = passwordBox1.Password;
在ViewModel中,当您需要密码时,您可以触发事件并从那里获取密码:
if (HarvestPassword == null)
//bah
return;
var pwargs = new HarvestPasswordEventArgs();
HarvestPassword(this, pwargs);
LoginHelpers.Login(Username, pwargs.Password);
我发布了一个GIST here,这是一个可绑定的密码框。
using System.Windows;
using System.Windows.Controls;
namespace CustomControl
{
public class BindablePasswordBox : Decorator
{
/// <summary>
/// The password dependency property.
/// </summary>
public static readonly DependencyProperty PasswordProperty;
private bool isPreventCallback;
private RoutedEventHandler savedCallback;
/// <summary>
/// Static constructor to initialize the dependency properties.
/// </summary>
static BindablePasswordBox()
{
PasswordProperty = DependencyProperty.Register(
"Password",
typeof(string),
typeof(BindablePasswordBox),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnPasswordPropertyChanged))
);
}
/// <summary>
/// Saves the password changed callback and sets the child element to the password box.
/// </summary>
public BindablePasswordBox()
{
savedCallback = HandlePasswordChanged;
PasswordBox passwordBox = new PasswordBox();
passwordBox.PasswordChanged += savedCallback;
Child = passwordBox;
}
/// <summary>
/// The password dependency property.
/// </summary>
public string Password
{
get { return GetValue(PasswordProperty) as string; }
set { SetValue(PasswordProperty, value); }
}
/// <summary>
/// Handles changes to the password dependency property.
/// </summary>
/// <param name="d">the dependency object</param>
/// <param name="eventArgs">the event args</param>
private static void OnPasswordPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs eventArgs)
{
BindablePasswordBox bindablePasswordBox = (BindablePasswordBox) d;
PasswordBox passwordBox = (PasswordBox) bindablePasswordBox.Child;
if (bindablePasswordBox.isPreventCallback)
{
return;
}
passwordBox.PasswordChanged -= bindablePasswordBox.savedCallback;
passwordBox.Password = (eventArgs.NewValue != null) ? eventArgs.NewValue.ToString() : "";
passwordBox.PasswordChanged += bindablePasswordBox.savedCallback;
}
/// <summary>
/// Handles the password changed event.
/// </summary>
/// <param name="sender">the sender</param>
/// <param name="eventArgs">the event args</param>
private void HandlePasswordChanged(object sender, RoutedEventArgs eventArgs)
{
PasswordBox passwordBox = (PasswordBox) sender;
isPreventCallback = true;
Password = passwordBox.Password;
isPreventCallback = false;
}
}
}
为了在不破坏MVVM的情况下解决OP问题,我将使用自定义值转换器和必须从密码框中检索的值(密码)的包装器。
public interface IWrappedParameter<T>
{
T Value { get; }
}
public class PasswordBoxWrapper : IWrappedParameter<string>
{
private readonly PasswordBox _source;
public string Value
{
get { return _source != null ? _source.Password : string.Empty; }
}
public PasswordBoxWrapper(PasswordBox source)
{
_source = source;
}
}
public class PasswordBoxConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Implement type and value check here...
return new PasswordBoxWrapper((PasswordBox)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("No conversion.");
}
}
在视图模型中:
public string Username { get; set; }
public ICommand LoginCommand
{
get
{
return new RelayCommand<IWrappedParameter<string>>(password => { Login(Username, password); });
}
}
private void Login(string username, string password)
{
// Perform login here...
}
因为视图模型使用IWrappedParameter<T>
,所以它不需要任何关于PasswordBoxWrapper
和PasswordBoxConverter
的知识。这样,您可以将PasswordBox
对象与视图模型隔离,而不会破坏MVVM模式。
在视图中:
<Window.Resources>
<h:PasswordBoxConverter x:Key="PwdConverter" />
</Window.Resources>
...
<PasswordBox Name="PwdBox" />
<Button Content="Login" Command="{Binding LoginCommand}"
CommandParameter="{Binding ElementName=PwdBox, Converter={StaticResource PwdConverter}}" />
这种实现略有不同。您将密码框传递给View通过ViewModel中的属性绑定,它不使用任何命令参数。 ViewModel保持对视图的无知。我有一个VB vs 2010项目,可以从SkyDrive下载。 Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511
我在Wpf MvvM应用程序中使用PasswordBox的方式非常简单,对我来说效果很好。这并不意味着我认为这是正确的方式或最好的方式。它只是使用PasswordBox和MvvM模式的一个实现。
基本上,您创建一个公共只读属性,View可以将其绑定为PasswordBox(实际控件)示例:
Private _thePassWordBox As PasswordBox
Public ReadOnly Property ThePassWordBox As PasswordBox
Get
If IsNothing(_thePassWordBox) Then _thePassWordBox = New PasswordBox
Return _thePassWordBox
End Get
End Property
我使用后备字段来执行属性的自我初始化。
然后从Xaml绑定ContentControl的内容或控件容器示例:
<ContentControl Grid.Column="1" Grid.Row="1" Height="23" Width="120" Content="{Binding Path=ThePassWordBox}" HorizontalAlignment="Center" VerticalAlignment="Center" />
从那里你可以完全控制密码箱我还使用PasswordAccessor(只是字符串函数)在登录时或其他任何你想要密码的地方返回密码值。在示例中,我在通用用户对象模型中有一个公共属性。例:
Public Property PasswordAccessor() As Func(Of String)
在用户对象中,密码字符串属性是只读的,没有任何后备存储,它只是从PasswordBox返回密码。例:
Public ReadOnly Property PassWord As String
Get
Return If((PasswordAccessor Is Nothing), String.Empty, PasswordAccessor.Invoke())
End Get
End Property
然后在ViewModel中,我确保创建了Accessor并将其设置为PasswordBox.Password属性'示例:
Public Sub New()
'Sets the Accessor for the Password Property
SetPasswordAccessor(Function() ThePassWordBox.Password)
End Sub
Friend Sub SetPasswordAccessor(ByVal accessor As Func(Of String))
If Not IsNothing(VMUser) Then VMUser.PasswordAccessor = accessor
End Sub
当我需要密码字符串说登录时,我只是获得真正调用函数来获取密码并将其返回的用户对象密码属性,然后用户对象不存储实际密码。示例:将在ViewModel中
Private Function LogIn() as Boolean
'Make call to your Authentication methods and or functions. I usally place that code in the Model
Return AuthenticationManager.Login(New UserIdentity(User.UserName, User.Password)
End Function
应该这样做。 ViewModel不需要任何View的控件知识。视图仅绑定到ViewModel中的属性,与视图绑定到图像或其他资源没有任何区别。在这种情况下,资源(Property)恰好是用户控件。它允许在ViewModel创建和拥有Property时进行测试,并且Property独立于View。至于安全性,我不知道这个实现有多好。但是通过使用Function,Value不会存储在Property所访问的Property本身中。