wpfwatermarkpasswordbox

WPF Watermark PasswordBox from Watermark TextBox


I am using a Watermark textbox as in Watermark TextBox in WPF

 <Grid Grid.Row="0" Background="{StaticResource brushWatermarkBackground}" Style="{StaticResource EntryFieldStyle}" >
        <TextBlock Margin="5,2" Text="This prompt dissappears as you type..." Foreground="{StaticResource brushWatermarkForeground}"
                   Visibility="{Binding ElementName=txtUserEntry, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}" />
        <TextBox Name="txtUserEntry" Background="Transparent" BorderBrush="{StaticResource brushWatermarkBorder}" />
    </Grid>

How can I apply this for a PasswordBox?


Solution

  • The general approach is the same: write a custom control style, and show the watermark whenever the password box is empty. The only problem here is that PasswordBox.Password is not a dependency property, and you can't use it in a trigger. Also PasswordBox is sealed, so you can't override this notification behavior, but you can use attached properties here.

    The following code demonstrates how.

    XAML:

    <Window x:Class="WpfTest.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:WpfTest="clr-namespace:WpfTest"
        Title="Password Box Sample" Height="300" Width="300">
      <Window.Resources>
        <Style x:Key="{x:Type PasswordBox}"
            TargetType="{x:Type PasswordBox}">
          <Setter Property="WpfTest:PasswordBoxMonitor.IsMonitoring"
                  Value="True"/>
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="{x:Type PasswordBox}">
                <Border Name="Bd"
                        Background="{TemplateBinding Background}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        SnapsToDevicePixels="true">
                  <Grid>
                    <ScrollViewer x:Name="PART_ContentHost"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    <TextBlock Text="Please enter your password" 
                               Margin="4, 2, 0, 0"
                               Foreground="Gray" 
                               Visibility="Collapsed"
                               Name="txtPrompt" />
                  </Grid>
                </Border>
                <ControlTemplate.Triggers>
                  <Trigger Property="IsEnabled"
                                             Value="false">
                    <Setter TargetName="Bd"
                                                Property="Background"
                                                Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                    <Setter Property="Foreground"
                                                Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                  </Trigger>
                  <Trigger Property="WpfTest:PasswordBoxMonitor.PasswordLength" Value="0">
                    <Setter Property="Visibility" TargetName="txtPrompt" Value="Visible"/>
                  </Trigger>
                </ControlTemplate.Triggers>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
      </Window.Resources>
      <Grid>
        <PasswordBox VerticalAlignment="Top"/>
      </Grid>
    </Window>
    

    C#:

    using System.Windows;
    using System.Windows.Controls;
    
    namespace WpfTest {
        public partial class Window1 : Window {
            public Window1() {
                InitializeComponent();
            }
        }
    
      public class PasswordBoxMonitor : DependencyObject {
        public static bool GetIsMonitoring(DependencyObject obj) {
          return (bool)obj.GetValue(IsMonitoringProperty);
        }
    
        public static void SetIsMonitoring(DependencyObject obj, bool value) {
          obj.SetValue(IsMonitoringProperty, value);
        }
    
        public static readonly DependencyProperty IsMonitoringProperty =
            DependencyProperty.RegisterAttached("IsMonitoring", typeof(bool), typeof(PasswordBoxMonitor), new UIPropertyMetadata(false, OnIsMonitoringChanged));
    
        public static int GetPasswordLength(DependencyObject obj) {
          return (int)obj.GetValue(PasswordLengthProperty);
        }
    
        public static void SetPasswordLength(DependencyObject obj, int value) {
          obj.SetValue(PasswordLengthProperty, value);
        }
    
        public static readonly DependencyProperty PasswordLengthProperty =
            DependencyProperty.RegisterAttached("PasswordLength", typeof(int), typeof(PasswordBoxMonitor), new UIPropertyMetadata(0));
    
        private static void OnIsMonitoringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
          var pb = d as PasswordBox;
          if (pb == null) {
            return;
          }
          if ((bool) e.NewValue) {
            pb.PasswordChanged += PasswordChanged;
          } else {
            pb.PasswordChanged -= PasswordChanged;
          }
        }
    
        static void PasswordChanged(object sender, RoutedEventArgs e) {
          var pb = sender as PasswordBox;
          if (pb == null) {
            return;
          }
          SetPasswordLength(pb, pb.Password.Length);
        }
      }
    }
    

    Please notice PasswordBoxMonitor in XAML code.