如何在MVVM绑定到密码盒

我遇到了绑定到PasswordBox的问题。这似乎是一个安全风险,但我正在使用MVVM模式,所以我希望绕过这个。我在这里发现了一些有趣的代码(有人使用过这个或类似的代码吗?)

http://www.wpftutorial.net/PasswordBox.html

从技术上讲,它看起来很棒,但我不确定如何检索密码。

我基本上在我的LoginViewModel中有UsernamePassword的属性。Username很好,并且正在工作,因为它是TextBox

我使用上面的代码,并输入这个

<PasswordBox ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

当我有PasswordBox作为TextBoxBinding Path=Password时,我的LoginViewModel中的属性被更新。

我的代码非常简单,基本上我的Button有一个Command。当我按下它时,CanLogin被调用,如果它返回true,则调用Login。< br / > 你可以看到,我在这里检查了属性Username,这很好

Login中,我发送到我的服务UsernamePasswordUsername包含来自我的View的数据,但PasswordNull|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,这是没有问题的,但在我的ViewModelPassword是空的。

是我做错了什么还是漏了一步?

我放了一个断点,果然代码进入静态助手类,但它从来没有更新我的Password在我的ViewModel

241110 次浏览

正如你所看到的,我绑定到密码,但也许它绑定到静态类..

它是附加属性。这种属性可以应用于任何类型的DependencyObject,而不仅仅是声明它的类型。因此,即使它是在PasswordHelper静态类中声明的,它也会应用到你使用它的PasswordBox上。

要使用这个附加属性,你只需要将它绑定到ViewModel中的Password属性:

<PasswordBox w:PasswordHelper.Attach="True"
w:PasswordHelper.Password="{Binding Password}"/>

你可以用附加属性来做,看到了吗。PasswordBox with MVVM

对不起,你做错了。

人们应该在眼皮内侧纹以下安全指南 永远不要在内存中保存纯文本密码

WPF/Silverlight PasswordBox不为Password属性公开DP的原因与安全有关 如果WPF/Silverlight要为Password保留一个DP,就需要框架在内存中保持密码本身不加密。这被认为是一个相当麻烦的安全攻击载体。 PasswordBox使用加密内存(某种程度上),访问密码的唯一方法是通过CLR属性。< / p > 我建议在访问PasswordBox.Password CLR属性时,不要将它放在任何变量中或作为任何属性的值 在客户端机器RAM中以明文形式保存密码是安全禁忌 所以去掉上面的public string Password { get; set; }。< / p >

当访问PasswordBox.Password时,只需将其取出并尽快发送到服务器。 不要保留密码的值,也不要像对待任何其他客户端机器文本一样对待它。不要在记忆中保存清晰的文本密码。< / p >

我知道这打破了MVVM模式,但你不应该绑定到PasswordBox.Password Attached DP,在ViewModel中存储你的密码或任何其他类似的伎俩。

如果你正在寻找一个过度架构的解决方案,这里有一个:
1. 使用一个方法创建IHavePassword接口,该方法返回密码明文 2. 让你的UserControl实现一个IHavePassword接口。
3.向你的IoC注册UserControl实例来实现IHavePassword接口 4. 当一个服务器请求你的密码发生时,调用你的IoC进行IHavePassword实现,只会得到梦寐以求的密码

这只是我的看法。

——贾斯汀

您可以在WPF应用框架(WAF)项目的ViewModel示例应用程序中找到PasswordBox的解决方案。

然而,犹斯丁是对的。不要在View和ViewModel之间以纯文本的形式传递密码。请使用SecureString代替(参见MSDN PasswordBox)。

这对我来说很好。

<Button Command="{Binding Connect}"
CommandParameter="{Binding ElementName=MyPasswordBox}"/>

我发布了一个GIST 在这里,这是一个可绑定的密码框。

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;
}
}
}

我的观点是:

