VC++深入详解(3):MFC文本编程

文本处理程序都有插入符,在MFC中使用CreateSolidCaret实现创建插入符,创建完成后使用ShowCaret显示插入符。

int CCH_5_TEXTView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	// TODO: Add your specialized creation code here
	CreateSolidCaret(20,100);
	ShowCaret();
	return 0;
}

实际上,文本处理程序往往根据字体来自动的调节插入符的大小,如何获得系统的字体呢?在SDK下,我们使用GetTextMetrics函数,而在MFC下,我们使用的是经过封装的这个函数,使用的方法是大同小异的:定义一个TEXTMETRIC类型的变量,将它的地址传给GetTextMetrics,然后系统的一些字体之类的信息就会储存在变量中了。

	CClientDC dc(this);
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	CreateSolidCaret(tm.tmAveCharWidth/8,tm.tmHeight);
	ShowCaret();

下面介绍如何创建自己定义的插入符。使用CreateCaret 来实现,注意到这个函数接受一个指向位图的指针,所以我们需要给自己的view类添加一个CBitmap类型的成员m_bitmap,然后:

	m_bitmap.LoadBitmap(IDB_BITMAP1);
	CreateCaret(&m_bitmap);
我们前面的画图程序,有一个明显的问题,就是如果我们改变了窗口的大小或者最小化以后再重新打开,之前画过的东西就会消失了,这是很正常的,因为上述操作会引起窗口的重绘,重绘会引起WM_PAINT消息,如果你对这个消息没有响应,那么自然显示出来的就是一个崭新的窗口了。如果你想在窗口重绘后来能保留原来的结果,那么必须要在这个消息的响应函数中重新写出之前的内容。
在文本编程中,主要处理的字符串,我们使用CString类型的变量str来承载字符串。这个类型类似于C++标准库中的string,可以当一个变长的char数组来使用。我们把每次需要输出的内容存放在这里,每次在响应WM_PAINT函数中重新输出它就OK了。
	CString str("啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊");
	pDC->TextOut(50,50,str);
CString 对象还可以通过LoadString装载字符串资源,字符串资源也在资源视图中。
	str.LoadString(IDS_STRINGTEXT);
	pDC->TextOut(50,50,str);

下面我要介绍一个比较抽象的概念:路径层。先看一个使用路径层的例子。用矩形覆盖文字,我们需要获得这个矩形的位置,矩形的左上角很好确定,就是(50,50),可是右下角就有点复杂了,还好MFC提供了GetTextExtent还获得某个特定字符所占的高度和宽度,用这个距离加上50就是右下角了。

	CSize sz = pDC->GetTextExtent(str);
	pDC->BeginPath();
	pDC->Rectangle(50,50,50+sz.cx,50+sz.cy);
	pDC->EndPath();
如果将BeginPath和EndPath注释起来,就会出现一个矩形框挡住文字的情况。
那么路径层到底有什么用呢?
先看一段代码:

//	pDC->SelectClipPath(RGN_DIFF);
	for(int i = 0; i < 300; i+= 10)
	{
		pDC->MoveTo(0,i);
		pDC->LineTo(300,i);
		pDC->MoveTo(i,0);
		pDC->LineTo(i,300);
	}
我们画了一个网格。程序运行时,文字和网格混合在一起,但是如果打开注释,网格就在有文字的区域断开了。
SelectClipPath将路径层和剪切区域(客户区内的某一个自己制定的可以画图的区域)按照一种模式进行互操作。我们这里选择的是RGN_DIFF,所以网格会在路径层断开,如果选择的是RGN_AND,那么就只有路径层和裁剪区域重叠的部分会有网格。


扯了这么多,我们的文本编程还不能完成最基本的任务:从键盘上输入文字,然后显示出来。
这看似是一件容易的事情,捕获WM_CHAR消息,然后把对应的字符打印出来就行了,可是仔细想想却很有学问。插入符的位置是要跟着输入的字符移动的!这个如何完成呢?不能简单地在每次输入一个字符以后让插入符移动一个固定的大小,因为对于每个字符,它的宽度是不定的。其实有一个很好的办法,就是在每次输出字符时,并不是输出单个字符,而是从头开始输出一个CSring类型的字符串,它占用的长度是可以计算的。解决了这个问题,我们还需要考虑一个问题,我们希望鼠标单击哪里,就从那里输出,所以,需要对WM_LBUTTONDOWN消息做出相应。于此同时,应该把CSring对象清空,因为我们要重新输入了。然后,我们还要考虑一些特殊的操作符的作用,我们这里只简单地考虑回车和退格两个操作符。对于回车操作符,需要清空字符串,然后保持插入点的横坐标不变,而让纵坐标向下移动一个固定的长度,这个长度是可以通过字体信息获得的。对于退格操作就要非常巧妙了,先将文本的颜色变为背景色,然后从字符串中删除一个字符,然后将文本颜色改回来,最后重新输出。因为这一切都是在一瞬间发生的,所以看不出来。
想明白了这些,我们就可以着手写程序了。
首先给类添加一个成员:CString m_strLine;然后在构造函数中将它初始化为空:

