MFC中CRectTracker在CScrollView中使用时问题的解决办法

本文原创,在图形开发中遇到问题,花费了大量时间与精力,现在终于解决问题,分享给大家。大笑大笑大笑大笑大笑大笑



问题描述:在CScrollView中使用CRectTracker绘制橡皮筋矩形拖动滚动条时,矩形与辅助框始终保持与显示器绝对位置(窗口移动,矩形位置不变)。


问题分析:要分析问题,就要明确这个问题是怎么产生的,首先在使用CRectTracker之前,在MSDN查阅了类文档,发现其中有示例程序,tracker.sln。打开,编译,运行,得到如下:

首先请忽略掉区域1234,把他们整体看做一个矩形,然后,注意周围的辅助框(虚线,定点的黑点)。这些是可以设置的,鼠标移动到黑点(辅助点)时会变化,示意鼠标在这个位置可以进行哪些操作。

我们来看一下源代码:(有兴趣的同学可以自己在vs2010安装目录下的samples/下的2052.zip,解压,然后搜索tracker.sln)

首先它的实例化,与我的需求不同,我需要自己用鼠标绘制矩形,而实例中是已经绘制好的矩形

CTrackerDoc::CTrackerDoc()
{
	m_tracker.m_rect.left = 10; //这四行设置初始矩形的位置大小
	m_tracker.m_rect.top = 10;
	m_tracker.m_rect.right = 101;
	m_tracker.m_rect.bottom = 101;
	m_bAllowInvert = FALSE;  //这个设置这个矩形是否允许翻转,如果为TRUE,则允许矩形区域12 34互换或者 13 24互换。
}

实例化好后,通过CView::OnDraw(CDC *pDC)显示到屏幕上,实例太复杂了,我简化一下:

