为啥我的 WPF PasswordBox 样式触发器不起作用?
Posted
技术标签:
【中文标题】为啥我的 WPF PasswordBox 样式触发器不起作用?【英文标题】:Why is my WPF PasswordBox style trigger not working?为什么我的 WPF PasswordBox 样式触发器不起作用? 【发布时间】:2017-10-02 15:02:35 【问题描述】:所以我的应用中有这个 PasswordBox
。
XAML
<PasswordBox Name="PB_PASSWORD" Padding="100,0,34,0" FontSize="20" Width="384" Height="34" PasswordChar="█" Password="" HorizontalAlignment="Left" VerticalAlignment="Top" FontFamily="Century Gothic" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" TabIndex="2" PasswordChanged="PB_PASSWORD_PasswordChanged" >
<PasswordBox.Style>
<Style BasedOn="x:Null" TargetType="x:Type PasswordBox">
<Setter Property="Background" Value="#FFCCCCCC" />
<Setter Property="Foreground" Value="#FFF22613" />
<Setter Property="BorderBrush" Value="#FFF22613" />
<Setter Property="BorderThickness" Value="0,2,0,2" />
<Setter Property="ClipToBounds" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="PasswordBox">
<Grid>
<Border Background="TemplateBinding Background"
BorderBrush="TemplateBinding BorderBrush"
BorderThickness="TemplateBinding BorderThickness">
<ScrollViewer x:Name="PART_ContentHost" Margin="0,-4,0,0" />
</Border>
<TextBlock Name="TB" Text="Password" HorizontalAlignment="Left" Margin="140,0,0,0" VerticalAlignment="Center" Foreground="#FF222222" Opacity="0.3"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="true">
<Setter TargetName="TB" Property="Text" Value="Password:" />
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="100,0,34,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="2,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="230,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="140,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="0.3" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="ClipToBounds" Value="false">
<Setter TargetName="TB" Property="Text" Value="Password:" />
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="100,0,34,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="2,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="230,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="140,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="0.3" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="false" />
<Condition Property="ClipToBounds" Value="true" />
</MultiTrigger.Conditions>
<Setter TargetName="TB" Property="Text" Value="Password" />
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="230,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="140,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="0.3" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="100,0,34,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="2,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.ExitActions>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="TB" Property="Text" Value="DISABLE"/>
<Setter TargetName="TB" Property="Margin" Value="140,0,0,0"/>
<Setter Property="Background" Value="#FFAAAAAA"/>
<Setter Property="Foreground" Value="#FF777777"/>
<Setter Property="BorderBrush" Value="#FF888888" />
<Setter Property="BorderThickness" Value="0,3,0,3" />
</Trigger>
<Trigger Property="Tag" Value="ShowPW">
<Setter Property="Visibility" Value="Hidden"/>
</Trigger>
<Trigger Property="Tag" Value="HidePW">
<Setter Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
</Style.Triggers>
</Style>
</PasswordBox.Style>
</PasswordBox>
而且我需要在输入的密码为空时更改它的边框颜色。
如果 PasswordBox 的密码值发生更改,BorderColor 会发生以下代码更改。 注意:PasswordBox 禁用是在 PasswordBox.Password 被清除后发生的。所以这不应该重要[我猜]。 c#
private void PB_PASSWORD_PasswordChanged(object sender, RoutedEventArgs e)
if (PB_PASSWORD.SecurePassword.Length == 0)
//Password is Empty.
PB_PASSWORD.ClipToBounds = true;
else
//Password Not Empty
PB_PASSWORD.ClipToBounds = false;
Int32 PWStrength = 0;
if (PB_PASSWORD.SecurePassword.Length >= 5)
//A Function that Return int Value between 0-5 depending on how Strong is Password.
PWStrength = GetPasswordStrength(Marshal.PtrToStringUni(Marshal.SecureStringToGlobalAllocUnicode(PB_PASSWORD.SecurePassword)));
//Corresponding Colors Are Set as per Returned Integer0=red, 1=Orange+Red, 2=Orange, 3=Yellow, 4=Light Green, 5=Green
switch (PWStrength)
case 0:
//Following 2 Lines Required to Unfreez Color From Control
PB_PASSWORD.Foreground = new SolidColorBrush(CustomColors.PasswordStrengthColors[PWStrength]);
PB_PASSWORD.BorderBrush = new SolidColorBrush(CustomColors.PasswordStrengthColors[PWStrength]);
ColorAnimation AnimateForegroundColor_0 = new ColorAnimation(CustomColors.PasswordStrengthColors[PWStrength], new Duration(TimeSpan.FromMilliseconds(200)));
PB_PASSWORD.Foreground.BeginAnimation(SolidColorBrush.ColorProperty, AnimateForegroundColor_0);
ColorAnimation AnimateBorderBrushColor_0 = new ColorAnimation(CustomColors.PasswordStrengthColors[PWStrength], new Duration(TimeSpan.FromMilliseconds(200)));
PB_PASSWORD.BorderBrush.BeginAnimation(SolidColorBrush.ColorProperty, AnimateBorderBrushColor_0);
break;
default:
ColorAnimation AnimateForegroundColor = new ColorAnimation(CustomColors.PasswordStrengthColors[PWStrength], new Duration(TimeSpan.FromMilliseconds(200)));
PB_PASSWORD.Foreground.BeginAnimation(SolidColorBrush.ColorProperty, AnimateForegroundColor);
ColorAnimation AnimateBorderBrushColor = new ColorAnimation(CustomColors.PasswordStrengthColors[PWStrength], new Duration(TimeSpan.FromMilliseconds(200)));
PB_PASSWORD.BorderBrush.BeginAnimation(SolidColorBrush.ColorProperty, AnimateBorderBrushColor);
break;
密码值不可访问,所以我使用了ClipToBounds
布尔值来设置它,如下所示:
C#
if (String.IsNullOrEmpty(PB_PASSWORD.Password))
PB_PASSWORD.ClipToBounds = true;
else
PB_PASSWORD.ClipToBounds = false;
当应用程序首次启动时,这工作正常。
当我从代码隐藏中修改启用/禁用值时,问题就开始了,如下所示:
C#
private void Button_Click(object sender, RoutedEventArgs e)
if (PB_PASSWORD.IsEnabled)
PB_PASSWORD.ClipToBounds = true;
PB_PASSWORD.Password = "";
BTN_BROWSE.Focus();
PB_PASSWORD.MoveFocus(new System.Windows.Input.TraversalRequest(System.Windows.Input.FocusNavigationDirection.Next));
PB_PASSWORD.IsEnabled = false;
else
PB_PASSWORD.IsEnabled = true;
在输入密码然后禁用后应该是这样的:
但它看起来像这样:
我需要在 XAML 代码中解决它。
【问题讨论】:
我认为问题不在于发布的代码中的任何地方。我将它全部复制到一个示例项目中,对我来说 - 一切都如你所描述的那样工作。请显示所有与密码框有关的代码。此外,永远不要使用控件自己的 DP 作为您自己的值的占位符。您应该在您的 VM 中创建一个属性,然后读取/写入它,而不是写入 ClipToBounds。 用代码更新。我认为这会引起注意,因为在 PasswordBox.Password 被清除后会发生禁用。另外,您能否向我解释一下如何创建自己的财产或提供指导。它会在很多方面帮助我.. 这个控件后面没有ViewModel吗?为什么要这样工作? PasswordStrength 应该是 ViewModel 上的计算属性,唯一需要发生的事情是将实例的 BorderBrush 和 Foreground 绑定到 PasswordStrength,并使用转换器返回颜色。你所拥有的只是错误的,之后很难维护。 @Mishka 确实似乎没有任何 ViewModel。一切都应该转移到 ViewModel 并使用绑定和命令进行管理(除了密码,最好的方法(即使它违反 MVVM)是从控件的 CLR 属性中实际获取它,就像他所做的那样。 我只是在学习基础知识,现在别让我太难,现在我必须在 Google 上搜索如何使用 MVVM :P... 我只需要回答为什么我的代码不工作...之后我会找出 MVVM :) 【参考方案1】:项目链接:HERE - 必须下载!
我决定为您提供一个完整的 MVVM 示例,以便您了解如何“以正确的方式”进行操作。
注意:我正在使用MVVM Light(因为它是 RelayCommand)。您可以通过 NuGet 安装它。值得拥有,因为它为 MVVM 开发提供了很多有用的类。它的另一种选择是Prism。
1.什么是 MVVM?
MVVM (Model - View - ViewModel) 是一种编程模式,与 WPF 完美搭配。它的主要目的是从 ViewModels(您的程序逻辑)中分离出 Views(您所看到的)。
这可能会导致需要更多的编码,但回报是巨大的 - 你会得到一个干净、结构化的代码,它是模块化的并且非常容易测试(即单元测试)。
1.1 - 模型
模型基本上是程序的结构。它应该为您的类提供主干,保存数据并在 ViewModel 中进一步使用。
在这个项目的情况下 - 虽然没有模型 - 因为它不需要(随着您的应用程序的进展,它肯定会变得需要!)
1.2 - 查看
视图基本上就是您所看到的。大多数情况下,它是一个窗口,它具有它显示的元素 - 但它不必只是那个! UserControl 本身可以是一个 View,并且绑定了自己的 ViewModel - 与它所在的 Window 不同。
1.3 视图模型
ViewModel 基本上是程序的核心。它拥有逻辑并具有属性,视图可以绑定到并在其控件中使用/显示。
将其视为您的应用程序的 brain
。
2。代码
查看:
<Window x:Class="PasswordBoxMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PasswordBoxMVVM"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:b="clr-namespace:System.Media;assembly=System"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:PasswordLengthToColorConverter x:Key="passwordLengthToColorConverter" />
</Window.Resources>
<Grid>
<StackPanel VerticalAlignment="Center">
<PasswordBox local:PasswordBoxMVVMAttachedProperties.EncryptedPassword="Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged"
IsEnabled="Binding Path=IsPasswordFieldDisabled, Mode=TwoWay ,UpdateSourceTrigger=PropertyChanged"
FontSize="20" Width="384" Height="34" PasswordChar="█" HorizontalAlignment="Center" FontFamily="Century Gothic"
HorizontalContentAlignment="Left" VerticalContentAlignment="Center" TabIndex="2" Foreground="Red"
PasswordChanged="MyPasswordBox_PasswordChanged"
IsEnabledChanged="PasswordBox_IsEnabledChanged">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PasswordChanged">
<i:InvokeCommandAction Command="Binding Path=PasswordChangedCommand" />
</i:EventTrigger>
</i:Interaction.Triggers>
<PasswordBox.Style>
<Style TargetType="x:Type PasswordBox">
<Setter Property="Background" Value="#FFCCCCCC" />
<Setter Property="BorderThickness" Value="0,2,0,2" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="ClipToBounds" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="PasswordBox">
<Grid>
<Border Background="TemplateBinding Background"
BorderThickness="TemplateBinding BorderThickness"
BorderBrush="TemplateBinding Foreground">
<ScrollViewer x:Name="PART_ContentHost" Margin="0,-4,0,0"/>
</Border>
<TextBlock Name="TB" Text="Password" HorizontalAlignment="Left" Margin="140,0,0,0" VerticalAlignment="Center" Opacity="0.3" Foreground="Gray">
</TextBlock>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="true">
<Setter TargetName="TB" Property="Text" Value="Password:" />
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="100,0,34,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="2,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="230,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="140,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="0.3" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<DataTrigger Binding="Binding Path=IsPasswordFieldEmpty,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" Value="false">
<Setter TargetName="TB" Property="Text" Value="Password:" />
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="100,0,34,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="2,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="230,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="140,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="0.3" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="Binding Path=IsFocused, RelativeSource=RelativeSource Self" Value="false" />
<Condition Binding="Binding Path=IsPasswordFieldEmpty, UpdateSourceTrigger=PropertyChanged" Value="true" />
</MultiDataTrigger.Conditions>
<Setter TargetName="TB" Property="Text" Value="Password" />
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="230,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="140,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="0.3" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<BackEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Padding" To="100,0,34,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<ThicknessAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Margin" To="2,0,0,0" Duration="0:0:0.3">
<ThicknessAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
<DoubleAnimation Storyboard.TargetName="TB" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.3">
<DoubleAnimation.EasingFunction>
<SineEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="TB" Property="Text" Value="DISABLE"/>
<Setter TargetName="TB" Property="Margin" Value="140,0,0,0"/>
<Setter Property="Background" Value="#FFAAAAAA"/>
<Setter Property="Foreground" Value="Gray"/>
<Setter Property="BorderBrush" Value="#FF888888" />
<Setter Property="BorderThickness" Value="0,3,0,3" />
</Trigger>
<Trigger Property="Tag" Value="ShowPW">
<Setter Property="Visibility" Value="Hidden"/>
</Trigger>
<Trigger Property="Tag" Value="HidePW">
<Setter Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
</Style.Triggers>
</Style>
</PasswordBox.Style>
<PasswordBox.Triggers>
<EventTrigger RoutedEvent="PasswordBox.PasswordChanged">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(PasswordBox.Foreground).(SolidColorBrush.Color)"
To="Binding Path=Password.Length, UpdateSourceTrigger=PropertyChanged,
Converter=StaticResource passwordLengthToColorConverter" Duration="0:0:0.1">
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="local:PasswordBoxAttachedEvent.HasBeenDisabled">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(PasswordBox.Foreground).(SolidColorBrush.Color)"
To="Gray" Duration="0:0:0.1">
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="local:PasswordBoxAttachedEvent.HasBeenEnabled">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(PasswordBox.Foreground).(SolidColorBrush.Color)"
To="Binding Path=Password.Length, UpdateSourceTrigger=PropertyChanged,
Converter=StaticResource passwordLengthToColorConverter" Duration="0:0:0.1">
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</PasswordBox.Triggers>
</PasswordBox>
<Button Width="200" Height="50" Margin="0,50,0,0" Command="Binding Path=ClickCommand">Click me</Button>
</StackPanel>
</Grid>
这与您提供的内容没有太大区别,但它已被扭曲到 MVVM 中 - 添加了绑定、命令和转换器。他们需要绑定(连接)我们的 View 和 ViewModel。
View 背后的代码:
public partial class MainWindow : Window
public MainWindow()
var vm = new PasswordViewModel();
this.DataContext = vm;
InitializeComponent();
private void MyPasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
PasswordBox pBox = sender as PasswordBox;
PasswordBoxMVVMAttachedProperties.SetEncryptedPassword(pBox, pBox.SecurePassword);
private void PasswordBox_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
PasswordBox pBox = sender as PasswordBox;
if (pBox.IsEnabled == false)
RoutedEventArgs eventArgs = new RoutedEventArgs(PasswordBoxAttachedEvent.HasBeenDisabledEvent);
pBox.RaiseEvent(eventArgs);
if (pBox.IsEnabled == true)
RoutedEventArgs eventArgs = new RoutedEventArgs(PasswordBoxAttachedEvent.HasBeenEnabledEvent);
pBox.RaiseEvent(eventArgs);
在 View 的构造函数中,我们定义了 ViewModel - 并将其设置为 DataContext
用于我们的 View。
以下是一些事件处理程序,它们允许我们附加事件和属性 - 但我们稍后会讨论。
重要提示:当您继续开始自学 MVVM 时,您可能会看到有人说“在好的 MVVM 中,视图中不应该有代码隐藏”。这实际上是完全的公牛**它:) 将代码隐藏在 View 中绝对没问题,只要它不违反任何 MVVM 原则 - 而且通常情况下,它会使某些东西的代码更容易一些。
ViewModel:
namespace PasswordBoxMVVM
public class PasswordViewModel : ViewModelBase
private bool isPasswordFieldEmpty;
public bool IsPasswordFieldEmpty
get return isPasswordFieldEmpty;
set
isPasswordFieldEmpty = value;
RaisePropertyChanged();
private SecureString password;
public SecureString Password
get return password;
set
password = value;
RaisePropertyChanged();
private bool isPassWordFieldDisabled;
public bool IsPasswordFieldDisabled
get return isPassWordFieldDisabled;
set
isPassWordFieldDisabled = value;
RaisePropertyChanged();
public ICommand ClickCommand get return new RelayCommand(doAction, canDoAction);
public ICommand PasswordChangedCommand get return new RelayCommand(updatePassword, canUpdatePassword);
public PasswordViewModel()
// Init conditions, need them to not get null reference at the start.
isPassWordFieldDisabled = true;
IsPasswordFieldEmpty = true;
private void doAction()
IsPasswordFieldDisabled = !IsPasswordFieldDisabled;
private bool canDoAction()
// Replace this with any condition that you need.
return true;
private void updatePassword()
if (Password != null)
if (Password.Length > 0)
isPasswordFieldEmpty = false;
else
isPasswordFieldEmpty = true;
else
isPasswordFieldEmpty = true;
private bool canUpdatePassword()
// Replace this with any condition that you need.
return true;
这里发生了很多事情。首先,我们有一些公共属性,例如 Password、isPasswordFieldDisabled 等。这些是我们的 View 可以绑定的属性,它让我们可以从 ViewModel 控制 View。
我们也有命令,这是 View 与我们的 ViewModel 交互的一种方式。 View 将某些内容绑定到这些命令(如事件),然后我们根据 ViewModel 中的内容执行代码。
在此示例中,单击按钮会触发对 ViewModel 的命令,该命令会更改 isPasswordBoxDisabled 属性,我们的 PasswordBox isEnabled 属性绑定到 - 实际上,启用/禁用 PasswordBox,而 View 和 ViewModel 之间没有任何直接交互!很酷吧?
转换器
namespace PasswordBoxMVVM
class PasswordLengthToColorConverter : IValueConverter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
int length = (int)value;
Color output = Colors.Red;
if (length >= 0 && length < 5)
output = Colors.Red;
else if (length >= 5 && length < 6)
output = Colors.Orange;
else if (length >= 6 && length < 7)
output = Colors.Yellow;
else if (length >= 7 && length < 8)
output = Colors.LightGreen;
else if (length >= 8)
output = Colors.Green;
return output;
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
throw new NotSupportedException();
转换器很像翻译器。在这里,我们将密码长度转换为颜色 - 以便我们以后可以在动画中使用它们。 当我说 MVVM 是模块化时,这是一个完美的例子——你想要不同的转换?只需将绑定绑定到新转换器即可完成,无需重写您的视图!
密码附加属性 - 查看链接项目
这里只是为了我们需要将 PasswordBox 的密码绑定到我们的 ViewModel。最初,PasswordBox 不支持这一点。输入附加属性!它允许“扩展”我们 PasswordBox 的可能性,方法是向它附加(因此得名)一个新属性 - 我们可以绑定到我们的 ViewModel 的一个。
附加事件也是如此 - 您可以在代码中找到其他所有内容,因为字符配额限制了我粘贴更多内容。
【讨论】:
@AkshayBhanawala 绝对。有什么问题可以私信我! 伙计,我想我弄清楚了您提供的代码的流程。但是现在,我不了解 MVVM 基础,所以我实际上无法理解 [此时]。并且您提供的代码中存在一些问题。当控件失去焦点时,控件中的密码文本和包含的密码不得返回控件的起始位置。看图:i.stack.imgur.com/di0wi.png @AkshayBhanawala 我会按照你的意愿解决这种行为。如果您希望我更深入地向您解释发生了什么,我们可以在晚上的某个地方开始聊天 我很想获得更多信息,我有很多新项目在我的脑海中,所以它会很棒。如果我从新的大学工作中抽出时间,我会通知你的。我需要在 1 或 2 周内转到另一个州的另一所大学,并且没有太多时间来发展。 @AkshayBhanawala 随时联系我以上是关于为啥我的 WPF PasswordBox 样式触发器不起作用?的主要内容,如果未能解决你的问题,请参考以下文章
WPF自定义控件与样式-TextBox & RichTextBox & PasswordBox样式水印Label标签功能扩展
为啥 IsMouseOver 被识别而 MouseDown 不是(Wpf 样式触发器)?
在 PasswordBox 中捕获返回键的最佳方法是啥? (WPF/XAML)