添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

关于WPF系列Adorner的学习笔记。Adorner在WPF中的界面设计中很常见,它可以用来实现更加美观的界面视觉效果,也可以设计事件动态效果等等。

一、Adorner介绍

  • Adorner,抽象基类,所有具有装饰器的实现都从该类继承;
  • Adorner,是一种特殊类型的FrameworkElement,用于向用户提供可视化提示;
  • Adorner,简单地说,就是WPF装饰器,作用是给WPF的控件上一层装饰效果;
  • Adorner,是WPF窗口中独立的一层,支持在界面元素之上执行独立的绘制及用户交互;
  • Adorner ,不包括任何继承呈现行为,确保装饰器呈现是装饰器实施者的责任。 实现呈现行为的常见方式是重写 OnRenderSizeChanged 方法,并使用一个或多个 DrawingContext 对象来按需呈现装饰器的视觉效果;
  • AdornerLayer,表示一个或多个装饰元素的装饰器的呈现层;
  • AdornerDecorator,使装饰器层与元素集合相关联;
  • WPF对Adorner的绘制是在单独的一个层AdornerLayer中完成的,这是一个始终位于装饰元素或装饰元素集合上方的呈现图面,在执行布局计算时单独地执行measure-arrange流程;
  • 在WPF中,从编辑框控件中光标的显示和选中效果的支持,到具有数据焦点的控件所具有的虚线外框,都是通过Adorner实现的;
  • 放置在 AdornerLayer 中的所有内容均呈现在您已设置的任何其余样式的顶部。也就是说,装饰器始终以可见的方式位于顶部,无法使用 z 顺序重写;
  • Adorner 就像任何其他 FrameworkElement 一样接收输入事件, 若要对装饰器下的元素启用传递命中测试,请在装饰器上将命中测试 IsHitTestVisible 的属性设置为 false;

二、Adorner的常见应用

2.1.在界面元素上提供视觉效果,以提示用户当前元素处于特定状态。

如在特定button周围绘制角框装饰
通常情况下,我们是按照以下的形式将Adorner绑定到特定的界面元素:
1.调用静态方法AdornerLayer.GetAdornerLayer(Visual visual),将需要被Adorner装饰的界面元素当作参数传入。该函数会从该界面元素开始沿视觉树向上查找,并返回它所发现的第一个AdornerLayer。
2.调用AdornerLayer.Add(Adorner adorner),函数将需要添加的装饰器加入AdornerLayer中。
3.通过remove实现移除装饰。
4.一般情况下,Adorner的派生类型需要考虑通过重写OnRender()或AddVisualChild()函数来指定Adorner如何绘制其外观。

