关于CScrollView中SetScrollSizes导致的OnDraw的无限调用

写在前面:以下问题是由于将SetScrollSizes函数写在OnDraw里导致的,最好的办法是将SetScrollSizes放在需要改变大小的位置而非OnDraw中。但是如果你一定要将SetScrollSizes写在OnDraw里请继续阅读以下篇幅:


1 在正式阐明之前先交代以下四种情况:

(1)如果一个客户窗口没有滚动条,此时希望的视图宽高比客户窗口的宽高大,此时SetScrollSizes后会调用OnSize函数,因为出现滚动条后,客户区窗口会减掉滚动条的相应宽高。
(2)如果一个客户窗口本身已有滚动条,希望的视图宽高比客户窗口的宽高大,此时SetScrollSizes后并不会调用OnSize函数。

(3)如果一个客户窗口已有滚动条,希望的视图宽高比客户窗口的不带滚动条时宽高小于或等于,此时SetScrollSizes后会调用OnSize函数,因为滚动条要消失,客户区窗口会增加滚动条相应的宽高。

(4)如果一个客户窗口没有滚动条,希望的视图宽高也不会造成滚动条的增加,此时SetScrollSizes后也不会调用OnSize函数。

2  SetScrollSizes函数的歧义区间

先上一段代码,以便叙述。新建一个基于CScrollView的单文档工程,OnDraw函数书写如下:

void CTestScrollviewView::OnDraw(CDC* /*pDC*/)
{
	CTestScrollviewDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;
	// TODO: add draw code for native data here
	//客户区窗口的初始宽高
	CRect rect;
	GetClientRect(rect);
	int originWidth=rect.Width();
	int originHeight=rect.Height();
	//滚动条的尺寸
	int vwidth=GetSystemMetrics(SM_CXVSCROLL);//垂直滚动条的宽度
	int hheight=GetSystemMetrics(SM_CYHSCROLL);//水平滚动条的高度
	//从无滚动条到有滚动条需设置的视图最小尺寸
	int minw=originWidth+vwidth+2;
	int minh=originHeight+hheight+2;
	SetScrollSizes(MM_TEXT,CSize(minw,minh));
}
当前情况如1阐明的第一种情况,从没有滚动条到有滚动条,此时调用SetScrollSizes设置希望的视图尺寸,而当前的设置尺寸是有歧义区间的:
(1)宽度的歧义区间为:(originWidth,originWidht+vwidth+2)  ,在这个区间内可以认为是从没有滚动条到有滚动条,也可以认为是从有滚动条到没有滚动条,于是SetScrollSizes就不停的调用OnSize然后调用Ondraw,于是就陷入了死循环。
(2)高度的歧义区间为:(originHeight,originHeight+hheight+2) ,它与宽度的歧义区间同理。

3 问题归纳

    2对情况1-1进行了演示和阐述,从中我们可以得知:应避免设置歧义区间的视图尺寸,否则很可能陷入重绘死循环。同理,情况1-3也存在歧义区间,其他两种情况不会出现问题。现在,对两种情况(1-1,1-3)下的问题进行归纳。
    设纵轴横轴都没有滚动条的客户窗口的尺寸为:( originWidth1, originHeight1 ) ,设纵轴横轴都有滚动条的客户窗口尺寸为:( originWidth2, originHeight2 ) ,设垂直滚动条的宽度为:vwidth ,设水平滚动条的高度为:hheight ,歧义区间归纳如下:
(1)从没有水平滚动条到有水平滚动条的歧义区间为:(originWidth1 , originWidth1+vwidth+3)
(2)从没有垂直滚动条到有垂直滚动条的歧义区间为:(originHeight1 , originHeight1+hheight+3)
(3)从有水平滚动条到没有水平滚动条的歧义区间为:(originWidth2 , originWidth2+vwidth+3)
(4)从有垂直滚动条到没有垂直滚动条的歧义区间为:(originHeight2 , originHeight2+hheight+3)

4 解决方案

//成员变量
//视图的宽与高;它们会由于其他设置而发生改变。
int m_Width;
int m_Height;
//客户区窗口的宽高
int m_OriginWidth;
int m_OriginHeight;
//绘制函数
void CTestScrollviewView::OnDraw(CDC* pDC)
{
	CTestScrollviewDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;
	// TODO: add draw code for native data here
	//客户区窗口的宽高
	CRect rect;
	GetClientRect(rect);
	if (rect.Width()!=m_OriginWidth||rect.Height()!=m_OriginHeight)
	{
		m_OriginHeight=rect.Height();
		m_OriginWidth=rect.Width();
	}
	//当前视图的宽高
	TRACE2("m_Width=%d,m_Height=%d\r\n",m_Width,m_Height);
	//在SetScrollSizes设置滚动条之前对m_Width和m_Height做微调
	int width=m_Width;
	int height=m_Height;
	int vwidth=GetSystemMetrics(SM_CXVSCROLL);//垂直滚动条的宽度
	int hheight=GetSystemMetrics(SM_CYHSCROLL);//水平滚动条的高度
	TRACE2("vwidth=%d,hheight=%d\r\n",vwidth,hheight);
	//当前滚动条的有无
	BOOL vbar,hbar;
	CheckScrollBars(hbar,vbar);
	TRACE2("hbar=%d,vbar=%d\r\n",hbar,vbar);
	//讨论滚动条SetScrollSizes后将要的出现状态
	if (!hbar&&(width>m_OriginWidth))//当前没有水平滚动条,SetScrollSizes后将有
	{
		TRACE0("WillHaveHbar\r\n");
		if (width>m_OriginWidth&&width<m_OriginWidth+vwidth+3)
		{
			width=m_OriginWidth+vwidth+3;
			TRACE1("width adjust up,%d\r\n",width);
		}
	}
	if (!vbar&&height>m_OriginHeight)//当前没有垂直滚动条,SetScrollSizes后将有
	{
		TRACE0("WillHaveVbar\r\n");
		if (height>m_OriginHeight&&height<m_OriginHeight+hheight+3)
		{
			height=m_OriginHeight+hheight+3;
			TRACE1("height adjust up,%d\r\n",height);
		}
	}
	if (hbar&&width<(m_OriginWidth+vwidth+3))//当前有水平滚动条,SetScrollSizes后将没有
	{
		TRACE0("WillNoHbar\r\n");
		if (width>m_OriginWidth&&width<m_OriginWidth+vwidth+3)
		{
			width=m_OriginWidth-1;
			TRACE1("width,adjust down,%d\r\n",width);
		}
	}
	if (vbar&&height<(m_OriginHeight+hheight+3))//当前有垂直滚动条,SetScrollSizes后将没有
	{
		TRACE0("WillNoVbar\r\n");
		if (height>m_OriginHeight&&height<m_OriginHeight+hheight+3)
		{
			height=m_OriginHeight-1;
			TRACE1("height,adjust down,%d\r\n",height);
		}	
	}
	//
	TRACE2("currentorigin=(%d,%d)\r\n",m_OriginWidth,m_OriginHeight);
	TRACE2("setscrollsize=(%d,%d)\r\n",width,height);
	//设置滚动
	SetScrollSizes(MM_TEXT,CSize(width,height));
	//当前的视图宽高更新
	m_Width=width;
	m_Height=height;	
	//在m_Width,m_Height定义的视图区做绘制...
}


完成!




版权声明:本文为u011442652原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。