1. 程式人生 > >擊鍵訊息、字元訊息、插入符號(游標)

擊鍵訊息、字元訊息、插入符號(游標)

注:以下內容為學習筆記,多數是從書本、資料中得來,只為加深印象,及日後參考。然而本人表達能力較差,寫的不好。因非翻譯、非轉載,只好選原創,但多數乃摘抄,實為慚愧。但若能幫助一二訪客,幸甚!

以下內容主要來自《Windows 程式設計》

1.焦點

程式用於從訊息佇列中讀取訊息的MSG結構中包含一個hwnd欄位。此欄位指出了接收訊息的視窗控制代碼。訊息迴圈中的DispatchMessage函式傳送訊息給需要該訊息的視窗過程。

接收到這個鍵盤事件的視窗稱為有輸入焦點的視窗。

有時沒有視窗具有輸入焦點。這種情況發生在所以程式都最小化時。

視窗過程通過捕獲WM_SETFOCUS和WM_KILLFOCUS訊息來確定自己的視窗是否具有輸入焦點。

2.佇列和同步

當用戶按下和釋放鍵盤上的一個鍵時,Windows和鍵盤裝置驅動程式將硬體掃描碼轉換為格式化後的訊息。但是這些訊息並不立即被放入應用程式訊息佇列,而是由Windows把這些訊息儲存在系統訊息佇列中。系統訊息佇列是一個單獨的訊息佇列,它被Windows用來初步儲存使用者從鍵盤和滑鼠輸入的訊息。僅當Windows應用程式完成了對當前一個使用者輸入訊息的處理後,Windows才從系統訊息佇列中取出下一條訊息,並把它放入應用程式訊息佇列。

3.擊鍵訊息

當用戶按下一個鍵時,Windows將WM_KEYDOWN或WM_SYSKEYDOWN訊息放入具有輸入焦點的訊息佇列中。當該鍵被釋放時,Windows把WM_KEYUP或WM_SYSKEYUP訊息放入相應的訊息佇列中。其中SYS代表系統,它表明該擊鍵對Windows比對應用程式更加重要。

虛擬鍵程式碼儲存在WM_KEYDOWN等訊息的wParam引數中,確定哪個鍵被按下或被釋放。

當處理擊鍵訊息時,可能需要知道是否有轉義鍵(Shift、Ctrl和Alt)或切換鍵(Caps Lock、Num Lock和Scroll Lock)鍵被按下。

可以用如下方式:

iState = GetKeyState(VK_SHIFT);

簡單方法:為視窗過程增加WM_KEYDOWN邏輯

更好的方法:把每一個WM_KEYDOWN訊息轉換為等同的WM_VSCROLL或WM_HSCROLL訊息。可以使用:

SendMessage(hwnd, message, wParam, lParam);

