Chinaunix首页 | 论坛 | 博客
  • 博客访问: 49387
  • 博文数量: 21
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 190
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-24 15:25
文章分类

全部博文(21)

文章存档

2013年(21)

我的朋友

分类: C#/.net

2013-07-12 16:31:35

Adorner Example – TextBox Watermark

Topic: Adorner Example – TextBox Watermark

标题: Adorner 示例 – TextBox 水印

本文章将会向你展示如何通过Adorner给一个adorned对象动态的增加和修改属性。在本文中,我们将采用Adoner给一个TextBox增加水印效果。

this will introduce you some technique that you might want to use to use Adorner to dynamically add/change the Properties/Contents of adorned elements.

 

In this example, we will use the Adorner to Adorn on the TextBox to give some default Watermark effect.


首先,我们创建一个TextBoxBehavior,通过它,我们给一个Textbox增加水印效果。

First, we shall create the necessary TextBoxBehavior, with which we can apply the adorner to one Textbox.

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.Windows;

    using System.Windows.Controls;

    using System.Windows.Documents;

    using System.Windows.Input;

    using System.Windows.Media;

 

    using TestWatermarkTextBoxAdorner.Adorners;

 

    public static class TextBoxBehavior

    {

 

        #region Fields

        // Using a DependencyProperty as the backing store for Watermark.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty WatermarkProperty =

            DependencyProperty.RegisterAttached("Watermark", typeof(object), typeof(TextBoxBehavior), new UIPropertyMetadata(null, new PropertyChangedCallback(OnWatermarkChanged)));

 

        // Using a DependencyProperty as the backing store for WatermarkForeground.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty WatermarkForegroundProperty =

            DependencyProperty.RegisterAttached("WatermarkForeground", typeof(Brush), typeof(TextBoxBehavior), new UIPropertyMetadata(null, new PropertyChangedCallback(OnWatermarkForegroundChanged)));

 

 

        #endregion Fields

 

        #region Dependency Properties

 

        public static object GetWatermark(DependencyObject obj)

        {

            return (object)obj.GetValue(WatermarkProperty);

        }

 

        public static void SetWatermark(DependencyObject obj, object value)

        {

            obj.SetValue(WatermarkProperty, value);

        }

 

        public static Brush GetWatermarkForeground(DependencyObject obj)

        {

            return (Brush)obj.GetValue(WatermarkForegroundProperty);

        }

 

        public static void SetWatermarkForeground(DependencyObject obj, Brush value)

        {

            obj.SetValue(WatermarkForegroundProperty, value);

        }

        #endregion Dependnecy Properties

 

 

        #region Private methods

        private static void OnWatermarkChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs changeArgs)

        {

            Control control = (Control)dependencyObject;

            control.Loaded += new RoutedEventHandler(TextBoxBehavior.ControlLoaded);

            if (dependencyObject is ComboBox || dependencyObject is TextBox)

            {

                control.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(TextBoxBehavior.ControlGetKeyboardFocus);

                control.LostFocus += new RoutedEventHandler(TextBoxBehavior.ControlLoaded);

            }

            if (!(dependencyObject is ItemsControl) || dependencyObject is ComboBox) return;

            // the rest is not import as for now

 

        }

 

        private static void OnWatermarkForegroundChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs changeArgs)

        {

            Control control = (Control)dependencyObject;

            if (control != null)

            {

                SetWatermarkForeground(control, TextBoxBehavior.GetWatermarkForeground(control));

            }

        }

 

        private static void ControlLoaded(object sender, RoutedEventArgs e)

        {

            Control control = (Control)sender;

            if (!TextBoxBehavior.ShouldShowWatermark(control)) return;

            TextBoxBehavior.ShowWatermark(control);

        }

 

        private static bool ShouldShowWatermark(Control control)

        {

            if (control is ComboBox) return (control as ComboBox).Text == string.Empty;

            if (control is TextBox) return (control as TextBox).Text == string.Empty;

            if (control is ItemsControl) return (control as ItemsControl).Items.Count == 0;

            else return false;

        }

 

        private static void RemoveWatermark(UIElement control)

        {

            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer((Visual)control);

            if (adornerLayer == null) return;

            Adorner[] adorners = adornerLayer.GetAdorners(control);

            if (adorners == null)

            {

                return;

            }

            foreach (var adorner in adorners)

            {

                if (adorner is WatermarkAdorner)

                {

                    adorner.Visibility = Visibility.Hidden;

                    adornerLayer.Remove(adorner); // dangerous

                }

            }

        }

 

        private static void ShowWatermark(Control control)

        {

            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer((Visual)control);

            if (adornerLayer == null) return;

            adornerLayer.Add((Adorner)new WatermarkAdorner((UIElement)control, TextBoxBehavior.GetWatermark((DependencyObject)control)));

            if (TextBoxBehavior.GetWatermarkForeground(control) != null)

                SetWatermarkForeground(control, GetWatermarkForeground(control));

        }

 

        private static void SetWatermarkForeground(Control control, Brush brush)

        {

            if (brush == null) throw new ArgumentNullException("brush");

            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer((Visual)control);

            if (adornerLayer == null) return;

            Adorner[] adorners = adornerLayer.GetAdorners(control);

            if (adorners != null)

            {

                foreach (Adorner adorner in adornerLayer.GetAdorners(control))

                {

                    if (adorner is WatermarkAdorner)

                    {

                        ((WatermarkAdorner)adorner).WatermarkForeground = brush;

                        break;

                    }

                }

            }

        }

 

        private static void ControlGetKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)

        {

            Control control = (Control)sender;

            if (!TextBoxBehavior.ShouldShowWatermark(control)) return;

            TextBoxBehavior.RemoveWatermark((UIElement)control);

        }

        #endregion Private Methods

    }

