使文本框帶有文字提示是一個非常有用特性,不但讓界面變得整潔,而且代碼也簡潔了不少。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"/>
顯示效果如圖: