本节我们将通过实例来说明不同国家的语言、字符集和字体之间的差异,以及Windows系统是如何处理的。
本节必须掌握的知识点:
第31练:显示键盘消息
非英语键盘问题
字符集和字体
第32练:显示默认字体信息
第33练:创建逻辑字体
5.4.1 第31练:显示键盘消息
/*------------------------------------------------------------------
031 WIN32 API 每日一练
第31个例子:显示键盘按键消息
SetBkMode函数
GetKeyNameText函数
ScrollWindow函数
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("KEYVIEW1.C") ;
…(略)
return msg.wParam ;
}
//窗口过程
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar;
static int cLinesMax, cLines;
TEXTMETRIC tm;
static RECT rectScroll;
static PMSG pmsg;
HDC hdc;
PAINTSTRUCT ps;
static TCHAR szTop[] = TEXT("Message Key Char Repeat Scan Ext ALT Prev Tran");
static TCHAR szUnd[] = TEXT("_______ ___ ____ ______ ____ ___ ___ ____ ____");
static TCHAR* szFormat[2] = {
TEXT("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),
TEXT("%-13s 0X%04X%1s%c %6u %4d %3s %3s %4s %4s") };
TCHAR szBuf[128], szKeyName[32];
static TCHAR* szMessage[] = {
TEXT("WM_KEYDOWN"), TEXT("WM_KEYUP"),
TEXT("WM_CHAR"), TEXT("WM_DEADCHAR"),
TEXT("WM_SYSKEYDOWN"), TEXT("WM_SYSKEYUP"),
TEXT("WM_SYSCHAR"), TEXT("WM_SYSDEADCHAR") };
int iType;
static TCHAR* szYes = TEXT("Yes");
static TCHAR* szNo = TEXT("No");
static TCHAR* szDown = TEXT("Down");
static TCHAR* szUp = TEXT("Up");
switch (message)
{
case WM_CREATE:
case WM_DISPLAYCHANGE://显示器分辨率改变时,此消息仅发送到顶级窗口
//获得最大的客户区
cxClientMax = GetSystemMetrics(SM_CXMAXIMIZED);
cyClientMax = GetSystemMetrics(SM_CYMAXIMIZED);
//获得等宽字体的大小
hdc = GetDC(hwnd);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));//选入系统等宽字体
GetTextMetrics(hdc, &tm);//检索字体文本的度量信息
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
ReleaseDC(hwnd, hdc);
//为消息数组分配内存
if (pmsg) free(pmsg);
cLinesMax = cyClientMax / cyChar;
pmsg = malloc(cLinesMax* sizeof(MSG));
cLines = 0;
//return 0 ; //继续执行WM_SIZE
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
//计算滚动窗口的范围
rectScroll.top = cyChar; //第二行开始(因第1行为标题)
rectScroll.left = 0;
//不等于cyClient,应等于每行高度*行数。
rectScroll.bottom = cyChar*(cyClient / cyChar);
rectScroll.right = cxClient;
//重绘,该行不可删除,可能是WM_INPUTLANGCHANGE或WM_DISPLAYCHANGE引起
InvalidateRect(hwnd, NULL, TRUE);
return 0;
case WM_KEYDOWN:
case WM_KEYUP:
case WM_CHAR:
case WM_DEADCHAR:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_SYSCHAR:
case WM_SYSDEADCHAR:
//重新安排消息数组
for (int i = cLinesMax - 1; i > 0; i--)
{
pmsg[i] = pmsg[i - 1];
}
//把当前消息存入消息数组的首元素
pmsg[0].message = message;
pmsg[0].hwnd = hwnd;
pmsg[0].wParam = wParam;
pmsg[0].lParam = lParam;
cLines = min(cLines + 1, cLinesMax); //每按一个按键,增加一行。
//滚屏
ScrollWindow(hwnd, 0, -cyChar, &rectScroll, &rectScroll);
//return 0; //不直接return,因为系统击键消息还要调用DefWindowProc处理
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
SetBkMode(hdc, TRANSPARENT);//背景模式---透明模式
TextOut(hdc, 0, 0, szTop, lstrlen(szTop));
TextOut(hdc, 0, 0, szUnd, lstrlen(szUnd));
//显示消息数组的内容
for (int i = 0; i < min(cLines, cyClient / cyChar - 1); i++)
{
iType = pmsg[i].message == WM_CHAR ||
pmsg[i].message == WM_SYSCHAR ||
pmsg[i].message == WM_DEADCHAR ||
pmsg[i].message == WM_SYSDEADCHAR;
//检索表示键名称的字符串
GetKeyNameText(pmsg[i].lParam, szKeyName, sizeof(szKeyName) /
sizeof(TCHAR));
int iLen = wsprintf(szBuf, szFormat[iType],
szMessage[pmsg[i].message - WM_KEYFIRST], //第1个参数,消息名称
//第2个参数,击键时虚拟键代码,字符消息时显示字符的十六进制
pmsg[i].wParam,
//第3个参数字符消息为 “”,击键名称
(PTSTR)(iType ? TEXT(" ") : szKeyName),
//第4个参数:字符消息时,显示字符本身
(TCHAR)(iType ? pmsg[i].wParam : ' '),
LOWORD(pmsg[i].lParam), //第5个参数,重复次数
//第6个参数,扫描码在第16-23位,共8位
HIWORD(pmsg[i].lParam) & 0xFF,
//第7个参数:扩展标记,在第24位
0x01000000 & pmsg[i].lParam ? szYes : szNo
//WM_SYSKEYUP和 WM_SYSKEYDOWN消息的ALT标记位始终为1,而WM_KEYUP
//和WM_KEYDOWN消息的此 位始终为0。
//某些非英语的键盘上,一些字符是通过Shift键、Ctrl键或Alt组合
//键。内容代码被设置为1,但消息并不是系统击键消息
//第8个参数:ALT标记,在第29位
0x20000000 & pmsg[i].lParam ? szYes : szNo,
//第9个参数:先前状态,在第30位
0x40000000 & pmsg[i].lParam ? szDown : szUp,
//第10个参数:转换状态,在第31位
0x80000000 & pmsg[i].lParam ? szUp : szDown);
TextOut(hdc, 0, (cyClient / cyChar - 1 - i)*cyChar, szBuf, iLen);
}
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
if (pmsg) free(pmsg);
pmsg = NULL;
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
/******************************************************************************
SetBkMode函数:指定设备上下文的背景混合模式。背景混合模式用于非实线的文本,阴影画笔和笔样式。
int SetBkMode(
HDC hdc,
int mode //后台模式-透明或不透明
);
*******************************************************************************
GetKeyNameText函数:检索表示键名称的字符串
int GetKeyNameTextA(
LONG lParam, //要处理的键盘消息的第二个参数
LPSTR lpString, //保存按键名称的字符缓冲区
int cchSize //键名的最大长度(以字符为单位),包括终止的空字符
);
*******************************************************************************
ScrollWindow函数:滚动指定窗口的客户区的内容
BOOL ScrollWindow(
HWND hWnd,
int XAmount,//指定设备水平滚动的数量
int YAmount,//指定设备垂直滚动的数量
const RECT *lpRect,//指向RECT结构的指针,该结构指定要滚动的客户区域的一部分
const RECT *lpClipRect//指向包含裁剪矩形坐标的RECT结构的指针 。裁剪矩形内的仅设备位会受到影响。
//从矩形的外部滚动到内部的位被绘制;从矩形内部滚动到外部的位不会绘制。
);
*/
运行结果:
图5-4 显示键盘按键消息
总结
KEYVIEW1程序展示了它的窗口过程接收到的每一个按键和字符消息的内容。它将此消息存储在MSG结构数组中。数组的大小基于最大化窗口的大小耜等宽的系统字体。如果用户在程序运行的时候调整视频显示的大小(在此种情况下,KEYVIEW1程序会收到一个WM_DISPLAYCHANGE消息),该数组将被重新产生。KEYVIEW1程序采用标准C的 malloc函数为此数组分配内存。
KEYVIEW1程序的屏幕显示。第一列显示了键盘消息。第二列显示了击键消息的虚拟键代码和键名称。这是使用GetKeyNameText 函数获得的。第三列(标注为“Char”)显示了字符消息的十六进制字符代码和字符本身,剩下的六列显示了 IParam消息参数的6个字段。
为便于分栏显示该消息,KEYVIEW1程序采用了等宽字体。像上一章中讨论的那样,这需要调用 GetStockObject 和 SelectObject 函数:
SelectObject(hdc, GetStockObject(SYSTEM_FXXED_FONT));
为了辨识该九列数据,KEYVIEW1在客户区的上部加了标题。标题加有下划线。虽然可以产生带下划线的字体,但此处采用了不同的方法。我定义了两个字符串变量,分别为 szTop(含有文字)和szUnd(含有下划线),然后在处理WM_PAINT消息时,同时在窗口上部的同一位置显示两个字符串。通常,Windows以“opaque”(不透明)模式显示文字,这意味 着Windows在显示字符的时候抹去了字符背景。此处将导致第二个字符串(szUnd)抹去第一个字符串(szTop)。为了阻止它的发生,转换设备环境到“TRANSPARENT”(透明)模式:
SeCBkMode(hdc, TRANSPARENT);
这种加下划线的方法只有在使用等宽字体时才可行。否则,这种在字符下面的下划线将不会和字符等宽。
【注意】ScrollWindow函数并非一个GDI绘图函数,它的第一个函数是窗口句柄,而不是设备环境句柄。ScrollWindow函数是通过移动窗口坐标实现窗口滚动效果的。
5.4.2 非英语键盘问题
Windows 系统提供了不同国家和语言版本的字符集和键盘布局解决方案,以支持各种语言和输入需求。以下是针对不同国家语言版本的字符集和键盘布局解决方案的一般指导:
■字符集(Code Page):Windows 使用字符集来映射字符与数字代码之间的关系。每个国家或地区的语言版本通常都有相应的字符集,用于表示该语言版本中使用的字符。在 Windows 中,可以通过以下步骤更改字符集:
●在 Windows 10 中,打开“设置”应用程序,选择“时间和语言”,然后在“区域和语言”部分选择“语言首选项”。
●在“首选语言”部分,选择你所使用的语言,然后点击“选项”按钮。
●在“区域设置”选项卡中,选择正确的字符集并应用更改。
■键盘布局:Windows 支持多种键盘布局,以适应不同国家和地区的键盘输入需求。可以根据自己的键盘布局选择进行设置。在 Windows 中,可以通过以下步骤更改键盘布局:
●在 Windows 10 中,打开“设置”应用程序,选择“时间和语言”,然后在“区域和语言”部分选择“语言首选项”。
●在“首选语言”部分,选择你所使用的语言,然后点击“选项”按钮。
●在“键盘”选项卡中,选择正确的键盘布局并应用更改。
■语言包(Language Pack):对于某些语言版本,Windows 提供了相应的语言包,用于提供更全面的本地化支持,包括界面翻译、日期/时间格式、货币符号等。可以通过以下步骤添加语言包:
●在 Windows 10 中,打开“设置”应用程序,选择“时间和语言”,然后在“区域和语言”部分选择“语言首选项”。
●在“首选语言”部分,点击“添加语言”按钮,选择你所需的语言并安装相应的语言包。
请注意,具体的字符集、键盘布局和语言包选项可能因 Windows 版本和具体的语言版本而略有不同。上述步骤是基于 Windows 10 的一般指导,实际操作时可能会有细微差异。
通过正确配置字符集、键盘布局和语言包,可以使 Windows 系统适应不同国家和语言版本的需求,并提供更好的本地化支持。
不论是在Windows英语版本上安装俄语或希腊语键盘布局然后运行KEYVTEW1,或者是在Windows希腊语版本上安装俄语或德语键盘布局然后运行KEYVTEW1,还有是在Windows俄语版本上安装德语、俄语或希腊语键盘布局后运行KEYVTEW1,都会显示不正确字符。
5.4.3 字符集和字体
■字符集
在 Windows 系统中,字符集(Character Set)用于定义字符与数字代码之间的映射关系,以便正确表示和处理不同字符。Windows 支持多种字符集,包括以下常见的字符集:
●ASCII(American Standard Code for Information Interchange):ASCII 是最早的字符集之一,用于表示英文字母、数字和一些特殊字符。它使用 7 位编码,共包含 128 个字符。
●Unicode:Unicode 是一种全球通用的字符编码标准,旨在支持几乎所有语言和字符。它使用 16 位或 32 位编码,可以表示超过 100 万个字符。在 Windows 中,常用的 Unicode 编码方案是 UTF-16(16 位 Unicode 转换格式),它使用 16 位编码来表示字符。在VS编译器中默认的就是Unicode字符集。
●UTF-8(Unicode Transformation Format 8-bit):UTF-8 是一种变长编码方案,用于表示 Unicode 字符。它可以使用 1 到 4 个字节来编码字符,可以表示所有 Unicode 字符。UTF-8 在互联网上广泛使用,因为它兼容 ASCII 字符集,并且可以节省存储空间。
●区域特定字符集:Windows 还支持各种区域特定的字符集,用于表示特定语言或地区的字符。例如,GBK(简体中文字符集)、Big5(繁体中文字符集)、Shift_JIS(日文字符集)等。
通过使用适当的字符集,Windows 可以正确地解析和显示各种字符。Unicode 已成为广泛使用的标准字符集,使得在 Windows 系统中支持多种语言和字符变得更加方便。对于大多数情况,使用 Unicode 字符集(如 UTF-8 或 UTF-16)是推荐的做法,以便在不同语言之间无缝交互和显示字符。
【注意】选择正确的字符集对于文本处理、编程和跨语言交互等方面都至关重要。在开发应用程序或进行国际化工作时,确保理解和正确处理字符集是必要的。
■字体
在 Windows 系统中,字体(Font)用于确定字符的外观、样式和形状,以便以可视化方式显示字符。Windows 提供了许多内置字体,并且还支持安装和使用其他字体。Windows支持3种字体——位图字体(bitmap fonts)、矢量字体(vector founts)和(Windows 3.1 中开始采用的)TrueType 字体。
●矢量字体其实已经过时了。这些字体中的字符由简单的线条组成,但这些线条没有定义填充区域。矢量字体可以缩放至任意大小,但字符看起来有些单薄。
●TrueType字体是由填充区域来定义字符的轮廓字体。TrueType字体是可缩放的,而且 字体定义中包含的“提示”信息能避免造成难看的或不可读的文本的舍入问题。Windows 利用TrueType字体达到了真正的所见即所得(what you see is what you get, WYSIWYG),使 得在显示器上显示的字体和打印机输出的字体是准确匹配的。
●位图字体中,每一个字符是由对应于显示器的像素的一组位值定义的。位图字体可以放大到较大的尺寸,但字体看起来有锯齿。在设计位图字体时通常会考虑到它在显示器上的效果,所以一般在显示器上看起来很舒服。因此,Windows在标题栏、菜单、按钮和对话框中使用位图字体。
你在默认的设备环境中得到的位图字体称为系统字体。你可通过调用带有 SYSTEM_FONT标识符的GetStockObject函数获得此字体的句柄。KEYVIEW1程序选择使用系统字体的等宽字体版本,用SYSTEM_FIXED_FONT表示。GetStockObject函数的另一个选择是 OEM_FIXED_FONT。
对许多标准控件和用户界面组件来讲,Windows不采用系统字体,而采用字体名为 MS Sans Serif的字体(MS代表微软)。这是一种位图字体。名为SSERIFE.FON的文件包含用于分辨率为96dpi的视频显示的字体,字号为8、 10、12、14、18和24磅。你可在 GetStockObject中采用DEFAULT_GUI_FONT标识符获得此字体。Windows使用的字号大小取决于你在控制而板的【显示】程序中设置的显示分辨率。
到目前为止,我己经提到了可在GetStockObject中使用的四种标识符,你能用它们获得在设备环境中使用的字体。还有其他三种标识符:ANSI_FIXED_FONT , ANSI_VAR_FONT和DEVICE_DEFAULT_FONT。
除了这些常见的字体外,Windows 还提供其他字体,如宋体、黑体、楷体等,用于支持中文字符的显示。
此外,用户还可以从互联网或其他来源下载和安装各种字体。安装新字体后,它们将在应用程序中可用,并可以在文档、网页设计等方面使用。
【注意】在选择字体时,应考虑所需的语言和字符集。确保选择支持所需字符集和语言的字体,以便正确显示和呈现文本。
5.4.4 第32练:显示默认字体信息
/*------------------------------------------------------------------
032 WIN32 API 每日一练
第32个例子STOKFONT.C:显示7种备用字体信息
SetTextAlign函数
GetTextFace函数
WM_DISPLAYCHANGE消息
注:将VS字符集改为“使用多字节字符集”,否则字体显式为中文乱字符
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("STOKFONT.C") ;
…(略)
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static struct
{
int idStockFont;
TCHAR * szStockFont;
}
//7种备用字体
stockfont [] = { OEM_FIXED_FONT, TEXT("OEM_FIXED_FONT"),
ANSI_FIXED_FONT, "ANSI_FIXED_FONT",
ANSI_VAR_FONT, "ANSI_VAR_FONT",
SYSTEM_FONT, "SYSTEM_FONT",
DEVICE_DEFAULT_FONT,"DEVICE_DEFAULT_FONT",
SYSTEM_FIXED_FONT,"SYSTEM_FIXED_FONT",
DEFAULT_GUI_FONT,"DEFAULT_GUI_FONT" };
static int iFont,cFonts = sizeof stockfont / sizeof stockfont[0];
HDC hdc;
PAINTSTRUCT ps;
int i,x,y,cxGrid,cyGrid;
TCHAR szFaceName[LF_FACESIZE],szBuffer[LF_FACESIZE + 64];
TEXTMETRIC tm;
switch (message)
{
case WM_CREATE:
//滚动条的范围0~6
SetScrollRange(hwnd,SB_VERT,0,cFonts-1,TRUE);
return 0;
//显示分辨率更改后,WM_DISPLAYCHANGE消息将发送到所有窗口
case WM_DISPLAYCHANGE:
InvalidateRect(hwnd,NULL,TRUE);//改变屏幕分辨率后重绘窗口
return 0;
case WM_VSCROLL:
switch (LOWORD(wParam))
{
case SB_TOP : iFont = 0; break;
case SB_BOTTOM : iFont = cFonts - 1; break;
case SB_PAGEUP :
case SB_LINEUP : iFont -= 1; break;
case SB_PAGEDOWN :
case SB_LINEDOWN : iFont += 1; break;
case SB_THUMBPOSITION : iFont = HIWORD(wParam); break;
}
iFont = max(0,min(cFonts-1,iFont));//滚动范围检测
SetScrollPos(hwnd,SB_VERT,iFont,TRUE);
InvalidateRect(hwnd,NULL,TRUE);
return 0;
case WM_KEYDOWN :
switch(wParam)
{
case VK_HOME :SendMessage(hwnd,WM_VSCROLL,SB_TOP,0); break;
case VK_END :SendMessage(hwnd,WM_VSCROLL,SB_BOTTOM,0); break;
case VK_PRIOR : //Page Up 键
case VK_LEFT :
case VK_UP :SendMessage(hwnd,WM_VSCROLL,SB_LINEUP,0); break;
case VK_NEXT :
case VK_RIGHT :
case VK_DOWN :SendMessage(hwnd,WM_VSCROLL,SB_PAGEDOWN,0); break;
}
return 0 ;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
//滚动条滑块位置iFont = 0~6
//设置字体
SelectObject(hdc, GetStockObject(stockfont[iFont].idStockFont));
GetTextFace(hdc, LF_FACESIZE, szFaceName); //获取字体的字体名称
GetTextMetrics(hdc, &tm); //获取字体信息
//2倍字符平均宽度+3倍字符最大宽度
cxGrid = max(3 * tm.tmAveCharWidth, 2 * tm.tmMaxCharWidth);
cyGrid = tm.tmHeight + 3;
TextOut(hdc, 0, 0, szBuffer,
wsprintf(szBuffer, TEXT(" %s: Face Name = %s, CharSet = %i"),
stockfont[iFont].szStockFont, //字体
szFaceName, tm.tmCharSet)); //字体名称和字符集
//设置文本对齐方式:向上、居中对齐
SetTextAlign(hdc, TA_TOP | TA_CENTER);
// 垂线和水平线---画表格
for (i = 0; i < 17; i++)
{
MoveToEx(hdc, (i + 2) * cxGrid, 2 * cyGrid, NULL);
LineTo(hdc, (i + 2) * cxGrid, 19 * cyGrid);
MoveToEx(hdc, cxGrid, (i + 3) * cyGrid, NULL);
LineTo(hdc, 18 * cxGrid, (i + 3) * cyGrid);
}
// 垂直和水平标题
for (i = 0; i < 16; i++)
{
TextOut(hdc, (2 * i + 5) * cxGrid / 2, 2 * cyGrid + 2, szBuffer,
wsprintf(szBuffer, TEXT("%X-"), i));
TextOut(hdc, 3 * cxGrid / 2, (i + 3) * cyGrid + 2, szBuffer,
wsprintf(szBuffer, TEXT("-%X"), i));
}
//输出字符
for (y = 0; y < 16; y++)
for (x = 0; x < 16; x++)
{
TextOut(hdc, (2 * x + 5) * cxGrid / 2,
(y + 3) * cyGrid + 2, szBuffer,
wsprintf(szBuffer, TEXT("%c"), 16 * x + y));
}
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
/******************************************************************************
GetTextFace函数:检索被选择转换为指定的设备上下文的字体的字体名称。
int GetTextFaceA(
HDC hdc, //设备上下文的句柄
int c, //指向的缓冲区的长度。对于ANSI函数,它是一个BYTE计数,对于Unicode函数,它是一个WORD计数。
LPSTR lpName //指向接收字体名称的缓冲区的指针。如果此参数为NULL,则该函数返回名称中的字符数,包括终止的空字符。
);
*******************************************************************************
WM_DISPLAYCHANGE消息:显示分辨率更改后,WM_DISPLAYCHANGE消息将发送到所有窗口。
窗口通过其WindowProc函数接收此消息。
参数wParam:显示器的新图像深度,以每像素位数为单位。
lPAram:低位字指定屏幕的水平分辨率。
高位字指定屏幕的垂直分辨率。
备注
此消息仅发送到顶级窗口。
*******************************************************************************
SetTextAlign函数:用于设置设备环境(Device Context)中文本输出的对齐方式。
UINT SetTextAlign(
HDC hdc,
UINT align
);
参数说明:
hdc:设备环境句柄(Device Context Handle),用于标识要设置文本对齐方式的设备环境。
align:对齐方式的标志,可以是以下值的组合:
TA_LEFT:左对齐。
TA_RIGHT:右对齐。
TA_CENTER:居中对齐。
TA_TOP:顶部对齐。
TA_BOTTOM:底部对齐。
TA_BASELINE:基线对齐。
返回值:
如果函数调用成功,返回值为先前的文本对齐方式。可以使用 GetTextAlign 函数获取先前的对齐方式。
如果函数调用失败,返回值为 GDI_ERROR。
*/
运行结果:
图5-5 显示7种默认字体信息
总结
上述实例在窗口过程中首先预定义了7种备用字体的结构数组,包含字体的ID和字面名称。然后WM_CREATE消息中这种窗口滚动范围(主程序CreateWindow添加窗口滚动条)。
WM_DISPLAYCHANGE消息:显示分辨率更改后,WM_DISPLAYCHANGE消息将发送到所有窗口。窗口过程添加WM_DISPLAYCHANGE消息的处理,将窗口客户区设置为无效区域重绘。这样处理使得程序更加严谨。
接下来就是滚动条及其键盘接口的消息处理。根据滚动条的变化重新设置窗口滚动条的位置。
【注意】检测滚动位置是否超出滚动范围:
iFont = max(0,min(cFonts-1,iFont));//滚动范围检测
WM_PAINT消息处理:根据滚动条的位置选入备用字体数组中对应的字体。调用GetTextMetrics函数获取字体信息,并将字体宽度重新设置为2倍字符平均宽度+3倍字符最大宽度,字体高设置为m.tmHeight + 3。接着由3个for循环语句输出0~255共计256个字符表格。调用函数SetTextAlign将表格内容格式设置为向上、居中对齐。
【注意】上述实例只支持ANSI字符集,需要将VS字符集改为“使用多字节字符集”,否则标题栏的中文字符字体字面名称将显式为中文乱字符。
5.4.5 第33练:创建逻辑字体
/*------------------------------------------------------------------
033 WIN32 API 每日一练
第33个例子KEYVIEW2.C:创建逻辑字体
CreateFont函数
WM_INPUTLANGCHANGE消息
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("KeyView2") ;
…(略)
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static DWORD dwCharSet = DEFAULT_CHARSET;//默认字符集
static cxClientMax,cyClientMax,cxClient,cyClient,cxChar,cyChar;
static int cLinesMax,cLines;
static PMSG pmsg;
static RECT rectScroll;
static TCHAR szTop[] = TEXT("Message Key Char ")
TEXT(" Repeat Scan Ext ALT Prev Tran");
static TCHAR szUnd[] = TEXT("_______ ___ ____ ")
TEXT(" ______ ____ ___ ___ ____ ____");
static TCHAR * szFormat[2] = {
TEXT("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),
TEXT("%-13s 0x%04X%1s%c %6u %4d %3s %3s %4s %4s")
};
static TCHAR * szYes = TEXT("Yes");
static TCHAR * szNo = TEXT("No");
static TCHAR * szDown = TEXT("Down");
static TCHAR * szUp = TEXT("Up");
static TCHAR * szMessage[] = {
TEXT("WM_KEYDOWN"), TEXT("WM_KEYUP"),
TEXT("WM_CHAR"), TEXT("WM_DEADCHAR"),
TEXT("WM_SYSKEYDOWN"),TEXT("WM_SYSKEYUP"),
TEXT("WM_SYSCHAR"), TEXT("WM_SYSDEADCHAR")
};
HDC hdc;
PAINTSTRUCT ps;
int i,iType;
TCHAR szBuffer[128],szKeyName[32];
TEXTMETRIC tm;
switch (message)
{
//用于通知应用程序输入语言或输入法的变化。当用户在系统中切换输入语言或输入
//法时,系统会发送 WM_INPUTLANGCHANGE 消息给相关的窗口。
case WM_INPUTLANGCHANGE:
dwCharSet = wParam;//当前字体字符集
//此处不能return 0;
case WM_CREATE:
case WM_DISPLAYCHANGE://更改显示器配置
//获取客户区最大尺寸
cxClientMax = GetSystemMetrics(SM_CXMAXIMIZED);
cyClientMax = GetSystemMetrics(SM_CYMAXIMIZED);
hdc = GetDC(hwnd);
//创建逻辑字体
SelectObject(hdc,CreateFont(0,0,0,0,0,0,0,0,dwCharSet,
0,0,0,FIXED_PITCH,NULL));//固定字宽
//获取等宽字体信息
GetTextMetrics(hdc,&tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
DeleteObject(SelectObject(hdc,
GetStockObject(SYSTEM_FONT)));//删除逻辑字体
ReleaseDC(hwnd,hdc);
if(pmsg)
free(pmsg);//清空消息结构
cLinesMax = cxClientMax / cyChar;
pmsg = malloc(cLinesMax*sizeof(MSG));
cLines = 0;
//未处理malloc异常
return 0;
case WM_SIZE:
if (message == WM_SIZE)
{
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
}
//滚屏
rectScroll.left = 0;
rectScroll.right = cxClient;
rectScroll.top = cyChar;
rectScroll.bottom = cyChar * (cyClient / cyChar);
InvalidateRect(hwnd,NULL,TRUE);
return 0 ;
case WM_KEYDOWN:
case WM_KEYUP:
case WM_CHAR:
case WM_DEADCHAR:
case WM_SYSCHAR:
case WM_SYSDEADCHAR:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
//重新调整数组存储顺序
for (i = cLinesMax - 1;i > 0;i--)
{
pmsg[i] = pmsg[i-1];
}
//存储新的消息
pmsg[0].hwnd = hwnd;
pmsg[0].message = message;
pmsg[0].lParam = lParam;
pmsg[0].wParam = wParam;
cLines = min(cLines + 1,cLinesMax);
//向上滚动显示
ScrollWindow(hwnd,0,-cyChar,&rectScroll,&rectScroll);
break;//不要使用return 返回。调用DefWindowProc,这样系统消息才能工作
case WM_PAINT:
hdc = BeginPaint(hwnd,&ps);
//创建逻辑字体---默认等宽字体
SelectObject(hdc,
CreateFont(0,0,0,0,0,0,0,0,dwCharSet,0,0,0,FIXED_PITCH,NULL));
SetBkMode(hdc,TRANSPARENT);//设置透明模式
TextOut(hdc,0,0,szTop,lstrlen(szTop));
TextOut(hdc,0,0,szUnd,lstrlen(szUnd));
for (i = 0;i < min(cLines,cyClient / cyChar - 1);i++)
{
iType = pmsg[i].message == WM_CHAR ||
pmsg[i].message == WM_SYSCHAR ||
pmsg[i].message == WM_DEADCHAR ||
pmsg[i].message == WM_SYSDEADCHAR;
//获取按键字符名
GetKeyNameText(pmsg[i].lParam, szKeyName,
sizeof(szKeyName) / sizeof(TCHAR));
TextOut(hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,
wsprintf(szBuffer, szFormat[iType],
szMessage[pmsg[i].message - WM_KEYFIRST],
pmsg[i].wParam,
(PTSTR)(iType ? TEXT(" ") : szKeyName),//
(TCHAR)(iType ? pmsg[i].wParam : ' '),
LOWORD(pmsg[i].lParam),
HIWORD(pmsg[i].lParam) & 0xFF,
0x01000000 & pmsg[i].lParam ? szYes : szNo,
0x20000000 & pmsg[i].lParam ? szYes : szNo,
0x40000000 & pmsg[i].lParam ? szDown : szUp,
0x80000000 & pmsg[i].lParam ? szUp : szDown));
}
//删除逻辑字体
DeleteObject(SelectObject(hdc,GetStockObject(SYSTEM_FONT)));
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
/***********************************************************************
CreateFont函数:创建具有指定特性的逻辑字体。随后可以将逻辑字体选择为任何设备的字体。
HFONT CreateFont(
int nHeight, // 字体的字符高度
int nWidth, // 字体的字符宽度
int nEscapement, // 字体的字符倾斜角度
int nOrientation, // 字体的字符方向
int fnWeight, // 字体的粗细程度
DWORD fdwItalic, // 字体是否为斜体
DWORD fdwUnderline, // 字体是否有下划线
DWORD fdwStrikeOut, // 字体是否有删除线
DWORD fdwCharSet, // 字体的字符集
DWORD fdwOutputPrecision, // 字体的输出精度
DWORD fdwClipPrecision, // 字体的剪辑精度
DWORD fdwQuality, // 字体的质量
DWORD fdwPitchAndFamily, // 字体的字距和族
LPCTSTR lpszFace // 字体的名称
);
***************************************************************************
字符集
#define ANSI_CHARSET 0
#define DEFAULT_CHARSET 1
#define SYMBOL_CHARSET 2
#define SHIFTJIS_CHARSET 128
#define HANGEUL_CHARSET 129
#define HANGUL_CHARSET 129
#define GB2312_CHARSET 134
#define CHINESEBIG5_CHARSET 136
#define OEM_CHARSET 255
*******************************************************************************
WM_INPUTLANGCHANGE消息:当窗口接收到WM_INPUTLANGCHANGE消息时,表示输入法发生了改变。
并响应输入语言或输入法的变化,根据需要进行相应的处理,例如更新界面、重新布局等。
#define WM_INPUTLANGCHANGE 0x0051
wParam:该输入法使用的字符集。提示:可以使用TranslateCharsetInfo这个API得到字符集的信息。
lParam:该输入法的HKL(KeyboardLayout——键盘布局,也被称为 Input locale identifier —— 输入区域标识)。
*/
运行结果:
图5-6 创建逻辑字体
总结
实例KEYVIEW2.C是实例KEYVIEW1.C的扩展,在KEYVIEW1.C的基础上,增加了创建逻辑字体,并使用新创建的逻辑字体显示键盘按键信息。
■实例KEYVIEW2.C写的比较严谨。窗口过程处理WM_INPUTLANGCHANGE消息时,更新当前系统使用字符集:dwCharSet = wParam;//当前字体字符集。不需要返回,接着窗口过程处理WM_CREATE消息和WM_DISPLAYCHANGE消息,调用GetSystemMetrics函数获取当前窗口客户区的最大宽和高,然后调用CreateFont函数创建一个新的逻辑字体,并调用SelectObject函数将新创建的逻辑字体选入当前设备环境。最后调用GetTextMetrics函数获取已选入的新逻辑字体的字符宽和字符高。
■CreateFont 函数用于创建一个逻辑字体(Logical Font)对象,该对象描述了要在设备上绘制文本时使用的字体属性。
函数原型如下:
HFONT CreateFont(
int nHeight, // 字体的字符高度
int nWidth, // 字体的字符宽度
int nEscapement, // 字体的字符倾斜角度
int nOrientation, // 字体的字符方向
int fnWeight, // 字体的粗细程度
DWORD fdwItalic, // 字体是否为斜体
DWORD fdwUnderline, // 字体是否有下划线
DWORD fdwStrikeOut, // 字体是否有删除线
DWORD fdwCharSet, // 字体的字符集
DWORD fdwOutputPrecision, // 字体的输出精度
DWORD fdwClipPrecision, // 字体的剪辑精度
DWORD fdwQuality, // 字体的质量
DWORD fdwPitchAndFamily, // 字体的字距和族
LPCTSTR lpszFace // 字体的名称
);
●参数说明:
nHeight:字体的字符高度。可以使用正值、负值或零来指定字体的高度。正值表示像素高度,负值表示设备单位高度,零表示默认高度。
nWidth:字体的字符宽度。通常使用零作为默认值。
nEscapement:字体的字符倾斜角度(以 0.1 度为单位)。通常使用零作为默认值。
nOrientation:字体的字符方向(以 0.1 度为单位)。通常使用零作为默认值。
fnWeight:字体的粗细程度。可以是以下值之一:
FW_DONTCARE:不指定粗细程度。
FW_THIN:细字体。
FW_NORMAL:普通字体。
FW_BOLD:粗体。
其他可用的粗细程度值。
fdwItalic:字体是否为斜体。可以是 TRUE 表示斜体,或 FALSE 表示非斜体。
fdwUnderline:字体是否有下划线。可以是 TRUE 表示有下划线,或 FALSE 表示无下划线。
fdwStrikeOut:字体是否有删除线。可以是 TRUE 表示有删除线,或 FALSE 表示无删除线。
fdwCharSet:字体的字符集。可以是以下之一:
DEFAULT_CHARSET:默认字符集。
ANSI_CHARSET:ANSI 字符集。
SYMBOL_CHARSET:符号字符集。
其他可用的字符集值。
fdwOutputPrecision:字体的输出精度。通常使用 OUT_DEFAULT_PRECIS 作为默认值。
fdwClipPrecision:字体的剪辑精度。通常使用 CLIP_DEFAULT_PRECIS 作为默认值。
fdwQuality:字体的质量。可以是以下值之一:
DEFAULT_QUALITY:默认质量。
DRAFT_QUALITY:草稿质量。
PROOF_QUALITY:校对质量。
其他可用的质量值。
fdwPitchAndFamily:字体的字距和族。通常使用 DEFAULT_PITCH 作为默认值。
lpszFace:字体的名称。可以是字体的名称字符串,如 "Arial"、"Times New Roman" 等。
●返回值:
如果函数调用成功,返回值为创建的逻辑字体的句柄(HFONT)。
如果函数调用失败,返回值为 NULL。
使用 CreateFont 函数可以根据指定的参数创建一个逻辑字体对象,该对象可以用于在设备上绘制文本时指定字体的属性。创建的逻辑字体对象可以通过 SelectObject 函数选入设备环境中,从而在绘制文本时使用该字体。
■窗口过程处理WM_PAINT消息,同样创建并选入新的逻辑字体用于显示输出。