作者:zhu
1 引言
很多的图形应用如统筹图应用、GIS应用、标图应用中都存在着编辑联动的问题。所谓编辑联动,是指在对一个图元编辑修改包括位置的变动后,其他图元由于与这个图元有某种关系,而能自动同步被修改。按图元类型,可将图形联动划分为点一点、点一线、点一面、线一线、线一而、面。面6种。文中以统筹图为例,讨论节点一箭线联动编辑问题。
节点与箭线图元的实现以VC自带的面向对象图形例程DRAWCLI为基础,添加自定义类CDrawNode和CDrawArrow来实现。依据功能设计,程序的主要工作包括:
(1)基本图元的绘制。包括绘制节点和箭线。绘制的节点图形表示为圆心标有编号圆圈;箭线的两侧标注有文字说明,且文字行与箭线保持平行,箭线带有起节点和终节点。
(2)处理图元及相互位置关系。包括删除操作和移动操作。删除某一节点,与该节点相连的箭线全部删除,且调整编号在其后面的节点编号,删除箭线不影响节点。移动节点时,与之相连的箭线相应移动,其关键是准确计算箭线的位置。
2基本图元及联动
2.1 基本图元实现
设在VC++ 6.0的环境下已建立了工程Draw.dsp。节点类和箭线类都是以CDrawObj为基类派生。在建立这两个类之前,需要添加以下全局变量:
enum DrawShape{...,circle,arrow,…};//图元类型以及相应 //的工具circleTool和arrowTool(arrow)、arrowlineTool
2.1.1 节点类CDrawNode的设计
class CDrawNode:public CDrawObj { public: CDrawNode(const CRect& position,int Radius); void SetCircleIndex(int Index){m_nIndex=Index}; ∥设置节点编号 virtual int GetObjIndex(){retum m_nindex}; //获取编号 public: int m_Radius;∥节点半径 int m_CentreX,m_CentreY;∥圆心 ∥在圆中心写上编号,要考虑编号的位数情况 virtual void Draw(CDC* pDC,CDrawView* pView); protected: int m_nIndex;//编号 CDrawNode* m_pDrawObj; friend class CCircleTool; };
绘制节点的工具CCircleTool在鼠标左键按下OnLButtonDown的消息处理函数中,实际分配内存,产生一个节点对象。
2.1.2 箭线类CDrawArrow的设计
class CDrawArrow: public CDrawObj { public: CDrawRect(const CRect& position); void SetProjectName(CString name) {m_ProjectName=name;}; void SetProjectTime(int time) {m_ProjectTime=time;}; CString GetProjectName(){return m_ProjectName;} void SetProjectTime(int time){return m_ProjectTime;} bool m_bForeConnect;//箭线始节点标志 bool m_bBackConnect;//箭线终节点标志 int m_ForeConnect;//箭线连接的始节点序号 int m_BackConnect;//箭线连接的终节点序号 virtual int GetObjIndex(){return o}; //绘制时,计算平行于箭线的说明文字的角度、位置 //项目名称总在线的上方 virtual void Draw(CDC* pDC,CDrawView* pView); enum ArrowType{ none, pureArrow, openArrow, stealthArrow, diamondArrow, ovaLArrow}; enum Shape{ rectangle, roundRectangle, ellipse, line,arrow }; protected CString m_ProjectName; //项目名称 int m_ProjectTime; //时间 Shape m_nShape; ArrowType m_lArrow, m_rArrow; //for arrow only int m_lArrowSize, m_rArrowSize; //for arrow only private: void DrawArrow(ArrowType arrowType, POINT pl, POINT p2,int arrowSize, CDC *,int,int,int,CBrush *); friend class CArrowTool; };
绘制箭线的工具CArrowTool在OnLButtonDown中实际生成箭线对象,并记录箭线所连的开始节点,箭线的终节点确定在后面说明。
2.2图元联动操作
2.2.1删除节点及箭线
void CDrawView::OnEditClear() { CDrawDoc* pDoc=(CDrawDoc*)GetDocument(); POSITION pos=m_selection.GetHeadPosition(); while (pos!=NULL) { CDrawObj* pObj=m_selection.GetNext(pos); GetDocument()->Remove(pObj); //删除节点 CDrawNode* pDrawCircIe=(CDrawNode*)pObj; nIndex=pDrawCircle->GetObjInclex(); //获取节点编号,若不是节点对象,返回O if(nIndex) { pDoc->m_nIndex=nIndex; pos=GetDocument()->m_objects.GetHeadPosition(); int i=0; int num=pDoc->m_objects.GetCount(); m_num=pDoc->m_nIndex; while(i < num) {//序号位于被删除节点之后的所有节点序号减1 CDrawNode* pDrawCircle=(CDrawNode*)pDoc-> m_objects.GetNext(pos); if(pDrawCircle->GetObjIndex()>m_num) { pDraeCircle->SetCircleIndex(m_num); m_num++; } i++; } pos=pDoc->m_objects.GetHeadPosition(); if(pos!=NULL) { i=0; num=pDoc->m_objectsGetCount(); while(i < num) {∥删除与节点相连的所有箭线 CDrawRect* pDrawRect=(CDrawRect*)pDoc-> m_objects.GetNext(pos); if(pDrawRect!=NULL) { if(nIndex==pDrawRect->m_ForeConnect) { pDoc->Remove(pDrawRect); pDrawRect->Remove(); } if(nIndex==pDrawRect->m_BackConnect) { pDoc->Remove(pDrawRect); pDrawRect->Remove(); } if(nIndex < pDrawRect-> m_ForeConnect) { pDrawRect->m_ForeConnect--; } if(nIndex < pDrawRect-> m_BackConnect) { pDrawRect->m_BackConnect--; } } i++; } } Invalidate(); pDoc->m_nIndex=m_num; } pObj->Remove(); } m_selection.RemoveAll(); UINT nFlags; OnLButtonDown(nFlags,CPoint(0,0)); OnLButtonUp(nFlags,CPoint(0,0)); }
2.2.2用鼠标移动某一节点时,同步移动与其相连接的箭线
void CSelectTool::OnMouseMove(CDrawView* pView, UINT nFlags, const CPoint& point) { if (pView->GetCapture() != pView) { ... CPoint local=point; int BackConnect; pView->ClientToDoc(local); POSITION pos; if(c_drawShape == arrow) //判断鼠标移动绘制箭线过程中是否与某一节点相连 //此时函数是 CRectTool::OnMouseMove()调用 //selectTool.OnMouseMove();而跳到这里 { CRect circlerect; CDrawNode* pForeCircle; CDrawNode* pBackCircle; pos=pView->GetDocument()-> m_objects GetHeadPosition(); if(pos!=NULL) { int i=0; int num=pView->GetDocument()-> m_objects.GetCount(); while(i < num) { CDrawNode* pDrawCircle=(CDfawNode*) pView->GetDocument()->m_objects GetNext(pos); if(pDrawCircle!=NULL) { int index=pDrawCircle->GetObjIndex(); if(index) {//判断 circlerect=pDrawCircle-> m_position; pView->DocToClient(circlerect); if(circlerect.PtInRect(point)) { local=pDrawCircle->GetHandle(8); BackConnect=pDrawCircle-> GetObjIndex(); } }}i++;}} CPoint delta =(CPoint)(local - lastPoint); pos=pView->m_selection.GetHeadPosition(); while (pos!=NULL) { CDrawObj* pObj = pView->m_selection.GetNext(pos); CRect position=pObj->m_position; if(c_drawShape==arrow) {∥记录终节点 pObj->m_BackConnect=BackConnect; pObj->m_bBackConnect=true; } //以下实现移动节点,使所有的箭线跟随节点同步 //移动 if(selectMode==move) { if(pView->m_selection.GetCount()==l) { CDrawNode*pDrawCircle=(CDrawNode*)pObj; int Index=pDrawCircle->GetObjIndex(); if(index) ∥移动节点 { POSITION Rectpos=pView->GetDocument()-> m_objects.GetHeadPosition(); if(Rectpos!=NULL) { int i=0; int num=pView->GetDocument()-> m_objects.GetCount(); while(i< num) {//所有与之相连的箭线同步移动 CDrawRect*pDrawObj=(CDrawRect*) pView->GetDocument()->m_ objects.GetNext(Rectpos); if(pDrawObj!=NULL) { if(Index==pDrawObj->m_ForeConnect) {//节点是箭线始节点 CRect deltarect=pDrawObj->m_position; deltarect.BottomRight()+=delta; int length=deltarect.top-deltarect.bottom; int width=deltarect.left-deltarect.right; //节点移动,箭线与节点的连接点改变,计算 //连接点位置 if(abs(length) < abs(width)&&width < 0) deltarect.BottomRight()=pDrawCircle-> GetHandle(8); if(abs(length) < abs(width)&&width > 0) deltarect.BottomRight()=pDrawCircle-> GetHandle(4); if((abs(length) > abs(width)&&width > O&&length < O)|| (abs(length) > abs(width)&& width < O&&length < 0)) deltarect.BottomRight()=pDrawCircle-> GetHandle(2); if((abs(length)>abs(width)&&length> O&&width > 0)||(abs(length)>abs(width) &&length > O&&width < 0)) deltarect.BottomRight()=pDrawCircle->GetHandle(6); int nIndex=pDrawObj->m_BackConnect; POSITION circlepos=pView->GetDocument()-> m_objects.GetHeadPosition(); if(circlepos!=NULL) { int i=0; int num=pView->GetDocument()-> m_objects.GetCount(); while(i < num) { CDrawNode* pDrawNode=(CDrawNode*)pView-> GetDocument()->m_objects.GetNext(circiepos); if(pDrawNode!=NULL) { if(nIndex==pDrawNode->GetObjindex()) {//计算箭线的新位置 if(abs(length) < abs(width)&&width < 0) deltarect.TopLeft()=pDrawNode->GetHandle(4); if(abs(length) < abs(width)&&width > 0) deltarect.TopLeft()=pDrawNode->GetHandle(8); if((abs(length)>abs(width)&&width> O&&length < O)|| (abs(length)>abs(width) &&width < O&&length < 0)) deltarect.TopLeft()=pDrawNode->GetHandle(6); if((abs(length)>abs(width)&&length> O&&width>O)||(abs(length)>abs(width) &&length>O&&width<0)) deltarect.TopLeft()=pDrawNode-> GetHandle(2); }}i++;}}} pDrawObj->MoveTo(deltarect,pView); } if(Index==pDrawObj->m_BackConnect) {//节点是箭线终节点 CRect deltarect=pDrawObj->m_position; deltarect.TopLeft()+=delta; int length=deltarect.top-deltarect.bottom; int width=deltarect.left-deltarect.right; if(abs(length) < abs(width)&&width < 0) deltarect.TopLeft()=pDrawCircle->GetHandle(4); if(abs(length) < abs(width)&&width > 0) deltarect.TopLeft()=pDrawCircle->GetHandle(8); if((abs(length)>abs(width)&&width>O&&length<0)|| (abs(length>abs(width)&&width < O&&length < 0)) deltarect.TopLeft()=pDrawCircle->GetHandle(6); if((abs(length)>abs(width)&&length>O&&width>O)|| (abs(length)>abs(width)&&length>O&&width<0)) deltarect.TopLeft()=pDrawCircle->GetElandle(2); int nIndex=pDrawObj->m_ForeConnect; POSITION circlepos=pView->GetDocument()-> m_objects.GetHeadPosition(); if(pos!=NULL) { int i=0, int num=pView->GetDocument()-> m_objects GetCount(); while(i < num) {CDrawNode* pDrawNode=(CDrawNode*) pView->GetDocument()->m_objects. GetNext(circlepos); if(pDrawNode!=NULL) { if(nIndex==pDrawNode->GetObjindex()) { if(abs(Length) < abs(width)&&width < 0) deltarect.BottomRight()=pDrawNode-> GetHandle(8); if(abs(length) < abs(width)&&width > 0) deltarect.BottomRight()=pDrawNode-> GetHamile(4); if((abs(length)>abs(width)&&width> O&&length<0)|| (abs(length)> abs(width)&&width < O&&length 0)) deltarect.BottomRight()=pDrawNode-> GetHandle(2); if((abs(iength)>abs(width)&&length> O&&width>O)||(abs(length)>abs(width) &&length>O&&width<0)) deltarect.BottomRight()=pDraxwNode-> GetHandle(6); }}i++;}}} pDrawObj ->MoveTo(deltarect,pView); } }i++; } } } } position += delta; pObj->MoveTo(position, pView); } else if (nDragHandle != O) pObj->MoveHandleTo (nDragHandle, local, pView); } lastPoint = Iocal; if(selectMode == size && c_drawShape==selection) { c_last = point; SetCursor(pView->m_selection.GetHead()-> GetHandleCursor(nDragHandle)); return; //bypass CDrawTool } c_last=point; if(c_drawShape==selection) CDrawTool::OnMouseMove(pView, nFlags, point) ; }
3 结束语
至此,基本上就达到了预定目标。图l是按以上叙述实现的绘制统筹图程序的运行图,示出了正在对编号为2的节点进行移动或删除的联动操作。本示例中没有对实现的效率问题有过多考虑,程序在MicrosoftWindows 2000和Visual C++ 6.0下完成。
图1绘制统筹图程序运行界面