《Windows API每日一练》5.4 键盘消息和字符集
本节我们将通过实例来说明不同国家的语言、字符集和字体之间的差异,以及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消息,同样创建并选入新的逻辑字体用于显示输出。
![]()
相关文章:
《Windows API每日一练》5.4 键盘消息和字符集
本节我们将通过实例来说明不同国家的语言、字符集和字体之间的差异,以及Windows系统是如何处理的。 本节必须掌握的知识点: 第31练:显示键盘消息 非英语键盘问题 字符集和字体 第32练:显示默认字体信息 第33练:创建逻…...
【uniapp】uniapp开发微信小程序入门教程
HBuilderx中uniapp开发微信小程序入门教程 一、 环境搭建 1. HBuilderx下载安装 HBuilderx下载安装地址 2. 微信开发者工具下载安装 微信开发者工地址具下载安装 二、创建uniapp项目 选择:文件>新建>项目>uni-app 输入项目名称>选择默认模板>…...
Python爬虫项目集:豆瓣电影排行榜top250
关于整理日常练习的一些爬虫小练习,可用作学习使用。 爬取项目以学习为主,尽可能使用更多的模块进行练习,而不是最优解。 爬虫概要 示例python 库爬取模块request解析模块BeautifulSoup存储类型list(方便存入数据库)…...
34-Openwrt uhttpd与rpcd
uhttpd作为一个简单的web服务器,其代码量并不多,而且组织结构比较清楚。和其它网络服务器差不多,其main函数进行一些初始化(首先parse config-file,然后parse argv),然后进入一个循环࿰…...
uni app 树状结构数据展示
树状数据展示,可以点击item 将点击数据给父组件 ,满足自己需求。不喜勿喷,很简单可以根据自己需求改哈,不要问,点赞收藏就好。其实可以和上一篇文章uni app 自定义 带popup弹窗的input组件-CSDN博客结合使用ÿ…...
KVM在线yum源部署-centos 7
一、虚拟化简介 虚拟化就是操作系统里嵌套操作系统,一台服务器买回来,可能只是用作一个http服务,资源不能充分利用,而虚拟化的诞生有效解决了这个问题,以硬件资源上使用虚拟化,实现单硬件多系统,充分挖掘硬件性能,节能增效。同时通过多年的改进发展,虚拟化进化成云服务…...
TSF的服务发现与Consul有何区别?
TSF(腾讯服务框架)和Consul都是用于服务发现的工具,但它们在设计理念、功能特性、集成方式等方面存在一些区别。 ### 设计理念和目标 **Consul** 是一个开源的工具,用于服务发现、配置和分段。它提供了一种简单的方式来注册和发现服务,以及健康检查和键值存储功能。Consul…...
kotlin集合框架
1、集合框架的接口类型对比 2、不可变和可变List fun main() {// 不可变List - 不能删除或添加元素val intList: List<Int> listOf(1,2,3)intList.forEach{println(it) // 1 2 3}println("")// 可变List - 可以删除或添加元素val mutableList mutableListO…...
服务器(Linux系统的使用)——自学习梳理
root表示用户名 后是机器的名字 ~表示文件夹,刚上来是默认的用户目录 ls -a 可以显示出隐藏的文件 蓝色的表示文件夹 白色的是文件 ll -a 查看详细信息 total表示所占磁盘总大小 一般以KB为单位 d开头表示文件夹 -代表文件 后面得三组rwx分别对应管理员用户-组…...
竞赛选题 python+opencv+深度学习实现二维码识别
0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 pythonopencv深度学习实现二维码识别 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:3分工作量:3分创新点:3分 该项目较为新颖&…...
Java读取指定 JAR 包路径中的 git.properties 文件
Java读取指定 JAR 包路径中的 git.properties 文件 在上述代码中,首先打开 JAR 文件,获取 git.properties 文件的 JarEntry 对象,如果存在该条目,就获取其输入流进行后续的读取和处理。具体的读取和处理逻辑需要根据您的实际需求在…...
逻辑回归(Logistic Regression)及其在机器学习中的应用
🚀时空传送门 🔍逻辑回归原理📕Sigmoid函数🎈逻辑回归模型 📕损失函数与优化🎈损失函数🚀优化算法 🔍逻辑回归的应用场景🍀使用逻辑回归预测客户流失使用scikit-learn库实…...
【计算机视觉】人脸算法之图像处理基础知识【七】
直方图均衡化 直方图均衡化是一种常用的图像处理技术,用于改善图像的对比度,特别是在图像的细节被埋没在暗部或亮部区域时。通过重新分配图像的像素强度值,使得图像的整体对比度增强,从而让更多的细节变得可见。 import cv2 imp…...
家政预约小程序14权限配置
目录 1 创建用户2 创建角色3 启用登录4 实现退出总结 我们现在小程序端的功能基本开发好了,小程序开发好之后需要给运营人员提供管理后台,要分配账号、配置权限,我们本篇就介绍一下权限如何分配。 1 创建用户 在微搭中,用户分为内…...
解决 vue 项目一直出现 sockjs-node/info?t=问题
其实如果是在开发环境,应该是开发的时候网络环境变更导致,比如你切换无线网络,导致开发服务器的IP地址换了,这样开发服务器会不知道如何确定访问源。开发环境中关闭npm dev server,然后重新npm run serve重新构建服务环…...
麒麟信安系统关闭core文件操作
在使用麒麟信安系统时,如果应用程序运行过程中崩溃了,此时并不会导致内核崩溃,只会在tmp目录下产生崩溃数据,如下图 不过tmp目录下的分区容量有限,当崩溃的应用core文件过大时将会占用tmp空间,导致tmpfs分区…...
微信小程序轮播图
效果图 详情可见 微信小程序 参照:swiper | uni-app官网 代码: <!--轮播图-- > <swiper interval"2000" autoplay"true" circular"true" style"height: 300px;"><swiper-item style&qu…...
redisson WRONGPASS invalid username-password pair or user is disable
1、技术架构:若依微服务框架 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2021.1</version></dependency> <dependency><…...
QT拖放事件之一:初识拖放4大事件处理函数
0、拖放 两个动作,合在一起称之为拖放事件; 拖:就是拖着走; 放:就是拖着走,然后松开鼠标了,释放了,这就是放; 注意:放:拖着的东西要放在什么地方??? 假如,我将一个记事本拖着跑,然后放到一个Widget窗口上,那么为了使得Widget能感知相应的事件(拖着进入事件…...
使用Python进行数据可视化:从基础到高级
使用Python进行数据可视化:从基础到高级 数据可视化是数据分析过程中不可或缺的一部分,通过图形化的方式展示数据,可以更直观地发现数据中的趋势和模式。Python凭借其丰富的库和强大的功能,成为数据可视化的首选编程语言。本文将介绍数据可视化的基础概念、常用的Python库…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