我曾经使用WPF和MVVM开发了一个典型的登录对话框(用户和密码框,加上“Ok”按钮)。我通过简单地将PasswordBox控件本身作为参数传递给附加到“Ok”按钮的命令来解决密码绑定问题。在我的视图中

<PasswordBox Name="txtPassword" VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding Path=OkCommand}"
CommandParameter="{Binding ElementName=txtPassword}"/>

在ViewModel中,附加命令的Execute方法如下所示:

void Execute(object parameter)
{
var passwordBox = parameter as PasswordBox;
var password = passwordBox.Password;
//Now go ahead and check the user name and password
}

这稍微违反了MVVM模式,因为现在ViewModel知道视图是如何实现的,但在那个特定的项目中,我可以负担得起。希望它对某些人也有用。

我使用这种方法并通过密码框,虽然这确实违反了MVVM,这对我来说是必不可少的,因为我正在使用一个内容控件与数据模板为我的登录在我的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);
}
这个实现略有不同。通过绑定ViewModel中的属性将PasswordBox传递给视图。它不使用任何命令参数。ViewModel对视图保持无知。 我有一个VB VS 2010项目,可以从SkyDrive下载。WPF MVVM PassWordBox

.使用实例

我在WPF MVVM应用程序中使用PasswordBox的方式非常简单,对我来说工作得很好。

基本上你创建了一个公共的readonly属性,视图可以将其绑定为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或Control Container的内容:

 <ContentControl Grid.Column="1" Grid.Row="1" Height="23" Width="120" Content="{Binding Path=ThePassWordBox}" HorizontalAlignment="Center" VerticalAlignment="Center" />
从那里你可以完全控制密码盒。我还使用PasswordAccessor(只是一个字符串的函数)返回密码值时,做登录或任何其他你想要的密码。在这个例子中,我在通用用户对象模型中有一个公共属性。 例子:< / p >
Public Property PasswordAccessor() As Func(Of String)
在用户对象中,密码字符串属性是readonly,没有任何备份存储。它只是从PasswordBox返回Password。 例子:< / p >
Public ReadOnly Property PassWord As String
Get
Return If((PasswordAccessor Is Nothing), String.Empty, PasswordAccessor.Invoke())
End Get
End Property

然后在ViewModel中,我确保Accessor被创建并设置为PasswordBox。密码属性:

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不需要任何关于视图控件的知识。视图只是绑定到ViewModel中的属性,与视图绑定到图像或其他资源没有任何不同。在这种情况下,资源(属性)恰好是一个用户控件。 它允许在ViewModel创建并拥有属性并且属性独立于视图时进行测试。 至于安全性,我不知道这个实现有多好。但是通过使用函数,值不会存储在属性本身中,只是由属性访问

一个不违反MVVM模式的简单解决方案是在ViewModel中引入一个获取密码的事件(或委托)。

视图模型中:

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

使用这些EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
public string Password;
}

视图中,订阅创建ViewModel时的事件并填写密码值。

_viewModel.HarvestPassword += (sender, args) =>
args.Password = passwordBox1.Password;

视图模型中,当你需要密码时,你可以触发事件并从那里获取密码:

if (HarvestPassword == null)
//bah
return;


var pwargs = new HarvestPasswordEventArgs();
HarvestPassword(this, pwargs);


LoginHelpers.Login(Username, pwargs.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应该不知道视图,但传递整个PasswordBox看起来是最简单的方法。因此,可能不是将传递的参数强制转换为PasswordBox,而是使用反射从它提取Password属性。在这种情况下,虚拟机期望某种密码容器的属性密码(我使用RelayCommands从MVMM Light-Toolkit):

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

对于任何意识到这种实现带来的风险的人来说,要让密码同步到你的ViewModel,只需添加模式= OneWayToSource

XAML

<PasswordBox
ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password, Mode=OneWayToSource}" />

