Brent Stewart

Live life on purpose

Add PlaceHolder text to a PasswordBox

Place holder text is UI candy that makes your app feel more polished and helps users better understand what needs to be entered into fields.  I had wanted to add place holder text to my WPF app’s login screen for quite a while, but never seem to find the time.

So today I decided to figure out what needed to be done to make this happen. After some searching and reading, I discovered that there are quite a few examples on how to add PlaceHolder text to TextBox controls, but that adding PlaceHolder text to PasswordBox controls is a little more difficult.  Most  of the examples of adding PlaceHolder text to TextBox controls relied on control triggers that checked the Text property to determine the visibility of the place holder text.  The problem with using this technique on a PasswordBox control is that the PasswordBox does not expose a dependency property for the password value, so there is no simple way to wire a trigger up to the value of a PasswordBox.  After some head scratching and a little more research, I decided that using an Attached Property would allow me to accomplish what I wanted.  First, I created a PlaceHolderHelper class that contains an Attached Property PlaceHolderText and a Dependency Property, HasPassword

public class PlaceHolderHelper : DependencyObject
{
    #region PlaceHolderText
    public static bool GetPlaceHolderText(DependencyObject obj)
    {
        return (bool)obj.GetValue(PlaceHolderTextProperty);
    }
    public static void SetPlaceHolderText(DependencyObject obj, string value)
    {
        obj.SetValue(PlaceHolderTextProperty, value);
    }
    public static readonly DependencyProperty PlaceHolderTextProperty =
        DependencyProperty.RegisterAttached("PlaceHolderText", typeof(string), 
            typeof(PlaceHolderHelper), 
            new UIPropertyMetadata(string.Empty, PlaceHolderTextChanged));
    private static void PlaceHolderTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is PasswordBox)) return;
        ((PasswordBox)d).PasswordChanged += 
            (sender, args) =>
            {
                var pb  = sender as PasswordBox;
                pb.SetValue(HasPasswordProperty, (pb.Password.Length > 0));
            };
    }
    #endregion
    #region HasPassword
    public bool HasPassword
    {
        get { return (bool)GetValue(HasPasswordProperty); }
        set { SetValue(HasPasswordProperty, value); }
    }
    private static readonly DependencyProperty HasPasswordProperty =
        DependencyProperty.RegisterAttached("HasPassword",  
            typeof(bool), typeof(PlaceHolderHelper), 
            new FrameworkPropertyMetadata(false));
    #endregion
}

This let me bind up a listener to the PasswordChanged event of the PasswordBox so I could track whether a password had been entered or not.  It also gave me the needed Dependency Property to use in my Control Trigger.  Next, I needed to create a Style that overlaid my place holder text over the PasswordBox.

<Style TargetType="{x:Type PasswordBox}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type PasswordBox}">                    
                <Border Name="MainBorder"
                     Background="{TemplateBinding Background}"
                     BorderBrush="{TemplateBinding BorderBrush}"
                     BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <ScrollViewer x:Name="PART_ContentHost" VerticalAlignment="Center" Margin="1" />
                            <TextBlock x:Name="PlaceHolder"
                                Text="{TemplateBinding ctrls:PlaceHolderHelper.PlaceHolderText}" 
                                Foreground="LightGray" IsHitTestVisible="False" 
                                HorizontalAlignment="Left" VerticalAlignment="Center" Margin="4,0,0,0"/>
                    </Grid>
                </Border>                        
                <ControlTemplate.Triggers>
                    <Trigger Property="ctrls:PlaceHolderHelper.HasPassword" Value="True">
                        <Setter TargetName="PlaceHolder" Property="Opacity" Value="0" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This is a very simplified style, but I wanted to boil this all down to the simplest example I could so you could see how this technique works.  Now all I had to do was declare a PasswordBox and set the PlaceHolderText.

<StackPanel VerticalAlignment="Top" Orientation="Horizontal" Margin="5">
  <PasswordBox Width="120" Height="24" ctrls:PlaceHolderHelper.PlaceHolderText="Enter your password" />
  <PasswordBox Width="140" Height="24" ctrls:PlaceHolderHelper.PlaceHolderText="Enter your secret phrase"  Margin="5"/>
  <Button Content="Submit" Width="50" Height="24" />
</StackPanel>

Here are the results, first with no text entered

and finally when entering text