自定义WPF折线的圆角半径

4

是否可以将WPF折线的角半径设置为自定义值? 对于WPF边框,这是可能的。 在我看来,折线只能设置StrokeLineJoin="Round",但不能设置半径:

<Polyline Points="0,0 0,100 200,100" StrokeLineJoin="Round" />

对于边框:CornerRadius="..." 是可行的:

<Border CornerRadius="8" ... />

有没有简单的方法或技巧来实现折线的自定义拐角圆角(在线段交汇处)? (例如,Microsoft Visio能够做到这一点。) 谢谢!

4个回答

8

使用WPF开箱即用的功能是无法直接实现的。但是,我编写了一段代码来完成它,虽然我不会称其为“简单的解决方法”,但它应该可以工作。在Xaml中,我认为声明应该如下:

<myCustomControls:RoundPolyline Points="0,0 0,100 200,100" Radius="10" />

C# 代码:

using System;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

namespace MyControls
{
    public sealed class RoundPolyline : Shape
    {
        public static readonly DependencyProperty FillRuleProperty = DependencyProperty.Register("FillRule", typeof(FillRule), typeof(RoundPolyline), new FrameworkPropertyMetadata(FillRule.EvenOdd, FrameworkPropertyMetadataOptions.AffectsRender));
        public static readonly DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(PointCollection), typeof(RoundPolyline), new FrameworkPropertyMetadata(GetEmptyPointCollection(), FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(RoundPolyline), new FrameworkPropertyMetadata(6.0, FrameworkPropertyMetadataOptions.AffectsRender));

        private Geometry _geometry;
        private void DefineGeometry()
        {
            PointCollection points = Points;
            if (points == null)
            {
                _geometry = Geometry.Empty;
                return;
            }

            PathFigure figure = new PathFigure();
            if (points.Count > 0)
            {
                // start point
                figure.StartPoint = points[0];

                if (points.Count > 1)
                {
                    // points between
                    double desiredRadius = Radius;
                    for (int i = 1; i < (points.Count - 1); i++)
                    {
                        // adjust radius if points are too close
                        Vector v1 = points[i] - points[i - 1];
                        Vector v2 = points[i + 1] - points[i];
                        double radius = Math.Min(Math.Min(v1.Length, v2.Length) / 2, desiredRadius);

                        // draw the line, and stop before the next point
                        double len = v1.Length;
                        v1.Normalize();
                        v1 *= (len - radius);
                        LineSegment line = new LineSegment(points[i - 1] + v1, true);
                        figure.Segments.Add(line);

                        // draw the arc to the next point
                        v2.Normalize();
                        v2 *= radius;
                        SweepDirection direction = (Vector.AngleBetween(v1, v2) > 0) ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
                        ArcSegment arc = new ArcSegment(points[i] + v2, new Size(radius, radius), 0, false, direction, true);
                        figure.Segments.Add(arc);
                    }

                    // last point
                    figure.Segments.Add(new LineSegment(points[points.Count - 1], true));
                }
            }
            PathGeometry geometry = new PathGeometry();
            geometry.Figures.Add(figure);
            geometry.FillRule = FillRule;
            if (geometry.Bounds == Rect.Empty)
            {
                _geometry = Geometry.Empty;
            }
            else
            {
                _geometry = geometry;
            }
        }

        protected override Size MeasureOverride(Size constraint)
        {
            DefineGeometry();
            return base.MeasureOverride(constraint);
        }

        protected override Geometry DefiningGeometry
        {
            get
            {
                return _geometry;
            }
        }

        public double Radius
        {
            get
            {
                return (double)GetValue(RadiusProperty);
            }
            set
            {
                SetValue(RadiusProperty, value);
            }
        }

        public FillRule FillRule
        {
            get
            {
                return (FillRule)GetValue(FillRuleProperty);
            }
            set
            {
                SetValue(FillRuleProperty, value);
            }
        }

        public PointCollection Points
        {
            get
            {
                return (PointCollection)GetValue(PointsProperty);
            }
            set
            {
                SetValue(PointsProperty, value);
            }
        }

        // NOTE: major hack because none of this is public, and this is very unfortunate, it should be...
        private static PointCollection _emptyPointCollection;
        private static ConstructorInfo _freezableDefaultValueFactoryCtor;
        private static object GetEmptyPointCollection()
        {
            if (_freezableDefaultValueFactoryCtor == null)
            {
                _emptyPointCollection = (PointCollection)typeof(PointCollection).GetProperty("Empty", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null, null);
                Type freezableDefaultValueFactoryType = typeof(DependencyObject).Assembly.GetType("MS.Internal.FreezableDefaultValueFactory");
                _freezableDefaultValueFactoryCtor = freezableDefaultValueFactoryType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
            }
            return _freezableDefaultValueFactoryCtor.Invoke(new object[] { _emptyPointCollection });
        }
    }     
}

1

为了那些可能需要的人。我也在寻找一个支持圆角的WPF多边形类。 我在Simon Mourier的代码基础上进行了进一步的开发。

每个点都有自己的可选半径和全局半径。

