locked
Watermark password box from my textbox RRS feed

  • Question

  • Hi, I've got a nice(ish) design of a watermarked textbox. It's for a username box, but I also need one for password. Without having to completely redesign it how can I port it across to a password box:

    WPF

     

    <UserControl x:Class="prjP.Controls.ucWaterMarkedTextBox"
        x:Name="uc"
        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:s="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:prjP"
        mc:Ignorable="d" 
        d:DesignHeight="22" d:DesignWidth="163" Focusable="True">
     
     <UserControl.Resources>
    
      <ControlTemplate TargetType="TextBoxBase" x:Key="roundTransTxtB">
       <Grid>
    
        <Border CornerRadius="2" BorderThickness="2" BorderBrush="#fd9733" x:Name="Bd" >
    
         <ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
    
        </Border>
    
    
       </Grid>
    
    
    
       <ControlTemplate.Triggers>
        <Trigger Property="UIElement.IsEnabled">
         <Setter Property="Panel.Background" TargetName="Bd">
          <Setter.Value>
           <DynamicResource ResourceKey="{x:Static SystemColors.ControlBrushKey}" />
          </Setter.Value>
         </Setter>
    
         <Setter Property="TextElement.Foreground">
          <Setter.Value>
           <DynamicResource ResourceKey="{x:Static SystemColors.GrayTextBrushKey}" />
          </Setter.Value>
         </Setter>
         <Trigger.Value>
          <s:Boolean>False</s:Boolean>
         </Trigger.Value>
        </Trigger>
        <Trigger SourceName="PART_ContentHost" Property="IsFocused" Value="True">
         <Setter TargetName="Bd" Property="BorderThickness" Value="5"/>
        </Trigger>
    
       </ControlTemplate.Triggers>
      </ControlTemplate>
    
    
      <local:TextInputToVisibilityConverter x:Key="TextInputToVisibilityConverter" />
    
     </UserControl.Resources>
     
     <!-- <Border BorderBrush="Silver" BorderThickness="1" Height="Auto" Width="Auto" Name="bd">
      
     </Border> -->
     <Grid Height="Auto" Width="Auto" Name="cv1" DataContext="{Binding ElementName=uc}">
    
      <Grid.Background>
    
       <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
        <GradientStop Color="{Binding UpperColor, Mode=TwoWay}" Offset="0"/>
        <GradientStop Color="{Binding LowerColor, Mode=TwoWay}" Offset="1"/>
       </LinearGradientBrush>
    
      </Grid.Background>
    
      <TextBlock Foreground="White" Height="Auto" Name="tb1" Text="Watermark Text" Width="Auto" FontStyle="Italic" FontSize="12" Margin="3">
         <TextBlock.Visibility>
       <MultiBinding Converter="{StaticResource TextInputToVisibilityConverter}">
        <Binding ElementName="textBox1" Path="Text.IsEmpty" />
        <Binding ElementName="textBox1" Path="IsFocused" />
       </MultiBinding>
      </TextBlock.Visibility>
      </TextBlock>
    
      <TextBox Height="Auto" Name="textBox1" Width="Auto" Background="#00000000" Template="{DynamicResource roundTransTxtB}" Text="{Binding UserText}" TextChanged="textBox1_TextChanged" />
    
     </Grid>
    
    </UserControl>
    
    

     

     

    C# Code

     

     

     

    public partial class ucWaterMarkedTextBox : UserControl
    
     {
    
    
    
      private string userText, waterMarkText;
    
      private bool password;
    
    
    
      public bool Password
    
      {
    
       get { return password; }
    
       set { password = value; }
    
      }
    
    
    
      private Color
    
       upperColor = Colors.LightGray,
    
       lowerColor = Colors.DarkGray;
    
    
    
      public Color UpperColor
    
      {
    
       get { return upperColor; }
    
       set { upperColor = Colors.Red; }
    
      }
    
    
    
      public Color LowerColor
    
      {
    
       get { return lowerColor; }
    
       set { lowerColor = value; }
    
      }
    
    
    
      public string WaterMarkText
    
      {
    
       get { return waterMarkText; }
    
       set { waterMarkText = value; tb1.Text = value; }
    
      }
    
    
    
      public string UserText
    
      {
    
       get { return userText; }
    
       set { userText = value; textBox1.Text = value; }
    
      }
    
    
    
      public ucWaterMarkedTextBox()
    
      {
    
       InitializeComponent();
    
      }
    
    
    
      private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
    
      {
    
    
    
      }
    
     }
    
    
    
     public class TextInputToVisibilityConverter : IMultiValueConverter
    
     {
    
      public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    
      {
    
       // 
    
    Always test MultiValueConverter inputs for non-null
    
       // (to avoid crash bugs for views in the designer)
    
       if (values[0] is bool && values[1] is bool)
    
       {
    
        bool hasText = !(bool)values[0];
    
        bool hasFocus = (bool)values[1];
    
    
    
        if (hasFocus || hasText)
    
         return Visibility.Collapsed;
    
       }
    
    
    
       return Visibility.Visible;
    
      }
    
    
    
    
    
      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    
      {
    
       throw new NotImplementedException();
    
      }
    
     }
    
    

     

     

     

     

    It's not like the old days when you could specify a password char, and I don't really want to do it in c# code around the current control... that doesn't seem like an elegant solution.


    2010: Q6700 3GHz; 6GB DDRII; ~ 2.7TB internal; ASUS 512mb EN8800GT; Elixir Keyboard; Gigabyte GM-M6800; 2 x E173FPf 2005: 2GIG RAM, 3.6Ghz P4, 2 x 200gb SATA HD 8mb cache, 256mb 9950 ATI RADEON,19" LCD
    Sunday, December 19, 2010 5:19 AM