namespace WpfApp6
    /// <summary>
    /// FrmAdorner.xaml 的交互逻辑
    /// </summary>
    public partial class FrmAdorner : UserControl
        public FrmAdorner()
            InitializeComponent();
        private void BtnAdd_Click(object sender, RoutedEventArgs e)
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(btn1);
            adornerLayer.Add(new ButtonAdorner(btn1));
        private void BtnClr_Click(object sender, RoutedEventArgs e)
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(btn1);
            Adorner[] adorners = adornerLayer.GetAdorners(btn1);
            if (adorners != null)
                for (int i = adorners.Length - 1; i >= 0; i--)
                    adornerLayer.Remove(adorners[i]);
    /// <summary>
    /// 创建装饰器
    /// 后缀加上Adorner规范命名,继承Adorner类,命名空间:System.Windows.Documents
    /// </summary>
    public class ButtonAdorner : Adorner
        //必须生成构造函数
        public ButtonAdorner(UIElement adornedElement) : base(adornedElement)
        //重写OnRender
        protected override void OnRender(DrawingContext drawingContext)
            Rect adornedElementRect = new Rect(this.AdornedElement.RenderSize);
            Pen renderPen = new Pen(new SolidColorBrush(Colors.Red), 1.0);
            //绘制角框
            drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopLeft.X - 3, adornedElementRect.TopLeft.Y - 3), new Point(adornedElementRect.TopLeft.X + 3, adornedElementRect.TopLeft.Y - 3));
            drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopLeft.X - 3, adornedElementRect.TopLeft.Y - 3), new Point(adornedElementRect.TopLeft.X - 3, adornedElementRect.TopLeft.Y + 3));
            drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopRight.X + 3, adornedElementRect.TopRight.Y - 3), new Point(adornedElementRect.TopRight.X - 3, adornedElementRect.TopRight.Y - 3));
            drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopRight.X + 3, adornedElementRect.TopRight.Y - 3), new Point(adornedElementRect.TopRight.X + 3, adornedElementRect.TopRight.Y + 3));
            drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomLeft.X - 3, adornedElementRect.BottomLeft.Y + 3), new Point(adornedElementRect.BottomLeft.X + 3, adornedElementRect.BottomLeft.Y + 3));
            drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomLeft.X - 3, adornedElementRect.BottomLeft.Y + 3), new Point(adornedElementRect.BottomLeft.X - 3, adornedElementRect.BottomLeft.Y - 3));
            drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomRight.X + 3, adornedElementRect.BottomRight.Y + 3), new Point(adornedElementRect.BottomRight.X - 3, adornedElementRect.BottomRight.Y + 3));
            drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomRight.X + 3, adornedElementRect.BottomRight.Y + 3), new Point(adornedElementRect.BottomRight.X + 3, adornedElementRect.BottomRight.Y - 3));
 

xaml:

<StackPanel>
    <WrapPanel>
        <Button Content="添加装饰" Height="30" Width="100" Margin="10" Click="BtnAdd_Click"/>
        <Button Content="移除装饰" Height="30" Width="100" Margin="10" Click="BtnClr_Click"/>
    </WrapPanel>
    <StackPanel>
        <Button Name="btn1" Height="50" Width="50" Margin="30"/>
    </StackPanel>
</StackPanel>

2.2.从视觉上遮盖或重写UIElement的一部分或全部

如IE上方的搜索栏在没有输入时显示当前搜索引擎的名称

private void SetTextBoxAdorner()
    TextBox maskTextBox = new TextBox();
    maskTextBox.Text = "搜索...";
    maskTextBox.BorderThickness = new Thickness(1, 1, 0, 0);
    maskTextBox.Opacity = 0.6;
    AddAdorner(new AdornerContentPresenter(btn4, maskTextBox), btn4);
private void btn4_TextChanged(object? sender, TextChangedEventArgs? e)
    if (sender is TextBox textBox)
        if (textBox.Text.ToString().Trim() != string.Empty)
            RemoveAdorner(btn4);
            SetTextBoxAdorner();
public class AdornerContentPresenter : Adorner
    private VisualCollection _Visuals;
    private ContentPresenter _ContentPresenter;
    public AdornerContentPresenter(UIElement adornedElement) : base(adornedElement)
        _Visuals = new VisualCollection(this);
        _ContentPresenter = new ContentPresenter();
        _Visuals.Add(_ContentPresenter);
    public AdornerContentPresenter(UIElement adornedElement, Visual content) : this(adornedElement)
        Content = content;
    protected override Size MeasureOverride(Size constraint)
        _ContentPresenter.Measure(constraint);
        return _ContentPresenter.DesiredSize;
    protected override Size ArrangeOverride(Size finalSize)
        //布局位置和大小
        _ContentPresenter.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
        return _ContentPresenter.RenderSize;
    protected override Visual GetVisualChild(int index)
        return _Visuals[index];
    protected override int VisualChildrenCount
        get { return _Visuals.Count; }
    public object Content
        get { return _ContentPresenter.Content; }
        set { _ContentPresenter.Content = value; }
<TextBox Name="btn4"  Margin="30" TextChanged="btn4_TextChanged"/>

2.3.向UIElement添加功能控点,使用户可以操作元素(调整大小、旋转、重新定位等)

下面通过实例来是呀Adorner实现控制UIElement的大小调整

