VC++设置字体 字幕变色功能的实现 DrawText函数 本章小结

本文介绍了在VC++中如何使用MFC的CFont类设置字体,通过CreatePointFont函数创建字体对象并改变文字大小。同时,详细讲解了如何利用DrawText函数实现文字的平滑变色效果,模拟卡拉OK字幕的动态变色。通过设置定时器和调整矩形宽度,使得文字颜色渐变,实现了一种视觉上的平滑过渡。
摘要由CSDN通过智能技术生成

目录

设置字体

字幕变色功能的实现

DrawText函数

本章小结

接上:VC++字符输入 ASCII码

接下:


设置字体

MFC提供了一个CFont类专门用来设置字体。这个类派生于CGdiObject类,封装了一个Windows图形设备接口(GDI)的字体。在实际编程时,在构造了一个CFont对象后,还必须利用该类提供的几个初始化函数之一对该对象进行初始化,然后才能使用这个对象。CFont类提供的初始化函数有:

■ CreateFont

■ CreateFontIndirect

■ CreatePointFont

■ CreatePointFontIndirect

这些初始化函数的作用主要是将CFont这个C++对象字体资源关联起来。本例将使用CreatePointFont这个初始化函数,其声明形式如下所示。

CFont::CreatePointFont

BOOL CreatePointFont(int nPointSize, LPCTSTR lpszFaceName, CDC* pDC = NULL);

该函数带有三个参数,各个参数的含义如下所述。

■ nPointSize设置将要创建的字体的高度,单位是一个点的十分之一。例如,如果该参数值为120,那么要求创建一个12个点的字体。

■ lpszFaceName字体的名称,就像Word中使用的“楷体”“宋体”这些字体名称一样。

在VisualStudio开发环境中,可以看到这些字体的名称,方法是单击【工具】菜单下的【选项】菜单命令,这时会弹出“选项”对话框,在左边的列表框中展开“环境”节点,选中“字体和颜色”,接下来就可以在“字体”的下拉列表中看到所有可用的字体名称,如下图所示。

但是, Visual Studio开发环境支持的字体还是少了一些,我们可以在机器的系统目录下看到系统已安装的所有字体。系统目录是Windows。在系统目录下有一个Fonts目录,在该目录下列出的内容就是操作系统已安装的字体。我们编写的程序并不能够对所有这些字体提供支持,程序支持哪些字体,需要通过试验才能知道。

■ pDC这是一个 CDC 对象的指针,用来把 nPointSize 中指定的高度转换为逻辑单位。如果其值为空,就使用一个屏幕设备描述表来完成这种转换。 

在程序中,与其他GDI对象一样,当创建了一个字体对象并初始化后,还必须将它选入设备描述表,之后这个新字体才能发挥作用。这可以利用CDC类的SelectObject函数来实现,同样,该函数会返回先前的字体,我们可以保存这个字体,在使用完新字体后,再把设备描述表中的字体恢复为先前的字体。

OnChar函数中添加了字体的设置之后,该函数完整的代码如例所示。

	CFont font;
	font, CreatePointFont(300, _T("华文行楷"), NULL);
	CFont *pOldFont = dc.SelectObject(&font);
	dc.SelectObject(pOldFont);
void CTextView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	CClientDC dc(this);
	CFont font;
	font, CreatePointFont(300, _T("华文行楷"), NULL);
	CFont *pOldFont = dc.SelectObject(&font);
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	if (0x0d == nChar)
	{
		m_strLine.Empty();
		m_ptOrigin.y += tm.tmHeight;
	}
	else if (0x08 == nChar)
	{
		COLORREF clr = dc.SetTextColor(dc.GetBkColor());
		dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine);
		m_strLine = m_strLine.Left(m_strLine.GetLength() - 1);
		dc.SetTextColor(clr);
	}
	else
	{
		m_strLine += (TCHAR)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);

	dc.SelectObject(pOldFont);

	CView::OnChar(nChar, nRepCnt, nFlags);
}

重新编译并运行Text程序,在程序窗口中输入文字看看效果,发现字体改变了。程序运行结果如图所示。

 

这里创建的这个文本程序功能很简单,如果要实现一个功能完整的字处理程序的话,那么所要做的工作还有很多。MFC提供了CEditViewCRichEditView这两个类,可以用来帮助我们实现功能强大的字处理程序,其中,后者提供的功能比前者更为强大。如果让程序的视类直接派生于这两个类之一的话,程序就已经具备字处理程序的一些基本功能了,例如输出字符、回车键的功能,还有一些简单的编辑功能。因此,以后要实现字处理程序,则可以让程序的视类直接派生于这两个类中的其中之一即可。 