Answers

  • OK Here's what I came up with based on Rao's style:

    First we add a helper class in the prjP namespace:
    
     public static class AttachedProperties
     {
     public static string GetWatermark(DependencyObject obj)
      {
      return (string)obj.GetValue(WatermarkProperty);
      }
    
     public static void SetWatermark(DependencyObject obj, string value)
      {
      obj.SetValue(WatermarkProperty, value);
      }
    
     public static readonly DependencyProperty WatermarkProperty =
      DependencyProperty.RegisterAttached("Watermark", typeof(string), typeof(AttachedProperties), new UIPropertyMetadata(string.Empty));
    
    
     public static bool GetPasswordBoxHasText(DependencyObject obj)
      {
      return (bool)obj.GetValue(PasswordBoxHasTextProperty);
      }
    
     public static void SetPasswordBoxHasText(DependencyObject obj, bool value)
      {
      obj.SetValue(PasswordBoxHasTextProperty, value);
      }
    
     // Using a DependencyProperty as the backing store for PasswordBoxHasText. This enables animation, styling, binding, etc...
     public static readonly DependencyProperty PasswordBoxHasTextProperty =
      DependencyProperty.RegisterAttached("PasswordBoxHasText", typeof(bool), typeof(AttachedProperties),
      new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPasswordBoxHasTextPropertyChanged));
    
     private static void OnPasswordBoxHasTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
      {
      PasswordBox passwordBox = sender as PasswordBox;
      OnPasswordBoxHasTextChanged(passwordBox);
      passwordBox.PasswordChanged -= delegate { OnPasswordBoxHasTextChanged(passwordBox); };
      passwordBox.PasswordChanged += delegate { OnPasswordBoxHasTextChanged(passwordBox); };
      }
    
     private static void OnPasswordBoxHasTextChanged(PasswordBox box)
      {
      SetPasswordBoxHasText(box, !string.IsNullOrEmpty(box.Password));
      }
     }
    
    Next the styles: 
    
     <UserControl.Resources>
    
     <Style x:Key="styleWaterMarkedTextBox" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBoxBase}}">
      <Setter Property="MinWidth" Value="268"/>
      <Setter Property="Template">
      <Setter.Value>
       <ControlTemplate TargetType="{x:Type TextBox}">
       <Grid>
        <Border x:Name="BorderBase" CornerRadius="2" BorderThickness="2" BorderBrush="#fd9733" />
        <Label x:Name="LabelPrompt" VerticalAlignment="Center"
          Content="{Binding Path=(local:AttachedProperties.Watermark), RelativeSource={RelativeSource FindAncestor,
          AncestorType={x:Type TextBox}}}" Visibility="Collapsed" Focusable="False" FontStyle="Italic" Padding="3,0,0,0"/>
        <ScrollViewer Margin="0" x:Name="PART_ContentHost" />
       </Grid>
       <ControlTemplate.Triggers>
        <MultiTrigger>
        <MultiTrigger.Conditions>
         <Condition Property="Text" Value=""></Condition>
        </MultiTrigger.Conditions>
        <MultiTrigger.Setters>
         <Setter Property="Visibility" TargetName="LabelPrompt" Value="Visible"></Setter>
        </MultiTrigger.Setters>
        </MultiTrigger>
        <Trigger Property="IsEnabled" Value="False">
        <Setter Property="Foreground" Value="Gray" />
        </Trigger>
       </ControlTemplate.Triggers>
       </ControlTemplate>
      </Setter.Value>
      </Setter>
     </Style>
    
     <Style x:Key="styleWaterMarkedPasswordBox" TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource {x:Type PasswordBox}}">
      <Setter Property="MinWidth" Value="268"/>
      <Setter Property="Template">
      <Setter.Value>
       <ControlTemplate TargetType="{x:Type PasswordBox}">
       <Grid>
        <Border x:Name="BorderBase" CornerRadius="2" BorderThickness="2" BorderBrush="#fd9733" />
        <Label x:Name="LabelPrompt" VerticalAlignment="Center"
          Content="{Binding Path=(local:AttachedProperties.Watermark), RelativeSource={RelativeSource FindAncestor,
          AncestorType={x:Type PasswordBox}}}" Visibility="Collapsed" Focusable="False" FontStyle="Italic" Padding="3,0,0,0"/>
        <ScrollViewer Margin="0" x:Name="PART_ContentHost" />
       </Grid>
       <ControlTemplate.Triggers>
        <Trigger Property="local:AttachedProperties.PasswordBoxHasText" Value="False">
        <Setter Property="Visibility" TargetName="LabelPrompt" Value="Visible"></Setter>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
        <Setter Property="Foreground" Value="Gray" />
        </Trigger>
       </ControlTemplate.Triggers>
       </ControlTemplate>
      </Setter.Value>
      </Setter>
     </Style>
    
     </UserControl.Resources>
    
    And the usage:
    
     <TextBox Height="Auto" Name="textBox1" local:AttachedProperties.Watermark="Test Watermark" Width="200" 
        Text="{Binding UserText}" Style="{StaticResource styleWaterMarkedTextBox}"/>
     <PasswordBox Grid.Row="1" local:AttachedProperties.Watermark="Test Password" local:AttachedProperties.PasswordBoxHasText="True" 
         Height="Auto" Width="200" Margin="0,5,0,0" Style="{StaticResource styleWaterMarkedPasswordBox}" />
    

     

    Note: setting local:AttachedProperties.PasswordBoxHasText="True" is required to setup the event.

    You'll need to clean these up to match your style, but they should work for you.


    John Fenton
    Wordmasters Direct Mail and Data Processing Services
    • Proposed as answer by Wodahs Thursday, December 23, 2010 2:33 AM
    • Marked as answer by Yves.Z Monday, December 27, 2010 10:59 AM
    Tuesday, December 21, 2010 7:01 PM

