创建WatermarkTextBox和WatermarkPasswordBox(带水印的文本框)

使文本框带有文字提示是一个非常有用特性,不但让界面变得整洁,而且代码也简洁了不少。Windows登录时,输入用户名和密码的文本框和密码框就有这个功能。在WPF里面创建这样的文本框和密码框非常简单,下面就一步一步介绍如何实现这个功能。

首先创建一个WatermarkTextBox类从TextBox派生。然后添加两个依赖属性:

Watermark:该属性类型为String,用于设置要显示在文本框里面的提示文本。

WatermarkStyle:该属类型为Style,用于设置文本框里面提示文本的样式,比如字体类别,是否斜体显示等等。

下面是该类的代码:

[StyleTypedProperty(Property = "WatermarkStyle", StyleTargetType = typeof(TextBlock))]

  public classWatermarkTextBox : TextBox

  {

    static WatermarkTextBox()

    {

      DefaultStyleKeyProperty.OverrideMetadata(typeof(WatermarkTextBox),new FrameworkPropertyMetadata(typeof(WatermarkTextBox)));

    }

 

    public string Watermark

    {

      get { return (string)GetValue(WatermarkProperty); }

      set { SetValue(WatermarkProperty, value); }

    }

 

    public Style WatermarkStyle

    {

      get { return (Style)GetValue(WatermarkStyleProperty); }

      set { SetValue(WatermarkStyleProperty, value); }

    }

 

    public staticStyle GetWatermarkStyle(DependencyObject obj)

    {

      return (Style)obj.GetValue(WatermarkStyleProperty);

    }

 

    public staticvoid SetWatermarkStyle(DependencyObject obj,Style value)

    {

      obj.SetValue(WatermarkStyleProperty, value);

    }

 

    public staticreadonly DependencyProperty WatermarkStyleProperty =

        DependencyProperty.RegisterAttached("WatermarkStyle",typeof(Style),typeof(WatermarkTextBox));

 

    public staticstring GetWatermark(DependencyObject obj)

    {

      return (string)obj.GetValue(WatermarkProperty);

    }

 

    public staticvoid SetWatermark(DependencyObject obj,string value)

    {

      obj.SetValue(WatermarkProperty, value);

    }

 

    public staticreadonly DependencyProperty WatermarkProperty =

        DependencyProperty.RegisterAttached("Watermark",typeof(string), typeof(WatermarkTextBox),

        new FrameworkPropertyMetadata(OnWatermarkChanged));

 

    private staticvoid OnWatermarkChanged(DependencyObject sender,DependencyPropertyChangedEventArgs e)

    {

      PasswordBox pwdBox = sender as PasswordBox;

     

      if (pwdBox == null)

      {

        return;

      }

 

      pwdBox.PasswordChanged -= OnPasswordChanged;

      pwdBox.PasswordChanged += OnPasswordChanged;

    }

 

    private staticvoid OnPasswordChanged(object sender,RoutedEventArgs e)

    {

      PasswordBox pwdBox = sender as PasswordBox;

      TextBlock watermarkTextBlock = pwdBox.Template.FindName("WatermarkTextBlock", pwdBox)as TextBlock;

 

      if (watermarkTextBlock != null)

      {

        watermarkTextBlock.Visibility = pwdBox.SecurePassword.Length == 0

 ? Visibility.Visible : Visibility.Hidden;

      }

    }

首先不要奇怪为什么该类的代码中为什么有关于PasswordBox的逻辑,我们稍后再谈原因。代码完成后,剩下的就是创建样式了,由于当用户在文本框中输入了文本后,提示文字就会隐藏起来,所以我们需要一个Value Converter,当文本为null或者empty时,返回Visible,否则返回Collapsed。为了复用这个Converter,我加了一些额外的属性,相信不难理解。下面是该类的代码:

public class NullOrEmptyStringToVisibilityConverter : IValueConverter

  {

    public NullOrEmptyStringToVisibilityConverter()

    {

      NullOrEmpty = Visibility.Collapsed;

      NotNullOrEmpty = Visibility.Visible;

    }

 

    public Visibility NullOrEmpty {get; set; }

    public Visibility NotNullOrEmpty {get; set; }

 

    public object Convert(object value,Type targetType, object parameter,CultureInfo culture)

    {           

      string strValue = value == null ? string.Empty : value.ToString();

      return string.IsNullOrEmpty(strValue) ? NullOrEmpty : NotNullOrEmpty;

    }

 

    public object ConvertBack(object value,Type targetType, object parameter,CultureInfo culture)

    {

      throw newNotImplementedException();

    }

  }

最后的样式的XAML没什么好说的了。粘贴如下:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                    xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"

                    xmlns:local="clr-namespace:Watermark">

 

  <local:NullOrEmptyStringToVisibilityConverter x:Key="NullOrEmptyStringtoVisibilityConverter" NotNullOrEmpty="Collapsed" NullOrEmpty="Visible"/>

 

  <LinearGradientBrush x:Key="TextBoxBorder" EndPoint="0,20" MappingMode="Absolute" StartPoint="0,0">

    <GradientStop Color="#ABADB3" Offset="0.05"/>

    <GradientStop Color="#E2E3EA" Offset="0.07"/>

    <GradientStop Color="#E3E9EF" Offset="1"/>

  </LinearGradientBrush>

 

  <Style TargetType="{x:Type local:WatermarkTextBox}">

    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>

    <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>

    <Setter Property="BorderBrush" Value="{DynamicResource TextBoxBorder}"/>

    <Setter Property="BorderThickness" Value="1"/>

    <Setter Property="Padding" Value="1"/>

    <Setter Property="AllowDrop" Value="true"/>

    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>

    <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>

    <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>

    <Setter Property="VerticalContentAlignment" Value="Center"/>

    <Setter Property="Template">

      <Setter.Value>

        <ControlTemplate TargetType="{x:Type local:WatermarkTextBox}">

          <Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true">

            <Grid>

              <TextBlock Text="{TemplateBinding Watermark}"                          

                         Margin="{TemplateBinding Padding}"

                         Padding="5 0 0 0"

                         IsHitTestVisible="False"

                         VerticalAlignment="Center"

                         FontStyle="Italic"

                         Opacity="0.7"

                         Style="{TemplateBinding WatermarkStyle}"

                         Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Converter={StaticResource NullOrEmptyStringtoVisibilityConverter}}"/>

 

              <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>

            </Grid>

          </Microsoft_Windows_Themes:ListBoxChrome>

          <ControlTemplate.Triggers>

            <Trigger Property="IsEnabled" Value="false">

              <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>

              <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>

            </Trigger>

          </ControlTemplate.Triggers>

        </ControlTemplate>

      </Setter.Value>

    </Setter>

  </Style>

