Contents
- News
- Introduction
- Background
- Requirements
- Using the Control
- List of all class members
- The code of the UserControl
- Points of Interest
- Recent Changes
- History
News
After the member
IFFI has asked me if there shouldn't be
an option for entering the user name, I decided to implement this
functionality. I
am
also
of the opinion that it
makes more sense
when the user
has the ability to
enter
both : the user name and the password.
Only in this way
it
is
guaranteed that
an user switch is easy as it
should be and that the control
can be
used in various applications.
However, I also leave the old
functionality (without
the option to enter the user name).
All code changes can
be found here.
Furthermore i have added a second Demo to demonstrate this new functionality
(see
here).
Introduction
For everbody who is interested in a lightweight user validation control (for WPF
applications) I have implemented a Smart Login Overlay WPF Control. The Login Overlay
- especially the PasswordBox - has the same visual styles and behaviour like the
Windows 8
Login Screen. I have made several Screenshots of the Windows 8 Login Screen and try to imitate it as good as possible.
Hereinafter are some screenshots of the control in action so you can see what I'm
talking about.
Behaviour of the control
If you try to sign in with no credentials you will get this behaviour (the text is customizable) :
If you try to sign in with wrong credentials you will get this behaviour (the text is customizable) :
There is also a fully functional RevealButton implemented (like in the Windows 8
PasswordBox) :
Furthermore there is a hint when CapsLock is active (the text is customizable) :
Background
A few days earlier I have implemented and written an article about a Smart Password Box in my blog (Windows 8 PasswordBox Style for WPF).
After this was made I decided to make it a little bit smarter and style it like the
Windows 8
Login Screen.
I implement
most of these components in my spare
time. Therefore I ask you to please be considerate if anything should not be perfect.
If you need some assistance please do not hesitate to ask me.
Requirements
To use the SmartLoginOverlay WPF Control, you require :
Using the Control
The usage of the control is really simple. Just add a reference to the
"WPFSmartLibraryLight35.dll" in your project (RightClick on References and choose "Add Reference").
Your references should look like this:
After the reference was added you should declare an XAML Namespace which maps
to the various WPFSmartLibrary
CLR-Namespaces. Add the following XAML
Namespace
to your Window Tag:
<... xmlns:wpfsl="http://schemas.softarcs.com/wpfsmartlibrary" ...>
After you have done this it will be possible to use the control in your XAML
code like this:
<wpfsl:SmartLoginOverlay FullSpan="On" Background="#FF16499A"
UserName="{Binding UserName}"
Password="{Binding Password}"
AccessPassword="{Binding UserPassword}"
UserImageSource="{Binding UserImageSource}"
AdditionalUserInfo="{Binding EMailAddress}"
AdditionalSystemInfo="Locked" />
Two
things are very important and
should be followed:
- The control should be a child of a
Grid
panel. If the parent of the
control is not a Grid panel, the Span properties will not work properly (More
information about the Span properties).
- To Guarantee that the control is the topmost element it must either be
the last defined control in your XAML or the
Panel.ZIndex
property must be set to
the highest value of all defined controls.
There are three possible approaches using the control:
- Full MVVM with auto password validation provided by the control
- You can see an example of this approach in the XAML code above. The
password validation will be performed automatically by the user control
if the
AccessPassword
property is set.
- Full MVVM with custom password validation in the viewmodel using command binding (recommended)
- The XAML Code for this approach would look like this (as you can see the
AccessPassword
property is not set) :
<wpfsl:SmartLoginOverlay x:Name="SmartLoginOverlayControl"
FullSpan="On" Background="#FF16499A"
UserName="{Binding UserName}"
Password="{Binding Password}"
UserImageSource="{Binding UserImageSource}"
AdditionalUserInfo="{Binding EMailAddress}"
Command="{Binding SubmitCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self} }" />
Here is a sample implementation of the Command property and the corresponding
command methods
this.SubmitCommand = new ActionCommand( this.ExecuteSubmit, this.CanExecuteSubmit );
public ICommand SubmitCommand { get; private set; }
private void ExecuteSubmit(object commandParameter)
{
Debug.WriteLine( "Here you would implement the submission and a following validation of this data:\n" +
this.UserName + "\n" + this.EMailAddress + "\n" + this.Password );
var accessControlSystem = commandParameter as SmartLoginOverlay;
if (accessControlSystem != null)
{
if (this.Password.Equals( this.UserPassword ))
{
accessControlSystem.Unlock();
}
else
{
accessControlSystem.ShowWrongCredentialsMessage();
}
}
}
private bool CanExecuteSubmit(object commandParameter)
{
return !string.IsNullOrEmpty( this.Password );
}
- Assign fixed values to the properties and let the control do the
validation or validate the password in code behind (for
everybody who is not familiar with MVVM or don't wanna use it)
-
The XAML Code for this approach would look like this :
<wpfsl:SmartLoginOverlay x:Name="SmartLoginOverlayControl"
FullSpan="On" Background="#FF16499A"
UserName="PSY" AccessPassword="gangnamstyle"
UserImageSource="E:\WPFSmartLibraryLight\LoginOverlayDemo\Images\PSY.png"
AdditionalUserInfo="psy@youtuberecord.com"
AdditionalSystemInfo="Locked" />
And if you want to handle the password validation on your own in code
behind just leave the AccessPassword
unset and assign an event
handler to the SubmitRequested
event.
All three approaches are shown in the demo application.
List of all class members
SmartLoginOverlay Class
Namespace : SoftArcs.WPFSmartLibrary.UserControls
Assembly : WPFSmartLibraryLight35.dll
XMLNS for XAML : http://schemas.softarcs.com/wpfsmartlibrary
The SmartLoginOverlay type exposes the following members :
Constructors
|
Name |
Description |
|
SmartLoginOverlay |
Initializes a new instance of the SmartLoginOverlay class. |
Properties
|
Name |
Description |
Property Type |
|
AccessPassword |
Gets or sets the password which will be accepted by the intern validation process.
This is a dependency property (BindsTwoWayByDefault). |
String |
|
AdditionalSystemInfo |
Gets or sets the additional system information which will be displayed under the AdditionalUserInfo
with a smaller font size and a opacity of 0.6. This is a dependency property. |
String |
|
AdditionalUserInfo |
Gets or sets the additional user information which will be displayed under the UserName with a smaller font size.
This is a dependency property. |
String |
|
CapsLockInfo |
Gets or sets the hint which will be displayed when CapsLock is active.
This is a dependency property. The default value is "Caps Lock is
active". |
String |
|
Command |
Gets or sets the command to invoke when the submit button is clicked or the enter key is pressed.
This is a dependency property. (Inherited from AdvancedUserControl) |
ICommand |
|
CommandParameter |
Gets or sets the parameter to pass to the Command property. This is a dependency property. (Inherited from AdvancedUserControl) |
ICommand |
|
DisappearAnimation |
Gets or sets the type of the disappear animation. This is a dependency property.
The default value is MoveAndFadeOutToRight. |
DisappearAnimationType |
|
FullColumnSpan |
Turn a full column span of the UserControl On or Off. This is a dependency property. The default value is Off.
This applies only when the parent element is a Grid panel. (Inherited from
AdvancedUserControl) |
SwitchState |
|
FullRowSpan |
Turn a full row span of the UserControl On or Off. This is a dependency property. The default value is Off.
This applies only when the parent element is a Grid panel. (Inherited from
AdvancedUserControl) |
SwitchState |
|
FullSpan |
Turn a full row and column span of the UserControl On or Off. This is a dependency property. The default value is Off.
This applies only when the parent element is a Grid panel. (Inherited from
AdvancedUserControl) |
SwitchState |
|
IsUserOptionAvailable
|
Gets or sets the option for entering the user name. This is a dependency property. |
Boolean |
|
NoCredentialsInfo |
Gets or sets the hint which will be displayed when trying to sign in with NO credentials.
This is a dependency property. The default value is "Enter your credentials and try again.". |
String |
|
Password |
Gets or sets the Password which is inputted by the user. This is a dependency property (BindsTwoWayByDefault).
(Because it is a dependency property it is bindable contrary to the not bindable Password property of the standard PasswordBox) |
String |
|
SubmitButtonTooltip |
Gets or sets the tooltip of the submit button. This is a dependency property. The default value is "Submit". |
String |
|
UserImageSource |
Gets or sets the URI to the image which will be displayed for the recent user. This is a dependency property. |
String |
|
UserName |
Gets or sets the name of the user which will be displayed as the recent user. This is a dependency property
(BindsTwoWayByDefault and DefaultUpdateSourceTrigger=PropertyChanged). |
String |
|
Watermark |
Gets or sets the Watermark in the PasswordBox which is shown when the PasswordBox is empty.
This is a dependency property. The default value is "Enter password". |
String |
|
WrongCredentialsInfo |
Gets or sets the hint which will be displayed when trying to sign in with WRONG credentials.
This is a dependency property. The default value is "The password is incorrect. Make sure that you use the password for your account. You can reset the password at any time under 'myaccount.credentialserver.com/reset'.". |
String |
Methods
|
Name |
Description |
|
Lock |
Reset all animations, assimilate the background if neccessary and set the focus to the PasswordBox Control. |
|
ShowNoCredentialsMessage |
Shows the NoCredentialsInfo and set the focus to the OK button. |
|
ShowWrongCredentialsMessage |
Shows the WrongCredentialsInfo and set the focus to the OK button. |
|
Unlock |
Perform the defined (or the default) DisappearAnimation and a FadeOut animation to make the overlay disappear. |
Events
|
Name |
Description |
|
SubmitRequested |
Occurs when the sumbit button is clicked or the enter key is pressed while the control has the focus. |
The DisappearAnimationType enumeration
public enum DisappearAnimationType
{
FadeOut,
MoveAndFadeOutToRight,
MoveAndFadeOutToTop,
MoveAndFadeOutToRightSimultaneous,
MoveAndFadeOutToTopSimultaneous
}
The AdvancedUserControl base class
The AdvancedUserControl
exposes some interesting properties which are useful when creating new UserControls.
First off all it has the Span Properties : FullSpan, FullRowSpan, FullColumnSpan.
With this properties it is posssible to span the UserControl over all rows resp. columns of the parent Grid.
Now you will say : “Ok, bu I can do this with the AttachedProperties Grid.Row resp. Grid.Column !”
Yes that’s true but when you add columns or rows to your parent Grid you have always to adjust the AttachedProperties to fit
all columns resp. rows. With the Span Properties of the AdvancedUserControl
you have the advantage that it will
always span over all columns resp. rows. You have not to take care about it when you add more columns resp. rows.
The Span Properties themselves use also an interesting enumeration : The SwitchState
.
The SwitchState enumeration is something similar to the boolean type but in my
opinion it fits better in some use cases. The
SwitchState enumeration contains only two values "On and Off".
In some cases I was not really satisfied with the build-in boolean "true / false" - type. I think that in some cases it is more
properly to use a SwitchState than a boolean type.
Other properties exposed by the AdvancedUserControl
are the Command property and the corresponding
CommandParameter property. If you are using MVVM you will always try to use commanding instead of
code behind event handler.
So if you are creating a new UserControl you can connect the most important event
to the Command property so that the user
of your UserControl is able to use it in his ViewModel.
The SwitchState enumeration
public enum SwitchState
{
On,
Off
}
The code of the UserControl
Now let's get to the code. First of all here is the XAML code of the
UserControl (see recent changes here) :
<ft:AdvancedUserControl x:Class="SoftArcs.WPFSmartLibrary.SmartUserControls.SmartLoginOverlay"
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:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ap="clr-namespace:SoftArcs.WPFSmartLibrary.UIClassAttachedProperties"
xmlns:ft="clr-namespace:SoftArcs.WPFSmartLibrary.FoundationTypes"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="350"
x:Name="VisualRoot" Loaded="SmartLoginOverlay_Loaded">
<ft:AdvancedUserControl.RenderTransform>
<TranslateTransform x:Name="VisualRootTranslateTransform" />
</ft:AdvancedUserControl.RenderTransform>
<ft:AdvancedUserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="..\ResourceDictionaries\CommonRD\TextAndFontsRD.xaml" />
<ResourceDictionary Source="..\ResourceDictionaries\SmartStyles\SmartPasswordBoxesRD.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource {x:Type Label}}">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontFamily" Value="Segoe UI Light" />
<Setter Property="FontSize" Value="{StaticResource StandardFontSize}" />
-->
</Style>
<Style TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource Win8ExtendedPasswordBoxStyle}">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Height" Value="25" />
<Setter Property="Width" Value="160" />
<Setter Property="FontSize" Value="{StaticResource MediumFontSize}" />
</Style>
<KeyTime x:Key="FadeOutDurationSimultaneousKeyTime">0:0:0.5</KeyTime>
<KeyTime x:Key="FadeOutDurationKeyTime">0:0:0.3</KeyTime>
<KeyTime x:Key="FadeInDurationKeyTime">0:0:0.1</KeyTime>
<Storyboard x:Key="MoveOutToTopStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRootTranslateTransform"
Storyboard.TargetProperty="Y">
<SplineDoubleKeyFrame KeyTime="0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="{StaticResource FadeOutDurationKeyTime}" Value="-300.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="MoveOutToRightStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRootTranslateTransform"
Storyboard.TargetProperty="X">
<SplineDoubleKeyFrame KeyTime="0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="{StaticResource FadeOutDurationKeyTime}" Value="300.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeOutStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot"
Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="0" Value="1"/>
<SplineDoubleKeyFrame KeyTime="{StaticResource FadeOutDurationKeyTime}" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="{StaticResource FadeOutDurationKeyTime}"
Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="MoveOutToTopSimultaneousStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="VisualRootTranslateTransform"
Storyboard.TargetProperty="Y">
<SplineDoubleKeyFrame KeyTime="0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="{StaticResource FadeOutDurationSimultaneousKeyTime}"
Value="-300.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="MoveOutToRightSimultaneousStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="VisualRootTranslateTransform"
Storyboard.TargetProperty="X">
<SplineDoubleKeyFrame KeyTime="0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="{StaticResource FadeOutDurationSimultaneousKeyTime}"
Value="300.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeOutSimultaneousStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="VisualRoot"
Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="0" Value="1"/>
<SplineDoubleKeyFrame KeyTime="{StaticResource FadeOutDurationSimultaneousKeyTime}"
Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="VisualRoot"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<DiscreteColorKeyFrame KeyTime="0" Value="Transparent" />
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VisualRoot"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="{StaticResource FadeOutDurationSimultaneousKeyTime}"
Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</ResourceDictionary>
</ft:AdvancedUserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="170" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RenderTransform>
<TranslateTransform x:Name="LayoutRootTranslateTransform" />
</Grid.RenderTransform>
<Border Grid.RowSpan="3" BorderThickness="1" VerticalAlignment="Top" Margin="0,20,0,0">
<Image x:Name="imgUser" Source="../CommonImages/UserSilhouette.png" MaxHeight="150" />
</Border>
<StackPanel Grid.Row="0" Grid.Column="1" Margin="0,20,0,0">
<Label x:Name="lblUserName" Margin="12,0" Padding="0" FontSize="22"
Content="{Binding ElementName=VisualRoot, Path=UserName, Mode=TwoWay}" />
<Label x:Name="lblAdditionalUserInfo" Margin="12,0" Padding="0" FontSize="12"
Content="{Binding ElementName=VisualRoot, Path=AdditionalUserInfo, Mode=TwoWay}" />
<Label x:Name="lblAdditionalSystemInfo" Margin="12,1" Padding="0" FontSize="12" Opacity="0.6"
Content="{Binding ElementName=VisualRoot, Path=AdditionalSystemInfo, Mode=TwoWay}" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1">
<PasswordBox x:Name="PasswordBoxControl" Margin="12,18,12,5" Width="200"
KeyDown="PasswordBoxControl_OnKeyDown"
GotFocus="PasswordBoxControl_OnGotFocus"
LostFocus="PasswordBoxControl_OnLostFocus"
ap:PasswordBoxBinding.Password="{Binding ElementName=VisualRoot, Path=Password,
Mode=TwoWay}"/>
<Label x:Name="lblCapsLockInfo" Margin="12,1" Padding="0" FontSize="10" Foreground="#FFFD8B6C"
Content="{Binding ElementName=VisualRoot, Path=CapsLockInfo, Mode=TwoWay}"
Visibility="Hidden" />
</StackPanel>
<StackPanel x:Name="FaultMessagePanel" Grid.Row="2" Grid.Column="1" Visibility="Hidden">
<TextBlock x:Name="tblNoCredentialsMessage" Margin="12,18,12,5" Padding="0" FontSize="10"
Foreground="#FFFD8B6C" Width="200" TextWrapping="Wrap"
Text="{Binding ElementName=VisualRoot, Path=NoCredentialsInfo, Mode=TwoWay}" />
<TextBlock x:Name="tblWrongCredentialsMessage" Margin="12,18,12,5" Padding="0" FontSize="10"
Foreground="#FFFD8B6C" Width="200" TextWrapping="Wrap"
Text="{Binding ElementName=VisualRoot, Path=WrongCredentialsInfo, Mode=TwoWay}" />
<Button x:Name="btnOK" Style="{StaticResource Win8_OKButtonStyle}" Margin="12,1"
HorizontalAlignment="Left" FontFamily="Segoe UI" Click="btnOK_OnClick"/>
</StackPanel>
</Grid>
</Grid>
</ft:AdvancedUserControl>
Now let me explain the most important parts of the XAML Code.
I would like to point you to the following line
<Style TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource Win8ExtendedPasswordBoxStyle}">
Here we define an implicit style for the PasswordBox type which is based
on the
Win8ExtendedPasswordBoxStyle
style. This is the most important part
of the UserControl because this style defines the
Win8
appearance
and the Win8 behaviour of the PasswordBox. You can find the XAML code of the
style here.
Next i want to show you the XAML code of the PasswordBox
<PasswordBox x:Name="PasswordBoxControl" Margin="12,18,12,5" Width="200"
KeyDown="PasswordBoxControl_OnKeyDown"
GotFocus="PasswordBoxControl_OnGotFocus"
LostFocus="PasswordBoxControl_OnLostFocus"
ap:PasswordBoxBinding.Password="{Binding ElementName=VisualRoot, Path=Password,
Mode=TwoWay}"/>
The most important thing here is the Password attached
property of the
PasswordBoxBinding class. Without this attached property it would not be possible to bind
the Password property of the PasswordBox because
it is not a DependencyProperty. Here is the code of the attached
property :
public class PasswordBoxBinding
{
private static bool IsUpdating;
public static string GetPassword(DependencyObject dpo)
{
return (string)dpo.GetValue( PasswordProperty );
}
public static void SetPassword(DependencyObject dpo, string value)
{
dpo.SetValue( PasswordProperty, value );
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.RegisterAttached( "Password", typeof( string ),
typeof( PasswordBoxBinding ),
new FrameworkPropertyMetadata( String.Empty,
new PropertyChangedCallback( OnPasswordPropertyChanged ) ) );
private static void OnPasswordPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
PasswordBox targetPasswordBox = sender as PasswordBox;
if (targetPasswordBox != null)
{
targetPasswordBox.PasswordChanged -= PasswordBox_PasswordChanged;
if (IsUpdating == false)
{
targetPasswordBox.Password = (string)e.NewValue;
}
targetPasswordBox.PasswordChanged += PasswordBox_PasswordChanged;
}
}
private static void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox passwordBox = sender as PasswordBox;
IsUpdating = true;
SetPassword( passwordBox, passwordBox.Password );
IsUpdating = false;
}
}
At least i would like to say a few words about this part of the XAML code
<Button x:Name="btnOK" Style="{StaticResource Win8_OKButtonStyle}" Margin="12,1"
HorizontalAlignment="Left" FontFamily="Segoe UI" Click="btnOK_OnClick"/>
This is the submit button. The look of the Button is defined in the
Win8_OKButtonStyle
which is implemented like this
<Style x:Key="Win8_OKButtonStyle" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Border x:Name="OuterBorder" Background="White" Width="70" Height="25">
<Border x:Name="ContentBorder" Background="#FF1FAEFF" Margin="0.7"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock x:Name="OK_TextBlock" Text="OK" HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="White" />
</Border>
</Border>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True">
<Setter TargetName="ContentBorder" Property="Background" Value="#FF3EB9FF" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsPressed}" Value="True">
<Setter TargetName="ContentBorder" Property="Background" Value="Transparent" />
<Setter TargetName="OK_TextBlock" Property="Foreground" Value="Black" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style
There
is just one thing
I want to point on.
As you can see in the XAML code of the UserControl i
have defined an implicit style for the label type where i have first used the Segoe WP SemiLight
font because it fits best to the Windows 8 font (no this is commented out). Unfortunately
this font is not
available as a standard font in Windows 7 so i switched to the available Segoe UI Light
font (if you install the Windows Phone SDK 7.0, 7.1 - maybe 8.0 - the just
mentioned font will be installed).
Here is the code of the
Win8ExtendedPasswordBoxStyle
used for the PasswordBox :
<Style x:Key="Win8ExtendedPasswordBoxStyle" TargetType="{x:Type PasswordBox}"
BasedOn="{StaticResource {x:Type PasswordBox}}">
<Setter Property="Foreground" Value="{StaticResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{StaticResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="PasswordChar" Value="?"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="4,1,1,2"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type PasswordBox}">
<Border x:Name="OuterBorder"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<TextBox x:Name="RevealedPassword"
Text="{TemplateBinding ap:PasswordBoxBinding.Password}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
VerticalContentAlignment="Center"
Padding="{TemplateBinding Padding}"
Margin="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
Visibility="Hidden" BorderThickness="0" IsReadOnly="True" FontFamily="Segoe UI" />
<TextBlock x:Name="PART_TextBlockHint"
Padding="{TemplateBinding Padding}"
Background="Transparent"
Cursor="IBeam" Margin="2,0" FontFamily="Segoe UI Light" Foreground="#FF5D5F62"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Visibility="{Binding ElementName=RevealedPassword, Path=Text,
Converter={StaticResource StringToOppositeVisibilityConverter}}" />
<Button x:Name="PART_RevealButton"
Grid.Column="1" SnapsToDevicePixels="True"
Style="{StaticResource RevealButtonExtendedStyle}"
Visibility="{Binding ElementName=RevealedPassword, Path=Text,
Converter={StaticResource StringToVisibilityConverter}}">
</Button>
<Button x:Name="PART_SubmitButton" FontSize="11"
Grid.Column="2" SnapsToDevicePixels="True"
Style="{StaticResource SubmitButtonStyle}">
</Button>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="OuterBorder"
Value="{StaticResource {x:Static SystemColors.ControlBrushKey}}" />
<Setter Property="Foreground"
Value="{StaticResource {x:Static SystemColors.GrayTextBrushKey}}" />
<Setter Property="Opacity" TargetName="PART_RevealButton" Value="0.5" />
<Setter Property="Text" TargetName="PART_TextBlockHint" Value="{}{disabled} "/>
</Trigger>
<DataTrigger Binding="{Binding ElementName=PART_RevealButton, Path=IsPressed}" Value="True">
<Setter TargetName="RevealedPassword" Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
... and afterwards the both corresponding Button styles.
First the RevealButtonExtendedStyle
:
<Style x:Key="RevealButtonExtendedStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Border x:Name="PasswordRevealGlyphBorder" Background="Transparent" Margin="0,1"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock x:Name="GlyphElement" Foreground="Black"
VerticalAlignment="Center" HorizontalAlignment="Center"
Text="" FontFamily="Segoe UI Symbol" Margin="3,0"
FontSize="{TemplateBinding FontSize}" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=GlyphElement, Path=IsMouseOver}" Value="True">
<Setter TargetName="PasswordRevealGlyphBorder" Property="Background"
Value="Gainsboro" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsPressed}"
Value="True">
<Setter TargetName="PasswordRevealGlyphBorder" Property="Background" Value="Black" />
<Setter TargetName="GlyphElement" Property="Foreground" Value="White" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
... and second the SubmitButtonStyle
:
<Style x:Key="SubmitButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Border x:Name="SignInGlyphBorder"
Background="#FF1FAEFF" Margin="1"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock x:Name="GlyphElement" Foreground="White" Padding="3,0"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Text="→" FontFamily="Calibri" Margin="3" SnapsToDevicePixels="True"
FontWeight="Bold" FontSize="{TemplateBinding FontSize}" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=GlyphElement, Path=IsMouseOver}" Value="True">
<Setter TargetName="SignInGlyphBorder" Property="Background" Value="#FF3EB9FF" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsPressed}"
Value="True">
<Setter TargetName="SignInGlyphBorder" Property="Background" Value="Transparent" />
<Setter TargetName="GlyphElement" Property="Foreground" Value="Black" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Conclusion
I hope that you picked up something useful from this article. Suggestions or
informations about mistakes will be beneficial.
And one request : If you like reading the article (or using the code) please
rate it. Thanks.
Points of Interest
I have implemented the control using the .NET 3.5 Framework and not
the .NET 4.0
Framework ... Why ? Because there are still many applications running
outside with .NET 3.5 ! And if I use some classes from the .NET 4.0
Framework in my Styles (like the
VisualStateManager
class) it would not be so easy to adapt the code to
.NET 3.5 applications. I know that it would be possible to use the WPF Toolkit Library
for some certain classes like the just mentioned
(VisualStateManager
), but then it would be neccessary to reference
one more library. As often as possible
i try to
write my libraries
in
.NET 3.5
because
the possibilities for reusing them are
more
versatile.
If you have a .NET 4.0 project you can use the library without drawbacks. All the styles and classes are working
properly.
There will be more cool stuff available soon here on CodeProject, because I decided to write
some articles about all the smart controls I have
created in the last years resp. months.
Recent Changes
Adding the
input capability of the user name,
has led to the following code
changes.
First of all i have added a TextBox
control to the
XAML code of the UserControl
:
<TextBox x:Name="tbUserName" Margin="12,5,12,0"
FontSize="{StaticResource MediumFontSize}"
Style="{StaticResource SmartWatermarkTextBoxStyle}"
Height="{Binding ElementName=PasswordBoxControl, Path=ActualHeight}"
Text="{Binding ElementName=VisualRoot, Path=UserName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ElementName=VisualRoot, Path=IsUserOptionAvailable,
Converter={conv:BoolToVisibilityConverter}}" />
Now let me explain the most important parts of this TextBox control.
To make it a little bit smarter the TextBox was styled using the
SmartWatermarkTextBoxStyle
. This ensures, that we have a Watermark in
the TextBox. The XAML code of the SmartWatermarkTextBoxStyle will be shown
afterwards. The second interesting point is the binding of the Height
property. It is binded to the ActualHeight of the PasswordBoxControl. This
ensures that we have balanced represantation of the controls. But the most
important thing is the binding of the Visibility property. It is binded to
the IsUserOptionAvailable
property using a
BoolToVisibilityConverter converter. So you have
only to set
this property to true if you want to activate the input capability of the user name.
As promised here is the XAML code of the
SmartWatermarkTextBoxStyle
:
<Style x:Key="SmartWatermarkTextBoxStyle" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="4,1,1,1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="OuterBorder" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<Grid>
<ScrollViewer x:Name="PART_ContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<TextBlock x:Name="PART_TextBlockHint"
Text="Enter Username" SnapsToDevicePixels="True"
Padding="{TemplateBinding Padding}" Background="Transparent"
Cursor="IBeam" Margin="2,0" FontFamily="Segoe UI Light" Foreground="#FF5D5F62"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Visibility="{TemplateBinding Text,
Converter={StaticResource StringToOppositeVisibilityConverter}}" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="OuterBorder"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
History
04th Dec 2012 - Added the option to enter the username and described all
code changes here
28th Nov 2012 - Improved the article and added much more information about
the UserControl
23rd Nov 2012 - First published