1. 插入符
在文本处理程序的编辑窗口都有一条闪烁的竖线,称为插入符(Caret)。插入符可以提示用户:你输入的文字信息将在这个插入符所在的位置显示出来。
先创建一个单文档类型的MFC AppWizard(exe)工程,取名为Text;我们可以用CWnd类的CreateSolidCaret()函数来完成,再用ShowCaret()来显示。另外,考虑到插入符的创建应该在窗口创建之后,所以我们可以在WM_CREATE消息响应函数OnCreate中添加创建插入符的代码:
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
CreateSolidCaret(1, 20); //插入符初始时是隐藏的
ShowCaret(); //显示该插入符
return 0;
}
但是光标的大小不太适合,我们知道word中插入符的大小是根据所选字体大小来决定的,所有首先需要得到设备描述表中当前字体的信息,也就是文本信息,然后根据字体的信息来调整插入符的大小。调用CDC类中的GetTextMetrics成员函数可以获得设备描述表中当前字体的度量信息,该函数的原型声明为:
BOOL GetTextMetrics (LPTEXTMETRIC lpMetrics) const;
参数是一个TEXTMETRIC结构体的指针,也就是说,我们得先定义一个TEXTMETRIC结构体类型的变量,然后将该变量的地址传递给这个参数。通过GetTextMetrics的调用, 它会用设备描述表中当前字体的信息来填充这个结构体。TEXTMETRIC结构体定义:
typedef struct tagTEXTMETRIC { // tm
LONG tmHeight; //字体的高度(tmAscent+tmDescent)
LONG tmAscent; //升序高度
LONG tmDescent; //降序高度
LONG tmInternalLeading;
LONG tmExternalLeading;
LONG tmAveCharWidth; //平均宽度
LONG tmMaxCharWidth; //最大字符宽度
LONG tmOverhang;
LONG tmDigitizedAspectX;
LONG tmDigitizedAspectY;
BCHAR tmFirstChar;
BCHAR tmLastChar;
BCHAR tmDefaultChar;
BCHAR tmBreakChar;
BYTE tmItalic;
BYTE tmUnderlined;
BYTE tmStruckOut;
BYTE tmPitchAndFamily;
BYTE tmCharSet; } TEXTMETRIC;
得到了字体的信息,我们就可以利用字体的高度和平均宽度来设定插入符的高度和宽度了,代码如下:
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
CClientDC dc(this);//创建设备描述表
TEXTMETRIC tm;//定义文本信息的结构体变量
dc.GetTextMetrics(&tm);//获得设备描述表中当前的文字信息
CreateSolidCaret(tm.tmAveCharWidth/8, tm.tmHeight);//创建合适的插入符
ShowCaret(); //显示该插入符
return 0;
}
2. 创建图形插入符
上面创建的是一般文字处理程序所使用的文本插入符,那么如何插入图形插入符呢?我们可以利用CWnd类的另一个函数CreateCaret来实现:
void CreateCaret (CBitmap* pBitmap); 参数为CBitmap类的指针,所以我们先要构造一个CBitmap类的对象,并利用CBitmap的成员函数LoadBitmap来加载位图对象,然后才能使用这个位图对象。关于如何创建位图在上一节已经阐明,如果已经有了位图这里也可以导入:单击插入(Insert) ->资源(Resource) ->选择Bitmap资源类型->导入(Import)
程序如下:
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
/*CBitmap bitmap;不能在这里定义bitmap对象在这里是个局部变量,当OnCreate函数执行完后,bitmap对象就会发生析构,与之相关联的的资源都会被销毁,所以我们应该将这个局部位图对象修改为CTextView类的成员变量private: CBitmap bitmap;*/
bitmap.LoadBitmap(TDB_BITMAP1);//加载位图
CreateCaret(&bitmap);//创建位图插入符
ShowCaret(); //显示该插入符
return 0;
}
3. 窗口重绘
Windows程序运行时,如果窗口大小发生改变,窗口会发生重绘,窗口中已输入的文字和图形就会被擦除掉。如果希望输入的内容始终保存在窗口上,就要在WM_PAINT消息响应函数中将内容再次输出,在MFC中,OnDraw函数类似于WM_PAINT消息响应函数(WM_OnPaint函数会调用OnDraw函数),当窗口发生重绘时,应用程序框架代码就会调用该函数。
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CString str(“VC++深入编程”);//相当于CString str; str=”VC++深入编程”;
pDC->TextOut(50, 50, str);//在坐标为(50,50)处输出str的内容
Str.LoadString(IDS_STRINGVC);//将字符串的ID号传递进去
pDC->TextOut(50, 200, str);//输出我爱VC++
}
OnDraw函数被调用时,应用程序框架会构造一个CDC类对象的指针并传递给这个参数,所以在函数内部就不需要再构造CDC类的对象了,可以直接使用传递进来的CDC对象指针去调用CDC类的成员函数,完成绘图功能。
MFC中为我们提供了一个字符串类CString,它没有基类,一个CString对象由一串可变长度的字符组成。CString在操作字符串时,无论存储多少个字符,我们都不需要对它进行内存分配,这些操作在CString类的内部已经完成了。CString内部也重载了多个操作符,我们可以把CString类型对象当做简单类型的变量一样赋值、相加操作等,具体内部成员可以查阅一下MSDN。
CString类还提供了一个成员函数LoadString:
BOOL LoadString (UNIT nTD);
该函数可以装载一个有nTD标识的字符串资源。这样的好处是:我们可以构造一个字符串资源,在需要使用的时将其ID号装在进函数中,这样就不需要在程序中对字符串变量直接赋值了。那我们如何去构造字符串资源呢?在VC++开发界面的左边Resource View选项卡中打开String Table(字符串表),我们可以看到第一列就是ID号,第三列就是字符串的文本内容。我们可以在字符串表最底部空行处双击来构造自己的字符串资源,定义自己的ID号和文本内容,上面程序中,我们定义的是:ID:IDS_STRINGVC 内容:我爱VC++.
TextOut用来在窗口中输出一串文字的功能,这里是CDC类封装的TextOut函数与SDK平台提供的全局TextOut函数的区别是:前者不需要DC句柄作为参数,因为CDC内部专门有一个成员变量(m_hDC)保存了句柄。
4. 路径层
什么是路径层呢?在绘图时,如果希望图的某一部分与其他部分分开处理,就可以利用路径层的独立性,说的通俗一点,就像军阀割据一样,在地域上划清了界限,就是自己的地盘。
在MFC中,创建路径层是利用CDC类中提供的BeginPath和EndPath这两个函数来实现的,首先调用BeginPath在设备描述表中打开一个路径层,然后利用图形设备接口(GDI)提供的绘图函数进行绘图操作(矩形、椭圆等),绘图完成后,利用EndPath函数关闭路径层。
现在我们可以创建个路径层将上面输出的“VC++深入编程”用矩形框起来,所以首先我们应该知道这个字符串在窗口中的坐标值(左上角为(50,50),但右下角我们不知道),我们可以用CDC类中的GetTextExtent函数来获得一个字符串在屏幕上的宽度和高度:
CSize GetTextExtent (const CString& str) const;//返回一个CSize类的对象
CSize类类似于Windows的结构体SIZE:
typedef struct tagSIZE{
int cx;//表示宽度
int cy;//表示高度
} SIZE;
(注:这里要注意与GetTextMetrics函数的区别:GetTextMetrics获得的是设备描述表中当前字体的度量信息,而GetTextExtent获得的是某个特定的字符串在窗口中显示的宽度和高度。)
具体代码如下:
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CString str(“VC++深入编程”);//相当于CString str; str=”VC++深入编程”;
pDC->TextOut(50, 50, str);//在坐标为(50,50)处输出str的内容
CSize sz = pDC->GetTextExtent(str);//获得字符宽度和高度,存在对象sz中
str.LoadString(IDS_STRINGVC);//将字符串的ID号传递进去
pDC->TextOut(50, 200, str);//输出我爱VC++
pDC->BeginPath();
pDC->Rectangle(50,50,50+sz.cx,50+sz.cy);//绘出一个矩形,把输出的字符覆框起来,但是矩形我们看不到
pDC->EndPath();//路径层结束
pDC->SelectClipPath(RGN_DIFF);//见下面函数说明
for(int i=0; i<300;i+=10)//横竖画30条线,间距10
{
pDC->MoveTo(0,i);
pDC->LineTo(300,i);
pDC->MoveTo(i,0);
pDC->LineTo(i,300);
}
}
CDC::SelectClipPath:
BOOL SelectClipPath(int nMode);//参数nMode是设置路径与刚刚路径层的交互方式,五种方式详见MSDN
5. 字符输入与设置字体
首先我们需要捕获鼠标按下消息(WM_LBUTTONDOWN),并把插入符移到鼠标点击的位置,我们还得捕获键盘按下消息(WM_CHAR),然后定义一个CString类的成员变量m_strLiece(在CTextView类的构造函数中初始化为空)来存储输入的字符串,具体代码如下:
void CMyTextView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
SetCaretPos(point); //(1)
m_strLine.Empty(); //(2)
m_ptOrigin = point; //(3)
CView::OnLButtonDown(nFlags, point);
}
(1). 我们可以用CWnd::SetCaretPos函数将插入符移动到鼠标左键单击处,该函数的形式为:
static void PASCAL SetCaretPos (POINT point);它是个静态函数,并带有一个POINT结构体类型的参数,该参数表示一个点,也就是传递鼠标单击的点
(2). 当鼠标左键单击窗口的一个新的地方时,插入符就会移动到新的位置,那么以后的输入字符都应该在新的地方开始输入,而之前输入的字符不应该在新的地方再输出,所以要将strLine中的字符清空。可以调用CString类的成员函数Empty()来实现
(3). 因为每次输入的字符串都应该在当前插入符的位置(即鼠标点击的位置)显示,所以我们就要先把鼠标点击的位置保存下来,以便在OnChar函数中使用,m_ptOrigin是View类的CPoint类型成员变量,并在构造函数中初始化为0
void CMyTextView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc(this);
CFont font;
font.CreatePointFont(300, ”华文行楷”, NULL); //(1)
CFont* pOldFont = dc.SelectObject(&font);
TEXTMETRIC tm; //定义文本信息的结构体变量
dc.GetTextMetrics(&tm);//获得设备描述表中当前的文字信息
if(0x0d == nChar)//判断是否为回车键,0x0d是回车键的ASCAII码
{
m_strLine.Empty();//清空字符串中的内容
m_ptOrigin.y += tm.tmHeight;//换到下一行 (2)
}
else if(0x08 == nChar)//判断是否为退格键
{
COLORREF clr = dc.SetTextColor(dc.GetBkColor());//(3)
dc.TextOut(m_ptOrigin.x,m_ptOrigin.y,m_strLine);//输出一次(看不见)
m_strLine = m_strLine.Left(m_strLine.GetLength()-1);//(4)
dc.SetTextColor(clr);//
}
else
{
m_strLine += nChar;
}
dc.TextOut(m_ptOrigin.x,m_ptOrigin.y,m_strLine);
/*以下为了让光标随着字符的插入,一起向后移动*/
CSize sz1 = dc.GetTextExtent(m_strLine);
CPoint pt;
pt.x = m_ptOrigin.x + sz1.cx;
pt.y = m_ptOrigin.y;
SetCaretPos(pt);
dc.TextOut (m_ptOrigin.x, m_ptOrigin.y, m_strLine);//输出字符
dc.SelectObject (pOldFont);//还原字体
CView::OnChar(nChar, nRepCnt, nFlags);
}
(1). MFC提供了一个CFont类专门用来设置字体,这个类派生于CGdiObject类,封装了一个Windows图形设备接口(GDI)的字体,实际编程时,在构造了一个CFont对象后,还必须利用初始化函数对该对象进行初始化(详见MSDN),然后才能使用这个对象,这里用CreatePointFont来初始化:
BOOL CreatePointFont (int nPointSize, LPCTSTR lpszFaceName, CDC* pDC = NULL);
参数nPointSize设置将要创建的字体高度,单位是一个点的十分之一,比如120表示创建一个12个点的字体; lpszFaceName是字体的名称,在VC++开发环境中可以看到字体的名称,单击工具(Tools)->选择(Options)->格式(Format)中可以看到VC++开发环境支持的字体; pDC是一个CDC对象的指针,用来把nPointSize中指定的高度转换为逻辑单位,空表示用一个屏幕设备描述表来完成这种转换
(2). 回车时,光标的位置应该往下一行,随后的输入也应该从这一行开始输出。这样就要清空上一行保存的字符,同时计算插入符下一行的新位置。所以我们获得设备描述表中当前的文字信息后,就可对光标的纵坐标做一个修改
(3). CDC::GetTextColor:
virtual COLORREF SetTextColor (COLORREF crColor);该函数将文本设置为参数的颜色,并返回文本先前的颜色,我们需要将这个颜色保存起来,因为后面还要将先前的颜色再次显示出来。这里我们将文本设置成背景色(看不见了)。
获得背景色用CDC::GetBkColor:
COLORREF GetBkColor () const; 返回背景颜色
(4). CString::GetLength:
int GetLength () const; 返回为字符串中的字节数,但不包括空的结束符
CString::Left:
CString Left (int nCount) const; 返回一个CString对象,即返回指定字符串左边指定数目(nCount的值)的字符。所以这句代码的整体意思是删除字符串中最后一个字符
6. 字母变色功能的实现
int DrawText (const CString& str, LPRECT lpRect, UINT nFormat);
UINT SetTimer (UNIT nIDEvent, UINT nElapse, void(CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD));
void CTextView::OnTimer(UINT nIDEvent)
{
//这里就一个定时器,所以不需要判断nIDEvent来确定是哪个定时器
m_nWidth += 5;/*CTextView类的一个int型的成员变量,并初始化0,用来赋予矩形的可变的宽度,这里没100毫秒增加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;
str.LoadString(IDS_STRINGVC);
dc.DrawText(str, rect, DT_LEFT);//显示
CSize sz = dc.GetTextExtent(str);
if(m_nWidth > sz.x)//判断字体有没有显示完
{
m_nWidth = 0;
dc.SetTextColor(RGB(0,255,0)); dc.TextOut(0,200,str);
}
CView::OnTimer(nIDEvent);
}