为了在不破坏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>,它不需要有任何关于PasswordBoxWrapperPasswordBoxConverter的知识。这样你可以将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调用的子(它也实现了身份验证检查),将密码写入数据类。

这不是一个完美的解决方案;但是,它弥补了我无法移动密码的问题。

我使用的是简洁的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友好。

上面的代码依赖于我博客上发布的助手类

也许我遗漏了一些东西,但似乎大多数解决方案都把事情复杂化了,并且忽略了安全实践。

此方法不违反MVVM模式,并保持完全的安全性。是的,从技术上讲,它是后面的代码,但它只不过是一个“特殊情况”绑定。ViewModel仍然没有视图实现的知识,在我看来,如果你试图将PasswordBox传递到ViewModel,它就会知道。

Code Behind !=自动违反MVVM。这完全取决于你怎么处理它。在这种情况下,我们只是手动编码绑定,所以它都被认为是UI实现的一部分,因此是ok的。

在ViewModel中,只是一个简单的属性。我把它设置为“只写”,因为无论出于什么原因,都不需要从ViewModel外部检索它,但它并不一定要这样。注意,它是一个SecureString,而不仅仅是一个字符串。

public SecureString SecurePassword { private get; set; }

在xaml中,您设置了PasswordChanged事件处理程序。

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

在后面的代码中:

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (this.DataContext != null)
{ ((dynamic)this.DataContext).SecurePassword = ((PasswordBox)sender).SecurePassword; }
}

使用这种方法,您的密码始终保持在SecureString中,因此提供了最大的安全性。如果你真的不关心安全性,或者你需要为需要它的下游方法提供明文密码(注意:大多数需要密码的。net方法也支持SecureString选项,所以你可能真的不需要明文密码,即使你认为你需要),你可以只使用password属性。是这样的:

(ViewModel属性)

public string Password { private get; set; }

(代码后面)

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (this.DataContext != null)
{ ((dynamic)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

如果你想保持事物的强类型,你可以用ViewModel的接口代替(动态的)强制转换。但实际上,“正常的”数据绑定也不是强类型的,所以这不是什么大问题。

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (this.DataContext != null)
{ ((IMyViewModel)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

所以最好的是-你的密码是安全的,你的ViewModel就像任何其他属性一样有一个属性,你的View是自包含的,不需要外部引用。

你可以使用这个XAML:

<PasswordBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PasswordChanged">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}" CommandParameter="{Binding ElementName=PasswordBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</PasswordBox>

该命令的执行方法:

private void ExecutePasswordChangedCommand(PasswordBox obj)
{
if (obj != null)
Password = obj.Password;
}

这需要将System.Windows.Interactivity程序集添加到项目中,并通过xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"引用它。

对我来说,这两件事都不对:

  • 实现明文密码属性
  • PasswordBox作为命令参数发送给ViewModel

传输史蒂夫在公司中描述的SecurePassword (SecureString实例)似乎是可以接受的。我更喜欢Behaviors后面的代码,我也有能够从视图模型重置密码的额外要求。

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;
}
}


}
}

虽然我同意避免将密码存储在任何地方很重要,但我仍然需要能够在没有视图的情况下实例化视图模型,并针对它执行测试。

对我来说,有效的解决方案是注册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());

这样,当我想要测试视图模型时,我可以简单地将PasswordHandler设置为一个匿名方法,让我在测试中交付我想要使用的任何密码。

我花了很长时间才把它弄好。最后,我放弃了,只使用了DevExpress的PasswordBoxEdit。

这是迄今为止最简单的解决方案,因为它允许绑定而不使用任何可怕的技巧。

DevExpress官网解决方案

郑重声明,我和开发快递没有任何关系。

我想我应该把我的解决方案放在一起,因为这是一个很常见的问题……拥有大量的选择总是一件好事。