CCH_5_TEXTView::CCH_5_TEXTView()
{
	// TODO: add construction code here
	m_strLine = "";
}
在响应WM_LBUTTONDOWN消息中使用SetCaretPos函数来设置插入光标的位置,并且清空字符串:
void CCH_5_TEXTView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	SetCaretPos(point);
	m_strLine.Empty();
	CView::OnLButtonDown(nFlags, point);
}
写到这里,我们突然发现似乎需要保存鼠标单击的位置,这个位置在以后的输出文字中用的上,所以我们有添加了一个CPoint类型的成员变量。
在构造函数中需要初始化它:

	m_ptorigin = (0,0);
而在OnLButtonDown函数中用它保存当前点击的位置:
	m_ptorigin = point;
有了它之后我们就可以开始写对WM_CHAR消息的响应函数了:
void CCH_5_TEXTView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	// TODO: Add your message handler code here and/or call default
	CClientDC dc(this);
	//获得系统字体
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	//如果是回车
	if(0x0d == nChar)
	{
		//清空字符串
		m_strLine.Empty();
		//换行
		m_ptorigin.y += tm.tmHeight;
	}
	//如果是退格
	else if(0x08 == nChar)
	{
		//将颜色变为背景色,并将原来的的颜色保存在clr中
		COLORREF clr = dc.SetTextColor(dc.GetBkColor());
		//用背景色输出文字
		dc.TextOut(m_ptorigin.x,m_ptorigin.y,m_strLine);
		//删除最后那个字符:取源字符串的前m_strLine.GetLength() - 1个字符
		m_strLine = m_strLine.Left(m_strLine.GetLength() - 1);
		//将颜色改回来
		dc.SetTextColor(clr);
	}
	else
	{
		m_strLine += nChar;
	}
	//调整插入符位置
	CSize sz = dc.GetTextExtent(m_strLine);
	CPoint pt;
	pt.x = m_ptorigin.x + sz.cx;
	pt.y = m_ptorigin.y ;
	SetCaretPos(pt);
	//输出内容
	dc.TextOut(m_ptorigin.x,m_ptorigin.y,m_strLine);
	CView::OnChar(nChar, nRepCnt, nFlags);
}
当然,我们只是“模拟”文本编程,所以很多问题都是没有考虑的。假如想建立更好的文档处理程序,可以从CEditView或者CRichEditView派生出来。


下面介绍一些文字处理常用的内容。
1.设置字体。
他需要用到了类时CFront。它的使用方法是定义一个CFront对象,然后调用创建函数创建字体,然后把这个字体选到设备描述表中,然后就能使用了:

	CFont font;
	font.CreatePointFont(123,"华文行楷",NULL);
	CFont *pOldFont = dc.SelectObject(&font);
使用完成后,把字体再改回来:
	dc.SelectObject(pOldFont);
2.让文字字逐渐的显示出来
这种感觉就是慢慢滚动的字幕,我们要使用DrawText函数。这个函数是格式化输出矩形框里的消息,看起来跟我们的要求关系不大,但是我们可以这样使用:设置一个定时器,每次时钟到来的时候,改变矩形框的大小,然后重新换一个颜色输出,最后显示矩形框的内容。具体的代码如下:
在OnCreate函数中设置一个定时器:

	SetTimer(1,100,NULL);
添加一个int型成员变量:m_nWidth,并在构造函数中初始化为0。
然后添加消息响应函数:

void CCH_5_TEXTView::OnTimer(UINT nIDEvent) 
{
	// TODO: Add your message handler code here and/or call default
	//始终到来时宽度增加5
	m_nWidth += 5;
	CClientDC dc(this);
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	//设置矩形框的大小
	CRect rect;
	rect.left = 0;
	rect.top = 200;
	rect.right = m_nWidth;
	rect.bottom = rect.top + tm.tmHeight;
	dc.SetTextColor(RGB(255,0,0));
	CString str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	dc.DrawText(str,rect,DT_LEFT );
	
	CView::OnTimer(nIDEvent);
}
rawText还可以选择从右边开始输出,可以将模式设为DT_RIGHT。但是这会出一个问题,就是字母全部输出以后,会输出一些垃圾值,我们可以设定:如果矩形的长度大于字符串长度,把宽度设为0,把把字符串重新输出为背景色,再重新从开始的位置输出字符串。

	CSize sz = dc.GetTextExtent(str);
	if(m_nWidth >= sz.cx)
	{
		m_nWidth = 0;
		dc.SetTextColor(dc.GetBkColor());
		dc.TextOut(0,200,str);
	}





你可能感兴趣的:(VC++深入详解(3):MFC文本编程)