當你呼叫SendMessage函式時,Windows呼叫視窗控制代碼hwnd的視窗過程,同時把四個函式變數傳遞給他。當視窗過程處理完此訊息,Windows把控制權還給緊跟著SendMessage呼叫的下一條語句。

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// cxChar平均字元寬度,cyChar字元的總高度(包括外部間距),cxCaps大寫字元的平均寬度
	// 等寬字型中,cxCaps等於cxChar,變寬字型中,cxCaps等於cxChar的1.5倍
	static int	cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth, iVscrollPos;	
	HDC			hdc;
	int			i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd;
	PAINTSTRUCT	ps;
	SCROLLINFO	si;
	TCHAR		szBuffer[10];
	TEXTMETRIC	tm;	

	switch (message)
	{
	case WM_CREATE:
		hdc = GetDC(hwnd);
		
		GetTextMetrics(hdc, &tm);		// 獲取系統預設字型的尺寸
		cxChar = tm.tmAveCharWidth;
		// tmPitchAndFamily為1表示變寬字型,為0表示等寬字型
		cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
		cyChar = tm.tmHeight + tm.tmExternalLeading;

		ReleaseDC(hwnd, hdc);

		iMaxWidth = 40*cxChar + 22*cxCaps;
		return 0;

	case WM_SIZE:
		cxClient = LOWORD(lParam);
		cyClient = HIWORD(lParam);
		
		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 + iMaxWidth/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);
		iVertPos = si.nPos;

		switch (LOWORD(wParam))
		{
		case SB_TOP:
			si.nPos = si.nMin;
			break;

		case SB_BOTTOM:
			si.nPos = si.nMax;
			break;

		case SB_LINEUP:
			si.nPos -= 1;
			break;

		case SB_LINEDOWN:
			si.nPos += 1;
			break;

		case SB_PAGEUP:
			si.nPos -= si.nPage;
			break;

		case SB_PAGEDOWN:
			si.nPos += si.nPage;
			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 != iVertPos)
		{
			ScrollWindow(hwnd, 0, cyChar*(iVertPos-si.nPos), NULL, NULL);
			UpdateWindow(hwnd);
		}
		return 0;

	case WM_HSCROLL:
		si.cbSize	= sizeof(si);
		si.fMask	= SIF_ALL;

		GetScrollInfo(hwnd, SB_HORZ, &si);
		iHorzPos = si.nPos;

		switch (LOWORD(wParam))
		{
		case SB_LINELEFT:
			si.nPos -= 1;
			break;

		case SB_LINERIGHT:
			si.nPos += 1;
			break;

		case SB_PAGELEFT:
			si.nPos -= si.nPage;
			break;

		case SB_PAGERIGHT:
			si.nPos += si.nPage;
			break;

		case SB_THUMBPOSITION:
			si.nPos = si.nTrackPos;
			break;

		default:
			break;
		}

		si.fMask = SIF_POS;
		SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
		GetScrollInfo(hwnd, SB_HORZ, &si);

		if (si.nPos != iHorzPos)
		{
			ScrollWindow(hwnd, cxChar*(iHorzPos-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:
			SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
			break;

		case VK_LEFT:
			SendMessage(hwnd, WM_HSCROLL, SB_PAGEUP, 0);
			break;

		case VK_RIGHT:
			SendMessage(hwnd, WM_HSCROLL, SB_PAGEDOWN, 0);
			break;
		}
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		si.cbSize = sizeof(si);
		si.fMask = SIF_POS;
		
		GetScrollInfo(hwnd, SB_VERT, &si);
		iVertPos = si.nPos;

		GetScrollInfo(hwnd, SB_HORZ, &si);
		iHorzPos = si.nPos;

		iPaintBeg = max(0, iVertPos + ps.rcPaint.top/cyChar);
		iPaintEnd = min(NUMLINES-1, iVertPos + ps.rcPaint.bottom/cyChar);

		for (i = iPaintBeg; i <= iPaintEnd; i++)
		{
			x = cxChar * (1-iHorzPos);
			y = cyChar * (i-iVertPos);

			TextOut(hdc, x, y, sysmetrics[i].szLabel, lstrlen(sysmetrics[i].szLabel));

			TextOut(hdc, x + 22*cxCaps, y, sysmetrics[i].szDesc, lstrlen(sysmetrics[i].szDesc));

			SetTextAlign(hdc, TA_RIGHT | TA_TOP);

			TextOut(hdc, x + 22*cxCaps + 40*cxChar, y, szBuffer, wsprintf(szBuffer, TEXT("%5d"), 
				GetSystemMetrics(sysmetrics[i].iIndex)));

			// 將對齊方式設回正常方式
			SetTextAlign(hdc, TA_LEFT | TA_TOP);
		}

		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}

有鍵盤了,就差滑鼠了~很快會有的。

4.字元訊息

通過轉義狀態資訊可把擊鍵訊息轉換為字元訊息。

GetMessage從訊息佇列中取出下一條訊息;

TranslateMessage負責把擊鍵訊息轉換為字元訊息;

DispatchMessage呼叫此訊息的視窗過程。

5.訊息排序

假如Caps Lock沒有鎖定,按下再釋放A,相應視窗過程會接收:

1)WM_KEYDOWN:’A‘ 的虛擬鍵程式碼(0x41)

2)WM_CHAR:  'a'的字元編碼(0x61)

3)WM_KEYUP: 'A' 的虛擬鍵程式碼(0x41)

按下Shift+A,釋放A,再釋放Shift:

1)WM_KEYDOWN:虛擬鍵程式碼VK_SHIFT(0x10)

2)WM_KEYDOWN:’A‘ 的虛擬鍵程式碼(0x41)

3)WM_CHAR:  'A' 的字元編碼(0x41)

4)WM_KEYUP: 'A' 的虛擬鍵程式碼(0x41)

5)WM_UP:虛擬鍵程式碼VK_SHIFT(0x10)

連續按住A:

1)WM_KEYDOWN:’A‘ 的虛擬鍵程式碼(0x41)

2)WM_CHAR:  'a'的字元編碼(0x61)

3)WM_KEYDOWN:’A‘ 的虛擬鍵程式碼(0x41)

4)WM_CHAR:  'a'的字元編碼(0x61)

……

n)WM_KEYUP: 'A' 的虛擬鍵程式碼(0x41)


6.控制字元的處理

按照以下基本規則來處理擊鍵和字元訊息:如果你需要讀取輸入到視窗中的鍵盤字元,就處理WM_CHAR訊息;如果你需要讀取游標鍵、功能鍵、Delete鍵、Insert鍵、Shift鍵、Ctrl鍵和Alt鍵,則處理WM_KEYDOWN訊息。

/*---------------------------------------------------------------------------
	keyView.cpp -- Displays keyboard and character messages
----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------------
	SysMets4.cpp -- System Metrics Display Program ver4 use keyboard
 *----------------------------------------------------------------------------------*/

