用装饰器实现,装饰器最大的好处就是不影响控件原本的表现的基础之上增加一些显示的效果,而且利于扩展。
通过扩展控件也能实现,但是部分控件是密封的,比如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>
2016/07/05
发现一处Bug:
AdornerLayer.GetAdornerLayer(element)可能存在返回值为空的状况,那么程序会直接崩溃,不够友好,这里是比较粗心大意的一个写代码陋习,在使用引用对象的时候没有检查值是否存在。
1 public class WatermarkAdorner : Adorner 2 { 3 public WatermarkAdorner(UIElement adornedElement) 4 : base(adornedElement) 5 { 6 IsHitTestVisible = false; //不可命中 7 InvalidateVisual(); 8 } 9 10 //强制布局发生改变,根据当前信息实时绘制 11 protected void Adorner_RoutedEvent(object sender, RoutedEventArgs e) 12 { 13 InvalidateVisual(); 14 } 15 16 #region 附加属性 17 18 public static Thickness GetMargin(DependencyObject obj) 19 { 20 return (Thickness)obj.GetValue(MarginProperty); 21 } 22 23 public static void SetMargin(DependencyObject obj, Thickness value) 24 { 25 obj.SetValue(MarginProperty, value); 26 } 27 28 public new readonly static DependencyProperty MarginProperty = 29 DependencyProperty.RegisterAttached("Margin", typeof(Thickness), typeof(WatermarkAdorner), new PropertyMetadata(new Thickness(0))); 30 31 public static string GetText(DependencyObject obj) 32 { 33 return (string)obj.GetValue(TextProperty); 34 } 35 36 public static void SetText(DependencyObject obj, string value) 37 { 38 obj.SetValue(TextProperty, value); 39 } 40 41 /// <summary> 42 /// 文本属性 43 /// </summary> 44 public static readonly DependencyProperty TextProperty = 45 DependencyProperty.RegisterAttached("Text", typeof(string), typeof(WatermarkAdorner), new PropertyMetadata(string.Empty, OnTextChanged)); 46 47 private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 48 { 49 var element = d as FrameworkElement; 50 if (element != null) 51 { 52 WatermarkAdorner adorner = new WatermarkAdorner(element); 53 WatermarkAdorner watermarkAdorner = null; 54 Type type = adorner.GetType(); 55 Assembly assembly = type.Assembly; 56 watermarkAdorner = (WatermarkAdorner)assembly.CreateInstance(type.Namespace + "." + element.GetType().Name + "WatermarkAdorner", false, BindingFlags.CreateInstance, null, new object[] { element }, CultureInfo.CurrentCulture, null); 57 58 element.Loaded += (sender, arg) => 59 { 60 if (watermarkAdorner != null) 61 { 62 var adornerLayer = GetAdornerLayer(element); 63 if (adornerLayer != null) 64 { 65 adornerLayer.Add(watermarkAdorner); 66 } 67 } 68 }; 69 } 70 } 71 72 private static AdornerLayer GetAdornerLayer(FrameworkElement element) 73 { 74 if (element == null) return null; 75 76 AdornerLayer layer = AdornerLayer.GetAdornerLayer(element); 77 var parent = element.Parent as FrameworkElement; 78 while (layer == null) 79 { 80 if (parent != null) 81 { 82 layer = AdornerLayer.GetAdornerLayer(parent); 83 parent = parent.Parent as FrameworkElement; 84 } 85 else break; 86 } 87 88 return layer; 89 } 90 91 public static Brush GetForeground(DependencyObject obj) 92 { 93 return (Brush)obj.GetValue(ForegroundProperty); 94 } 95 96 public static void SetForeground(DependencyObject obj, Brush value) 97 { 98 obj.SetValue(ForegroundProperty, value); 99 } 100 101 /// <summary> 102 /// 前景色 103 /// </summary> 104 public static readonly DependencyProperty ForegroundProperty = 105 DependencyProperty.RegisterAttached("Foreground", typeof(Brush), typeof(WatermarkAdorner), new UIPropertyMetadata(Brushes.Gray)); 106 107 public static Brush GetBackground(DependencyObject obj) 108 { 109 return (Brush)obj.GetValue(BackgroundProperty); 110 } 111 112 public static void SetBackground(DependencyObject obj, Brush value) 113 { 114 obj.SetValue(BackgroundProperty, value); 115 } 116 117 /// <summary> 118 /// 背景色 119 /// </summary> 120 public static readonly DependencyProperty BackgroundProperty = 121 DependencyProperty.RegisterAttached("Background", typeof(Brush), typeof(WatermarkAdorner), new UIPropertyMetadata(Brushes.Transparent)); 122 123 124 public static FontStyle GetFontStyle(DependencyObject obj) 125 { 126 return (FontStyle)obj.GetValue(FontStyleProperty); 127 } 128 129 public static void SetFontStyle(DependencyObject obj, FontStyle value) 130 { 131 obj.SetValue(FontStyleProperty, value); 132 } 133 134 /// <summary> 135 /// 字体风格 136 /// </summary> 137 public static readonly DependencyProperty FontStyleProperty = 138 DependencyProperty.RegisterAttached("FontStyle", typeof(FontStyle), typeof(WatermarkAdorner), new UIPropertyMetadata(FontStyles.Italic)); 139 140 #endregion 141 }
2016.8.16添加,用于WatermarkAdorner在无法获取AdornerLayer的场景下获取AdornerLayer