关键点是在两个attached properties, 一个是Watermark and 另一个是WatermarkForeground.

在Watermark的PropertyChangedCallback回调中,我们会侦听TextBox的ControlGetKeyboardFocus 、LostFocus和Loaded事件。在Loaded事件的处理方法中,最基本的是测试是否可以显示水印,如果可以,显示。

The important things here are the two Attached Properties, one is called the “Watermark” and the other is called the “WatermarkForeground”.

One the PropertyChangedCallback of property “Watermark”, it try to attach event handler for the TextBox’s ControlGetKeyboardFocus and LostFocus as well as handler when the control is loaded.

On the ControlLoaded event handler, it basically do: check if the Control should show Watermark, and if it is does show, try to make it ShowWaterark..

代码的关键点ShowWatermark,它

The core part is in the ShowWatermark, which basically

            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer((Visual)control);

 

然后 加入“WatermarkAdorner”到 AdornerLayer

And add the Custom Adorner “WatermarkAdorner” to the AdornerLayer.


adornerLayer.Add((Adorner)new WatermarkAdorner((UIElement)control, TextBoxBehavior.GetWatermark((DependencyObject)control)));


Watermark Adorner 层的数据如下

Here is the definition for the Watermark Adorner layer.

    using System.Windows;

    using System.Windows.Controls;

    using System.Windows.Data;

    using System.Windows.Documents;

    using System.Windows.Media;

 

    public class WatermarkAdorner : Adorner

    {

        #region Fields

 

        private readonly ContentPresenter _contentPresenter;

 

        private Brush _watermarkForeground = null;

 

        #endregion Fields

 

        private Control Control

        {

            get

            {

                return (Control)this.AdornedElement;

            }

        }

 

        public Brush WatermarkForeground

        {

            get

            {

                return _watermarkForeground;

            }

            set

            {

                _watermarkForeground = value;

                if (_contentPresenter != null)

                {

                    TextElement.SetForeground(_contentPresenter, _watermarkForeground);

                }

            }

        }

 

        #region Constructor

 

        public WatermarkAdorner(UIElement adornedElement, object watermark)

            : base(adornedElement)

        {

            this.IsHitTestVisible = false; // should not accept focus or Hit test

 

            ContentPresenter contententPresenter1 = new ContentPresenter();

            contententPresenter1.Content = watermark;

            contententPresenter1.Opacity = 0.5; // makes this to be some kinds of configurable one

 

            //Thickness thickness = this.Control.Margin;

            double left = this.Control.Margin.Left + this.Control.Padding.Left + 4.0;

            double top = this.Control.Margin.Top + this.Control.Padding.Top + 3.0;

            double right = 0;

            double bottom = 0;

 

            Thickness thickness = new Thickness(left, top, right, bottom);

            this._contentPresenter = contententPresenter1;

            this.Margin = thickness;

 

            if (this.Control is ItemsControl && !(this.Control is ComboBox))

            {

                this._contentPresenter.VerticalAlignment = VerticalAlignment.Center;

                this._contentPresenter.HorizontalAlignment = HorizontalAlignment.Center;

            }

            Binding binding = new Binding("IsVisible")

                                  {

                                      Source = (object) adornedElement,

                                      Converter = (IValueConverter) new BooleanToVisibilityConverter()

                                  };

            this.SetBinding(UIElement.VisibilityProperty, (BindingBase)binding);

        }

        #endregion Constructor

 

        #region Override

 

        // this is not necessary?

        protected override int VisualChildrenCount

        {

            get

            {

                return 1;

            }

        }

 

        protected override System.Windows.Media.Visual GetVisualChild(int index) // replace the Visual of the TextBox with the visual of the _contentPresenter;

        {

            return (Visual)this._contentPresenter;

        }

 

        protected override Size MeasureOverride(Size constraint)

        {

            this._contentPresenter.Measure(this.Control.RenderSize); // delegate the measure override to the ContentPresenter's Measure

            return this.Control.RenderSize;

        }

 

        protected override Size ArrangeOverride(Size finalSize)

        {

            this._contentPresenter.Arrange(new Rect(finalSize));

            return finalSize;

        }

 

        #endregion Override

 

    }


