转自:http://blog.youkuaiyun.com/norains/archive/2008/02/27/2125640.aspx
//========================================================================
//TITLE:
// 文字滚动的技术实现
//AUTHOR:
// norains
//DATE:
// Wednesday 27-February-2008
//Environment:
// VS2005 + SDK-WINCE5.0-MIPSII
// EVC 4.0 + SDK-WINCE5.0-MIPSII
//========================================================================
在WinCE平台下,使用evc或vs2005写出文字滚动的代码并不是一件难事,甚至可以说非常简单。
大体上来说,程序的失败与否取决于两个关键点:
1.准确计算文本的长度以及宽度;
2.定时刷新窗口。
现在我们来看看这两点分别有什么注意的地方。
首先是计算文本的长度。
其实要做到这点也并非难事,因为WinCE给我们一个现成的函数:GetTextExtentPoint。
函数的原型和解释如下:
BOOL GetTextExtentPoint(
HDC hdc,
LPCTSTR lpString,
int cbString,
LPSIZE lpSize
);
hdc
[in] Handle to the device context (DC).
lpString
[in] Long pointer to the string of text. The string does not need to be zero-terminated because cbString specifies the length of the string.
cbString
[in] Integer that specifies the number of characters in the string.
lpSize
[out] Long pointer to a SIZE structure that receives the dimensions of the string.
我们需要注意的是lpString形参的解释:The string does not need to be zero-terminated because cbString specifies the length of the string。 翻译为中文的大概的意思是,字符串不需要以0为结尾,因为你可以在cbString形参定义字符串的长度。那么,如果我们以0为结尾又如何呢?该函数会不会自动计算?cbString也没有正式表明如果该值为-1则自动计算。也许你现在的环境依次设置可能会计算出正确的长度,但并不代表以后WinCe的版本甚至是不同CPU环境下也有相同的表现,所以我建议,以在cbString传入字符串个数的方式。
现在我们先来看两段代码:
SIZE size1 = {0};
GetTextExtentPoint(hdc,TEXT("无换行"),_tcslen(TEXT("无换行")),&size1);
SIZE size2 = {0};
GetTextExtentPoint(hdc,TEXT("有换行/n/r第二行"),_tcslen(TEXT("有换行/n/r第二行")),&size2);
直觉是不是告诉你,这代码应该为真: size2.cy > size1.cy ?
但事实确实无情的,因为 size2.cy == size1.cy !
也就是说,GetTextExtentPoint是将“TEXT("有换行/n/r第二行")”这个字符串当成一行来计算!所以,如果你想编写一段上移或下移的代码,你无法直接通过该函数获取其所需的高度。
换个角度想,虽然GetTextExtentPoint获取的仅仅是一行的高度,但我们却可以自行判断有多少行,然后再用行数相乘每行的高度不就是所需要的总高度了么?
例如:
//Get the amount of the "/n"
TCHAR *pFind = m_pszTxtInfo;
int iCountLine = 1;
while(TRUE)
{
pFind = _tcsstr(pFind,TEXT("/n"));
if(pFind != NULL)
{
pFind += 1;
iCountLine ++;
}
else
{
break;
}
}
//所需要的总高度
m_iTxtInfoHeight = size.cy * iCountLine;
接下来的另一个难点就是采用什么方式刷新文字。
不必说,最基本的自然是首先创建一个线程,然后在该线程中刷新文字。但,采用什么方式来刷新呢?
熟悉SDK编码的朋友都不会陌生,我们可以调用DrawText来绘制文本。也许大家对该函数的lpRect形参比较感兴趣,因为该形参定义了绘制的坐标。一个很自然的想法是,我们在线程中不停调整坐标,然后再强制窗口重绘。
举个简单的例子:
线程代码:
//右移一像素
m_rcDraw.left += 1;
//强制重绘
InvalidateRect(m_hWnd,NULL,FALSE)
在WM_PAINT响应消息中加入如下代码:
DrawText(hdc,pszText,-1,&m_rcDraw,uFormat);
很简单,也很方便,是不是?但其实这样做有一个问题被我们忽略了,就是调用DrawText耗费的时间!一段不过二十个字的字符串,调用该函数耗费的时间是调用BitBlt的三倍!更不用说绘制一个文本文件的内容了!
我们再换另外一个角度来想,既然调用BitBlt的速度远远比DrawText的快,那么我们为何不先创建一个内存DC,然后在这DC中调用DrawText初始化文本,最后在使用的使用再将该DC绘制到目标DC中去呢?
接下来的问题就出来了,这内存DC应该在哪里创建?大小如何确定?前面我们已经提到,如何计算显示文本的总宽度和总高度,现在这两个数值就能派上用场了。因为我们的内存DC主要是存储文本,所以我们的内存DC的长高完全可以用文本的总宽度和总高度。
所以,在计算完毕文本所需要的区域大小后,我们可以马上创建内存DC:
//Create the memory DC
CMemDC memDC;
SIZE size = {m_iTxtInfoWidth,m_iTxtInfoHeight};
memDC.Create(hdc,&size);
DrawText(memDC.GetDC(),pszText,-1,&m_rcDraw,uFormat);
接下来我们只需要在WM_PAINT的处理函数中将该DC按坐标绘制出来即可:
BitBlt(memDC.GetDC(),
m_rcDraw.left,
m_rcDraw.top,
m_iTxtInfoWidth,
m_iTxtInfoHeight,
m_DCTxtInfo.GetDC(),
0,
0
SRCCOPY);
只要解决这两个关键问题,文字的滚动并不是一件非常困难的事情。
在我另一篇《CTextWnd轻松实现文字的滚动》文章中,封装了一个CTextWnd窗口类,能够简便地实现文字的滚动,有兴趣的朋友可以参考。