字幕变色功能的实现

读者平时在唱卡拉OK时,应该注意到歌曲字幕会随着曲调的播放而有一个平滑的变色过程。如何在程序中实现这种效果呢?如果我们先把字符串输出到屏幕上,接着把文本的颜色设置为新的颜色,然后一个字符、一个字符地输出显示该字符串,那么也可以达到一种变色效果,但不能达到平滑的变色效果。为了达到卡拉OK字幕那样平滑的变色效果,我们需要利用CDC类提供的另一个输出文字的函数DrawText来实现。DrawText函数的作用是在指定的矩形范围内输出文字。该函数的一种声明形式如下所示:

DrawText函数

int DrawText(
   const CString& str,
   LPRECT lpRect,
   UINT nFormat 
);

该函数三个参数的含义如下所述:

■ str指定要输出的字符串。

■ lpRect指定文字显示范围的矩形。

■ nFormat指定文本的输出格式。

DrawText函数实际上是把文字的输出局限在一个矩形范围内。当输出的文字太多,以至于超过设定的矩形范围时,DrawText函数就会截断输出的文字,只显示在设定矩形内能够显示的那部分文字。利用 DrawText 函数的这个特点,我们可以将文本设置为一个新的颜色,在窗口中已有文本的位置重新输出一遍该文本,在初始输出文本时先把矩形的宽度设置为一个较小的值,然后不断地加大矩形的宽度,这样就可以不断地增加显示文字的内容,从而实现文字的平滑变色效果。

lpRect指向RECT结构或CRect对象的指针,结构(或对象)中包含有矩形(逻辑单位表示),其中的文本带有格式。
str含有要被绘制的文本的CString对象。
nFormat指定格式化文本的方法。它可以是下列值的组合(可用运算符或位操作符进行组合):
DT_BOTTOM底部对齐的文本。该值必须同DT_SINGLELINE组合使用。
DT_CALCRECT决定矩形的宽度和高度,如果是多行文字,DrawText将会使用lpRect指向的矩形,并扩展矩形以容纳文本。如果是单行文本,DrawText将会调整矩形的右边以便容纳该行文字。两种情况下,DrawText都返回格式化文本的高度,但并不绘制它们。
DT_CENTER将文本水平居中。
DT_END_ELLIPSIS或DT_PATH_ELLIPSIS如果必要,将用椭圆替代部分指定的文本,以便适合给定的矩形。除非指定DT_MODIFYSTRING 标志,否则给定的字符串不会被修改。可以指定DT_END_ELLIPSIS在字符串末端替代字符,或者指定DT_PATH_ELLIPSIS在字符串中间替代字符。如果字符串中含有反斜杠(\),DT_PATH_ELLIPSIS将尽可能保存在最后一个反斜杠后的文本。
DT_EXPANDTABS扩展制表符,每一制表符所含字符的缺省数目是8。
DT_EXTERNALLEADING包括在行高中的字体外部间隔。通常外部间隔不包括在一行文本的高度之中。
DT_LEFT文本左对齐。
DT_MODIFYSTRING修正给定字符串以便与显示文本匹配。只有在指定了DT_END_ELLIPSIS 或DT_PATH_ELLIPSIS 标志时,该标志才起作用。
注意:某些uFormat标志组合能导致传递的字符串受到修改。DT_MODIFYSTRING同DT_END_ELLIPSIS 或DT_PATH_ELLIPSIS 一起使用会导致字符串修改,导致在CString覆盖中插入断言。
DT_NOCLIP绘制但不剪切, DrawText在使用DT_NOCLIP后总能运行得快一些。
DT_NOPREFIX关闭前缀字符,通常DrawText将助记前缀&理解为其后字符加上下划线,&&前缀符表示显示一个&字符,指定DT_NOPREFIX后,也关闭了这种处理。
DT_PATH_ELLIPSIS
DT_RIGHT文本右对齐。
DT_SINGLELING指定单行,回车与换行不会中断该行。
DT_TABSTOP设置制表位,nFormat的高位字节即为每一制表符所含字符的数目,缺省值为8。

文字变色是一个不断变化、自动进行的过程,这意味着我们需要不断地调用DrawText函数,同时增大包含文本的矩形宽度。要实现这个功能,我们需要用到定时器,通过定时器来自动控制文字变色的进程。