All replies

  • Someone...
    2010: Q6700 3GHz; 6GB DDRII; ~ 2.7TB internal; ATI RAEDON 5770 1GB @ Stock; Elixir Keyboard; Gigabyte GM-M6800; 2 x E173FPf 2005: 2GIG RAM, 3.6Ghz P4, 2 x 200gb SATA HD 8mb cache, 256mb 9950 ATI RADEON,19" LCD Core i7 3.2GHz, 6GB DDRIII, ASUS 512MB EN8800GT
    Tuesday, December 21, 2010 3:24 AM
  • Hi,

    try out this

     <Style x:Key="WaterMarkTextBox" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBoxBase}}">
        <Setter Property="MinWidth" Value="268"/>
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
              <Grid>
                <Border x:Name="BorderBase" BorderThickness="1" BorderBrush="{DynamicResource ControlBorderBrush}" Background="{DynamicResource ControlBrush}" CornerRadius="2"
                  />
                <Label x:Name="LabelPrompt" Content=" Search Here" Visibility="Collapsed" Focusable="False" FontStyle="Italic" Padding="3,0,0,0"/>
                <ScrollViewer Margin="0" x:Name="PART_ContentHost" />
              </Grid>
              <ControlTemplate.Triggers>
                <MultiTrigger>
                  <MultiTrigger.Conditions>
                    <Condition Property="Text" Value=""></Condition>
                  </MultiTrigger.Conditions>
                  <MultiTrigger.Setters>
                    <Setter Property="Visibility" TargetName="LabelPrompt" Value="Visible"></Setter>
                  </MultiTrigger.Setters>
                </MultiTrigger>
                <Trigger Property="IsEnabled" Value="False">
                  <Setter Property="Foreground" Value="Gray" />
                </Trigger>
              </ControlTemplate.Triggers>
            </ControlTemplate>
          </Setter.Value>
        </Setter>
      </Style>
    hope this helps u
    • Proposed as answer by Wodahs Thursday, December 23, 2010 2:33 AM
    Tuesday, December 21, 2010 5:21 AM
  • I like Rao's method a lot better than your original design.

    There is however a problem with watermarking a Passwordbox.

    Password is not a DependancyProperty so it cannot be bound to or used as a Trigger.

    I'll try and write something for you if someone else doesn't get to it first, but it may take me a day or two.

    The trick here is to use some attached properties.


    John Fenton
    Wordmasters Direct Mail and Data Processing Services
    Tuesday, December 21, 2010 7:48 AM
  • OK Here's what I came up with based on Rao's style:

    First we add a helper class in the prjP namespace:
    
     public static class AttachedProperties
     {
     public static string GetWatermark(DependencyObject obj)
      {
      return (string)obj.GetValue(WatermarkProperty);
      }
    
     public static void SetWatermark(DependencyObject obj, string value)
      {
      obj.SetValue(WatermarkProperty, value);
      }
    
     public static readonly DependencyProperty WatermarkProperty =
      DependencyProperty.RegisterAttached("Watermark", typeof(string), typeof(AttachedProperties), new UIPropertyMetadata(string.Empty));
    
    
     public static bool GetPasswordBoxHasText(DependencyObject obj)
      {
      return (bool)obj.GetValue(PasswordBoxHasTextProperty);
      }
    
     public static void SetPasswordBoxHasText(DependencyObject obj, bool value)
      {
      obj.SetValue(PasswordBoxHasTextProperty, value);
      }
    
     // Using a DependencyProperty as the backing store for PasswordBoxHasText. This enables animation, styling, binding, etc...
     public static readonly DependencyProperty PasswordBoxHasTextProperty =
      DependencyProperty.RegisterAttached("PasswordBoxHasText", typeof(bool), typeof(AttachedProperties),
      new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPasswordBoxHasTextPropertyChanged));
    
     private static void OnPasswordBoxHasTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
      {
      PasswordBox passwordBox = sender as PasswordBox;
      OnPasswordBoxHasTextChanged(passwordBox);
      passwordBox.PasswordChanged -= delegate { OnPasswordBoxHasTextChanged(passwordBox); };
      passwordBox.PasswordChanged += delegate { OnPasswordBoxHasTextChanged(passwordBox); };
      }
    
     private static void OnPasswordBoxHasTextChanged(PasswordBox box)
      {
      SetPasswordBoxHasText(box, !string.IsNullOrEmpty(box.Password));
      }
     }
    
    Next the styles: 
    
     <UserControl.Resources>
    
     <Style x:Key="styleWaterMarkedTextBox" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBoxBase}}">
      <Setter Property="MinWidth" Value="268"/>
      <Setter Property="Template">
      <Setter.Value>
       <ControlTemplate TargetType="{x:Type TextBox}">
       <Grid>
        <Border x:Name="BorderBase" CornerRadius="2" BorderThickness="2" BorderBrush="#fd9733" />
        <Label x:Name="LabelPrompt" VerticalAlignment="Center"
          Content="{Binding Path=(local:AttachedProperties.Watermark), RelativeSource={RelativeSource FindAncestor,
          AncestorType={x:Type TextBox}}}" Visibility="Collapsed" Focusable="False" FontStyle="Italic" Padding="3,0,0,0"/>
        <ScrollViewer Margin="0" x:Name="PART_ContentHost" />
       </Grid>
       <ControlTemplate.Triggers>
        <MultiTrigger>
        <MultiTrigger.Conditions>
         <Condition Property="Text" Value=""></Condition>
        </MultiTrigger.Conditions>
        <MultiTrigger.Setters>
         <Setter Property="Visibility" TargetName="LabelPrompt" Value="Visible"></Setter>
        </MultiTrigger.Setters>
        </MultiTrigger>
        <Trigger Property="IsEnabled" Value="False">
        <Setter Property="Foreground" Value="Gray" />
        </Trigger>
       </ControlTemplate.Triggers>
       </ControlTemplate>
      </Setter.Value>
      </Setter>
     </Style>
    
     <Style x:Key="styleWaterMarkedPasswordBox" TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource {x:Type PasswordBox}}">
      <Setter Property="MinWidth" Value="268"/>
      <Setter Property="Template">
      <Setter.Value>
       <ControlTemplate TargetType="{x:Type PasswordBox}">
       <Grid>
        <Border x:Name="BorderBase" CornerRadius="2" BorderThickness="2" BorderBrush="#fd9733" />
        <Label x:Name="LabelPrompt" VerticalAlignment="Center"
          Content="{Binding Path=(local:AttachedProperties.Watermark), RelativeSource={RelativeSource FindAncestor,
          AncestorType={x:Type PasswordBox}}}" Visibility="Collapsed" Focusable="False" FontStyle="Italic" Padding="3,0,0,0"/>
        <ScrollViewer Margin="0" x:Name="PART_ContentHost" />
       </Grid>
       <ControlTemplate.Triggers>
        <Trigger Property="local:AttachedProperties.PasswordBoxHasText" Value="False">
        <Setter Property="Visibility" TargetName="LabelPrompt" Value="Visible"></Setter>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
        <Setter Property="Foreground" Value="Gray" />
        </Trigger>
       </ControlTemplate.Triggers>
       </ControlTemplate>
      </Setter.Value>
      </Setter>
     </Style>
    
     </UserControl.Resources>
    
    And the usage:
    
     <TextBox Height="Auto" Name="textBox1" local:AttachedProperties.Watermark="Test Watermark" Width="200" 
        Text="{Binding UserText}" Style="{StaticResource styleWaterMarkedTextBox}"/>
     <PasswordBox Grid.Row="1" local:AttachedProperties.Watermark="Test Password" local:AttachedProperties.PasswordBoxHasText="True" 
         Height="Auto" Width="200" Margin="0,5,0,0" Style="{StaticResource styleWaterMarkedPasswordBox}" />
    

     

    Note: setting local:AttachedProperties.PasswordBoxHasText="True" is required to setup the event.

    You'll need to clean these up to match your style, but they should work for you.


    John Fenton
    Wordmasters Direct Mail and Data Processing Services
    • Proposed as answer by Wodahs Thursday, December 23, 2010 2:33 AM
    • Marked as answer by Yves.Z Monday, December 27, 2010 10:59 AM
    Tuesday, December 21, 2010 7:01 PM