public class BtnGripAdorner : Adorner
    double RectOffset = 0;
    private VisualCollection _Visuals;
    private ContentPresenter _ContentPresenter;
    UIElement _adornedElement;
    //必须生成构造函数
    public BtnGripAdorner(UIElement adornedElement, double offset) : base(adornedElement)
        _adornedElement = adornedElement;
        double _offset = 2;
        RectOffset = offset + _offset;
        Thumb _leftThumb = new Thumb();
        _leftThumb.Width = _offset;
        _leftThumb.HorizontalAlignment = HorizontalAlignment.Left;
        _leftThumb.Cursor = Cursors.SizeWE;//获取双向水平(西/东)大小调整光标
        Thumb _topThumb = new Thumb();
        _topThumb.Height = _offset;
        _topThumb.VerticalAlignment = VerticalAlignment.Top;
        _topThumb.Cursor = Cursors.SizeNS;//获取双向垂直(北/南)大小调整光标
        Thumb _rightThumb = new Thumb();
        _rightThumb.Width = _offset;
        _rightThumb.HorizontalAlignment = HorizontalAlignment.Right;
        _rightThumb.Cursor = Cursors.SizeWE;//获取双向水平(西/东)大小调整光标
        Thumb _bottomThumb = new Thumb();
        _bottomThumb.Height = _offset;
        _bottomThumb.VerticalAlignment = VerticalAlignment.Bottom;
        _bottomThumb.Cursor = Cursors.SizeNS;//获取双向垂直(北/南)大小调整光标
        SetLineThumbStyle(_leftThumb);
        SetLineThumbStyle(_topThumb);
        SetLineThumbStyle(_rightThumb);
        SetLineThumbStyle(_bottomThumb);
        Thumb _lefTopThumb = new Thumb();
        _lefTopThumb.HorizontalAlignment = HorizontalAlignment.Left;
        _lefTopThumb.VerticalAlignment = VerticalAlignment.Top;
        _lefTopThumb.Cursor = Cursors.SizeNWSE;//获取双向对角线(西北/东南)大小调整光标
        Thumb _rightTopThumb = new Thumb();
        _rightTopThumb.HorizontalAlignment = HorizontalAlignment.Right;
        _rightTopThumb.VerticalAlignment = VerticalAlignment.Top;
        _rightTopThumb.Cursor = Cursors.SizeNESW;//获取双向对角线(东北/西南)大小调整光标
        Thumb _rightBottomThumb = new Thumb();
        _rightBottomThumb.HorizontalAlignment = HorizontalAlignment.Right;
        _rightBottomThumb.VerticalAlignment = VerticalAlignment.Bottom;
        _rightBottomThumb.Cursor = Cursors.SizeNWSE;//获取双向对角线(西北/东南)大小调整光标
        Thumb _leftbottomThumb = new Thumb();
        _leftbottomThumb.HorizontalAlignment = HorizontalAlignment.Left;
        _leftbottomThumb.VerticalAlignment = VerticalAlignment.Bottom;
        _leftbottomThumb.Cursor = Cursors.SizeNESW;//获取双向对角线(东北/西南)大小调整光标
        SetPointThumbStyle(_lefTopThumb);
        SetPointThumbStyle(_rightTopThumb);
        SetPointThumbStyle(_rightBottomThumb);
        SetPointThumbStyle(_leftbottomThumb);
        Grid _grid = new Grid();
        _grid.Children.Add(_leftThumb);
        _grid.Children.Add(_topThumb);
        _grid.Children.Add(_rightThumb);
        _grid.Children.Add(_bottomThumb);
        _grid.Children.Add(_lefTopThumb);
        _grid.Children.Add(_rightTopThumb);
        _grid.Children.Add(_rightBottomThumb);
        _grid.Children.Add(_leftbottomThumb);
        _Visuals = new VisualCollection(this);
        _ContentPresenter = new ContentPresenter();
        _ContentPresenter.Content = _grid;
        _Visuals.Add(_ContentPresenter);
    private void SetLineThumbStyle(Thumb thumb)
        thumb.BorderBrush = Brushes.LightSlateGray;
        thumb.BorderThickness = new Thickness(1);
        thumb.DragDelta += Thumb_DragDelta;
    private void SetPointThumbStyle(Thumb thumb)
        thumb.Width = RectOffset;
        thumb.Height = RectOffset;
        thumb.Margin = new Thickness(-RectOffset / 2);
        thumb.BorderThickness = new Thickness(RectOffset/2);
        thumb.Background = Brushes.LightSlateGray;
        thumb.BorderBrush = Brushes.LightSlateGray;
        thumb.Template = new ControlTemplate(typeof(Thumb))
            VisualTree = GetFactory(Brushes.LightSlateGray)
        thumb.DragDelta += Thumb_DragDelta;
    FrameworkElementFactory GetFactory(Brush back)
        FrameworkElementFactory fef = new FrameworkElementFactory(typeof(Rectangle));
        fef.SetValue(Ellipse.FillProperty, back);
        fef.SetValue(Ellipse.StrokeProperty, back);
        fef.SetValue(Ellipse.StrokeThicknessProperty, (double)2.5);
        return fef;
    private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
        if (_adornedElement is FrameworkElement element && sender is FrameworkElement thumb)
            double width = 0;
            double height = 0;
            double minWidth = 10;
            double minHeight = 10;
            Thickness margin = element.Margin;
            if (thumb.HorizontalAlignment == HorizontalAlignment.Left)
                margin.Left += e.HorizontalChange;
                width = element.Width - e.HorizontalChange;
                if (width >= minWidth)
                    element.Margin = margin;
                    element.Width = width;
            else if (thumb.HorizontalAlignment == HorizontalAlignment.Right)
                margin.Right -= e.HorizontalChange;
                width = element.Width + e.HorizontalChange;
                if (width >= minWidth)
                    element.Margin = margin;
                    element.Width = width;
            if (thumb.VerticalAlignment == VerticalAlignment.Top)
                margin.Top += e.VerticalChange;
                height = element.Height - e.VerticalChange;
                if (height >= minHeight)
                    element.Margin = margin;
                    element.Height = height;
            else if (thumb.VerticalAlignment == VerticalAlignment.Bottom)
                margin.Bottom -= e.VerticalChange;
                height = element.Height + e.VerticalChange;
                if (height >= minHeight)
                    element.Margin = margin;
                    element.Height = height;
    protected override Visual GetVisualChild(int index)
        return _Visuals[index];
    protected override int VisualChildrenCount
        get { return _Visuals.Count; }
    protected override Size MeasureOverride(Size constraint)
        _ContentPresenter.Measure(constraint);
        return _ContentPresenter.DesiredSize;
    protected override Size ArrangeOverride(Size finalSize)
        //布局位置和大小
        _ContentPresenter.Arrange(new Rect(-RectOffset/2, -RectOffset/2, this.AdornedElement.RenderSize.Width + RectOffset, this.AdornedElement.RenderSize.Height + RectOffset));
        return _ContentPresenter.RenderSize;

