Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my WPF PasswordBox style trigger not working?

So I have this PasswordBox in my app.

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>

And I need to change its border color when the password entered is empty.

Following Code Changes BorderColor if PasswordBox's Password Value Changed.
Note: PasswordBox Disabling is Happening After PasswordBox.Password is Cleared. So this Shouldn't Matter [I Guess]. 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;
                }
        }
    }

The password value is not accessible, so I used the ClipToBounds Boolean value to set it, like this: C#

if (String.IsNullOrEmpty(PB_PASSWORD.Password))
{ PB_PASSWORD.ClipToBounds = true; }
else
{ PB_PASSWORD.ClipToBounds = false; }

This is working fine when the app first starts.

The problem starts when I modify the enable/disable value from the code-behind, such as in the following:

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

It's supposed to look like this, after typing in the password and then disabling:

expected

But it looks like this:

actual

I need to solve it in the XAML code.

like image 579
Akshay Bhanawala Avatar asked May 04 '17 12:05

Akshay Bhanawala


1 Answers

Link to project: HERE - Must download!

I have decided to provide you with a full - MVVM example, so that you can learn how to do it "the proper way".

Note: I am using MVVM Light (for it's RelayCommand). You can install it through NuGet. It's worth having, as it provides a lot of useful classes for MVVM development. Another alternative to it is Prism.

1. What is MVVM?

MVVM (Model - View - ViewModel) is a programming pattern, that goes along perfectly with WPF. It's main purpose is to detach Views - what you see, from ViewModels - your program's logic.

It can result in a little bit more coding needed, but the payoff is great - you get a clean, structured code, that is modular and VERY easily testable (namely, Unit Tests).

1.1 - Model

Model is basically the structure of your program. It should provide the backbone for your classes, that hold the Data and are further used in ViewModels.

In the case of this project - there is no model though - as its not needed (it surely will become needed as you progress with your application!)

1.2 - View

The View is basically what you see. Most often it is a Window, that has elements it displays - but it doesn't have to be only that! A UserControl can be a View in itself, and have it's own ViewModel bound to it - a different one than the Window it is in.

1.3 ViewModel

A ViewModel is basically the core of your program. It holds the logic and has properties, that the View can bind to and use/display in its' controls. Consider this a brain of your application.

2. Code

View:

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

This isn't much different from what you have provided, but it had been warped into MVVM - bindings, commands and converters were added. They are required to Bind (connect) our View and ViewModel.

The View's code behind:

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

In the constructor of the View, we define our ViewModel - and set it as DataContext for our View.

Below are some event handlers, that allow us to attach events and properties - but we will talk a bit later about that.

Important: When you go ahead and start learning about MVVM on your own, you might see people saying "In good MVVM there should be no code-behind in View". That is actually complete bull**it :) It is absolutely fine to put code-behind in View, as long as it doesn't violate any MVVM principles - and often times, it makes it a little bit easier to code some things.

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

There is a lot of things going on in here. First off, we have some public properties, like Password, isPasswordFieldDisabled etc. These are the ones that our View can bind to, and it lets us control the View from our ViewModel.

We also have commands, which are a way of the View to interact with our ViewModel. The View binds certain things to these commands (like events) and we then execute code based on that in our ViewModel.

On this example, a button click fires a command to our ViewModel, which changes the isPasswordBoxDisabled property, that our PasswordBox isEnabled property is bound to - in effect, enabling/disabling the PasswordBox, without any DIRECT interaction between View and ViewModel! Cool, huh?

Converter

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

The converter is much like a translator. Here, we translate our Password length into colors - so that we can later use them in animation. This is a perfect example of what I meant when I said MVVM was modular - you want different convertion? Just slap a binding to a new converter and you are done, no need to rewrite your View!

Password Attached property - look linked project

This is here solely for our need to bind our PasswordBox's password to our ViewModel. Originally, a PasswordBox doesn't support that. Enter attached properties! It allows as to "extend" the possibilites of our PasswordBox, by attaching (hence the name) a new property to it - a one we can bind to our ViewModel.

Same goes for attached events - you can find everything else in the code, as the character quota limits me from pasting more.

like image 112
Kamil Solecki Avatar answered Sep 27 '22 22:09

Kamil Solecki