我简单地将PasswordBox包装在UserControl中,并实现了DependencyProperty以便能够绑定。我正在尽我所能避免在内存中存储任何明文,所以一切都是通过SecureStringPasswordBox.Password属性完成的。在foreach循环中,每个字符都会被暴露,但非常简短。老实说,如果您担心您的WPF应用程序会因为这短暂的暴露而受到损害,那么您还有更大的安全问题需要处理。

这样做的美妙之处在于,你没有违反任何MVVM规则,即使是“纯粹的”规则,因为这是UserControl,所以它允许有代码隐藏。当你使用它时,你可以在ViewViewModel之间进行纯通信,而你的VideModel不知道View的任何部分或密码的来源。只要确保你在你的ViewModel中绑定到SecureString

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

我花了大量时间研究各种解决方案。我不喜欢装饰器的想法,行为弄乱了验证UI,背后的代码……真的吗?

最好的方法是坚持自定义附加属性并绑定到视图模型中的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元素,所以抵制使用静态事件处理程序在PasswordBox上的PasswordChanged事件的冲动。 我还发现了一个异常,当使用SecurePassword属性设置它时,控件没有更新UI,这就是为什么我将密码复制到Password的原因。< / p >
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解决方案。

在windows通用应用程序

你可以将此代码与属性“Password”一起使用,并与modelView绑定

.
 <PasswordBox x:Uid="PasswordBox" Password="{Binding Waiter.Password, Mode=TwoWay}" Name="txtPassword" HorizontalAlignment="Stretch" Margin="50,200,50,0" VerticalAlignment="Top"/>

<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);
}
}
}

,简单!

这很简单。为password创建另一个属性,并将其与TextBox绑定

但所有输入操作都使用实际的密码属性执行

private string _Password;

    public string PasswordChar
{
get
{
string szChar = "";


foreach(char szCahr in _Password)
{
szChar = szChar + "*";
}


return szChar;
}


set
{
_PasswordChar = value; NotifyPropertyChanged();
}
}

public string密码 { 得到 { 返回_Password; } < / p >

        set
{
_Password = value; NotifyPropertyChanged();
PasswordChar = _Password;
}
}

好吧,我的答案更简单,只是在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;


}

win提供的PasswordBox或XCeedtoolkit提供的WatermarkPasswordBox的password属性生成一个RoutedEventArgs,以便您可以绑定它。

现在在xmal视图中

<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));

编辑:根据评论,我觉得有必要指出这违反了MVVM模式,只有当这不是您主要关注的问题之一时才使用它。

以下是我的看法:

  1. 使用附加属性绑定密码不能达到保护密码的目的。由于某种原因,密码框的“密码”属性不可绑定。

  2. 将密码框作为命令参数传递将使ViewModel知道该控件。如果你打算让你的ViewModel跨平台可重用,这就行不通了。不要让你的虚拟机意识到你的视图或任何其他控件

  3. 我不认为引入一个新的属性、接口、订阅密码更改事件或任何其他复杂的事情对于提供密码的简单任务是必要的。

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()); });

对于像我这样的新手来说,这是上面Konamiman建议的一个完整的工作样本。谢谢Konamiman

XAML

    <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);
}
}

使用附加行为和ICommand向视图模型发送SecureString

在实现MVVM时,代码隐藏并没有错。MVVM是一种架构模式,旨在将视图与模型/业务逻辑分离。MVVM描述了如何以可重复的方式(模式)实现这一目标。它不关心实现细节,比如如何构造或实现视图。它只是画出边界,并根据这个模式的术语定义什么是视图,视图模型和什么是模型。

