文本与字体
范例:font_one
装入字体
Win32
GDI在处理不同的绘图类型,风格式样,语言和字符集的能力向来为人称道.但是在对字体的处理上有个例外,尤其是对初学者来说更是如此.主要的跟字体相关的API函数,CreateFont()有14个参数来指定高度,式样,線宽,字体族,和其它的一些属性.
幸好,它并不像看起来那么难以理解,很多这里的工作牵涉到处理一些敏感的默认参数值.除了其中的2个之外,所有的CreateFont()有参数都可以设为0或是NULL,这样系统就会用一些使你看到一般的效果的默认字体.
CreateFont()创建一个HFONT,即一个內存中的逻辑字体的句柄.这个句柄所包含的数据可以用GetObject()获取到一个LOGFONT结构体中,就像HBITMAP可以填充BITMAP结构体一样.
LOGFONT的成员跟CreateFont()的几乎一模一样,为了方便你也可以用CreateFontIndirect()从一个已有的LOGFONT直接创建一个字体.这样是相当简便的,当你只有改变某些己存在的字体句柄的参数来创建一个新字体时候.用GetObject()填充一个LOGFONT,按照你的需要来改变其中的某些成员,再用CreateFontIndirect()创建一个新字体.
HFONT hf;
HDC hdc;
long lfHeight;
hdc = GetDC(NULL);
lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
ReleaseDC(NULL, hdc);
hf = CreateFont(lfHeight, 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, 0, 0, "Times New Roman");
if(hf)
{
DeleteObject(g_hfFont);
g_hfFont = hf;
}
else
{
MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
}
这就是用来创建样例图片中字体的代码.这是Times New
Roman字体,12Point宽,并有Italics式样.italics的标志是CreateFont()的第6个参数,你可以看到我们将其设为了TRUE.我们要创建字体的名字是最后一个参数.
有一点要说明的是代码中用来指定字体大小的值, CreateFont()的lfHeight参数.一般来说人们习惯于用線宽来指定字体的大小,10
号,12号,等等...但CreateFont()卻不接受線宽指定大小的方法,它接受逻辑单位,在你的屏幕和打印机上是不同的,甚至在打印机之间,屏幕之间也是不同的.
这种情況的原因就是不同的设备的分辨率是相当不同的...打印机可以轻易达到600或1200dpi,而屏幕达到200就很不错了...如果在屏幕和打印机上用同样大小的字体,你可能甚至看不清单个的字母.
我们要做的就是把所要線宽的字体转为设备上接近的逻辑大小.本例中设备为屏幕,所以我们获取屏幕的HDC,并用GetDeviceCaps()来获取每个英寸它能显示多个像素,再传给MSDN慷慨提供的MulDiv()函数去来把我们的12号字体转为CreateFont()需要的正确的逻辑大小..我们把这个值在lfHeight存起来并当第一个参数传给CreateFont().
默认字体
当你首次用GetDC()来获取你窗口的HDC时,系统为你把默认字体选入其中,一般来说不是很好看.
要得到一个可以接受的字体最简单的方法(不用麻烦地调用CreatFont()了)就是调用GetStockObject()来获取一个DEFAULT_GUI_FONT.
这属于系统资源所以你想得到多少都可以,不用担忧会引起內存泄漏,在不需要的时候可以调用DeleteObject()对其进行刪除,这样做的好处就是你不需要来释放一个字体的时候一直跟蹤它是从CreateFont()或是GetStockObject()获取的.
绘文本
现在我们有了一个比较cute的字体了,我们怎样在屏幕上绘一点文本? 这里假设我们不用编辑框或是靜态控件.
你基本上有两个选择,TextOut()和DrawText().TextOut()简单些,但是选项少些并且不为你做单词換行和对齐等操作.
char szSize[100];
char szTitle[] = "These are the dimensions of your client area:";
HFONT hfOld = SelectObject(hdc, hf);
SetBkColor(hdc, g_rgbBackground);
SetTextColor(hdc, g_rgbText);
if(g_bOpaque)
{
SetBkMode(hdc, OPAQUE);
}
else
{
SetBkMode(hdc, TRANSPARENT);
}
DrawText(hdc, szTitle, -1, prc, DT_WORDBREAK);
wsprintf(szSize, "{%d, %d, %d, %d}", prc->left, prc->top, prc->right, prc->bottom);
DrawText(hdc, szSize, -1, prc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
SelectObject(hdc, hfOld);
我们所做的第一件事就是用SelectObject()来获取我们要在我们的HDC中使用的字体并为绘图作準备.在选入下一个字体之前所有的文本操作都用这个字体.
下一步我们设置文本和背景顏色.设置背景顏色并不会让整个背景都成这个顏色,它只影响某些用背景顏色来绘图的操作(绘文本是其中之一).这也跟当前的背景模式有关.如果为OPAQUE(默认),那么所有的文本绘制都会填入一个背景色的方框.如果设为TRANSPARENT,那么文本绘制就不会随著背景色一起绘制,原来在后面有什么就还是什么,这种情況下背景色沒有效果.
现在我们用DrawText()来绘制文本,我们传入要用的HDC和要绘的字符串.第三个参数是字符串的长度,但我们传入了-1因为DrawText()很聪明可以自己算出文本的长度.第四个参数我们传入了prc,指向客戶RECT的指针.DrawText()将会根据你给的其它的参数来在这个方框中进行绘制.
在第一次调用中,我们指定了DT_WORDBREAK,默认地把文本左对齐,并把所绘的文本自动地在方框的边缘換行...非常有用.
第二次调用中,我们只打印了一个单行而沒有換行,并且我们要它在水平和纵向的方向上都是居中的(DrawText()只会在单行绘制的时候这样做).
客戶区重绘
关于范例的一个说明...当WNDCLASS被注冊的时候我设置了CS_VREDRAW和CS_HREDRAW式样.这会导致如果窗口被拉抻的时候整个客戶区被重绘,默认地只是重绘改变的部分.这样看起来,当拉抻你的窗口时候,居中的文本就不如你所想像的那么更新,有点糟糕.
选择字体
一般讲,任何牵涉到字体的程序都要使用戶可以自己选字体显示的顏色和大小.
如同获取打开与保存文件名字的通用对话框样,有一个选择字的通用对话框.有点古怪,叫作ChooseFont()和一个叫CHOOSEFONT的结构体一起用,为你选择默认的开始字体,并返回用戶最终的选择结果.
HFONT g_hfFont = GetStockObject(DEFAULT_GUI_FONT);
COLORREF g_rgbText = RGB(0, 0, 0);
void DoSelectFont(HWND hwnd)
{
CHOOSEFONT cf = {sizeof(CHOOSEFONT)};
LOGFONT lf;
GetObject(g_hfFont, sizeof(LOGFONT), &lf);
cf.Flags = CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
cf.hwndOwner = hwnd;
cf.lpLogFont = &lf;
cf.rgbColors = g_rgbText;
if(ChooseFont(&cf))
{
HFONT hf = CreateFontIndirect(&lf);
if(hf)
{
g_hfFont = hf;
}
else
{
MessageBox(hwnd, "Font creation failed!", "Error", MB_OK | MB_ICONEXCLAMATION);
}
g_rgbText = cf.rgbColors;
}
}
这个调用中的hwnd就是你要作为调用这个字体对话框的父窗口.
最简单使用这个对话框的方式就是和一个已有的LOGFONT结构体一起使用,一般来讲就是你当前使用的HFONT的那个.我们把这个结构体中的lpLogFont成员指向我们刚刚填充过我们当前消息的LOGFONT,并加上CF_INITTOLOGFONTSTRUCT标志以使ChooseFont()知道用这个成员.CF_EFFECTS告诉ChooseFont()要允许用戶选择顏色和下划線和刪除線的属性.
更怪的是,黑体和斜体不算效果,而被看作字体的一部分,事实上有些字体只有黑体和斜体.如果你想检查或是阻止用戶选择一个黑体或斜体的字体你可以在用戶作出选择后分別检查LOGFONT中的lfWeight和lfItalic成员.你就可以在此提醒用戶更改选择或是做出別的什么操作对成员变量进行一些更改,再去调用CreateFontIndirect().
字体的顏色沒有跟HFONT关联,所以要单独存储,CHOOSEFONT 结构体中的rgbColors成员就可以用来传入初始的顏色,也可以用来获取新的顏色.
CF_SCREENFONTS指示我们要把字体设计为在屏幕上显示,而不是在打印机上使用.
有些字体都支持,有些只支持一种.根据你想要用字体的目的,还有很多用来限制用戶选择字体的各种标志,可以在MSDN中查到.
选择顏色
为了使用戶可以选择字体的顏色,或是让他为所有的东西指定一个顏色,还有一个ChooseColor()通用对话框.
这是样例程序中用来允许用戶来选择背景色的代码.
COLORREF g_rgbBackground = RGB(255, 255, 255);
COLORREF g_rgbCustom[16] = {0};
void DoSelectColour(HWND hwnd)
{
CHOOSECOLOR cc = {sizeof(CHOOSECOLOR)};
cc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR;
cc.hwndOwner = hwnd;
cc.rgbResult = g_rgbBackground;
cc.lpCustColors = g_rgbCustom;
if(ChooseColor(&cc))
{
g_rgbBackground = cc.rgbResult;
}
}
这应该相当直观,我们这里的hwnd也是对话框的父窗口.CC_RGBINIT参数说用了要用我们用rgbResult成员传入的顏色来开始,这个成员也是我们可以在对话框关闭后获取用戶选择的顏色的地方.
g_rgbCustom是16个COLORREF的数组,用来存储用戶在对话框中決定的放入自定义格子中的值.
你可以把这些值存在一些比如注冊表的位置,不然当你程序关闭的时候它们就丟失了. 这个参数不是可选的(要填).
控件字体
还有,你可能想使你的窗口或是对话框上的控件来在某个时候更改字体.
这如我们在前面的例子中用CreateWindow()来创建控件一样.如同系统默认用的窗口控件一样,我们用WM_SETFONT来设置一个新的字体的句柄(来自于GetStockObject())以便在控件中用.
你也可以对你用CreateFont()创建的字体用这个方法. 把字体句柄传入wParam并把lParam设为TRUE让控件重绘就行了.
我曾在前面的例子中做过这样的操作,但是值得在这再提一下,因为关系紧密,也比较短:
SendDlgItemMessage(hwnd, IDC_OF_YOUR_CONTROL, WM_SETFONT, (WPARAM)hfFont, TRUE);
hfFont当然是你要用的HFONT,IDC_OF_YOUR_CONTROL就是你要更改字体的那个控件的标识.
|