[WPF]附加属性和装饰器实现任意控件添加描述文本

导语:

以前写文本框这些控件都会添加个描述,但每次都写一个TextBlock太难受了,还得注意对齐,所以就有了这篇文章。

1.先看实现效果

 2.再看实现方法

(1)装饰器

WPF有个装饰器的功能,可以在UIElement上叠加一层装饰器用来显示绘制内容。见装饰器概述 - WPF .NET Framework | Microsoft Learn

代码如下

class DescriptionAdorner : Adorner
    {
        private string Description { get; set; }
        private int FonrSize { get; set; }
        private FormattedText formattedText { get; set; }
        private double TextXOffset { get; set; }
        private double TextYOffset { get; set; }
        public PlacementMode Placement { get; set; }


        public DescriptionAdorner(UIElement adornedElement, string description, int fontsize = 12)
            : base(adornedElement)
        {
            Description = description;
            FonrSize = fontsize;
            formattedText = new FormattedText(
                   Description,
                   CultureInfo.GetCultureInfo("en-us"),
                   FlowDirection.LeftToRight,
                   new Typeface("Verdana"),
                   FonrSize,
                   Brushes.Black);
            TextXOffset = 10;
            TextYOffset = 0;
        }
        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            Rect adornedElementRect = new Rect(this.AdornedElement.RenderSize);

            // Some arbitrary drawing implements.
            SolidColorBrush renderBrush = new SolidColorBrush(Colors.Green);
            renderBrush.Opacity = 0.2;
            Pen renderPen = new Pen(new SolidColorBrush(Colors.Navy), 1.5);
            double renderRadius = 5.0;

            if (string.IsNullOrEmpty(Description)) return;
            var textWidth = formattedText.WidthIncludingTrailingWhitespace;
            var textHeight = formattedText.Height;
            double textX = 0; double textY = 0;
            switch (Placement)
            {
                case PlacementMode.Left:
                    textX = adornedElementRect.Left - (textWidth + TextXOffset);
                    textY = adornedElementRect.Top + adornedElementRect.Height / 2 - textHeight / 2 + TextYOffset;
                    break;
                case PlacementMode.Top:
                    textX = adornedElementRect.Left + TextXOffset;
                    textY = adornedElementRect.Top - textHeight + TextYOffset;
                    break;
                case PlacementMode.Right:
                    textX = adornedElementRect.Right + TextXOffset;
                    textY = adornedElementRect.Top + adornedElementRect.Height / 2 - textHeight / 2 + TextYOffset;
                    break;
                case PlacementMode.Bottom:
                    textX = adornedElementRect.Left + TextXOffset;
                    textY = adornedElementRect.Bottom + TextYOffset;
                    break;
                default:
                    break;

            }
            drawingContext.DrawText(formattedText, new Point(textX, textY));

            //drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopLeft, renderRadius, renderRadius);
            //drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopRight, renderRadius, renderRadius);
            //drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomLeft, renderRadius, renderRadius);
            //drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomRight, renderRadius, renderRadius);
        }
    }

上面的代码在Onrender中将Description文本直接绘制到了控件的对应位置,并通过Placement实现了上下左右四个方向的排列。

但光装饰器并不能很好的实现我的需求,所以又想到了附加属性。

(2)附加属性

代码如下

public class AttachDescriptionAttrib : UIElement
    {
        public static bool GetIsShowDescription(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsShowDescriptionProperty);
        }

        public static void SetIsShowDescription(DependencyObject obj, bool value)
        {
            obj.SetValue(IsShowDescriptionProperty, value);
        }



        public static string GetDescription(DependencyObject obj)
        {
            return (string)obj.GetValue(DescriptionProperty);
        }

        public static void SetDescription(DependencyObject obj, string value)
        {
            obj.SetValue(DescriptionProperty, value);
        }



        public static int GetFontSize(DependencyObject obj)
        {
            return (int)obj.GetValue(FontSizeProperty);
        }

        public static void SetFontSize(DependencyObject obj, int value)
        {
            obj.SetValue(FontSizeProperty, value);
        }



        public static PlacementMode GetPlacement(DependencyObject obj)
        {
            return (PlacementMode)obj.GetValue(PlacementProperty);
        }

        public static void SetPlacement(DependencyObject obj, PlacementMode value)
        {
            obj.SetValue(PlacementProperty, value);
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PlacementProperty =
            DependencyProperty.RegisterAttached("Placement", typeof(PlacementMode), typeof(AttachDescriptionAttrib), new PropertyMetadata(PlacementMode.Left, new PropertyChangedCallback(ReloadAdorner)));



        // Using a DependencyProperty as the backing store for FontSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FontSizeProperty =
            DependencyProperty.RegisterAttached("FontSize", typeof(int), typeof(AttachDescriptionAttrib), new PropertyMetadata(12, new PropertyChangedCallback(ReloadAdorner)));



        // Using a DependencyProperty as the backing store for Description.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DescriptionProperty =
            DependencyProperty.RegisterAttached("Description", typeof(string), typeof(AttachDescriptionAttrib), new PropertyMetadata(string.Empty, new PropertyChangedCallback(ReloadAdorner)));



        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsShowDescriptionProperty =
            DependencyProperty.RegisterAttached("IsShowDescription", typeof(bool), typeof(AttachDescriptionAttrib), new UIPropertyMetadata(false, new PropertyChangedCallback(ReloadAdorner)));

        private static void Elemt_Loaded(object sender, RoutedEventArgs e)
        {
            SetDesctiptionAdorner(sender as FrameworkElement, GetDescription(sender as DependencyObject), GetFontSize(sender as DependencyObject)
                , GetPlacement(sender as DependencyObject));
        }
        public static void ReloadAdorner(DependencyObject d,DependencyPropertyChangedEventArgs e)
        {
            var elemt = d as FrameworkElement;
            if (GetIsShowDescription(d) == false)
            {
                ClearDescriptionAdorner(elemt);
                return;
            }
            if (elemt.IsLoaded)
                SetDesctiptionAdorner(elemt, GetDescription(d), GetFontSize(d)
                    , GetPlacement(d));
            else elemt.Loaded += Elemt_Loaded;
        }

        public static void SetDesctiptionAdorner(UIElement uIElement, string desc, int fontSize, PlacementMode placement)
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(uIElement);
            ClearDescriptionAdorner(uIElement);
            adornerLayer.Add(new DescriptionAdorner(uIElement, desc, fontSize) { Placement = placement });
        }
        public static void ClearDescriptionAdorner(UIElement uIElement)
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(uIElement);
            if (adornerLayer == null) return;
            var adorners = adornerLayer.GetAdorners(uIElement);
            for (int i = 0; i < adorners?.Length; i++)
            {
                if (adorners[i] is DescriptionAdorner)
                    adornerLayer.Remove(adorners[i]);
            }
        }
    }

添加装饰器的方法是SetDesctiptionAdorner,其他的附加属性只是为了更多的可控参数。

需要注意的是控件在Loaded后才能获取到AdornerLayer,所以在ReloadAdorner入口处给UIElement添加了Loaded事件回调来添加装饰器。

同时我们可能在控件加载完毕后改变属性,所以用IsLoaded来判断是否需要重新添加装饰器。

同时在SetDesctiptionAdorner中,添加装饰器前我们需要清理一下旧的装饰器,使用ClearDescriptionAdorner来完成。

代码很简单,主要是思路问题,可供参考。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值