MVVM不关心语言(XAML或c#)或编译器(partial类)。独立于语言是设计模式的强制性特征——它必须与语言无关。

然而,代码隐藏也有一些缺点,比如当它广泛分布在XAML和c#之间时,会使你的UI逻辑更难理解。但最重要的是,用c#实现UI逻辑或对象,如模板、样式、触发器、动画等,与使用XAML相比非常复杂和丑陋/可读性差。XAML是一种标记语言,它使用标记和嵌套来可视化对象层次结构。使用XAML创建UI非常方便。尽管在某些情况下,您可以选择用c#(或后台代码)实现UI逻辑。处理PasswordBox就是一个例子。

因此,在代码背后通过处理PasswordBox.PasswordChanged来处理PasswordBox,并不违反MVVM模式。

一个明显的违规是将控件(PasswordBox)传递给视图模型。许多解决方案都推荐这样做,例如,将PasswordBox的实例作为ICommand.CommandParameter传递给视图模型。这显然是一个非常糟糕和不必要的建议。

如果你不关心使用c#,只是想保持你的代码文件干净,或者只是想封装一个行为/UI逻辑,你总是可以使用附加属性并实现附加行为。

与臭名昭著的支持绑定纯文本密码的广泛传播帮助器(非常糟糕的反模式和安全风险)相反,此行为使用ICommand将密码作为SecureString发送给视图模型,每当PasswordBox引发PasswordBox.PasswordChanged事件时。

MainWindow.xaml

<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>


<PasswordBox PasswordBox.Command="{Binding VerifyPasswordCommand}" />
</Window>

ViewModel.cs

public class ViewModel : INotifyPropertyChanged
{
public ICommand VerifyPasswordCommand => new RelayCommand(VerifyPassword);


public void VerifyPassword(object commadParameter)
{
if (commandParameter is SecureString secureString)
{
IntPtr valuePtr = IntPtr.Zero;
try
{
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
string plainTextPassword = Marshal.PtrToStringUni(valuePtr);


// Handle plain text password.
// It's recommended to convert the SecureString to plain text in the model, when really needed.
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
}
}
}

PasswordBox.cs

// Attached behavior
class PasswordBox : DependencyObject
{
#region Command attached property


public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(PasswordBox),
new PropertyMetadata(default(ICommand), PasswordBox.OnSendPasswordCommandChanged));


public static void SetCommand(DependencyObject attachingElement, ICommand value) =>
attachingElement.SetValue(PasswordBox.CommandProperty, value);


public static ICommand GetCommand(DependencyObject attachingElement) =>
(ICommand) attachingElement.GetValue(PasswordBox.CommandProperty);


#endregion


private static void OnSendPasswordCommandChanged(
DependencyObject attachingElement,
DependencyPropertyChangedEventArgs e)
{
if (!(attachingElement is System.Windows.Controls.PasswordBox passwordBox))
{
throw new ArgumentException("Attaching element must be of type 'PasswordBox'");
}


if (e.OldValue != null)
{
return;
}


WeakEventManager<object, RoutedEventArgs>.AddHandler(
passwordBox,
nameof(System.Windows.Controls.PasswordBox.PasswordChanged),
SendPassword_OnPasswordChanged);
}


private static void SendPassword_OnPasswordChanged(object sender, RoutedEventArgs e)
{
var attachedElement = sender as System.Windows.Controls.PasswordBox;
SecureString commandParameter = attachedElement?.SecurePassword;
if (commandParameter == null || commandParameter.Length < 1)
{
return;
}


ICommand sendCommand = GetCommand(attachedElement);
sendCommand?.Execute(commandParameter);
}
}

不幸的是,我没有足够的代表来评论另一个答案:( 只是想继续“Steve In co”的回答。 更新他的“PasswrodChanged"如果在一个视图中有多个PasswordBox,通过使用发送方的Name属性,将事件设置为以下选项,以启用对所有PasswordBox使用相同的PasswordChanged事件。可能有更好的方法来做到这一点,如果有任何理由不这样做,请让我知道:)

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (sender is PasswordBox) {
if (this.DataContext != null)


{ ((dynamic)this.DataContext).GetType().GetProperty(((PasswordBox)sender).Name).SetValue((dynamic)this.DataContext, ((PasswordBox)sender).Password, null); }
}
}