#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");
	HWND			hwnd;
	MSG				msg;
	WNDCLASS		wndclass;

	wndclass.style			= CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc	= WndProc;
	wndclass.cbClsExtra		= 0;
	wndclass.cbWndExtra		= 0;
	wndclass.hInstance		= hInstance;
	wndclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName	= NULL;
	wndclass.lpszClassName	= szAppName;

	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires windows NT!"), szAppName, MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName,										// window class name
						TEXT("Keyboard Message Viewer #1"),				// window caption
						WS_OVERLAPPEDWINDOW,							// window style
						CW_USEDEFAULT,									// initial x position
						CW_USEDEFAULT,									// initial y position
						CW_USEDEFAULT,									// initial x size
						CW_USEDEFAULT,									// initial y size
						NULL,											// parent window handle
						NULL,											// window menu handle
						hInstance,										// program instance handle
						NULL);											// creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// cxChar平均字元寬度,cyChar字元的總高度(包括外部間距),cxCaps大寫字元的平均寬度
	// 等寬字型中,cxCaps等於cxChar,變寬字型中,cxCaps等於cxChar的1.5倍
	static int		cxChar, cyChar, cxClient, cyClient, cxClientMax, cyClientMax;
	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;
	int			i, iType;
	PAINTSTRUCT	ps;
	TCHAR		szBuffer[128], szKeyName[32];
	TEXTMETRIC	tm;	

	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 = (PMSG)malloc(cLinesMax * sizeof(MSG));
		cLines = 0;

		//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_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;
	

	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;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}


7.插入符號(就是平時說的游標)

插入符號指明你輸入的下一個字元將出現在螢幕上的位置。

相關函式:

CreateCaret:建立和視窗關聯的插入符號

SetCaretPos:設定視窗內的插入符號的位置

ShowCaret:顯式插入符號

HideCaret:隱藏插入符號

DestroyCaret:銷燬插入符號

GetCaretPos:獲取插入符號位置

GetCaretBlinkTime、SetCaretBlinkTime:獲取、設定插入符號閃爍時間

使用插入符號主要規則:在視窗過程處理WM_SETFOCUS訊息時呼叫CreateCaret,處理WM_KILLFOCUS訊息時呼叫DestroyCaret函式。

建立的插入符號是隱藏的,必須呼叫ShowCaret使之可見。

要在視窗內繪製某些東西時,它必須呼叫HideCaret隱藏插入符號。結束繪製後,再呼叫ShowCaret顯式插入符號。

HideCaret效果是疊加的。

一個簡單的文字編輯器雛形:

/*----------------------------------------------------------------------------
	typer.cpp -- Typing Program
----------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------
	keyView.cpp -- Displays keyboard and character messages
----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------------
	SysMets4.cpp -- System Metrics Display Program ver4 use keyboard
 *----------------------------------------------------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

#define BUFFER(x, y)	*(pBuffer + y*cxBuffer + x)

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static TCHAR	szAppName[] = TEXT("Typer");
	HWND			hwnd;
	MSG				msg;
	WNDCLASS		wndclass;

	wndclass.style			= CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc	= WndProc;
	wndclass.cbClsExtra		= 0;
	wndclass.cbWndExtra		= 0;
	wndclass.hInstance		= hInstance;
	wndclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName	= NULL;
	wndclass.lpszClassName	= szAppName;

	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires windows NT!"), szAppName, MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName,										// window class name
						TEXT("Typer"),									// window caption
						WS_OVERLAPPEDWINDOW,							// window style
						CW_USEDEFAULT,									// initial x position
						CW_USEDEFAULT,									// initial y position
						CW_USEDEFAULT,									// initial x size
						CW_USEDEFAULT,									// initial y size
						NULL,											// parent window handle
						NULL,											// window menu handle
						hInstance,										// program instance handle
						NULL);											// creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static DWORD	dwCharSet = DEFAULT_CHARSET;
	static int		cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer, xCaret, yCaret;
	static TCHAR*	pBuffer = NULL;

	int				x, y, i;
	HDC				hdc;
	PAINTSTRUCT		ps;
	TEXTMETRIC		tm;	

	switch (message)
	{
	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(sizeof(TCHAR) * cxBuffer * cyBuffer);

		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;

	case WM_SETFOCUS:
		CreateCaret(hwnd, NULL, cxChar, cyChar);
		SetCaretPos(xCaret*cxChar, yCaret*cyChar);
		ShowCaret(hwnd);
		return 0;

	case WM_KILLFOCUS:
		HideCaret(hwnd);
		DestroyCaret();
		return 0;
		
	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;

	case WM_CHAR:
		for (i = 0; i < (int)LOWORD(lParam); i++)
		{
			switch (wParam)
			{
			case '\b':			// backspace
				if (xCaret > 0)
				{
					xCaret--;
					SendMessage(hwnd, WM_KEYDOWN, VK_DELETE, 1);
				}
				break;

			case '\t':			// tab
				do
				{
					SendMessage(hwnd, WM_CHAR, ' ', 1);
				}
				while (xCaret % 8 != 0);
				break;

			case '\n':			// line feed
				if (++yCaret == cyBuffer)
					yCaret = 0;
				break;

			case '\r':			// carriage return
				xCaret = 0;
				if (++yCaret == cyBuffer)
					yCaret = 0;
				break;

			case '\x1B':		// escape
				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:			// character codes
				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;

	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;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}