void CTrackerView::OnDraw(CDC* pDC)
{
	CTrackerDoc* pDoc = GetDocument();

	CBrush* pOldBrush = NULL;
	TRY
	{
		// draw inside in various colors
		CBrush brush1, brush2;
		CRect rect;
		brush1.CreateSolidBrush(RGB(255, 0, 0));//创建红色画刷
		pOldBrush = pDC->SelectObject(&brush1);//选择画刷
		m_tracker,GetTrueRect(&rect);//获取到矩形位置
				pDC->Rectangle(rect);//绘制矩形
// draw tracker on outsidepDoc->m_tracker.Draw(pDC);//绘制辅助框就是有8个点的黑色框}CATCH_ALL(e){if (pOldBrush != NULL)pDC->SelectObject(pOldBrush);}END_CATCH_ALL}


接下来,通过捕获鼠标单击事件,对辅助框进行操作(简化):

void CTrackerView::OnLButtonDown(UINT nFlags, CPoint point)
{
	CTrackerDoc* pDoc = GetDocument();
	CRect rectSave;
	pDoc->m_tracker.GetTrueRect(rectSave);//这个是保存了操作之前的矩形,如果你是新手,想想如果操作失败时的还原,应该能理解
	if (pDoc->m_tracker.HitTest(point) < 0) //这个是检测鼠标点击的point是否在矩形中,它的返回值可以表示点击的区域,具体可以参考MSDN,-1为未选中任何区域
	{
		//未选中的操作
		 //do something
		//示例实现了重新新建一个tracker,画框,如果与存在的矩形有交集,就改变它的样式,我直接把代码都去掉了,没用
	}
	else if (pDoc->m_tracker.Track(this, point, pDoc->m_bAllowInvert))//如果选中,进行橡皮筋操作,这个函数如果你改变位置或者大小都会返回TRUE。
	{
		// normal tracking action, when tracker is "hit"
		pDoc->SetModifiedFlag();
		pDoc->UpdateAllViews(NULL, (LPARAM)(LPCRECT)rectSave); //如果位置被改变,就刷新view,此时矩形被重绘
		pDoc->UpdateAllViews(NULL);
	}
	CView::OnLButtonDown(nFlags, point);
}

最后就是如何让鼠标显示操作:

BOOL CTrackerView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	// forward to tracker
	CTrackerDoc* pDoc = GetDocument();
	if (pWnd == this && pDoc->m_tracker.SetCursor(this, nHitTest))
		return TRUE;

	return CView::OnSetCursor(pWnd, nHitTest, message);
}

至此,如和绘制一个橡皮筋矩形已经完成了。但这还没有接触到问题,我必须先描述我另外一个问题之后,再回过头来才遇到标题要解决的问题。

我的需求:用鼠标左键单击,拖拽绘制一个或多个橡皮筋矩形,每个矩形能够被选中,并且进行橡皮筋操作,并且能够删除。

Doc对象中不在包含CRectTracker对象,改为CPtrArray,在每次重绘(OnDraw)的时候,在屏幕上依次绘制出所有的矩形,同样的,在单击的时候要对每一个tracker hittest进行判断。如果一个都没选中,则new 一个tracker,用鼠标TrackRubberBand设置好之后,pDoc->arrTracker.add(tracker)交给容器管理。

至此,似乎已经实现了除删除外的所有需求,但当我移动滚动条,并且试图绘制一个新的矩形时,红色的矩形跑到了上面(表面现象),而辅助框则保持了正确的位置(事实上也不正确)。


注意括号的内容,如果想不明白为什么,请慢慢往下读,我会尽量详细一些。

首先,矩形本身跑到了上面,这是为何,相信很多朋友在网上都找到了一个例子,大致是这样的:

http://wenku.baidu.com/link?url=OaFdnw0n8G3YiT7661M6W9koqfYfjaXDmGKhMnWW6slA-ZL2pIvSMNWOvt1EPatt6tUHiwJicOAcYZ6AKcVwLPh_oUKhSZgoop0D2xVkbbO

在他的OnDraw中:

void CCRectTrackerView::OnDraw(CDC* pDC)
{
CCRectTrackerDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
    return;
//自己添加
CBrush brush(RGB(255,0,0));//画刷为红色
CBrush *pbrush=pDC->SelectObject(&brush);
CRect rect;
m_RectTracker.GetTrueRect(&rect);//得到矩形区域的大小
pDC->Rectangle(&rect);//画出矩形
if(m_IsChosen)
    m_RectTracker.Draw(pDC);//若选择了该区域,则显示边框以及8个调整点
}

这个代码放在没有滚动条的view中是正常工作的。因为此时view与设备之间是o偏移的。(这里引申出两个概念,逻辑坐标与设备坐标,view的坐标是逻辑的,而pDC绘制的坐标是设备的,如果把一个逻辑坐标交给pDC绘制,则需要进行转换)

一旦发生滚动,tracker.Draw只会根据自己的m_rect绘制辅助框,此时问题就明显了,无论如何滚动,辅助框都是在屏幕的绝对位置,不会发生移动。然后左思右想得不到解决,原因就是因为这两种坐标的转换上。


把设备坐标用来画逻辑坐标矩形,所以位置一直都不准确。


下面是我的一个解决办法,并且已经测试正确:

void CCAEImageTesterView::OnDraw(CDC* pDC)
{
	RECT clientRECT;
	CCAEImageTesterDoc *pDoc=GetDocument();


	CPen pPen(PS_SOLID,1,RGB(255,0,0)); //使用pen划线
	pDC->SelectObject(pPen);
	pDC->SelectStockObject(NULL_BRUSH);//中间无任何填充(这些都是需求问题)
	int tackerCount = pDoc->m_arrTestRegon.GetSize();
	
	for (int i=0; i<tackerCount; ++i) //遍历容器依次绘制
	{
		TestingRegon *region = (TestingRegon *)pDoc->m_arrTestRegon.GetAt(i);
		region->getTracker().m_rect = region->m_Lrect; //这m_Lrect个是在鼠标绘制矩形时保存的逻辑坐标,并且这个坐标才是唯一坐标,在view滚动时根据逻辑坐标,不停的调整tracker矩形的位置,使得他看起来似乎一直与矩形相对静止
		pDC->LPtoDP(®ion->getTracker().m_rect);//把tracker的逻辑坐标,转换成设备坐标,结合前一句注释理解
		pDC->Rectangle(region->m_Lrect);//绘制矩形
		region->Draw(pDC);//绘制辅助框
	}
}
void CCAEImageTesterView::OnLButtonDown( UINT nFlags, CPoint point )
{
	CCAEImageTesterDoc* pDoc = GetDocument();
	int tackerCount = pDoc->m_arrTestRegon.GetCount();
	BOOL hasSelectedOne = FALSE;
	TestingRegon *region = NULL;
	计算偏移

	CClientDC dc(this);
	//判断是否选中某个tracker
	for (int i=0; i<tackerCount; ++i)
	{
		region = (TestingRegon*)pDoc->m_arrTestRegon.GetAt(i);
		//选中tacker,操作tacker
		if(region->CheckHit(point)>=0 &&!hasSelectedOne)
		{
			region->SetChosen(TRUE);
			if (region->Track(this,point))
			{
				OnPrepareDC(&dc);
				region->getTracker().GetTrueRect(®ion->m_Lrect);//操作后,重新计算设备坐标的逻辑坐标,并保存
				dc.DPtoLP(®ion->m_Lrect);
				region->Draw(&dc);
			}
			region->DisplayTracker();
			hasSelectedOne = TRUE;
		}
		else
		{
			region->HideTracker();
		}
	}
	//未选中,创建tacker
	if (!hasSelectedOne)
	{
		region = new TestingRegon;
		region->TrackRubberBand(this,point);
		if (!region->IsRectEmpty())
		{
			OnPrepareDC(&dc);
			region->getTracker().GetTrueRect(®ion->m_Lrect);
			dc.DPtoLP(®ion->m_Lrect);
			region->DisplayTracker();
			pDoc->m_arrTestRegon.Add(region);
		}else{
			delete region;
		}
		Invalidate();
	}
	pDoc->UpdateAllViews(NULL);
	CView::OnLButtonDown(nFlags, point);
}



至此问题解决,如果有什么不清楚的,或者我写的不对,请与我联系,虽然问题解决了,但是代码很丑,旨在解决问题,下午我开始对这部分代码重构,不过这博客中的代码不会更新了,请不要对代码可读性或其他进行评论,旨在分析问题与解决问题。谢谢大家。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值