定时器与我们日常生活中使用的闹钟有些相似,我们可以把闹钟定在某个时刻,当时间到达这个时刻,闹钟就会振铃。当我们听到振铃声,就知道我们定的时间到了。定时器的功能也是这样的,当它到了一定的时间,就会发送一个消息。我们收到消息,就知道时间到了。但定时器与闹钟不同的地方是,定时器是每隔一定的时间发送一条消息,而闹钟是固定在某个时刻,只有到了这一时刻才会振铃。例如,我们把闹钟定在早上8点这个时刻,那么只有到了这个时刻,闹钟才会振铃;而定时器则可以设置为间隔10分钟发送一条消息,这样,每隔10分钟,我们就会收到一条定时器发送的消息。

利用定时器不断发送消息的特点,我们可以在响应定时器消息的响应函数中,不断增加显示文字的矩形宽度,从而实现平滑的文字变色效果。

利用CWnd类SetTimer成员函数可以设置定时器,该函数的声明形式如下所示:

UINT_PTR SetTimer(
   UINT_PTR nIDEvent,
   UINT nElapse,
   void (CALLBACK* lpfnTimer
)(HWND,
   UINT,
   UINT_PTR,
   DWORD
) 
);

如果这个函数调用成功,那么它将返回新定时器的标识。该函数各参数的含义如下所述:

■ nIDEvent指定一个非零值的定时器标识。

也就是说,当我们定义定时器时,可以为它设置一个标识。如果该函数调用成功,那么这个标识将作为返回值返回。这就是说,如果这个函数执行成功的话,它的第一个参数和返回值就是相等的。

■ nElapse指定定时器的时间间隔,也就是指定定时器每隔多长时间发送一次定时器消息(WM_TIMER)。

需要注意的是,它是以毫秒为单位的。例如,如果将该值设置为1000,那么每隔1秒钟,就发送一次定时器消息。

■ lpfnTimer这是一个函数指针,并且要求是一个回调函数。

在前面已经介绍了CALLBACK的含义。这个回调函数的写法已经在上述声明中列出了。当设定好定时器之后,每隔设定的时间间隔,它就会发送一条定时器消息。如果在这里设置了回调函数,那么操作系统就会调用这个回调函数来处理定时器消息。如果我们将此参数设置为NULL值,那么定时器消息,即WM_TIMER消息就会被放到应用程序的消息队列中,然后由程序中响应此消息的窗口对象来处理。

在这个Text例子中,我们在视类的OnCreate函数中设置定时器。在此函数中,设置一个时间间隔为100ms,标识为1的定时器。实现代码如例所示。

	SetTimer(1, 100, NULL);
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;

	bitmap.LoadBitmap(IDB_BITMAP1);
	CreateCaret(&bitmap);
	ShowCaret();

	SetTimer(1, 100, NULL);
	return 0;
}

另外,本例是在视类中对定时器消息进行处理,因此需要给 CTextView 类添加WM_TIMER消息的响应函数,该函数的初始定义如例所示。

void CTextView::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值

	CView::OnTimer(nIDEvent);
}

可以看到,这个响应函数有一个参数:nIDEvent,这是定时器的标识。在一个应用程序中,我们可以设置多个定时器,每个定时器都有自己的时间间隔和标识符。但所有的定时器都发送WM_TIMER消息,这时就可以通过这个nIDEvent参数来获得当前是哪个定时器发送的消息,然后针对不同的定时器做不同的处理。本例中只有一个定时器,因此就不需要对此参数进行判断了。

因为需要让DrawText函数的第二个参数(即显示文字的矩形范围)不断增加,所以需要设置一个变量,让它的值不断增加,然后在程序中把这个变量赋给矩形的宽度成员,从而实现该矩形的宽度值不断增加。因此,在CTextView类中再添加一个int类型的私有成员变量:m_nWidth,并在视类的构造函数中将其初始化为0。这一步很重要,如果不初始化这个变量的话,那么它的值将是一个随机值,在随后程序中对它进行自加或自减操作时,结果将很难被确定。

private:
	int m_nWidth;
	m_nWidth = 0;

提示:在对一个变量进行自加或自减操作前,一定要初始化这个变量。否则,结果是不确定的。

本程序将对前面已在窗口中显示的那行由字符串资源(IDS_STRINGVC)定义的文字实现平滑变色效果。需要先获得包围这行文字的矩形的位置,实际上,只需要获得这个矩形的高度就可以了,因为矩形的左上角坐标就是这行文字显示时的起始坐标。而这个矩形的宽度并不需要知道,它是由m_nWidth变量决定的,从0开始按某个值不断增加。为了获得这个矩形的高度,也就是要获得设备描述表中当前字体的高度,可以通过GetTextMetrics函数来实现。

在OnTimer函数中实现文字平滑变色效果,具体实现代码如下所示。

void CTextView::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	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;
	str.LoadString(IDS_STRINGVC);
	dc.DrawText(str, rect, DT_LEFT);

	CView::OnTimer(nIDEvent);
}