public sealed class ArcPolygon : Shape
    {
        public bool RefreshOnPointAdd { get; set; }

        private readonly Geometry _geometry;
        private readonly PathFigure _figure;

        public ArcPolygon()
        {
            RefreshOnPointAdd = true;

            _geometry = new PathGeometry();
            _figure = new PathFigure();

            ((PathGeometry)_geometry).Figures.Add(_figure);

            _pointCollection = new ArcPointCollection();
            _pointCollection.CollectionChanged += PointCollectionChanged;
        }

        public static readonly DependencyProperty FillRuleProperty = DependencyProperty.Register("FillRule", typeof(FillRule), typeof(ArcPolygon), new FrameworkPropertyMetadata(FillRule.EvenOdd, FrameworkPropertyMetadataOptions.AffectsRender));
        public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double?), typeof(ArcPolygon), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));

        public void Refresh()
        {
            DefineGeometry();
        }

        private void DefineGeometry()
        {
            var points = PointCollection;

            _figure.Segments.Clear();

            if(points.Any())
            {
                // start point
                _figure.StartPoint = points[0];

                if(points.Count > 1)
                {
                    // points between
                    for(int i = 1; i < (points.Count - 1); i++)
                    {
                        // adjust radius if points are too close
                        var v1 = (Point)points[i] - points[i - 1];
                        var v2 = (Point)points[i + 1] - points[i];

                        var radius = (points[i].Radius ?? Radius) ?? 0;

                        radius = Math.Min(Math.Min(v1.Length, v2.Length) / 2, radius);

                        // draw the line, and stop before the next point
                        double len = v1.Length;
                        v1.Normalize();
                        v1 *= (len - radius);
                        var line = new LineSegment((Point)points[i - 1] + v1, true);
                        _figure.Segments.Add(line);

                        // draw the arc to the next point
                        v2.Normalize();
                        v2 *= radius;
                        var direction = (Vector.AngleBetween(v1, v2) > 0) ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
                        var arc = new ArcSegment((Point)points[i] + v2, new Size(radius, radius), 0, false, direction, true);
                        _figure.Segments.Add(arc);
                    }

                    // last point
                    _figure.Segments.Add(new LineSegment(points[points.Count - 1], true));
                }
            }
        }

        protected override Size MeasureOverride(Size constraint)
        {
            DefineGeometry();
            return base.MeasureOverride(constraint);
        }

        protected override Geometry DefiningGeometry
        {
            get
            {
                return _geometry;
            }
        }

        public double? Radius
        {
            get
            {
                return (double?)GetValue(RadiusProperty);
            }
            set
            {
                SetValue(RadiusProperty, value);
            }
        }

        public FillRule FillRule
        {
            get
            {
                return (FillRule)GetValue(FillRuleProperty);
            }
            set
            {
                SetValue(FillRuleProperty, value);
                ((PathGeometry)_geometry).FillRule = value;
            }
        }

        private ArcPointCollection _pointCollection;
        /// <summary>
        /// Gets or sets a collection that contains the points of the polygon.
        /// </summary>
        public ArcPointCollection PointCollection
        {
            get { return _pointCollection; }
            set
            {
                _pointCollection = value;
                if(RefreshOnPointAdd) Refresh();
            }
        }

        // <summary>
        // Gets or sets the control's text
        // </summary>
        public string Points
        {
            get
            {
                return string.Join(" ", PointCollection.Select(x => x.ToString()));
            }

            set
            {
                var pointCollection = new ArcPointCollection();

                //10,50,45 180,50 180,150,45 10,150
                var points = value.Split(' ');
                foreach(var point in points)
                {
                    if(point.Trim() == string.Empty) continue;

                    var xyarc = point.Split(',');
                    var item = new ArcPoint();
                    if(xyarc.Length >= 1) item.X = double.Parse(xyarc[0], CultureInfo.InvariantCulture);
                    if(xyarc.Length >= 2) item.Y = double.Parse(xyarc[1], CultureInfo.InvariantCulture);
                    if(xyarc.Length >= 3) item.Radius = double.Parse(xyarc[2], CultureInfo.InvariantCulture);

                    pointCollection.Add(item);
                }

                PointCollection = pointCollection;
            }
        }

        private void PointCollectionChanged(object sender, EventArgs e)
        {
            if(RefreshOnPointAdd) Refresh();
        }

        public void Reset()
        {
            _pointCollection.Clear();
        }

        public void AddPoint(double x, double y)
        {
            _pointCollection.Add(x, y);
        }

        public void AddPoint(double x, double y, double? radius)
        {
            _pointCollection.Add(x, y, radius);
        }
    }

    public sealed class ArcPoint
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double? Radius { get; set; }

        public ArcPoint()
        {
        }

        public ArcPoint(double x, double y)
        {
            this.X = x;
            this.Y = y;
        }

        public ArcPoint(double x, double y, double? radius)
        {
            this.X = x;
            this.Y = y;
            this.Radius = radius;
        }

        public static implicit operator Point(ArcPoint point)
        {
            return new Point(point.X, point.Y);
        }
        public static implicit operator ArcPoint(Point point)
        {
            return new ArcPoint(point.X, point.Y);
        }

        public override string ToString()
        {
            if(Radius == null)
                return string.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y);
            return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2}", X, Y, Radius);
        }
    }

    public sealed class ArcPointCollection : ObservableCollection<ArcPoint>
    {
        public void Add(double x, double y)
        {
            Add(new ArcPoint(x, y));
        }

        public void Add(double x, double y, double? radius)
        {
            Add(new ArcPoint(x, y, radius));
        }
    }

1

我不知道有没有,但是有一个技巧...使用Expression Blend创建带有圆角的边框或矩形,然后将其转换为路径?这将把一个带有圆角的矩形转换成路径!


有趣的方法。但是如果折线角度不总是90度怎么办? 我需要一种动态显示带有自定义圆角或无圆角的折线的方法。如何数学算法来展示带有圆角的矩形(如Visio)?我认为这不仅仅是在角落上画一个弧线?! - Jo123

0

谢谢。但这不是我需要的。我想发布一张图片以澄清,但我没有权限这样做... - Jo123

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接