还有比较重要的地方在于该Adorner 层返回一个“ContentPresenter”给WPF visual element 系统。


The key part here is that it returns a “ContentPresenter” and this content presenter will be the one that get returned when inquired by the WPF visual element system.


所以, 如下的两个重载

So, the following two override from Adorner implementations.


        protected override int VisualChildrenCount

        {

            get

            {

                return 1;

            }

        }

 

        protected override System.Windows.Media.Visual GetVisualChild(int index) // replace the Visual of the TextBox with the visual of the _contentPresenter;

        {

            return (Visual)this._contentPresenter;

        }

 

告知WPF visual element 系统该Adorner层只有一个子元素,且子元素是内部维护的“ContentPresenter”。

Tells the WPF visual tree system that the Adorner layer has one child, and the child is “ContentPresenter” that we have kept internally.


但是, 为了正确的显示ContentPresenter,我们必须告诉WPF visual element 系统如何measure 和arrange内容。

However, in order to render the ContentPresenter correctly, we have tell the WPF control system how we are going to measure and arrange the content.


        protected override Size MeasureOverride(Size constraint)

        {

            this._contentPresenter.Measure(this.Control.RenderSize); // delegate the measure override to the ContentPresenter's Measure

            return this.Control.RenderSize;

        }

 

        protected override Size ArrangeOverride(Size finalSize)

        {

            this._contentPresenter.Arrange(new Rect(finalSize));

            return finalSize;

        }

 

这里做的是,当系统WPF询问Adorner层的大小的时候, 我们实际上用“ContentPresenter”作了measure 和arrange。

What is done here is that when the systems make inquiry on the size/arrange of the Adorner layer, the “ContentPresenter” is rendered with the desired Size and actual size hereby.


可以想到的一个改进是如果控件有style,那么水印颜色太难读了这样的话 ,我们可以使contentPresenter的前景色和修饰的控件的前景色一致。

 

An enhancement that we can think of is that for those controls that are styled, the default text’s foreground may be too illegible to read, you may want to make the contentPresenter’s foreground the same as the one used for the control.


            var foregroundBinding = new Binding("Foreground")

                                        {

                                            Source = adornedElement,

                                            Path = new PropertyPath("Foreground")

                                        };

            _contentPresenter.SetBinding(TextElement.ForegroundProperty, foregroundBinding);


这样是用该代码。

To use the code, here is the example.


        xmlns=""

        xmlns:x=""

        xmlns:behavior="clr-namespace:TestWatermarkTextBoxAdorner.Behaviors"

        Title="MainWindow" Height="350" Width="525">

   

       

           

           

       

       

                 behavior:TextBoxBehavior.Watermark="Input user's name"

                 behavior:TextBoxBehavior.WatermarkForeground="Red"

                 TabIndex="0"

                 Grid.Row="0"

                 />

       

   

 

引用:

References:


 

阅读(656) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~