</ResourceDictionary>

 

本来打算创建一个WatermarkPasswordBox类的,结果发现PasswordBox类是不可继承的,只好作罢。不过我们可以复用WatermarkTextBox类的两个依赖属性,然后重写PasswordBox的样式来达到同样的目的。这就是为什么在WatermarkTextBox类的代码中出现了PasswordBox字样的代码的原因。由于PasswordBox.Password属性不是依赖属性,不能使用数据绑定,所以只能在事件中用代码来判断是否隐藏提示文字。有了WatermarkPasswordBox中两个属性的支持,那么就只需要为PasswordBox提供一个新样式就可以实现为之显示提示文本的支持。新样式如下:

<LinearGradientBrush x:Key="TextBoxBorder" EndPoint="0,20" MappingMode="Absolute" StartPoint="0,0">

      <GradientStop Color="#ABADB3" Offset="0.05"/>

      <GradientStop Color="#E2E3EA" Offset="0.07"/>

      <GradientStop Color="#E3E9EF" Offset="1"/>

    </LinearGradientBrush>

 

    <Style TargetType="{x:Type PasswordBox}">

      <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>

      <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>

      <Setter Property="OverridesDefaultStyle" Value="True"/>

      <Setter Property="PasswordChar" Value="●"/>

      <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>

      <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>

      <Setter Property="VerticalContentAlignment" Value="Center"/>

      <Setter Property="BorderThickness" Value="1"/>

      <Setter Property="HorizontalContentAlignment" Value="Left"/>

      <Setter Property="Padding" Value="1"/>

      <Setter Property="FocusVisualStyle" Value="{x:Null}"/>

      <Setter Property="AllowDrop" Value="true"/>

      <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>

      <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>

      <Setter Property="Template">

        <Setter.Value>

          <ControlTemplate TargetType="{x:Type PasswordBox}">

            <Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true">

              <Grid>

                <TextBlock x:Name="WatermarkTextBlock"

                         Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:WatermarkTextBox.Watermark)}"                         

                         Margin="{TemplateBinding Padding}"

                         Padding="5 0 0 0"

                         FontStyle="Italic"

                         IsHitTestVisible="False"

                         VerticalAlignment="Center"

                         Opacity="0.7"

                         Style="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:WatermarkTextBox.WatermarkStyle)}"/>

                <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>

              </Grid>

            </Microsoft_Windows_Themes:ListBoxChrome>

            <ControlTemplate.Triggers>

              <Trigger Property="IsEnabled" Value="false">

                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>

                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>

              </Trigger>

            </ControlTemplate.Triggers>

          </ControlTemplate>

        </Setter.Value>

      </Setter>

    </Style>

 

Demo代码如下:

<local:WatermarkTextBox x:Name="tb" Text="" Height="30" Width="200" Watermark="Input Your User Name" Margin="10"/>

      <PasswordBox x:Name="pb" Height="30" Width="200" local:WatermarkTextBox.Watermark="Input Your Password"/>

 

显示效果如图:

创建WatermarkTextBox和WatermarkPasswordBox(带水印的文本框)_第1张图片

你可能感兴趣的:(object,String,Microsoft,setter,binding,themes)