2.4.作为控件的附加属性来控制控件外观显示的变化

Adorner装饰类

public class MDButtonAdorner : Adorner
    private VisualCollection _Visuals;
    private ContentPresenter _ContentPresenter;
    double offset = 20;
    public MDButtonAdorner(UIElement adornedElement) : base(adornedElement)
        _Visuals = new VisualCollection(this);
        _ContentPresenter = new ContentPresenter();
        Border _br = new Border();
        _br.CornerRadius = new CornerRadius(50);
        _br.Background = Brushes.Red;
        TextBlock _txt = new TextBlock();
        _txt.Text = "1";
        _txt.Width = offset;
        _txt.Height = offset;
        _txt.Foreground = Brushes.White;
        _txt.TextAlignment = TextAlignment.Center;
        _br.Child = _txt;
        Canvas _grid = new Canvas();
        _grid.Children.Add(_br);
        _grid.IsHitTestVisible = false;
        _ContentPresenter.Content = _grid;
        _Visuals.Add(_ContentPresenter);
    protected override Visual GetVisualChild(int index)
        return _Visuals[index];
    protected override int VisualChildrenCount
        get { return _Visuals.Count; }
    protected override Size MeasureOverride(Size constraint)
        _ContentPresenter.Measure(constraint);
        return _ContentPresenter.DesiredSize;
    protected override Size ArrangeOverride(Size finalSize)
        //布局位置和大小
        _ContentPresenter.Arrange(new Rect(this.AdornedElement.RenderSize.Width - offset / 2, -offset / 2, finalSize.Width, finalSize.Height));
        return _ContentPresenter.RenderSize;
    public Visibility IsVisibility
        get { return _ContentPresenter.Visibility; }
        set { _ContentPresenter.Visibility = value; }

