6.1键盘基础
键盘以消息的形式传递给窗口过程。键盘事件就是一个消息将不同类型信息传递给应用程序。
win有八种信息传递不同键盘事件,但我们可以忽略至少一半。
忽略键盘
简言之就是不用对所有键盘消息作出响应(如系统按键,一般带ALT)。同时对快捷键(如功能键,Ctrl+s)等,这些快捷键与程序菜单一起在程序资源描述文件中定义。Win将快捷键转换为菜单命令消息无需自行转换。
谁获得了焦点
键盘和其他硬件一样,被所有程序共享。接收键盘事件的窗口获得焦点,(有焦点的窗口为活动窗口或其衍生窗口)。从这里可以知道,子窗口一般不以成为活动窗口,只有为活动窗口衍生窗口时才会有输入焦点。
WM_SETFOCUS //指示窗口得到输入焦点
WM_KILLFOCUS //表示失去焦点
队列和同步
键盘消息按下时,win和键盘驱动程序将硬件扫描码转换为格式消息并初步保存于系统消息队列(非消息队列)。系统消息队列由win维护,是独立的消息队列。
过程:先保存于系统消息队列,再放入程序消息队列。这么做的主要原因是为了同步,如果输入速度大于处理按键速度,窗口焦点切换时,就会造成消息处理的混乱。
按键和字符
键盘事件的消息可以分为按键和字符两类。如A键,通常显示为小写a,但加组合键Ctrl Shift之后变成大写A。对于这种产生显示字符的按键,win发送键盘和字符消息。不产生字符的如shift、功能键等。win只产生按键消息。
6.2按键消息
系统按键与非系统按键
WM_SYSKEYDOWN
WM_SYSKEYUP
//通常带ALT,win处理所有该消息,传送默认窗口过程,当然可以自行处理后再放入默认//窗口过程
WM_KEYDOWN
WM_KEYUP
//通常不带ALT键,程序可使用或忽略,win本身不处理
这四类消息,wParam包含虚拟键码,表示按下或释放的键,lParam包含按键其他数据
虚拟键码
wParam参数中保存了虚拟键码,之所以不采用真实键码的原因是为了设备无关性。(早期的扫描码依键盘实际布局,过于与设备相关)
大多虚拟键码定义在WINUSER.H头文件中,VK开头
VK_LBUTTON //鼠标左键
VK_TAB //Tab
VK_SHIFT //shift
//程序一般不要监视Shift\ALT等键,其他按键用时查问
lParam信息
包含了按键的其他信息。如下为lParam的32位六个字段。
Repeat Count:记录按键次数,通常设为1。显然UP消息计数为1。大于1说明按键速度大于程序处理能力。可以根据实际需要忽略或处理。
OEM扫描码:硬件产生的代码。除非要依赖实际键盘布局,程序可以忽略所有该信息。
扩充键标志:通常忽略。
内容代码:如同时按下ALT,内容代码为1。WM_SYSKEYUP\DOWN总为1。WM_KEYUP\DOWN总为0。
此外,窗口最小化时没有输入焦点。所有的按键都会产生WM_SYSKEYUP\ DOWN消息,此情况下如果没按下ALT,内容字段为0,这样使最小化窗口不处理这些按键。
对于一些非英文键盘,有些字符是shift等组合键产生的,这时内容代码为1,但是其是非系统按键。
键之前状态:键此前是释放的,则为0,还则为1。很明显UP为1,DOWN可以为1或0,为1表示该键自动重复。
转换状态:键正被按下则为0,反之为1。如UP为1,DOWN为零。
位移状态:确定是否按下位移键(shift ctrl Alt)和开关键。如GetKeyState(VK_SHIFT)确定键是否按下。
LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
//为窗口加上滚动条并处理按键消息
static INT cxClient,cyClient,cxChar,cyChar,MaxSize,cxCaps;//初始化之后不再改变,设为静态
INT iVerPos,iHorPos;//滑块水平和垂直位置
INT iPaintBeg,iPaintEnd; //绘画的起始范围
//TCHAR *szBuffer;
TCHAR szBuffer[10];
SCROLLINFO si;
TEXTMETRIC tm;
HDC hdc;
PAINTSTRUCT ps;
INT x,y,i;
switch (uMsg)
{
case WM_CREATE:
hdc=GetDC(hwnd);
GetTextMetrics(hdc,&tm);
cxChar=tm.tmAveCharWidth;//字体宽度
cyChar=tm.tmHeight+tm.tmInternalLeading;//字体高度加行间距
cxCaps=(tm.tmPitchAndFamily&1?3:2)*cxChar/2;//字体间距
ReleaseDC(hwnd,hdc);
MaxSize=40*cxChar+22*cxCaps; //设置水平最大宽度
return 0;
case WM_SIZE:
cxClient=LOWORD(lParam);
cyClient=HIWORD(wParam);
si.cbSize=sizeof(si);
si.fMask=SIF_RANGE|SIF_PAGE;
si.nMin=0;
si.nMax=NUMLINES-1;
si.nPage=cyClient/cyChar;
SetScrollInfo(hwnd,SB_VERT,&si,TRUE);//设置垂直滚动
si.cbSize=sizeof(si);
si.fMask=SIF_RANGE|SIF_PAGE;
si.nMin=0;
si.nMax=2 + MaxSize / cxChar;
si.nPage=cxClient/cxChar;
SetScrollInfo(hwnd,SB_HORZ,&si,TRUE);//设置水平滚动
return 0;
case WM_VSCROLL:
si.cbSize=sizeof(si);
si.fMask=SIF_ALL;
GetScrollInfo(hwnd,SB_VERT,&si);
iVerPos=si.nPos;
switch (LOWORD(wParam))
{
case SB_TOP:
si.nPos=si.nMin;
break;
case SB_BOTTOM:
si.nPos=si.nMax;
break;
case SB_LINEDOWN:
si.nPos+=1;
break;
case SB_LINEUP:
si.nPos-=1;
break;
case SB_PAGEDOWN:
si.nPos+=5;
break;
case SB_PAGEUP:
si.nPos-=5;
break;
case SB_THUMBTRACK:
si.nPos=si.nTrackPos;
break;
default:
break;
}
si.fMask=SIF_POS;
SetScrollInfo(hwnd,SB_VERT,&si,TRUE);
GetScrollInfo(hwnd,SB_VERT,&si);
if(si.nPos!=iVerPos)
{
ScrollWindow(hwnd,0,cyChar*(iVerPos-si.nPos),NULL,NULL);
UpdateWindow(hwnd);
}
return 0;
case WM_HSCROLL:
si.cbSize=sizeof(si);
si.fMask=SIF_ALL;
GetScrollInfo(hwnd,SB_HORZ,&si);
iHorPos=si.nPos;
switch (LOWORD(wParam))
{
case SB_LINELEFT:
si.nPos-=1;
break;
case SB_LINERIGHT:
si.nPos+=1;
break;
case SB_THUMBPOSITION:
si.nPos=si.nTrackPos;
break;
///WM_HSCROLL处理拦截SB_THUMBPOSITION通知码并忽略SB_THUMBTRACK。
//如果使用者在水平滚动条上拖动卷动方块,在使用者释放鼠标按钮之前,程序不会水平卷动窗口的内容
default:
break;
}
si.fMask=SIF_POS;
SetScrollInfo(hwnd,SB_HORZ,&si,TRUE);
GetScrollInfo(hwnd,SB_HORZ,&si);
if(si.nPos!=iHorPos)
{
ScrollWindow(hwnd,cxChar*(iHorPos-si.nPos),0,NULL,NULL);
UpdateWindow(hwnd);
}
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:
SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0) ;
break ;
case VK_NEXT:
SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0) ;
break ;
case VK_UP:
SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0) ;
break ;
case VK_DOWN: //虚拟键对应见6.2.1
SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0) ;
break ;
case VK_LEFT:
SendMessage (hwnd, WM_HSCROLL, SB_LINELEFT, 0) ;
break ;
case VK_RIGHT:
SendMessage (hwnd, WM_HSCROLL, SB_LINERIGHT, 0) ;
break ;
default:
break;
}
return 0;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
//获得垂直滚动条位置
si.cbSize=sizeof(si);
si.fMask=SIF_POS; //设置该参数用于在si.nPos中得到垂直滚动条位置
GetScrollInfo(hwnd,SB_VERT,&si);
iVerPos=si.nPos;
//获得水平滚动条位置
GetScrollInfo(hwnd,SB_HORZ,&si);
iHorPos=si.nPos;
//找到显示的限制,即显示的行数
iPaintBeg=max(0,(iVerPos+ps.rcPaint.top /cyChar));//rcPaint.top总为0
iPaintEnd=min(NUMLINES-1,iVerPos+ps.rcPaint.bottom/cyChar);
//显示的具体行数,比喻iVerPos起始行为3,客户区能显示的行数为4,则实际显示的文字行数为第3到第七行
//rcPain.top rcPaint.bottom 即客户区显示的高度
for(i=iPaintBeg;i<=iPaintEnd;i++)
{//显示行数的具体位置坐标x,y
x=cxChar*(1-iHorPos);//初始iHorPos为0
//当讲窗口横向缩减至滚条出来 右移滚条 iHorzPos会增大,直至x为负数,负数 就是横向的输出在客户区外侧,不显示
y=cyChar*(i-iVerPos);
TextOut(hdc,x,y,
sysmetrics[i].szLabel,
lstrlen(sysmetrics[i].szLabel));
TextOut(hdc,x+22*cxCaps+10*cxChar,y,
sysmetrics[i].szDesc,
lstrlen(sysmetrics[i].szDesc));
SetTextAlign(hdc,TA_RIGHT|TA_TOP); //Index右上对齐
TextOut(hdc,x+22*cxCaps+60*cxChar,y,szBuffer,
wsprintf(szBuffer,TEXT("%5d"),
GetSystemMetrics(sysmetrics[i].Index)));
SetTextAlign(hdc,TA_LEFT|TA_TOP); //恢复下一行开始的左上对齐
}
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
break;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
6.3字符消息
四类字符消息: 非系统WM_CHAR、WM_DEADCHAR (由WM_KEYDOWN得到)
系统字符WM_SYSCHAR、WM_SYSDEADCHAR(由WM_KEYDOWN得到)
大多情况下Win会忽略除WM_CHAR之外的任何消息。
消息顺序:
TranslateMessage从WM_KEYDOWN或WM_SYSKEYOWN消息产生字符消息。如按下Shift,再按下A,释放A,窗口过程会接到5个消息。(shift本身不产生字符消息)如下:
处理按键和字符消息的基本规则:如果需要读取输入到窗口的键盘字符,可以处理WM_CHAR,如果读取光标或功能键,可以处理WM_KEYDOWN消息。
死字符消息:基本概念就是在一个非英语键盘,如可以给某些字母加音调。它们本身不产生字符,称之为死键。
6.4字符集和字体
Win支持三种字体,点阵、向量(已过时)、和TrueType字体。
在内定的设备内容下获得的字体为系统字体。
程序StockFont\\\\\\