【WPF Series】【Adorner】

WPF study notes series

Chapter 1 [WPF Series] [Adorner]



Preface

Study notes about WPF series Adorner. Adorner is very common in interface design in WPF. It can be used to achieve more beautiful interface visual effects, and can also design event dynamic effects, etc.

1. Introduction to Adorner

  • Adorner, abstract base class from which all implementations with decorators inherit;
  • Adorner is a special type of FrameworkElement used to provide visual prompts to users;
  • Adorner, simply put, is a WPF decorator, which is used to add a layer of decorative effect to WPF controls;
  • Adorner is an independent layer in the WPF window that supports independent drawing and user interaction on interface elements;
  • Adorner , does not include any inherited rendering behavior, ensuring that the decorator is rendered is the responsibility of the decorator implementer. A common way to implement rendering behavior is to override the OnRenderSizeChanged method and use one or more DrawingContext objects to render the decorator's visual effects on demand;
  • AdornerLayer represents the rendering layer of a decorator for one or more decorative elements;
  • AdornerDecorator, associates the decorator layer with the element collection;
  • WPF's drawing of Adorner is completed in a separate layer AdornerLayer, which is a rendering surface that is always located above the decorative elements or collections of decorative elements. The measure-arrange process is executed separately when performing layout calculations;
  • In WPF, everything from the cursor display and selection effect support in the edit box control to the dotted outline of the control with data focus are all implemented through Adorner;
  • Everything placed in the AdornerLayer is rendered on top of any remaining styles you have set. That is, the decorator is always visible on top and cannot be overridden using z-order;
  • Adorner receives input events just like any other FrameworkElement. To enable pass-through hit testing for elements under the decorator, set the hit test IsHitTestVisible property on the decorator to false;

2. Common applications of Adorner

2.1. Provide visual effects on interface elements to remind users that the current element is in a specific state.

For example, drawing a corner frame decoration around a specific button.
Normally, we bind Adorner to a specific interface element in the following form:
1. Call the static method AdornerLayer.GetAdornerLayer(Visual visual) to get the interface element that needs to be decorated by Adorner. Passed in as a parameter. This function searches up the visual tree starting from the interface element and returns the first AdornerLayer it finds.
2. Call AdornerLayer.Add(Adorner adorner), the function will add the decorator to be added to AdornerLayer.
3. Remove the decoration through remove.
4. In general, the derived type of Adorner needs to consider specifying how Adorner draws its appearance by overriding the OnRender() or AddVisualChild() function.

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. Visually cover or rewrite part or all of UIElement

For example, the search bar above IE displays the name of the current search engine when no input is made.

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);
        }
        else
        {
    
    
            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. Add functional handles to UIElement so that users can manipulate elements (resize, rotate, reposition, etc.)

The following is an example of how Adorner controls the size adjustment of 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. As an additional attribute of the control to control changes in the appearance and display of the control

Adorner Decoration Class

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; }
    }
}

Add additional properties to the control

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;
                }
            }
        }
    }
}

Xaml

<local:MDButton x:Name="btn5" Height="50" Width="50" Margin="30"/>

Method to realize

//显示装饰
btn5.ShowAdorner1 = true;
//隐藏装饰
btn5.ShowAdorner1 = false;

3. Display effect

Insert image description here

4. Source code download

WPF_Adorner project source code download

Summarize

How can you reach a thousand miles without accumulating steps?

Guess you like

Origin blog.csdn.net/Aflashstar/article/details/128952811