windows SDK chapter 6
keyboard
键盘基础
who is using the keyboard?
正在使用键盘的窗口,称为有输入焦点的窗口
windows操作系统上处于最顶层的窗口往往是焦点窗口,然而存在两分屏看似并列的情况,实际上这时候可以从多方面判断哪个窗口是焦点窗口
比如从任务栏
image-20220812083837696
这里Typora上有高亮,就是焦点窗口,
windows终端,DEVC++,VisualStudio等等都是黑色的,不是焦点窗口
又如从标题栏
image-20220812083907654
左侧typora为焦点窗口,其标题栏为深色.
在word,wps等等文字编辑器中,是否获得焦点更加明显,只要光标在闪烁就意味着持有焦点
实际上窗口过程通过处理WM_SETFOCUS,WM_KILLFOCUS
两个消息来判断自己有没有焦点
1 2 3 4 5 6 case WM_SETFOCUS: cout <<"focused" <<endl ; return 0 ; case WM_KILLFOCUS: cout <<"left" <<endl ; return 0 ;
1 PS C:\Users\86135 \desktop\myWin32> g++ main.cpp -O0 -o main -lkernel32 -lgdi32 -luser32 -m32
焦点在windows终端
焦点在the Hello Program
队列与同步
键盘动作由键盘驱动程序转化为格式化的消息之后,首先发往windows消息队列,不会直接发往焦点窗口
这是因为,有的键盘消息具有修改焦点窗口的功能,比如Alt+F4可关闭当前窗口.又比如win+r可以打开运行框,此时窗口焦点自动放在运行框上
windows需要先看一看键盘消息是针对当前焦点窗口的还是针对整个系统的.
如果发生了焦点转移,那么windows需要保证后续的相应键盘消息指向新的焦点窗口
击键消息
image-20220812085353287
其中系统键一般是Alt+其他键的组合键,比如Alt+Tab,切换任务窗口.
键按下之后如果不松开,会间隔一定时间(这个间隔在系统启动时BIOS中设置)之后进入连续输入状态(连续输入的速度也取决于BIOS中的设置),此时应用程序会收到一连串的WM_KEYDOWN或者WM_SYSKEYDOWN消息
系统键对于windows操作系统来说更加重要,应用程序一般忽略系统键信息,交给DefWindowProc处理.windows会处理所有Alt组合键信息
如果非得在应用程序中处理Alt消息,并且处理完成之后立刻返回,不调用DefWindowProc,那么系统键消息将会被应用程序截胡,不能发给操作系统.此时按下Alt+F4就无法关闭该窗口了
比如:
1 2 3 4 5 6 case WM_SYSKEYDOWN: cout << "sysKey down" << endl ; return 0 ; case WM_SYSKEYUP: cout << "sysKey up" << endl ; return 0 ;
此时只会在终端上打印一下,但是只要时Alt键的消息都不会发往windows
当组合键中没有Alt时,不会产生系统键消息,顶多产生WM_KEYDOWN和WM_KEYUP,应用程序可以根据自己的兴趣选择处理其中的部分消息,如果应用程序不做处理,windows也不做处理
不管是系统键消息还是非系统键消息,不管是按下还是起来,所有的键盘消息都会伴随着wParam表示虚拟键代码,lParam包含本次击键的其他数据
虚拟键代码wParam
捕获WM_KEYDOWN或者WM_KEYUP消息只能说明非系统键被按下或者松开了,单凭着一个信息无法判断谁被按下或者松开了.wParam就提供了更多的细节
该案件消息是一个枚举类型,大多数都以VK_开头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define VK_LBUTTON 0x01 #define VK_RBUTTON 0x02 #define VK_CANCEL 0x03 #define VK_MBUTTON 0x04 #define VK_XBUTTON1 0x05 #define VK_XBUTTON2 0x06 #define VK_BACK 0x08 #define VK_TAB 0x09 #define VK_CLEAR 0x0C #define VK_RETURN 0x0D #define VK_SHIFT 0x10 #define VK_CONTROL 0x11 #define VK_MENU 0x12 #define VK_PAUSE 0x13 ...
前两个鼠标动作貌似通过处理不到
数字键(不是右侧数字键盘,是左侧主键盘上一长条的数字键)和字母键:
image-20220812092101119
虽然键盘可以接收到键盘上@#$%*
这种字符的消息,但是这是对于英语键盘而言的,对于欧洲人来说欧元符号比美元符号更加重要,如果要写出地区无关的代码,那么我们需要和windows操作系统协作处理键盘消息,对于有意义的字符的处理,一般使用字符消息,不使用击键消息
微软键盘特有:
image-20220812092200252
需要给哪个键安排任务的时候就去查它的宏定义编码即可
其他消息lParam
image-20220812094137721
重复计数Repeat Count
低16位为重复计数,当一个键按下一直不松开时,应用程序会接收到一大串WM_KEYDOWN消息.如果窗口过程的处理速度跟不上,那么windows会把多个WM_KEYDOWN消息合并成一条消息,然后修改其lParam的低16位,决定这条消息重复了多少次
在比较卡顿的电脑上使用word有过这种体验,一直按着某个键不放,计算机可能不会实时跟着显示字符,但是一段时间后呼哧出来一摊这个字符
OEM扫描码
历史古董了,现在几乎不用了
拓展键标记Extended Key Flag
对于IBM拓展键盘才有用,一般的键盘上已经有足够多的键用了,不需要拓展了
内容代码Context Code
如果Alt键被按下,则相应的键盘消息中该值为1
先前状态Previous Key State
如果先前改键是松开的则该值为0
如果先前该键是按下的则该值为1
这个值可以用来去除重复输入,比如老年人动作缓慢,本来就只想输入一个A结果按下A忘了松开了
,可以判断先前该键的状态,如果也是按下的则抛弃
1 2 3 4 case WM_KEYDOWN: if ((lParam>>30 )&1 ){ return 0 ; }
转换状态Transition State
键正在被按下则转换状态为0
键正在被释放则转换状态为1
转义状态
转义键:Shift,Alt,Ctrl
切换键:Caps Lock,Num Lock,Scroll Lock
区分主键盘上大小写输入就要看这些键的状态
考虑如何判断一套组合键都有谁按下了呢?比如Ctrl+F,首先按下的是Ctrl,然后按下F,但是按下F的时候引用程序已经处理过了Ctrl,现在只知道按下了F.这样看来是不是还得开一些变量记录刚才按下了谁呢?当Ctrl松开的时候这个变量置零,当Ctrl按下的时候这个变量置1.
windows操作系统确实是按照这个思想做的,它给我们代劳了.我们要判断Ctrl的状态,只需要GetKeyState(VK_CONTROL);
就可以获取windows帮我们记录好的Ctrl状态了
1 2 3 SHORT GetKeyState ( [in] int nVirtKey ) ;
当需要查询状态的键处于按下时,该函数返回负值.否则最低位置1
可以通过是否小于0或者和1按位与判断状态
1 2 3 4 5 6 7 8 9 if (wParam<=0x5A &&wParam>=0x41 ){ if (GetKeyState(VK_CAPITAL)){ szBuffer[cnt_keys]=wParam; } else { szBuffer[cnt_keys]=wParam-'A' +'a' ; } ++cnt_keys; }
应用
给滚动条添加键盘动作:
按下PgUp往前翻页,按下PgDn往后翻页
按上下键滚动一行
按左右键左右滚动一个字符的宽度
实际上直接发送信息处理滚动条消息即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 case WM_KEYDOWN: switch (wParam) { case VK_HOME: SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0 ); break ; case VK_END: SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0 ); break ; case VK_PRIOR: SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0 ); break ; case VK_NEXT: SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0 ); case VK_UP: SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0 ); break ; case VK_DOWN: SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0 ); break ; case VK_RIGHT: SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0 ); break ; case VK_LEFT: SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0 ); break ; }
字符消息
再看消息循环
1 2 3 4 5 while (GetMessage(&msg, NULL , 0 , 0 )){ TranslateMessage(&msg); DispatchMessage(&msg); }
在主函数的最后,返回之前,像定式一样必须要有一个消息循环.
其中GetMessage从应用程序的消息队列中,获取一条新消息,用msg承载该消息
DispatchMessage将该消息分拣交给相应窗口
TranslageMessage将可打印字符 的击键消息转换为字符消息 ,然后将该字符消息放入应用程序的消息队列
可打印字符包括英文字母,阿拉伯数字,标点符号,运算符号等等
不包括Ctrl,Shift,Insert,Delete,Alt
字符消息的种类:
image-20220812105605047
死字符只对需要重音符号的语言键盘有用,比如德语.可以忽略
应用程序主要就处理WM_CHAR消息,该消息从WM_KEYDOWN翻译而来.
WM_SYSCHAR消息从WM_SYSKEYDOWN翻译而来
参数意义
对于字符消息,lParam参数的含义和击键消息相同
image-20220812201421640
wParam是ANSI或者Unicode编码的字符码,这一点和击键消息中不同
具体是用的ANSI还是Unicode编码,要看注册窗口类的时候调用的是RegisterClassA还是RegisterClassW
如果使用RegisterClass,最近的windows操作系统上会被宏定义为RegisterClassW.因为Windows
2000之后Unicode标识符就被定义了
1 2 3 4 5 #ifdef UNICODE #define RegisterClass RegisterClassW #else #define RegisterClass RegisterClassA #endif
先来后到
当字符键盘比如'A'按下后,对于应用程序的消息队列
首先收到的是'A'的击键消息WM_KEYDOWN,虚拟键代码wParam=0x41
然后收到的是'A'的字符消息WM_CHAR,字符编码wParam=0x61
然后'A'松开时收到击键消息WM_KEYUP,虚拟键代码wParam=0x41
各司其职
对于功能键比如Ctrl,Shift等等,作为虚拟键,需要处理WM_KEYDOWN消息
对于可打印字符就需要处理字符消息WM_CHAR
Tab,回车,空格,Esc作为控制字符也处理WM_CHAR
image-20220812203020207
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 case WM_CHAR: switch (wParam) { case '\b' : cout << "space down" << endl ; break ; case '\t' : cout << "Tab down" << endl ; break ; case '\n' : cout << "Ctrl+Enter down" << endl ; break ; case '\r' : cout << "Enter down" << endl ; break ; } return 0 ;
应用
windows程序设计在此给出了一个例子,记录键盘动作并且输出到屏幕
分析其过程函数WndProc
变量定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static int 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 ][100 ] = { 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[8 ][100 ] = { 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; int i, iType; PAINTSTRUCT ps; TCHAR szBuffer[128 ], szKeyName[32 ]; TEXTMETRIC tm;
pmsg
存放消息的结构体
1 2 3 4 5 6 7 8 9 10 11 typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; #ifdef _MAC DWORD lPrivate; #endif } MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
其前四个参数和WndProc窗口过程函数相同
1 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
该结构体的作用就是完整记录一条消息,甚至连消息产生时间time都有记录
POINT pt记录的是发生该消息时鼠标的位置(相对于整块屏幕的坐标)
ps
记录绘画信息的结构体
1 2 3 4 5 6 7 8 typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32 ]; } PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;
hdc
设备环境句柄
fErase
表明背景是否擦除,非0则擦除
rcPaint
需要重绘的矩形范围
剩下三个成员尚未使用
窗口创建和变形消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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+tm.tmExternalLeading; ReleaseDC(hwnd, hdc); if (pmsg) free (pmsg); cLinesMax = cyClientMax / cyChar; pmsg = (PMSG)malloc (cLinesMax * sizeof (MSG)); cLines = 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 ;
键盘消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 (i = cLinesMax - 1 ; i > 0 ; i--) { pmsg[i] = pmsg[i - 1 ]; } pmsg[0 ].hwnd = hwnd; pmsg[0 ].message = message; pmsg[0 ].wParam = wParam; pmsg[0 ].lParam = lParam; cLines = min(cLines + 1 , cLinesMax); ScrollWindow(hwnd, 0 , -cyChar, &rectScroll, &rectScroll); break ;
"i.e., call DefWindowProc so Sys messages work"翻译成人话:
"也就是说,调用DefWindowProc"处理系统键消息
最后这里用的是break不是return,意味着只是看看键盘消息是谁而不进行拦截,记录一下接着丢给系统做取舍,保证Alt系统消息能够被正确处理
绘图消息
前面的一切消息处理都是在给绘图消息的处理做准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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 (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)); } EndPaint(hwnd, &ps); return 0 ;
其中szMessage[pmsg[i].message - WM_KEYFIRST]
很巧妙
szMessage在WndProc一开始有定义
1 2 3 4 5 6 static TCHAR szMessage[8 ][100 ] = { TEXT("WM_KEYDOWN" ), TEXT("WM_KEYUP" ), TEXT("WM_CHAR" ), TEXT("WM_DEADCHAR" ), TEXT("WM_SYSKEYDOWN" ),TEXT("WM_SYSKEYUP" ), TEXT("WM_SYSCHAR" ), TEXT("WM_SYSDEADCHAR" ) };
1 2 3 4 5 6 7 8 9 #define WM_KEYFIRST 0x0100 #define WM_KEYDOWN 0x0100 #define WM_KEYUP 0x0101 #define WM_CHAR 0x0102 #define WM_DEADCHAR 0x0103 #define WM_SYSKEYDOWN 0x0104 #define WM_SYSKEYUP 0x0105 #define WM_SYSCHAR 0x0106 #define WM_SYSDEADCHAR 0x0107
WM_KEYFIRST表示WM消息的第一个,WM_KEYDOWN-WM_KEYFIRSST=0x100-0x100=0
正好对应szMessage下标为0的的成员TEXT("WM_KEYDOWN")
窗口销毁消息
1 2 3 4 case WM_DESTROY: PostQuitMessage(0 ); return 0 ; }
插入符号
向文本编辑器中输入文本时,当前输入位置会有一个方格或者竖线或者下划线闪烁,
这个闪烁的玩意儿叫做"插入符号(caret)",而不是"光标(cursor)",光标是指鼠标位置
windows终端中的插入符号
windowsAPI提供了插入符号的现成的函数,
在此之前我还以为这个闪烁效果得是行尾一个竖线|和空格交替重绘.客户区其他部位一直重绘.显然客户区重新打印一遍太耗时了
1 2 3 4 5 6 7 8 CreateCaret 建立与视窗有关的插入符号 SetCaretPos 在视窗中设定插入符号的位置 ShowCaret 显示插入符号 HideCaret 隐藏插入符号 DestroyCaret 撤消插入符号 GetCaretPos 获取当前插入符号位置 GetCaretBlinkTime 获取符号闪烁时间 SetCaretBlinkTime 设置符号闪烁时间
时机
插入符号的作用就是提示用户当前输入位置,显然这个正在被输入的窗口是焦点窗口
而一个窗口过程可能负责多个窗口的消息处理,那么插入符号的改变应该是窗口特定的,不应是窗口过程特定的
因此最好的处理时机是WM_SETFOCUS和WM_KILLFOCUS消息
在处理WM_SETFOCUS时调用CreateCaret,在处理WM_KILLFOCUS消息时调用DestoryCaret
创建插入符号之后并没有立刻输出到屏幕,需要再挑一个适当的时候调用ShowCaret显示它
在处理WM_PAINT时调用CreateCaret,在处理其他需要绘图的消息时调用HideCaret暂时隐藏插入符号.其他消息处理完成之后再调用CreateCaret重新显示插入符号
HideCaret的效果叠加,假设连续调用了10次HideCaret函数,那么就需要调用ShowCaret函数10次才可以把插入符号拽出来.
baby notepad
"弟弟军训完了,非要给我露一手"
书上在这里给出了一个例子,一个婴儿版的文本编辑器
分析一下它的窗口过程WndProc函数
宏定义
1 #define BUFFER(x,y) *(pBuffer + y * cxBuffer + x)
本定义用于取缓冲区的第x行第y列这个字符.
实际上用了一个一维数组模拟二维数组,BUFFER(x,y)
相当于buffer[x][y]
变量定义
1 2 3 4 5 6 7 8 9 static DWORD dwCharSet = DEFAULT_CHARSET;static int cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer, xCaret, yCaret; static TCHAR* pBuffer = NULL ;HDC hdc; int x, y, i;PAINTSTRUCT ps; TEXTMETRIC tm;
字符集变换,窗口创建,尺寸变化消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 case WM_INPUTLANGCHANGE: dwCharSet = wParam; case WM_CREATE: 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); case WM_SIZE: if (message == WM_SIZE) { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); } cxBuffer = max(1 , cxClient / cxChar); cyBuffer = max(1 , cyClient / cyChar); if (pBuffer != NULL ) free (pBuffer); pBuffer = (TCHAR*)malloc (cxBuffer * cyBuffer * sizeof (TCHAR)); for (y = 0 ; y < cyBuffer; y++) for (x = 0 ; x < cxBuffer; x++) BUFFER(x, y) = ' ' ; xCaret = 0 ; yCaret = 0 ; if (hwnd == GetFocus()) SetCaretPos(xCaret * cxChar, yCaret * cyChar); InvalidateRect(hwnd, NULL , TRUE); return 0 ;
获取焦点消息
1 2 3 4 5 6 case WM_SETFOCUS: CreateCaret(hwnd, NULL , cxChar, cyChar); SetCaretPos(xCaret * cxChar, yCaret * cyChar); ShowCaret(hwnd); return 0 ;
失去焦点消息
1 2 3 4 5 case WM_KILLFOCUS: HideCaret(hwnd); DestroyCaret(); return 0 ;
键盘消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 case WM_KEYDOWN: switch (wParam) { case VK_HOME: xCaret = 0 ; break ; case VK_END: xCaret = cxBuffer - 1 ; break ; case VK_PRIOR: yCaret = 0 ; break ; case VK_NEXT: yCaret = cyBuffer - 1 ; break ; case VK_LEFT: xCaret = max(xCaret - 1 , 0 ); break ; case VK_RIGHT: xCaret = min(xCaret + 1 , cxBuffer - 1 ); break ; case VK_UP: yCaret = max(yCaret - 1 , 0 ); break ; case VK_DOWN: yCaret = min(yCaret + 1 , cyBuffer - 1 ); break ; case VK_DELETE: for (x = xCaret; x < cxBuffer - 1 ; x++) BUFFER(x, yCaret) = BUFFER(x + 1 , yCaret); BUFFER(cxBuffer - 1 , yCaret) = ' ' ; HideCaret(hwnd); hdc = GetDC(hwnd); SelectObject(hdc, CreateFont(0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , dwCharSet, 0 , 0 , 0 , FIXED_PITCH, NULL )); TextOut(hdc, xCaret * cxChar, yCaret * cyChar, &BUFFER(xCaret, yCaret), cxBuffer - xCaret); DeleteObject(SelectObject(hdc, GetStockObject (SYSTEM_FONT))); ReleaseDC(hwnd, hdc); ShowCaret(hwnd); break ; } SetCaretPos(xCaret * cxChar, yCaret * cyChar); return 0 ;
字符消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 case WM_CHAR: for (i = 0 ; i < (int )LOWORD(lParam); i++) { switch (wParam) { case '\b' : if (xCaret > 0 ) { xCaret--; SendMessage(hwnd, WM_KEYDOWN, VK_DELETE, 1 ); } break ; case '\t' : do { SendMessage(hwnd, WM_CHAR, ' ' , 1 ); } while (xCaret % 8 != 0 ); break ; case '\n' : if (++yCaret == cyBuffer) yCaret = 0 ; break ; case '\r' : xCaret = 0 ; if (++yCaret == cyBuffer) yCaret = 0 ; break ; case '\x1B' : for (y = 0 ; y < cyBuffer; y++) for (x = 0 ; x < cxBuffer; x++) BUFFER(x, y) = ' ' ; xCaret = 0 ; yCaret = 0 ; InvalidateRect(hwnd, NULL , FALSE); break ; default : BUFFER(xCaret, yCaret) = (TCHAR)wParam; HideCaret(hwnd); hdc = GetDC(hwnd); SelectObject(hdc, CreateFont(0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , dwCharSet, 0 , 0 , 0 , FIXED_PITCH, NULL )); TextOut(hdc, xCaret * cxChar, yCaret * cyChar, &BUFFER(xCaret, yCaret), 1 ); DeleteObject( SelectObject(hdc, GetStockObject(SYSTEM_FONT))); ReleaseDC(hwnd, hdc); ShowCaret(hwnd); if (++xCaret == cxBuffer) { xCaret = 0 ; if (++yCaret == cyBuffer) yCaret = 0 ; } break ; } } SetCaretPos(xCaret * cxChar, yCaret * cyChar); return 0 ;
绘图消息
1 2 3 4 5 6 7 8 9 10 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 )); for (y = 0 ; y < cyBuffer; y++) TextOut(hdc, 0 , y * cyChar, &BUFFER(0 , y), cxBuffer); DeleteObject(SelectObject(hdc,GetStockObject(SYSTEM_FONT))); EndPaint(hwnd, &ps); return 0 ;
窗口销毁消息
1 2 3 4 case WM_DESTROY: PostQuitMessage(0 ); return 0 ; }