【Windows编程】系列第十篇:文本插入符
大家知道,在使用微软的编程环境创建工程时会让你选择是控制台模式还是Windows应用程序。如果选择控制台的console模式,就会在运行时出现一个黑洞洞的字符模式窗口,里面就有等待输入一闪一闪的插入符。输入光标从DOS时代就存在,但是在Win32中赋予了更强大的功能。下图就是Windows的CMD窗口,其中的输入点就是插入光标:
要注意的是这里的插入符或插入光标并不是Windows中另外一个“光标”,这里是指示插入字符的位置,而不是用于鼠标,手写输入等可以定位、移动的光标(Cursor),而是插入符Caret,本文也成为插入光标,注意插入二字,为了方便,以下在本文中也简称为光标或插入符,但要注意此光标非彼光标。
为什么会有插入光标(插入符)?了解了这个基本问题,就成功了一半了。我们知道计算机可以通过键盘来输入各种字符和控制符,那么自然就存在一个问题,输入的字符应该放到屏幕的什么位置?这个就是光标产生的原因,光标实际上就是一个字符插入标识。简单的说就是我们想把字符输入到哪个位置,就先把插入符设置到那里,设置时其实就是告诉电脑输入的字符你给我在哪里显示!从这个我们也可以推断,插入符在同一时刻只能有一个。
- 光标相关API函数
要使用光标,首先得创建一个光标,创建光标的API函数为:
BOOL CreateCaret(HWND hWnd, HBITMAP hBitmap, int nWidth, int nHeight);
hWnd参数表示光标是属于哪个窗口。
hBitmap参数是一个位图的句柄,计算机将使用这个句柄的位图来作为光标的形状。
既然光标是给使用电脑的人插入字符用的,那就得有形状让使用者能看到,因此光标需要有一个可见的小图标。当然为了不同的情况和需求,Windows让我们可以自定义光标的形状。常见的位图创建或者加载API函数如CreateBitmap、CreateDIBitmap、LoadBitmap等都可以创建或加载一个位图,将句柄作为该参数。
nWidth和nHeight分别是位图的宽和高。
光标创建之后是不会自动显示的,也就是默认是隐藏状态,需要主动调用下面的显示函数:
BOOL ShowCaret(HWND hWnd);
当然有显示光标也可以隐藏光标:
BOOL HideCaret(HWND hWnd);
以上两个函数的参数很简单,都是要显示窗口的句柄。
要设置插入符的位置,使用下面API函数:
BOOL SetCaretPos(int X, int Y);
参数X、Y分别是相对于窗口客户区的坐标。
我们可以用如下API函数获取当前光标的位置:
BOOL GetCaretPos(LPPOINT lpPoint);
参数lpPoint返回当前光标所在的位置。
我们知道光标会闪烁,这个闪烁的时间间隔是可以设置的,我们可以用如下API来设置和获取插入光标的闪烁时间:
BOOL SetCaretBlinkTime(UINT uMSeconds); UINT GetCaretBlinkTime(VOID);
参数uMSeconds为闪烁的间隔毫秒数。
最后不再使用时需要销毁光标:
BOOL DestroyCaret(VOID);
- 光标处理相关消息
与插入光标相关的消息主要有WM_SETFOCUS、WM_KILLFOCUS。通常在WM_SETFOCUS中创建和显示光标,而在WM_KILLFOCUS中销毁光标。一般应有中再结合WM_KEYDOWN和WM_CHAR消息,实现文本的输入。
- 光标应用实例
以下是一个简单的虚拟终端,我们常见的很多终端软件都是这样来实现的,比如常见的SecureCRT、Tera Term、XShell、putty等等。本实例就是用了插入光标来实现字符输入、插入,完整实例代码如下:
#include <windows.h> static TCHAR szWinName[] = TEXT("VirtualTerminal"); #define TEXTMATRIX(x, y) *(pTextMatrix + ((y) * nLineChars) + (x)) static TEXTMETRIC tm; //metric dimention of virtual terminal static TCHAR *pTextMatrix; //VT character buffer static int nCharWidth, nCharHeight; //the width and height of characeters static int nCaretPosX, nCaretPosY; //the current position of caret static int nVTWidth, nVTHeight; //the width and height of virtual terminal window static int nLineChars, nRowChars; //character number in one line and row static int nCaretOffsetY; //the offset of vertical height static COLORREF TextColor; //text color for terminal window, that is fore color static LRESULT CALLBACK VTWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); HWND CreateVirtualTerminal(HWND hWndParent, int left, int top, int width, int height, int id) { HWND hWnd; WNDCLASS wndclass; HINSTANCE hInst = GetModuleHandle(NULL); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = VTWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInst; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szWinName; if (!RegisterClass(&wndclass)) { return 0; } hWnd = CreateWindowEx(0, szWinName, NULL, WS_CHILD|WS_VISIBLE, left, top, width, height, hWndParent, (HMENU)id, hInst, NULL); return hWnd; } static void DrawChar(HDC hDC, int x, int y, TCHAR *str, int num) { RECT rect; SelectObject(hDC, GetStockObject(SYSTEM_FIXED_FONT)); SetTextColor(hDC, TextColor); SetBkMode(hDC, TRANSPARENT); rect.left = x; rect.top = y; rect.right = x + num * nCharWidth; rect.bottom = y + nCharHeight; FillRect(hDC, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH)); TextOut(hDC, nCaretPosX * nCharWidth, nCaretPosY * nCharHeight, &TEXTMATRIX(nCaretPosX, nCaretPosY), nLineChars - nCaretPosX); } static LRESULT CALLBACK VTWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int x, y; HDC hDC; switch (message) { case WM_CREATE: hDC = GetDC(hWnd); SelectObject(hDC, GetStockObject(SYSTEM_FIXED_FONT)); GetTextMetrics(hDC, &tm); ReleaseDC(hWnd, hDC); nCharWidth = tm.tmAveCharWidth; nCharHeight = tm.tmHeight; TextColor = RGB(255, 255, 255); nCaretOffsetY = 12; return 0; case WM_SIZE: nVTWidth = LOWORD(lParam); nLineChars = max(1, nVTWidth/nCharWidth); nVTHeight = HIWORD(lParam); nRowChars = max(1, nVTHeight/nCharHeight); if (pTextMatrix != NULL) { free(pTextMatrix); } pTextMatrix = (TCHAR *)malloc(nLineChars * nRowChars); if (pTextMatrix) { for (y=0; y<nRowChars; y++) { for (x=0; x<nLineChars; x++) { TEXTMATRIX(x, y) = TEXT(' '); } } } SetCaretPos(0, nCaretOffsetY); return 0; case WM_LBUTTONDOWN: SetFocus(hWnd); break; case WM_KEYDOWN: switch (wParam) { case VK_HOME: // Home nCaretPosX = 0; break; case VK_END: // End nCaretPosX = nLineChars - 1; break; case VK_PRIOR: // Page Up nCaretPosY = 0; break; case VK_NEXT: // Page Down nCaretPosY = nRowChars -1; break; case VK_LEFT: // Left arrow nCaretPosX = max(nCaretPosX - 1, 0); break; case VK_RIGHT: // Right arrow nCaretPosX = min(nCaretPosX + 1, nLineChars - 1); break; case VK_UP: // Up arrow nCaretPosY = max(nCaretPosY - 1, 0); break; case VK_DOWN: // Down arrow nCaretPosY = min(nCaretPosY + 1, nRowChars - 1); break; case VK_DELETE: // Delete // Move all the characters that followed the // deleted character (on the same line) one // space back (to the left) in the matrix. for (x = nCaretPosX; x < nLineChars; x++) TEXTMATRIX(x, nCaretPosY) = TEXTMATRIX(x + 1, nCaretPosY); // Replace the last character on the // line with a space. TEXTMATRIX(nLineChars - 1, nCaretPosY) = ' '; // The application will draw outside the // WM_PAINT message processing, so hide the caret. HideCaret(hWnd); // Redraw the line, adjusted for the // deleted character. hDC = GetDC(hWnd); DrawChar(hDC, nCaretPosX * nCharWidth, nCaretPosY * nCharHeight, &TEXTMATRIX(nCaretPosX, nCaretPosY), nLineChars - nCaretPosX/nCharWidth); ReleaseDC(hWnd, hDC); // Display the caret. ShowCaret(hWnd); break; } // Adjust the caret position based on the // virtual-key processing. SetCaretPos(nCaretPosX * nCharWidth, nCaretPosY * nCharHeight + nCaretOffsetY); return 0; case WM_SHOWWINDOW: SetFocus(hWnd); break; case WM_SETFOCUS: CreateCaret(hWnd, NULL, nCharWidth, 2); SetCaretPos(nCaretPosX * nCharWidth, nCaretPosY * nCharHeight + nCaretOffsetY); ShowCaret(hWnd); break; case WM_KILLFOCUS: case WM_DESTROY: HideCaret(hWnd); DestroyCaret(); break; case WM_CHAR: switch (wParam) { case 0x08: //process a backspace. if (nCaretPosX > 0) { nCaretPosX--; SendMessage(hWnd, WM_KEYDOWN, VK_DELETE, 1L); } break; case 0x09: //process a tab. do { SendMessage(hWnd, WM_CHAR, TEXT(' '), 2L); } while (nCaretPosX % 4 != 0); break; case 0x0D: //process a carriage return. nCaretPosX = 0; if (++nCaretPosY == nRowChars) { nCaretPosY = 0; } break; case 0x1B: case 0x0A: //process a linefeed. MessageBeep((UINT)-1); break; default: TEXTMATRIX(nCaretPosX, nCaretPosY) = (TCHAR)wParam; HideCaret(hWnd); hDC = GetDC(hWnd); DrawChar(hDC, nCaretPosX * nCharWidth, nCaretPosY * nCharHeight, &TEXTMATRIX(nCaretPosX, nCaretPosY), 1); ReleaseDC(hWnd, hDC); ShowCaret(hWnd); if (++nCaretPosX == nLineChars) { nCaretPosX = 0; if (++nCaretPosY == nRowChars) { nCaretPosY = 0; } } break; } SetCaretPos(nCaretPosX * nCharWidth, nCaretPosY * nCharHeight + nCaretOffsetY); return 0; case WM_PAINT: { PAINTSTRUCT ps; hDC = BeginPaint(hWnd, &ps); SelectObject(hDC, GetStockObject(SYSTEM_FIXED_FONT)); SetBkMode(hDC, TRANSPARENT); SetTextColor(hDC, TextColor); for (y=0; y<nLineChars; y++) { TextOut(hDC, 0, y * nCharHeight, &TEXTMATRIX(0, y), nLineChars); } EndPaint(hWnd, &ps); } return 0; default: break; } return DefWindowProc (hWnd, message, wParam, lParam); }
下面是主程序文件,该文件调用上面的CreateVirtualTerminal函数来创建一个窗口。
#include <windows.h> #define IDC_VTID 9876 static TCHAR szAppName[] = TEXT("Caret demo"); static LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); extern HWND CreateVirtualTerminal(HWND hWndParent, int left, int top, int width, int height, int id); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { 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 szAppName, // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position 400, // initial x size 300, // 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; } static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; switch (message) { case WM_CREATE: CreateVirtualTerminal(hWnd, 10, 10, 360, 240, IDC_VTID); return 0; case WM_PAINT: hDC = BeginPaint(hWnd, &ps); ; EndPaint(hWnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0 ; } return DefWindowProc (hWnd, message, wParam, lParam); }
示例程序运行后,在窗口中输入部分文本(模仿上面的cmd窗口^_^),效果如下:
本例实现了一个简单的终端模拟小程序,为了读者重用方便,我将终端模拟的小窗口单独作为一个完整的源文件,并且把窗口背景设为黑色,前景色设为白色,看起来更像CMD、Linux等命令行窗口。
更多经验交流可以加入Windows编程讨论QQ群:454398517。
关注微信公众平台:程序员互动联盟(coder_online),你可以第一时间获取原创技术文章,和(java/C/C++/Android/Windows/Linux)技术大牛做朋友,在线交流编程经验,获取编程基础知识,解决编程问题。程序员互动联盟,开发人员自己的家。
转载请注明出处http://www.coderonline.net/?p=1905,谢谢合作!