WPF arrows and wpf arrows
Some time ago, the WPF arrows were used for work, after reference to Using WPF to Visualize a Graph with Circular Dependencies. Write a WPF arrow library.
First,
<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 = "100,100" EndPoint = ""/> <arrow: arrowLineWithText ArrowEnds = "Both" IsTextUp = "True" Stroke = "Blue" StrokeDashArray = "5, 3" Text = "Export" TextAlignment = "Center" StartPoint = "110,110" EndPoint =" 180,180 "/> <arrow: arrowquadraticbezercontrolpoint = "200,100" Stroke = "Yellow" StartPoint = "250,180" EndPoint = ""/> <arrow: adjustableArrowBezierCurve ControlPoint1 = "230,200" ControlPoint2 = "300,300" ShowControl = "True" Stroke = "Black" StartPoint = "200,200" EndPoint = "500,300"/> </Canvas> </Window>
The main class relationships are as follows:
(Image Excerpt from <WPF programming book>)
Go to the Shape Definition and find a virtual method.
// Summary: // Gets a value that represents the System. windows. media. geometry of the System. windows. shapes. shape. //// return result: // The System. windows. media. geometry of the System. windows. shapes. shape. protected abstract Geometry DefiningGeometry {get ;}
If we use a tool (I use ILSpy) to decompile the PresentationFramework where the Shape class is located. dll source code, you will find DefiningGeometry is the most important method, in MeasureOverride, ArrangeOverride, OnRender will indirectly call this method.
In the Line class, the content of the overloaded method is as follows:
Point startPoint = new Point(this.X1, this.Y1); Point endPoint = new Point(this.X2, this.Y2); this._lineGeometry = new LineGeometry(startPoint, endPoint);
A new LineGeometry instance is returned.
This method is also overloaded in ArrowBase, as follows:
Protected override Geometry DefiningGeometry {get {_ figureConcrete. startPoint = StartPoint; // clear the specific shape to avoid repeated addition of _ figureConcrete. segments. clear (); var segements = FillFigure (); if (segements! = Null) {foreach (var segement in segements) {_ figureConcrete. segments. add (segement) ;}}// draw the arrow if (ArrowEnds & ArrowEnds. start) = ArrowEnds. start) {CalculateArrow (_ figureStart, GetStartArrowEndPoint (), StartPoint);} // draw the arrow if (ArrowEnds & ArrowEnds. end) = ArrowEnds. end) {CalculateArrow (_ figureEnd, GetEndArrowStartPoint (), GetEndArrowEndPoint ();} return _ wholeGeometry ;}}
In this example, _ figureConcrete is used to save the PathFigure of a specific shape. Several other protected methods are defined as follows:
/// <Summary> /// obtain the components of a specific shape /// </summary> protected abstract PathSegmentCollection FillFigure (); /// <summary> /// obtain the end point at the start arrow /// </summary> /// <returns> end point at the start arrow </returns> protected abstract Point GetStartArrowEndPoint (); /// <summary> /// obtain the start point at the end arrow /// </summary> /// <returns> start point at the end arrow </returns> protected abstract Point GetEndArrowStartPoint (); /// <summary> /// obtain the end point at the end arrow /// </summary> /// <returns> end point at the end arrow </returns> protected abstract Point GetEndArrowEndPoint ();
In ArrowBase, another important method is to calculate arrows:
/// <Summary> /// calculate the directed Arrow between two points /// </summary> /// <param name = "pathfig"> shape of the arrow </param> /// <param name = "startPoint"> start point </param> /// <param name = "endPoint"> end point </param> /// <returns> calculated shape </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; // obtain the unit Vector vect. normalize (); vect * = ArrowLength; // half of the rotation angle matx. rotate (ArrowAngle/2); // calculate the pathfig of the top half arrow. startPoint = endPoint + vect * matx; polyseg. points. clear (); polyseg. points. add (endPoint); matx. rotate (-ArrowAngle); // calculates the point polyseg of the lower half arrow. points. add (endPoint + vect * matx);} pathfig. isClosed = IsArrowClosed ;}
ArrowLine: a straight line with an arrow. This class is very simple. It reloads the related methods defined in ArrowBase.
/// <Summary> // a straight line with an arrow between two points /// </summary> public class ArrowLine: arrowBase {# region Fields // <summary> // line segment // </summary> private readonly LineSegment _ lineSegment = new LineSegment (); # endregion Fields # region Properties // <summary> // end point /// </summary> public static readonly DependencyProperty EndPointProperty = DependencyProperty. register ("EndPoint", typeof (Point), typeof (ArrowLine), new FrameworkPropertyMetadata (default (Point), FrameworkPropertyMetadataOptions. affectsMeasure); // <summary> // end Point // </summary> public Point EndPoint {get {return (Point) GetValue (EndPointProperty );} set {SetValue (EndPointProperty, value );}} # endregion Properties # region Protected Methods // <summary> // fill in Figure // </summary> protected override PathSegmentCollection FillFigure () {_ lineSegment. point = EndPoint; return new PathSegmentCollection {_ lineSegment };} /// <summary> /// obtain the end point at the start arrow /// </summary> /// <returns> end point at the start arrow </returns> protected override Point GetStartArrowEndPoint () {return EndPoint ;} /// <summary> /// obtain the start point at the end arrow /// </summary> /// <returns> start point at the end arrow </returns> protected override Point GetEndArrowStartPoint () {return StartPoint ;} /// <summary> /// obtain the end point at the end arrow /// </summary> /// <returns> end point at the end arrow </returns> protected override Point GetEndArrowEndPoint () {return EndPoint ;}# endregion Protected Methods }}
ArrowLineWithText, which can display text above or below a straight line, inherits ArrowLine. The main task is to reload the rendering event to draw text.
/// <Summary> /// overload rendering event /// </summary> /// <param name = "drawingContext"> drawing context </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); // use Rotation Transformation, make it equal to line 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, defatypetypeface, SystemFonts. statusFontSize, Brushes. black) {// MaxTextWidth = vec. length, // set the text alignment mode TextAlignment = TextAlignment}; var offsetY = StrokeThickness; if (IsTextUp) {// calculate the number of lines of text double textLineCount = formattedText. width/formattedText. maxTextWidth; if (textLineCount <1) {// how can I have a row textLineCount = 1;} // calculate the upward offset offsetY =-formattedText. height * textLineCount-StrokeThickness;} startPoint = startPoint + new Vector (0, offsetY); drawingContext. drawText (formattedText, startPoint); drawingContext. pop ();}}
The ArrowBezierCurve and arrowquadraticbesuppliers codes are similar to those of ArrowLine, but the dependency attributes of control points are added. Represent the besell curve and the secondary besell curve respectively.
Adjustablearrowquadraticbetilles indicates the adjustable secondary besell curve. Update the control point by pressing and holding the control point (drawn by reloading rendering) to adjust it. Mainly reload the mouse press, move the mouse, release the mouse, rendering and other methods.
/// <Summary> /// when not processed <see cref = "E: System. windows. input. mouse. mouseDown "/> this method is called when an additional event arrives at an element derived from this class in its route. To implement this method, you can add class processing for this event. /// </Summary> /// <param name = "e"> <see cref = "T: System. Windows. Input. MouseButtonEventArgs"/> that contains event data. This event data report provides detailed information about the pressed mouse button and processed status. /// </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> // when not processed <see cref = "E: System. windows. input. mouse. mouseUp "/> This method is called when a routing event reaches an element derived from this class in its route. To implement this method, you can add class processing for this event. /// </Summary> /// <param name = "e"> <see cref = "T: System. Windows. Input. MouseButtonEventArgs"/> that contains event data. Event data will report that the mouse button has been released. /// </Param> protected override void OnMouseUp (MouseButtonEventArgs e) {base. onMouseUp (e); ReleaseMouseCapture (); _ isPressedControlPoint = false;} // <summary> // <see cref = "E: System. windows. input. mouse. mouseMove "/> this method is called when an additional event arrives at an element derived from this class in its route. To implement this method, you can add class processing for this event. /// </Summary> /// <param name = "e"> <see cref = "T: System. Windows. Input. MouseEventArgs"/> that contains event data. /// </Param> protected override void OnMouseMove (MouseEventArgs e) {base. onMouseMove (e); if (ShowControl) & (e. leftButton = MouseButtonState. pressed) & (_ isPressedControlPoint) {// update control point ControlPoint = e. getPosition (this) ;}/// <summary> /// when you override a derived class, it is displayed by the layout system. When this method is called, the rendering command of this element is not directly used, but is reserved for layout and painting for future asynchronous use. /// </Summary> /// <param name = "drawingContext"> specifies the Drawing Instruction for a specific element. This context is provided for the layout system. /// </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 is an adjustable beiser curve, and the code is similar to adjustablearrowquadraticbesuppliers. It is changed from a control point to two control points.
For code, see WPFArrows