using System; using System.Drawing; using System.Drawing.Drawing2D; namespace DevComponents.Tree { namespace Display { /// /// Base class for drawing node connectors. /// public abstract class NodeConnectorDisplay { //private bool m_RootNode=false; //private bool m_DrawRootAllLevels=false; private bool m_EndCap=true; //private bool m_DrawConnectorUnderNodes=true; /// /// Creates new instance of the object. /// public NodeConnectorDisplay() { } /// /// Draws connector line between two nodes. /// /// Connector context information. public virtual void DrawConnector(ConnectorRendererEventArgs info){} /// /// Returns the connector starting coordinates. /// /// Connector display information. /// Point object. protected Point GetStartPoint(ConnectorRendererEventArgs info) { Point p=Point.Empty; if(info.IsRootNode) { //int toMidPoint=info.ToNode.Bounds.Top+info.ToNode.Bounds.Height/2; //if(info.FromNode.Bounds.Top>toMidPoint) if(IsAbove(info.FromNode,info.ToNode)) p=new Point(info.FromNode.BoundsRelative.Left+info.FromNode.BoundsRelative.Width/2,info.FromNode.BoundsRelative.Top); //else if(info.FromNode.Bounds.Bottom /// Returns true if fromNode is above the toNode. /// /// From Node object. /// To Node object /// True if fromNode is above toNode. protected bool IsAbove(Node fromNode, Node toNode) { //int toMidPoint=toNode.Bounds.Top+toNode.Bounds.Height/2; //if(fromNode.Bounds.Top>toMidPoint) if(fromNode.BoundsRelative.Top>=toNode.BoundsRelative.Bottom) return true; return false; } /// /// Returns true if fromNode is below toNode. /// /// From Node object. /// To Node object. /// True if fromNode is below toNode. protected bool IsBelow(Node fromNode, Node toNode) { int toMidPoint=toNode.BoundsRelative.Top+toNode.BoundsRelative.Height/2; if(fromNode.BoundsRelative.Bottom /// Returns whether connector is extended to underline the node. /// /// Refernce to Node style. /// True if node should be underlined by connector. protected bool UnderlineNode(ElementStyle nodeStyle) { if(!nodeStyle.PaintBottomBorder && !nodeStyle.PaintTopBorder && !nodeStyle.PaintLeftBorder && !nodeStyle.PaintRightBorder) return true; return false; } /// /// Returns the connector end point. The array of end points. Two valid points will be returned if node needs to be underlined by connector. /// /// Connector display info. /// Array of point objects. protected Point[] GetEndPoint(ConnectorRendererEventArgs info) { // If to element is to the right of the from node and has left border end point is the vertical mid-point // If to element is to the left of the from node and has right border end point is the vertical mid-point // If there is no border end point is text bottom // If this is link connector the end point is the middle bottom or top point of the node Point p=Point.Empty; Point pLineEnd=Point.Empty; int capWidthOffset=GetCapWidthOffset(info.NodeConnector.EndCap,info.NodeConnector.EndCapSize); bool leftSide=this.IsOnLeftSide(info.FromNode,info.ToNode); if(info.LinkConnector && info.FromNode.BoundsRelative.Top>info.ToNode.BoundsRelative.Bottom) p=new Point(info.ToNode.BoundsRelative.X+info.ToNode.BoundsRelative.Width/2+(leftSide?capWidthOffset:-capWidthOffset),info.ToNode.BoundsRelative.Bottom+1); else if(info.LinkConnector && info.FromNode.BoundsRelative.Bottom /// Returns the offest for the node connector cap. /// /// Cap type. /// Cap size. /// protected int GetCapWidthOffset(eConnectorCap cap,Size size) { int capWidthOffset=0; switch(cap) { case eConnectorCap.Arrow: capWidthOffset=size.Width+1; break; case eConnectorCap.Ellipse: capWidthOffset=size.Width; break; } return capWidthOffset; } /// /// Returns true if source node is on the left side of the target node. /// /// Reference to source node. /// Reference to target node. /// True if source is on the left side of target. protected bool IsOnLeftSide(Node source, Node target) { if((source.BoundsRelative.Left+source.BoundsRelative.Width/2)>target.BoundsRelative.Left) return true; return false; } /// /// Returns new instance of pen object for node connector line. Caller is responsible for /// disposing of this object. /// /// Node connector display info. /// New instance of Pen object. protected Pen GetLinePen(ConnectorRendererEventArgs info) { return new Pen(info.NodeConnector.LineColor,info.NodeConnector.LineWidth); } /// /// Returns new instance of pen object for the end node connector line. Caller is responsible for /// disposing of this object. /// /// Node connector display info. /// New instance of Pen object. protected Pen GetEndLinePen(ConnectorRendererEventArgs info) { return new Pen(info.NodeConnector.LineColor,EndLineWidth); } /// /// Returns new instance of pen object for the node underline line. Caller is responsible for /// disposing of this object. /// /// Node connector display info. /// New instance of Pen object. protected Pen GetEndUnderlinePen(ConnectorRendererEventArgs info) { return new Pen(info.NodeConnector.LineColor,EndLineWidth); } private int EndLineWidth { get {return 1;} } /// /// Draws straight line connector between start and end point. /// /// Node connector display info. /// Start point. /// End point. /// Underline end point if any. protected void DrawLineConnector(ConnectorRendererEventArgs info,Point pStart,Point pEnd, Point pEndUnderLine) { if(info.NodeConnector.LineWidth>1) { // Merge lines nicely by filling and creating path... int rootLineWidth=this.EndLineWidth; int lineWidth=info.NodeConnector.LineWidth; using(Brush brush=GetLineBrush(info)) { GraphicsPath path=GetConnectingPath(pStart,pEnd,lineWidth,rootLineWidth,info.IsRootNode,!(IsAbove(info.FromNode,info.ToNode) || IsBelow(info.FromNode,info.ToNode))); info.Graphics.FillPath(brush,path); } } else { using(Pen pen=this.GetLinePen(info)) { info.Graphics.DrawLine(pen,pStart,pEnd); } } if(!pEndUnderLine.IsEmpty) { using(Pen pen=this.GetEndUnderlinePen(info)) { info.Graphics.DrawLine(pen,pEnd,pEndUnderLine); } } } private GraphicsPath GetConnectingPath(Point pStart, Point pEnd, int lineStartWidth, int lineEndWidth, bool bRoot, bool bRootSide) { int direction=1; if(pStart.X>pEnd.X) direction=-1; lineStartWidth++; lineEndWidth++; GraphicsPath path=new GraphicsPath(); if(bRoot && !bRootSide) { path.AddLine(pStart.X,pStart.Y,pStart.X+lineStartWidth*direction,pStart.Y); // if(direction>0) // path.AddLine(pEnd.X+lineEndWidth*direction,pEnd.Y,pEnd.X,pEnd.Y); // else // path.AddLine(pEnd.X,pEnd.Y,pEnd.X+lineEndWidth*direction,pEnd.Y); if(direction>0) { path.AddLine(pStart.X+lineStartWidth*direction,pStart.Y, pEnd.X, pEnd.Y); path.AddLine(pEnd.X, pEnd.Y, pEnd.X, pEnd.Y + lineEndWidth*direction); path.AddLine(pEnd.X, pEnd.Y + lineEndWidth*direction, pStart.X, pStart.Y); } else path.AddLine(pEnd.X, pEnd.Y, pEnd.X, pEnd.Y + lineEndWidth*direction); path.CloseAllFigures(); // if(Math.Abs(pEnd.Y-pStart.Y)<=8) // path.Widen(SystemPens.Highlight); } else { int offsetStart=lineStartWidth/2; int offsetEnd=lineEndWidth/2; path.AddLine(pStart.X,pStart.Y-offsetStart,pStart.X,pStart.Y+offsetStart); path.AddLine(pEnd.X,pEnd.Y+offsetEnd,pEnd.X,pEnd.Y-offsetEnd); path.AddLine(pEnd.X,pEnd.Y-offsetEnd,pStart.X,pStart.Y-offsetStart); path.CloseAllFigures(); } return path; } protected Brush GetLineBrush(ConnectorRendererEventArgs info) { return new SolidBrush(info.NodeConnector.LineColor); } protected void DrawEndLine(ConnectorRendererEventArgs info,Point pStart,Point pEnd,Point pEndUnderLine) { if(pEndUnderLine.IsEmpty) { switch(info.NodeConnector.EndCap) { case eConnectorCap.Ellipse: { using(Pen pen=this.GetEndLinePen(info)) { Size endCapSize=info.NodeConnector.EndCapSize; if(pStart.XpEnd.X) direction=-1; info.Graphics.DrawLine(pen,pEnd,new Point(pEnd.X+info.NodeConnector.EndCapSize.Width/3*direction,pEnd.Y)); Size endCapSize=info.NodeConnector.EndCapSize; GraphicsPath arrow=GetArrowPath(endCapSize,pStart,pEnd); info.Graphics.DrawPath(pen,arrow); } break; } } } else { using(Pen pen=this.GetEndUnderlinePen(info)) { info.Graphics.DrawLine(pen,pEnd,pEndUnderLine); // Connect underline to expand part if(NodeDisplay.DrawExpandPart(info.ToNode)) { Rectangle re=NodeDisplay.GetNodeRectangle(eNodeRectanglePart.ExpandBounds,info.ToNode,info.Offset); if (re.IsEmpty) return; Point p2=new Point((re.X>pEndUnderLine.X?re.X:re.Right)+(re.Width/2*(re.X>pEndUnderLine.X?1:-1)),re.Bottom); Point p1=new Point(p2.X,pEndUnderLine.Y+(pEndUnderLine.Y>p2.Y?(p2.Y-pEndUnderLine.Y)/2:-(p2.Y-pEndUnderLine.Y)/2)); info.Graphics.DrawCurve(pen,new Point[]{pEndUnderLine,p1,p2},.5f); } } } } private GraphicsPath GetArrowPath(Size capSize,Point pStart,Point pEnd) { GraphicsPath path=new GraphicsPath(); int direction=1; if(pStart.X>pEnd.X) direction=-1; pEnd.X+=(GetCapWidthOffset(eConnectorCap.Arrow,capSize)*direction); path.AddLine(pEnd.X,pEnd.Y,pEnd.X-capSize.Width*direction,pEnd.Y-capSize.Height/2); path.AddLine(pEnd.X-(2*capSize.Width/3*direction),pEnd.Y,pEnd.X-capSize.Width*direction,pEnd.Y+capSize.Height/2); path.CloseAllFigures(); return path; } internal virtual ConnectorPointInfo GetConnectorPointInfo(ConnectorRendererEventArgs info, Point pStart, Point pEnd) { ConnectorPointInfo pointInfo=new ConnectorPointInfo(); int xMulti=1/*, yMulti=1*/; int lineWidth=info.NodeConnector.LineWidth; // used for direction control if(pStart.X>pEnd.X) xMulti=-1; //if(pStart.Y>pEnd.Y) // yMulti=-1; if(info.ConnectorPoints!=null) { Point connPointsOffset=info.ToNode.BoundsRelative.Location; connPointsOffset.Offset(info.Offset.X,info.Offset.Y); GraphicsPath path=new GraphicsPath(); pointInfo.Points1=new Point[info.ConnectorPoints.Count+2]; pointInfo.Points1[0]=pStart; pointInfo.Points1[pointInfo.Points1.Length-1]=pEnd; if(lineWidth>1) { pointInfo.Points2=new Point[info.ConnectorPoints.Count+2]; pointInfo.Points2[pointInfo.Points2.Length-1]=pStart; pointInfo.Points2[0]=pEnd; int i=pointInfo.Points1.Length-2; int k=1; foreach(Point pcp in info.ConnectorPoints) { pointInfo.Points1[i]=pcp; pointInfo.Points1[i].Offset(connPointsOffset.X,connPointsOffset.Y); pointInfo.Points2[k]=new Point(pcp.X+lineWidth*xMulti,pcp.Y); pointInfo.Points2[k].Offset(connPointsOffset.X,connPointsOffset.Y); k++; i--; } } else { int i=pointInfo.Points1.Length-2; foreach(Point pcp in info.ConnectorPoints) { pointInfo.Points1[i]=pcp; pointInfo.Points1[i].Offset(connPointsOffset.X,connPointsOffset.Y); i--; } } } return pointInfo; } } } /// /// Represents custom connector path info. /// internal class ConnectorPointInfo { public Point[] Points1=null; public Point[] Points2=null; } }