控件增加附加属性

public class MDButton : Button
    public static readonly DependencyProperty ShowAdorner1Property;
    public bool ShowAdorner1
        get => (bool)GetValue(ShowAdorner1Property);
        set => SetValue(ShowAdorner1Property, value);
    static MDButton()
        ShowAdorner1Property = DependencyProperty.RegisterAttached("ShowAdorner1", typeof(bool), typeof(MainWindow), new PropertyMetadata(false, Method));
    public MDButton() { }
    private static void Method(DependencyObject d, DependencyPropertyChangedEventArgs e)
        var ele = d as Visual;
        var uie = ele as UIElement;
        if (AdornerLayer.GetAdornerLayer(ele) is AdornerLayer adolay && uie is not null)
            var ados = adolay.GetAdorners(uie);
            if (ados == null)
                adolay.Add(new MDButtonAdorner(uie));
            ados = adolay.GetAdorners(uie);
            if (ados != null && ados.Count() != 0)
                if (ados.FirstOrDefault() is MDButtonAdorner ado)
                    ado.IsVisibility = (bool)e.NewValue ? Visibility.Visible : Visibility.Collapsed;
<local:MDButton x:Name="btn5" Height="50" Width="50" Margin="30"/>
//显示装饰
btn5.ShowAdorner1 = true;
//隐藏装饰
btn5.ShowAdorner1 = false;

三、展现效果

四、源码下载

WPF_Adorner项目源码下载

不积硅步,何以至千里

关于WPF系列Adorner的学习笔记。AdornerWPF中的界面设计中很常见,它可以用来实现更加美观的界面视觉效果,也可以设计事件动态效果等等。 1.在界面元素上提供视觉效果,以提示用户当前元素处于特定状态。 2.从视觉上遮盖或重写UIElement的一部分或全部 3.向UIElement添加功能控点,使用户可以操作元素(调整大小、旋转、重新定位等) 4.作为控件的附加属性来控制控件外观显示的变化 具体请参考本博客文章!
  看到这个标题,您可能会在脑中产生一个疑问:Adorner是什么?AdornerWPF窗口中独立的一层,支持在界面元素之上执行独立的绘制及用户交互。可以说,Adorner在您的WPF程序中无处不在。在WPF中,从编辑框控件中光标的显示和选中效果的支持,到具有数据焦点的控件所具有的虚线外框,都是通过Adorner实现的。 什么是Adorner   鉴于您可能不熟悉Adorne...
,我之前是修改的ControlTemplate。类似于将一个带数字的控件,放在另一个控件的右上角,来实现的这个效果。 原来WPF有个Adorner,也可以实现这样的效果。 WPF中很多控件,都带Adorner层。这相当于一个控件的装饰层。我们在这里面可以做出很多蛮好的效果。比如错误提示等。 项目中有时需要在图片上标注热区,在HTML中有<area>标签,但在WPF中目前还没现成的控件来实现这这一功能。至于图片热区功能有道词典的【图解词典】是个不错的例子,如图1: 什么是AdornerAdoner 是一个绑定到某个UIElement自定义的FrameWorkElemnt 。Adoner被渲染在AdornerLayer,而AdornerLay... DreamMesh4D: Video-to-4D Generation with Sparse-Controlled Gaussian-Mesh HybridRepresentation 论文解读