使文本框带有文字提示是一个非常有用特性,不但让界面变得整洁,而且代码也简洁了不少。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"/>
显示效果如图: