用装饰器实现,装饰器最大的好处就是不影响控件原本的表现的基础之上增加一些显示的效果,而且利于扩展。
通过扩展控件也能实现,但是部分控件是密封的,比如PasswordBox,而且还需要修改样式是比较麻烦费力;
效果
WatermarkAdorner:定义了附加属性、在Text的附加属性的OnTextChanged中给对应的控件的加载事件添加了Adorner(利用反射消除控件类型及对应的控件装饰器的判断)
/// <summary> /// 水印装饰器 /// </summary> public class WatermarkAdorner : Adorner { public WatermarkAdorner(UIElement adornedElement) : base(adornedElement) { IsHitTestVisible = false; //不可命中 InvalidateVisual(); } //强制布局发生改变,根据当前信息实时绘制 protected void Adorner_RoutedEvent(object sender, RoutedEventArgs e) { InvalidateVisual(); } #region 附加属性 public static Thickness GetMargin(DependencyObject obj) { return (Thickness)obj.GetValue(MarginProperty); } public static void SetMargin(DependencyObject obj, Thickness value) { obj.SetValue(MarginProperty, value); } public new readonly static DependencyProperty MarginProperty = DependencyProperty.RegisterAttached("Margin", typeof(Thickness), typeof(WatermarkAdorner), new PropertyMetadata(new Thickness(0))); public static string GetText(DependencyObject obj) { return (string)obj.GetValue(TextProperty); } public static void SetText(DependencyObject obj, string value) { obj.SetValue(TextProperty, value); } /// <summary> /// 文本属性 /// </summary> public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached("Text", typeof(string), typeof(WatermarkAdorner), new PropertyMetadata(string.Empty, OnTextChanged)); private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var element = d as FrameworkElement; if (element != null) { WatermarkAdorner adorner = new WatermarkAdorner(element); WatermarkAdorner watermarkAdorner = null; Type type = adorner.GetType(); Assembly assembly = type.Assembly; watermarkAdorner = (WatermarkAdorner)assembly.CreateInstance(type.Namespace + "." + element.GetType().Name + "WatermarkAdorner", false, BindingFlags.CreateInstance, null, new object[] { element }, CultureInfo.CurrentCulture, null); element.Loaded += (sender, arg) => { if (watermarkAdorner != null) { AdornerLayer.GetAdornerLayer(element).Add(watermarkAdorner); } }; } } public static Brush GetForeground(DependencyObject obj) { return (Brush)obj.GetValue(ForegroundProperty); } public static void SetForeground(DependencyObject obj, Brush value) { obj.SetValue(ForegroundProperty, value); } /// <summary> /// 前景色 /// </summary> public static readonly DependencyProperty ForegroundProperty = DependencyProperty.RegisterAttached("Foreground", typeof(Brush), typeof(WatermarkAdorner), new UIPropertyMetadata(Brushes.Gray)); public static Brush GetBackground(DependencyObject obj) { return (Brush)obj.GetValue(BackgroundProperty); } public static void SetBackground(DependencyObject obj, Brush value) { obj.SetValue(BackgroundProperty, value); } /// <summary> /// 背景色 /// </summary> public static readonly DependencyProperty BackgroundProperty = DependencyProperty.RegisterAttached("Background", typeof(Brush), typeof(WatermarkAdorner), new UIPropertyMetadata(Brushes.Transparent)); public static FontStyle GetFontStyle(DependencyObject obj) { return (FontStyle)obj.GetValue(FontStyleProperty); } public static void SetFontStyle(DependencyObject obj, FontStyle value) { obj.SetValue(FontStyleProperty, value); } /// <summary> /// 字体风格 /// </summary> public static readonly DependencyProperty FontStyleProperty = DependencyProperty.RegisterAttached("FontStyle", typeof(FontStyle), typeof(WatermarkAdorner), new UIPropertyMetadata(FontStyles.Italic)); #endregion } }
TextBoxWatermarkAdorner:构造函数为控件的对应事件订阅刷新水印事件、OnRender方法判断是否满足添加水印的条件以及绘制水印
public class TextBoxWatermarkAdorner : WatermarkAdorner { //被装饰的文本输入框 private readonly TextBox _adornerTextBox; public TextBoxWatermarkAdorner(UIElement adornedElement) : base(adornedElement) { var adornerTextBox = adornedElement as TextBox; if (adornerTextBox != null) { _adornerTextBox = adornerTextBox; _adornerTextBox.LostFocus += Adorner_RoutedEvent; _adornerTextBox.GotFocus += Adorner_RoutedEvent; _adornerTextBox.TextChanged += Adorner_RoutedEvent; } InvalidateVisual(); } protected override void OnRender(DrawingContext drawingContext) { if (_adornerTextBox != null && (!_adornerTextBox.IsFocused && string.IsNullOrEmpty(_adornerTextBox.Text))) { FormattedText formattedText = new FormattedText( GetText(_adornerTextBox), CultureInfo.CurrentUICulture, _adornerTextBox.FlowDirection, _adornerTextBox.FontFamily.GetTypefaces().FirstOrDefault(), _adornerTextBox.FontSize, GetForeground(_adornerTextBox)); formattedText.SetFontStyle(GetFontStyle(_adornerTextBox)); drawingContext.DrawText(formattedText, new Point(GetMargin(_adornerTextBox).Left, GetMargin(_adornerTextBox).Left)); } } }
PasswordBoxWatermarkAdorner:构造函数为控件的对应事件订阅刷新水印事件、OnRender方法判断是否满足添加水印的条件以及绘制水印
public class PasswordBoxWatermarkAdorner : WatermarkAdorner { private readonly PasswordBox _adornedPasswordBox; public PasswordBoxWatermarkAdorner(UIElement adornedElement) : base(adornedElement) { var passwordBox = adornedElement as PasswordBox; if (passwordBox != null) { _adornedPasswordBox = passwordBox; _adornedPasswordBox.LostFocus += Adorner_RoutedEvent; _adornedPasswordBox.GotFocus += Adorner_RoutedEvent; _adornedPasswordBox.PasswordChanged += Adorner_RoutedEvent; } InvalidateVisual(); } protected override void OnRender(DrawingContext drawingContext) { if (_adornedPasswordBox != null && (_adornedPasswordBox.IsFocused || !string.IsNullOrEmpty(_adornedPasswordBox.Password))) return; FormattedText formatted = new FormattedText(GetText(_adornedPasswordBox), CultureInfo.CurrentCulture, GetFlowDirection(_adornedPasswordBox), _adornedPasswordBox.FontFamily.GetTypefaces().FirstOrDefault(), _adornedPasswordBox.FontSize, GetForeground(_adornedPasswordBox)); formatted.SetFontStyle(GetFontStyle(_adornedPasswordBox)); drawingContext.DrawText(formatted, new Point(GetMargin(_adornedPasswordBox).Left, GetMargin(_adornedPasswordBox).Top)); } }
界面使用
<Window x:Class="Watermarker.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Watermarker" Title="MainWindow" Height="350" Width="525"> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBox Height="40" Width="100" FontSize="18" local:TextBoxWatermarkAdorner.Text="请输入文本" local:TextBoxWatermarkAdorner.Margin="5,5,0,0"/> <PasswordBox Height="40" Width="100" local:PasswordBoxWatermarkAdorner.Text="请输入密码" local:PasswordBoxWatermarkAdorner.Margin="5,5,0,0"/> </StackPanel> </Window>