- 使用CRectTracker类进行对象动态定位
- 内容提要
- 使用VC,VB,Delphi等可视化工具进行程序设计的时候用的最多的可能就是控件的拾取,拖动以及动态定位了。例如我们可以在VC中用鼠标一次拾取数个控件,然后通过鼠标或者通过左右上下方向键进行控件的微移,从而使控件移到合适的位置,这种技术就是对象动态定位。动态定位也是交互式程序设计中最基本的操作之一。在VC中我们可以通过CRectTracker类来实现这些操作。
- 一 关于CRectTracker类
- MFC中的CrectTRacker类已经进行了很好的封装,从而使得我们只需要很少的开发就可以实现上面的几个操作。对象允许我们在窗口里面移动一个矩形对象或者改变一个矩形对象的大小,它适合与任何的包括OLE在内的应用程序。首先我们了解一下类的成员变量和成员函数.
- 1.CRectTracker类的成员变量:
- m_nHandleSize: 对象的调整句柄的数目,默认情况下为8个
- CRect m_rect: 对象矩形目前所占大小的位置
- m_sizeMin: 对象所占的矩形的最小宽度和最小高度
- m_nStyle: 目前对象边框和调整句柄的类型
- 2.CRectTracker类的成员函数
- Draw(CDC* pDC):通过调用这个函数来画对象边框和调整句柄的类型。对象边框和调整句柄的具体类型由成员变量m_nStyle决定,一共有以下几种形式:
- CRectTracker::solidLine 外部边界使用实线形式
- CRectTracker::dottedLine 外部边界使用点虚线形式
- CRectTracker::hatchedBorder 外部边界使用带阴影的形式
- CRectTracker::resizeInside 调整句柄处于对象内部区域
- CRectTracker::resizeOutside 调整句柄处于对象外部区域
- CRectTracker::hatchInside 内部所有区域使用带阴影的形式
- 需要注意的是
- CRectTracker::solidLine和CRectTracker::dottedLine不能同时并存
- CRectTracker::resizeInside和CRectTracker::resizeOutside不能同时共存
- Track(CWnd* pWnd, CPoint point, BOOL bAllowInvert = FALSE, CWnd* pWndClipTo = NULL)
- 这个函数和下面的TrackRubberBand函数是整个CRectTracker类中最重要的函数,通常在消息WM_LBUTTONDOWN中调用处理,如果鼠标指针落在矩形的边框上,用户就可以拖动以调整矩形的大小;如果鼠标落在了矩形的内部,则用户可以拖动鼠标移动矩形。当ESC键按下时候,函数返回FALSE,函数没有起作用, 否则松开鼠标时候,返回TRUE;
- TrackRubberBand(CWnd* pWnd,CPoint point,BOOL bAllowInvert=FALSE,CWnd* pWndClipTo=NULL)
- 如果鼠标没有点中对象矩形,那么鼠标移动时候将会产生橡皮条,这个函数就是用来定位”橡皮条”,橡皮条内的项目称之为被选中.
- GetTrueRect(LPRECT lpTrueRect):获取对象所占矩形的大小,如果边框形式为CRectTracker::hatchOutside则矩形大小包括外部调整句柄的范围
- Int HitTest(Cpoint point):函数返回光标在CrectTrack类中的位置,通过返回值可以确定拖动句柄的位置
- CRectTracker::hitNothing -1: 没有点击任何地方
- CRectTracker::hitTopLeft 0: 点击调整标记的左上角
- CRectTracker::hitTopRight 1: 点击调整标记的右上角
- CRectTracker::hitBottomRight 2: 点击调整标记的右下角
- CRectTracker:hitBottomLeft 3: 点击调整标记的左下角
- CRectTracker:hitTop 4: 点击调整标记的上方
- CRectTracker:hitRight 5: 点击调整标记的右方
- CRectTracker:hitBottom 6: 点击调整标记的下方
- CRectTracker:hitLeft 7: 点击调整标记的左方
- CRectTracker:hitMiddle 8: 点击调整标记的中央
- BOOL SetCursor(CWnd* pWnd,UINT nHitTest):当点击特定的位置时改变光标的形状
- 二 .应用示例
- 整个程序界面如下,我们可以通过工具栏来设置图像的边界和调整句柄的类型,同时我们可以用鼠标移动和调整整个图像,另外程序还支持用←↑→↓来微调矩形的位置,通过Shift+←↑→↓来微调矩形的大小.程序的开发步骤如下:
- 步骤一:使用VC的MFC AppWizard创建单文档应用程序ExamTracker,创建过程中保留默认值。
- 步骤二:在CExamTrackerDoc中增加两个成员变量
- CRectTracker m_tracker;
- BOOL m_bAllowInvert; //是否允许逆向拖动,即橡皮条的落点是否小于起点。
- 步骤三:在CrectTrckerDemoDoc的构造函数中给m_tracker对象赋初值
- m_tracker.m_rect.left = 200;
- m_tracker.m_rect.top = 100;
- m_tracker.m_rect.right = 301;
- m_tracker.m_rect.bottom = 201;
- //初始边界形式为实线
- m_tracker.m_nStyle=CRectTracker::solidLine;
- //允许逆向拖动
- m_bAllowInvert=TRUE;
- 步骤四;在CExamTrackerView中编写函数OnDraw()绘制图像所示的矩形.下面的代码相信大家都看的懂.
- void CExamTrackerView::OnDraw(CDC* pDC)
- {
- CExamTrackerDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- CBrush* pOldBrush = NULL;
- TRY
- {
- //用不同的画刷绘制矩形
- CBrush brush1, brush2;
- CRect rect;
- int nWidth = pDoc->m_tracker.m_rect.Width();
- int nHeight = pDoc->m_tracker.m_rect.Height();
- int nSgnX = nWidth != 0 ? nWidth / abs(nWidth) : 1;
- int nSgnY = nHeight != 0 ? nHeight / abs(nHeight) : 1;
- pDC->SetTextAlign(TA_CENTER);//设置字体显示方式为中间对齐方式
- pDC->SetBkMode(TRANSPARENT);//设置背景色为透明色
- int nCenterX, nCenterY;
- TEXTMETRIC tm;
- pDC->GetTextMetrics(&tm);//获取字体大小
- brush1.CreateSolidBrush(RGB(255, 0, 0));
- pOldBrush = pDC->SelectObject(&brush1);
- //设置第一个图形区域矩形的大小函数为自定义.
- SetNormalRect(rect, pDoc->m_tracker.m_rect.left, pDoc->m_tracker.m_rect.top, nWidth/2, nHeight/2);
- //绘制矩形
- pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
- nCenterX = rect.left + rect.Width()/2;
- nCenterY = rect.top + rect.Height()/2 - tm.tmHeight/2;
- //在矩形中央显示字符1
- pDC->ExtTextOut(nCenterX, nCenterY, ETO_CLIPPED, rect, _T("1"), 1, NULL);
- brush2.CreateSolidBrush(RGB(0, 255, 0));
- pDC->SelectObject(&brush2);
- brush1.DeleteObject();
- SetNormalRect(rect, pDoc->m_tracker.m_rect.left+nWidth/2, pDoc->m_tracker.m_rect.top, (nWidth+nSgnX)/2, nHeight/2);
- pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
- nCenterX = rect.left + rect.Width()/2;
- nCenterY = rect.top + rect.Height()/2 - tm.tmHeight/2;
- pDC->ExtTextOut(nCenterX, nCenterY, ETO_CLIPPED, rect, _T("2"), 1, NULL);
- brush1.CreateSolidBrush(RGB(0, 0, 255));
- pDC->SelectObject(&brush1);
- brush2.DeleteObject();
- SetNormalRect(rect, pDoc->m_tracker.m_rect.left, pDoc->m_tracker.m_rect.top+nHeight/2, nWidth/2, (nHeight+nSgnY)/2);
- pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
- nCenterX = rect.left + rect.Width()/2;
- nCenterY = rect.top + rect.Height()/2 - tm.tmHeight/2;
- pDC->ExtTextOut(nCenterX, nCenterY, ETO_CLIPPED, rect, _T("3"), 1, NULL);
- brush2.CreateSolidBrush(RGB(192, 192, 192));
- pDC->SelectObject(&brush2);
- brush1.DeleteObject();
- SetNormalRect(rect, pDoc->m_tracker.m_rect.left+nWidth/2, pDoc->m_tracker.m_rect.top+nHeight/2, (nWidth+nSgnX)/2, (nHeight+nSgnY)/2);
- pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
- nCenterX = rect.left + rect.Width()/2;
- nCenterY = rect.top + rect.Height()/2 - tm.tmHeight/2;
- pDC->ExtTextOut(nCenterX, nCenterY, ETO_CLIPPED, rect, _T("4"), 1, NULL);
- // cleanup DC
- if (pOldBrush != NULL)
- pDC->SelectObject(pOldBrush);
- brush2.DeleteObject();
- //这一行代码是必不可少的,通过它可以绘制限定形式的图形区域的外部边界和内部区域.
- pDoc->m_tracker.Draw(pDC);
- }
- CATCH_ALL(e)
- {
- if (pOldBrush != NULL)
- pDC->SelectObject(pOldBrush);
- }
- END_CATCH_ALL
- }
- 在程序中用到了自定义的全局函数
- static void SetNormalRect(CRect& rect, int left, int top, int width, int height),函数用来设置矩形的大小,同时对矩形进行相应的校正.这种校正一般在逆向拖动时候需要,作用相当于CRect::NormalizeRect().函数定义如下:
- static void SetNormalRect(CRect& rect, int left, int top, int width, int height)
- {
- rect.left = left;
- rect.top = top;
- rect.right = left + width;
- rect.bottom = top + height;
- int nTemp;
- if (rect.left > rect.right)
- {
- nTemp = rect.left;
- rect.left = rect.right;
- rect.right = nTemp;
- }
- if (rect.top > rect.bottom)
- {
- nTemp = rect.top;
- rect.top = rect.bottom;
- rect.bottom = nTemp;
- }
- }
- 步骤五:使用AppWizard为CExamTrackerView增加WM_LBUTTONDOWN处理消息,在函数中我们必须处理三种情况;鼠标选中矩形对象但是不处于矩形边界,这种情况仅仅是对矩形进行移动,第二种情况:鼠标处于矩形边界,这时候拖动鼠标将会调整矩形的大小.第三种情况时鼠标没有选中矩形的任何地方,这个时候将会产生橡皮条效果,橡皮条内的所有内容被选中.具体代码如下:
- void CExamTrackerView::OnLButtonDown(Uint nFlags, CPoint point)
- {
- CExamTrackerDoc* pDoc = GetDocument();
- CRect rectSave;
- //获取图形区域矩形对象的大小
- pDoc->m_tracker.GetTrueRect(rectSave);
- //如果没有点中图形,这时候HitTest将返回-1.这时候产生橡皮条.
- if (pDoc->m_tracker.HitTest(point) < 0)
- {
- CRectTracker tracker;
- //画橡皮擦
- if (tracker.TrackRubberBand(this, point, pDoc->m_bAllowInvert))
- {
- // 下面的工作将用来检查橡皮条的矩形是否与图形区域的矩形相交.
- CRect rectT;
- //对橡皮条的矩形进行校正.
- tracker.m_rect.NormalizeRect(); // so intersect rect works
- //橡皮条区域与图形区域的交叉区域不为空,则将图形区域的调整句柄进行相应
- //的设置
- if (rectT.IntersectRect(tracker.m_rect, pDoc->m_tracker.m_rect))
- {
- //如果调整句柄位于矩形内部(resizeInside)则将调整句柄设置在矩形的外
- //部(resizeOutside)
- if (pDoc->m_tracker.m_nStyle & CRectTracker::resizeInside)
- {
- //去除resizeInside 形式
- pDoc->m_tracker.m_nStyle &= ~CRectTracker::resizeInside;
- //设置resizeOutside形式
- pDoc->m_tracker.m_nStyle |= CRectTracker::resizeOutside;
- }
- //否则如果调整句柄在外部就将调整句柄放在区域内部
- else
- {
- // just use inside resize handles on first time
- pDoc->m_tracker.m_nStyle &= ~CRectTracker::resizeOutside;
- pDoc->m_tracker.m_nStyle |= CRectTracker::resizeInside;
- }
- //更新所有视图,显示调整后的图形
- pDoc->UpdateAllViews(NULL, (LPARAM)(LPCRECT)rectSave);
- pDoc->UpdateAllViews(NULL);
- }
- }
- }
- //如果选中了图形区域,则调用Track函数自动处理,同时处理后更新视图即可.
- else if (pDoc->m_tracker.Track(this, point, pDoc->m_bAllowInvert))
- {
- // normal tracking action, when tracker is "hit"
- pDoc->UpdateAllViews(NULL, (LPARAM)(LPCRECT)rectSave);
- pDoc->UpdateAllViews(NULL);
- }
- CView::OnLButtonDown(nFlags, point);
- }
- 步骤六:到目前程序的原型基本上已经定下了,可以运行了!运行结果试试看!但是运行中你会发现一个问题,在VC,Delphi中鼠标再不同的区域可以有不同的形状,比如如果选中图形区域鼠标为十字形状,选中边界时成一字形.在程序中我们通过处理WM_SETCURSOR消息来获取这种效果。
- BOOL CExamTrackerView::OnSetCursor(CWnd* pWnd, Uint nHitTest, Uint message)
- {
- CExamTrackerDoc* pDoc = GetDocument();
- if (pWnd == this && pDoc->m_tracker.SetCursor(this, nHitTest))
- return TRUE;
- return CView::OnSetCursor(pWnd, nHitTest, message);
- }
- 从上面的代码可以看出函数中仅仅是调用了CRectTracker自己的SetCursor函数,具体的处理过程我们已经不需要做了.
- 写到这儿的时候程序已经基本上结束了,用鼠标可以做大部分的事情,剩下的事情就是位置和大小的微调了,具体的微调通过处理WM_KEYDOWN消息来实现.具体的代码如下。
- void CExamTrackerView::OnKeyDown(Uint nChar, Uint nRepCnt, Uint nFlags)
- {
- CExamTrackerDoc* pDoc = GetDocument();
- switch (nChar)
- {
- //如果按下的是←
- case VK_LEFT:
- //按下←的同时按下了Shift键,将图像区域向左减少5个位置
- if(::GetKeyState(VK_SHIFT)&0xff00)
- {
- pDoc->m_tracker.m_rect.right=pDoc->m_tracker.m_rect.right-5;
- pDoc->UpdateAllViews(NULL);
- }
- //否则,图像将向左移动5个位置
- else
- {
- pDoc->m_tracker.m_rect.left=pDoc->m_tracker.m_rect.left-5;
- pDoc->m_tracker.m_rect.right=pDoc->m_tracker.m_rect.right-5;
- pDoc->UpdateAllViews(NULL);
- }
- break;
- //如果按下的是→ case VK_RIGHT:
- //按下→的同时按下了Shift键,将图像区域向右增加5个位置
- if(::GetKeyState(VK_SHIFT)&0xff00)
- {
- pDoc->m_tracker.m_rect.right=pDoc->m_tracker.m_rect.right+5;
- pDoc->UpdateAllViews(NULL);
- }
- //否则,图像将向右移动5个位置
- else
- {
- pDoc->m_tracker.m_rect.left=pDoc->m_tracker.m_rect.left+5;
- pDoc->m_tracker.m_rect.right=pDoc->m_tracker.m_rect.right+5;
- pDoc->UpdateAllViews(NULL);
- }
- break;
- //如果按下的是↑
- case VK_UP:
- //按下↑的同时按下了Shift键,将图像区域向上减少5个位置
- if(::GetKeyState(VK_SHIFT)&0xff00)
- {
- pDoc->m_tracker.m_rect.bottom=pDoc->m_tracker.m_rect.bottom-5;
- pDoc->UpdateAllViews(NULL);
- }
- //否则,图像将向上移动5个位置
- else
- {
- pDoc->m_tracker.m_rect.top=pDoc->m_tracker.m_rect.top-5;
- pDoc->m_tracker.m_rect.bottom=pDoc->m_tracker.m_rect.bottom-5;
- pDoc->UpdateAllViews(NULL);
- }
- break;
- //如果按下的是↓,图像将向下移动5个位置
- case VK_DOWN:
- if(::GetKeyState(VK_SHIFT)&0xff00)
- {
- pDoc->m_tracker.m_rect.bottom=pDoc->m_tracker.m_rect.bottom+5;
- pDoc->UpdateAllViews(NULL);
- }
- else
- {
- pDoc->m_tracker.m_rect.top=pDoc->m_tracker.m_rect.top+5;
- pDoc->m_tracker.m_rect.bottom=pDoc->m_tracker.m_rect.bottom+5;
- pDoc->UpdateAllViews(NULL);
- }
- break;
- }
- CView::OnKeyDown(nChar, nRepCnt, nFlags);
- }
- 所有的任务都完成了, 慢,还有通过工具栏改变区域边界的方法,我就提供一个吧,剩下的大家就看源代码吧,应该很简单的,大家都能看的懂,需要注意的是几种形式的不可兼容性,否则会出错,其余的我就不费笔墨和口舌了
- void CExamTrackerDoc::OnSolidline()
- {
- CRect rectTrue;
- m_tracker.GetTrueRect(&rectTrue);
- m_tracker.m_nStyle &= ~CRectTracker::dottedLine;
- m_tracker.m_nStyle ^= CRectTracker::solidLine;
- UpdateAllViews(NULL, (LPARAM)(LPCRECT)rectTrue);
- UpdateAllViews(NULL);
- }