在上述代码中,首先设置m_nWidth变量的值,按5个像素点增加,也就是说后面调用的DrawText函数的第二个参数即限制显示文字范围的那个矩形的宽度按5个像素点不断增加。接着,根据设备描述表中当前字体的高度得到这个矩形的高度,并利用这些信息初始化矩形对象。接下来,程序将设备描述表中文本颜色设置为红色,并根据字符串资源获得要显示的字符串。然后就调用DrawText 函数,完成在指定矩形范围内文字的输出。因为定时器每隔100ms 就会发出一次WM_TIMER消息,也就是每隔100ms,OnTimer函数就会被调用一次,每调用一次,这个矩形的宽度就会增加5个像素点,所以,以红色输出的文字范围就会增加一些,从而实现了一种文字平滑变色的效果。

编译并运行Text程序,将会看到一种很平滑的变色效果,而不是一个字、一个字地变色。在程序运行过程中某个时间点处的结果如图所示。

 

在上述例所示代码中,DrawText 函数使用的输出格式(即它的第三个参数)是DT_LEFT,这是一种左对齐格式。我们可以再试试其他格式(例如DT_RIGHT)看看效果。将下面这几行代码添加到上述例所示OnTimer函数的第15行代码的后面。 

	rect.top = 150;
	rect.bottom = rect.top + tm.tmHeight;
	dc.DrawText(str, rect, DT_RIGHT);
void CTextView::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	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;
	str.LoadString(IDS_STRINGVC);
	dc.DrawText(str, rect, DT_LEFT);

	rect.top = 150;
	rect.bottom = rect.top + tm.tmHeight;
	dc.DrawText(str, rect, DT_RIGHT);

	CView::OnTimer(nIDEvent);
}

编译并运行Text程序,程序运行结果如图所示。我们可以发现DT_LEFT输出格式从字符串的左边开始,逐渐向右输出文字。而 DT_RIGHT 输出格式从要输出字符串的最右边的那个字符开始输出,逐渐向左输出文字。
                 

在这个Text程序运行时,我们发现还有一些问题。其中一个问题是,当以DT_RIGHT输出格式显示文字时,在字符串全部输出完毕后,应该让它从头开始输出,而不是随着限制显示范围的矩形的宽度不断加大慢慢地从程序窗口上消失。           

此外,当我们唱卡拉OK时,会发现字幕会随着音乐的播放而平滑变色,当一句话唱完后,它会变成另外一种颜色,用另外一种颜色表明这句话已经唱过了。那么在程序中要实现这个功能,需要判断限制显示范围的矩形宽度是否超过了需要显示的字符串在屏幕上显示时的宽度。而要获取字符串在屏幕上显示时的宽度,需要用到GetTextExtent函数。这时完整的OnTimer函数的实现代码如下例所示。

	CSize sz = dc.GetTextExtent(str);
	if (m_nWidth > sz.cx)
	{
		m_nWidth = 0;
		dc.SetTextColor(RGB(0, 255, 0));
		dc.TextOut(0, 200, str);
	}
void CTextView::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	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;
	str.LoadString(IDS_STRINGVC);
	dc.DrawText(str, rect, DT_LEFT);

	rect.top = 150;
	rect.bottom = rect.top + tm.tmHeight;
	dc.DrawText(str, rect, DT_RIGHT);

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

	CView::OnTimer(nIDEvent);
}

上述所示OnTimer函数中新增的代码段(加灰显示的部分)首先利用GetText Extent函数得到需要显示的字符串的尺寸。接着判断限制显示范围的矩形宽度是否超过了该字符串在屏幕上显示时的宽度。一旦发现其超过了,就将该矩形宽度设置为0,让文本重新开始输出。并将设备描述表中文本的颜色设置为绿色,但此时,先前已输出到窗口中的文本的颜色并未改变,因此还需要再调用一次TextOut函数,在原位置以新的颜色重新输出文本。 

编译并运行Text程序,可以看到当字符串全部显示完毕后,会立即从头开始重新显示,并且(0,200)处的字符串在全部显示完毕后会变成绿色。另外,还可以看看把DrawText函数的第三个参数设置为DT_CENTER时的效果。这时,我们会发现文字是从字符串的中间字符开始向两边扩展显示的。以上就是模拟卡拉OK字幕变色效果的实现。这个程序的功能比较简单,可以遵照这样的思路,去实现一个卡拉OK这样的系统。

本章小结

本章主要介绍了一些文字处理编程方面的知识,以及在处理文字时的一些技巧。

接上:VC++字符输入 ASCII码

接下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米酒馆

鼓励鼓励,鼓励很重要啦~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值