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 hereCreateSolidCaret(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 herem_strLine = "";}在响应WM_LBUTTONDOWN消息中使用SetCaretPos函数来设置插入光标的位置,并且清空字符串:void CCH_5_TEXTView::OnLButtonDown(UINT nFlags, CPoint point) {// TODO: Add your message handler code here and/or call defaultSetCaretPos(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 defaultCClientDC 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//始终到来时宽度增加5m_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文本编程

相关文章:

你感兴趣的文章:

标签云: