創建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"/>

 

顯示效果如圖:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章