zoukankan      html  css  js  c++  java
  • WPF画箭头

    简介

    参考Using WPF to Visualize a Graph with Circular Dependencies的基础上写了一个WPF画箭头的库。

    效果图如下:
    arrow

    使用的XAML代码如下:

    <Window x:Class="WPFArrows.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:arrow="clr-namespace:WPFArrows.Arrows"
            Title="MainWindow"
            Width="525"
            Height="350">
        <Canvas>
            <arrow:ArrowLine Stroke="Black"
                             StartPoint="10,10"
                             EndPoint="100,100" />
            <arrow:ArrowLineWithText ArrowEnds="Both"
                                     IsTextUp="True"
                                     Stroke="Blue"
                                     StrokeDashArray="5,3"
                                     Text="推导出"
                                     TextAlignment="Center"
                                     StartPoint="110,110"
                                     EndPoint="180,180" />
            <arrow:ArrowQuadraticBezier ControlPoint="200,100"
                                        Stroke="Yellow"
                                        StartPoint="250,180"
                                        EndPoint="500,20" />
            <arrow:AdjustableArrowBezierCurve ControlPoint1="230,200"
                                              ControlPoint2="300,300"
                                              ShowControl="True"
                                              Stroke="Black"
                                              StartPoint="200,200"
                                              EndPoint="500,300" />
        </Canvas>
    </Window>

    类关系

    arrow2

    形状绘制原理

    我们常用的形状,如Rectangle、Ellipse、Line、Path等,都继承自Shape类,类关系如下:

    3

    (图像摘自<<WPF编程宝典>>)

    而具体Shape类是如何绘制形状的呢?我们转到Shape的定义,发现其中有一个虚方法

    // 摘要: 
            //     Gets a value that represents the System.Windows.Media.Geometry of the System.Windows.Shapes.Shape.
            //
            // 返回结果: 
            //     The System.Windows.Media.Geometry of the System.Windows.Shapes.Shape.
            protected abstract Geometry DefiningGeometry { get; }

    使 用工具(我用的是ILSpy)反汇编Shape类所在的PresentationFramework.dll的源码,就会发现 DefiningGeometry是最重要的方法,在MeasureOverride、ArrangeOverride、OnRender都会间接调用该 方法。

    在Line类中,重载后的方法内容如下:

    Point startPoint = new Point(this.X1, this.Y1);
                Point endPoint = new Point(this.X2, this.Y2);
                this._lineGeometry = new LineGeometry(startPoint, endPoint);

    即直接返回了一个LineGeometry的新实例。

    在其余各类中,原理与Line类中一样。

    各个类介绍

    ArrowBase

    ArrowBase是箭头的基类,继承自Shape类。

    在ArrowBase中,重载了DefiningGeometry方法,如下:

    protected override Geometry DefiningGeometry
            {
                get
                {
                    _figureConcrete.StartPoint = StartPoint;
    
                    //清空具体形状,避免重复添加
                    _figureConcrete.Segments.Clear();
                    var segements = FillFigure();
                    if (segements != null)
                    {
                        foreach (var segement in segements)
                        {
                            _figureConcrete.Segments.Add(segement);
                        }
                    }
    
                    //绘制开始处的箭头
                    if ((ArrowEnds & ArrowEnds.Start) == ArrowEnds.Start)
                    {
                        CalculateArrow(_figureStart, GetStartArrowEndPoint(), StartPoint);
                    }
    
                    // 绘制结束处的箭头
                    if ((ArrowEnds & ArrowEnds.End) == ArrowEnds.End)
                    {
                        CalculateArrow(_figureEnd, GetEndArrowStartPoint(), GetEndArrowEndPoint());
                    }
    
                    return _wholeGeometry;
                }
            }

    在其中_figureConcrete是用来保存具体形状的PathFigure,其余几个受保护的方法定义如下:

    /// <summary>
            /// 获取具体形状的各个组成部分
            /// </summary>
            protected abstract PathSegmentCollection FillFigure();
    
            /// <summary>
            /// 获取开始箭头处的结束点
            /// </summary>
            /// <returns>开始箭头处的结束点</returns>
            protected abstract Point GetStartArrowEndPoint();
    
            /// <summary>
            /// 获取结束箭头处的开始点
            /// </summary>
            /// <returns>结束箭头处的开始点</returns>
            protected abstract Point GetEndArrowStartPoint();
    
            /// <summary>
            /// 获取结束箭头处的结束点
            /// </summary>
            /// <returns>结束箭头处的结束点</returns>
            protected abstract Point GetEndArrowEndPoint();

    在ArrowBase中,一个重要的方法是计算箭头的方法:

    /// <summary>
            /// 计算两个点之间的有向箭头
            /// </summary>
            /// <param name="pathfig">箭头所在的形状</param>
            /// <param name="startPoint">开始点</param>
            /// <param name="endPoint">结束点</param>
            /// <returns>计算好的形状</returns>
            private void CalculateArrow(PathFigure pathfig, Point startPoint, Point endPoint)
            {
                var polyseg = pathfig.Segments[0] as PolyLineSegment;
                if (polyseg != null)
                {
                    var matx = new Matrix();
                    Vector vect = startPoint - endPoint;
                    //获取单位向量
                    vect.Normalize();
                    vect *= ArrowLength;
                    //旋转夹角的一半
                    matx.Rotate(ArrowAngle / 2);
                    //计算上半段箭头的点
                    pathfig.StartPoint = endPoint + vect * matx;
    
                    polyseg.Points.Clear();
                    polyseg.Points.Add(endPoint);
    
                    matx.Rotate(-ArrowAngle);
                    //计算下半段箭头的点
                    polyseg.Points.Add(endPoint + vect * matx);
                }
    
                pathfig.IsClosed = IsArrowClosed;
            }

    ArrowLine

    ArrowLine是带箭头的直线,该类非常简单,重载了ArrowBase中定义的相关方法

    /// <summary>
        /// 两点之间带箭头的直线
        /// </summary>
        public class ArrowLine:ArrowBase
        {
            #region Fields
    
            /// <summary>
            /// 线段
            /// </summary>
            private readonly LineSegment _lineSegment=new LineSegment();
    
            #endregion Fields
    
            #region Properties
    
            /// <summary>
            /// 结束点
            /// </summary>
            public static readonly DependencyProperty EndPointProperty = DependencyProperty.Register(
                "EndPoint", typeof(Point), typeof(ArrowLine), 
                new FrameworkPropertyMetadata(default(Point), FrameworkPropertyMetadataOptions.AffectsMeasure));
    
            /// <summary>
            /// 结束点
            /// </summary>
            public Point EndPoint
            {
                get { return (Point) GetValue(EndPointProperty); }
                set { SetValue(EndPointProperty, value); }
            }
    
            #endregion Properties
    
            #region Protected Methods
    
            /// <summary>
            /// 填充Figure
            /// </summary>
            protected override PathSegmentCollection FillFigure()
            {
                _lineSegment.Point = EndPoint;
                return new PathSegmentCollection
                {
                    _lineSegment
                };
            }
    
            /// <summary>
            /// 获取开始箭头处的结束点
            /// </summary>
            /// <returns>开始箭头处的结束点</returns>
            protected override Point GetStartArrowEndPoint()
            {
                return EndPoint;
            }
    
            /// <summary>
            /// 获取结束箭头处的开始点
            /// </summary>
            /// <returns>结束箭头处的开始点</returns>
            protected override Point GetEndArrowStartPoint()
            {
                return StartPoint;
            }
    
            /// <summary>
            /// 获取结束箭头处的结束点
            /// </summary>
            /// <returns>结束箭头处的结束点</returns>
            protected override Point GetEndArrowEndPoint()
            {
                return EndPoint;
            }
    
            #endregion  Protected Methods
    
        }
    }

    ArrowLineWithText

    ArrowLineWithText,可在直线上方或下方显示文字,继承自ArrowLine。所做的主要工作就是重载渲染事件,使其绘制文字

    /// <summary>
            /// 重载渲染事件
            /// </summary>
            /// <param name="drawingContext">绘图上下文</param>
            protected override void OnRender(DrawingContext drawingContext)
            {
                base.OnRender(drawingContext);
    
                if (ShowText&&(Text != null))
                {
                    var txt = Text.Trim();
                    var startPoint = StartPoint;
                    if (!string.IsNullOrEmpty(txt))
                    {
                        var vec = EndPoint - StartPoint;
                        var angle = GetAngle(StartPoint, EndPoint);
    
                        //使用旋转变换,使其与线平行
                        var transform = new RotateTransform(angle) { CenterX = StartPoint.X, CenterY = StartPoint.Y };
                        drawingContext.PushTransform(transform);
    
                        var defaultTypeface = new Typeface(SystemFonts.StatusFontFamily, SystemFonts.StatusFontStyle,
                            SystemFonts.StatusFontWeight, new FontStretch());
                        var formattedText = new FormattedText(txt, CultureInfo.CurrentCulture,
                            FlowDirection.LeftToRight,
                            defaultTypeface, SystemFonts.StatusFontSize, Brushes.Black)
                        {
                            //文本最大宽度为线的宽度
                            MaxTextWidth = vec.Length,
                            //设置文本对齐方式
                            TextAlignment = TextAlignment
                        };
    
                        var offsetY = StrokeThickness;
                        if (IsTextUp)
                        {
                            //计算文本的行数
                            double textLineCount = formattedText.Width/formattedText.MaxTextWidth;
                            if (textLineCount < 1)
                            {
                                //怎么也得有一行
                                textLineCount = 1;
                            }
                            //计算朝上的偏移
                            offsetY = -formattedText.Height*textLineCount -StrokeThickness;
                        }
                        startPoint = startPoint +new Vector(0,offsetY);
                        drawingContext.DrawText(formattedText, startPoint);
                        drawingContext.Pop();
                    }
                }

    ArrowBezierCurve和ArrowQuadraticBezier

    ArrowBezierCurve和ArrowQuadraticBezier代码与ArrowLine基本相似,只是添加了控制点的依赖属性。分别表示贝塞尔曲线和二次贝塞尔曲线,代码从略。

    AdjustableArrowQuadraticBezier

    AdjustableArrowQuadraticBezier表示可调整的二次贝塞尔曲线。根据鼠标按住控制点(通过重载渲染绘制)的移动来更新控制点,从而起到调整的作用。主要重载了鼠标按下、鼠标移动、鼠标释放、渲染等方法。

    /// <summary>
            /// 当未处理的 <see cref="E:System.Windows.Input.Mouse.MouseDown"/> 附加事件在其路由中到达派生自此类的元素时,调用该方法。实现此方法可为此事件添加类处理。
            /// </summary>
            /// <param name="e">包含事件数据的 <see cref="T:System.Windows.Input.MouseButtonEventArgs"/>。此事件数据报告有关按下的鼠标按钮和已处理状态的详细信息。
            ///                 </param>
            protected override void OnMouseDown(MouseButtonEventArgs e)
            {
                base.OnMouseDown(e);
    
                if (ShowControl&&(e.LeftButton == MouseButtonState.Pressed))
                {
                    CaptureMouse();
                    Point pt = e.GetPosition(this);
                    Vector slide = pt - ControlPoint;
                    //在控制点的圆圈之内
                    if (slide.Length < EllipseRadius)
                    {
                        _isPressedControlPoint = true;
                    }
                }
            }
    
            /// <summary>
            /// 当未处理的 <see cref="E:System.Windows.Input.Mouse.MouseUp"/> 路由事件在其路由中到达派生自此类的元素时,调用该方法。实现此方法可为此事件添加类处理。
            /// </summary>
            /// <param name="e">包含事件数据的 <see cref="T:System.Windows.Input.MouseButtonEventArgs"/>。事件数据将报告已释放了鼠标按钮。
            ///                 </param>
            protected override void OnMouseUp(MouseButtonEventArgs e)
            {
                base.OnMouseUp(e);
                ReleaseMouseCapture();
                _isPressedControlPoint = false;
            }
    
            /// <summary>
            /// 当未处理的 <see cref="E:System.Windows.Input.Mouse.MouseMove"/> 附加事件在其路由中到达派生自此类的元素时,调用该方法。实现此方法可为此事件添加类处理。
            /// </summary>
            /// <param name="e">包含事件数据的 <see cref="T:System.Windows.Input.MouseEventArgs"/>///                 </param>
            protected override void OnMouseMove(MouseEventArgs e)
            {
                base.OnMouseMove(e);
                if ((ShowControl)&&(e.LeftButton == MouseButtonState.Pressed) && (_isPressedControlPoint))
                {
                    //更新控制点
                    ControlPoint = e.GetPosition(this);
                }
            }
    
            /// <summary>
            /// 在派生类中重写时,会参与由布局系统控制的呈现操作。调用此方法时,不直接使用此元素的呈现指令,而是将其保留供布局和绘制在以后异步使用。
            /// </summary>
            /// <param name="drawingContext">特定元素的绘制指令。此上下文是为布局系统提供的。
            ///                 </param>
            protected override void OnRender(DrawingContext drawingContext)
            {
                base.OnRender(drawingContext);
    
                if (ShowControl)
                {
                    drawingContext.DrawLine(_linePen, StartPoint, ControlPoint);
                    drawingContext.DrawEllipse(_ellipseBrush, _ellipsePen, ControlPoint, EllipseRadius, EllipseRadius);
                }
            }

    AdjustableArrowBezierCurve

    AdjustableArrowBezierCurve为可调整的贝塞尔曲线,代码与AdjustableArrowQuadraticBezier相似,只是从一个控制点变成两个控制点。代码从略。

    代码

    博客园:WPFArrows

    GitHub:WPFArrows

  • 相关阅读:
    mybatis逆向工程
    fastdfs搭建和使用
    solr学习笔记
    自己搭建anki服务器
    redis总结
    java基础——队列
    遍历文件夹下的文件,并且获取文件名字
    xls到xml
    xls文件导入数据库
    PyCharm怎样添加Qt designer
  • 原文地址:https://www.cnblogs.com/yiyan127/p/WPF-Arrow.html
Copyright © 2011-2022 走看看