WPF Watermark PasswordBox from Watermark TextBox

2019-01-15 03:18发布

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?

4条回答
女痞
2楼-- · 2019-01-15 03:54

General approach is the same: you write custom control style, and show watermark whenever password box is empty. The only problem here is that PasswordBox.Password property is not a dependency property, and you can't use it in trigger. Also PasswordBox is sealed, so you can't override this notification behaviour. 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.

查看更多
ゆ 、 Hurt°
3楼-- · 2019-01-15 04:02

you can show/hide the background by yourself instead of using triggers:

XAML:

<PasswordBox x:Name="passwordBox" PasswordChanged="passwordChanged" 
        Background="{StaticResource PasswordHint}" />

Code behind:

// helper to hide watermark hint in password field
private void passwordChanged(object sender, RoutedEventArgs e)
{           
    if (passwordBox.Password.Length == 0)
        passwordBox.Background.Opacity = 1;
    else
        passwordBox.Background.Opacity = 0;
}
查看更多
再贱就再见
4楼-- · 2019-01-15 04:04

@blindmeis's suggestion is good. For PasswordBox the class would be as follows.

public class PasswordBoxWatermarkBehavior : System.Windows.Interactivity.Behavior<PasswordBox>
{
    private TextBlockAdorner adorner;
    private WeakPropertyChangeNotifier notifier;

    #region DependencyProperty's

    public static readonly DependencyProperty LabelProperty =
        DependencyProperty.RegisterAttached("Label", typeof(string), typeof(PasswordBoxWatermarkBehavior));

    public string Label
    {
        get { return (string)GetValue(LabelProperty); }
        set { SetValue(LabelProperty, value); }
    }

    public static readonly DependencyProperty LabelStyleProperty =
        DependencyProperty.RegisterAttached("LabelStyle", typeof(Style), typeof(PasswordBoxWatermarkBehavior));

    public Style LabelStyle
    {
        get { return (Style)GetValue(LabelStyleProperty); }
        set { SetValue(LabelStyleProperty, value); }
    }

    #endregion

    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.Loaded += this.AssociatedObjectLoaded;
        this.AssociatedObject.PasswordChanged += AssociatedObjectPasswordChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.Loaded -= this.AssociatedObjectLoaded;
        this.AssociatedObject.PasswordChanged -= this.AssociatedObjectPasswordChanged;

        this.notifier = null;
    }

    private void AssociatedObjectPasswordChanged(object sender, RoutedEventArgs e)
    {
        this.UpdateAdorner();
    }

    private void AssociatedObjectLoaded(object sender, System.Windows.RoutedEventArgs e)
    {
        this.adorner = new TextBlockAdorner(this.AssociatedObject, this.Label, this.LabelStyle);

        this.UpdateAdorner();

        //AddValueChanged for IsFocused in a weak manner
        this.notifier = new WeakPropertyChangeNotifier(this.AssociatedObject, UIElement.IsFocusedProperty);
        this.notifier.ValueChanged += new EventHandler(this.UpdateAdorner);
    }

    private void UpdateAdorner(object sender, EventArgs e)
    {
        this.UpdateAdorner();
    }


    private void UpdateAdorner()
    {
        if (!String.IsNullOrEmpty(this.AssociatedObject.Password) || this.AssociatedObject.IsFocused)
        {
            // Hide the Watermark Label if the adorner layer is visible
            this.AssociatedObject.TryRemoveAdorners<TextBlockAdorner>();
        }
        else
        {
            // Show the Watermark Label if the adorner layer is visible
            this.AssociatedObject.TryAddAdorner<TextBlockAdorner>(adorner);
        }
    }
}
查看更多
劫难
5楼-- · 2019-01-15 04:06

you can use my approach for a watermark behavior. all you have to do is copy and paste the TextBoxWatermarkBehavior and the change the Behavior<TextBox> to Behavior<PasswordBox>.

you can find a demo project here

查看更多
登录 后发表回答