標籤:
大家知道,在使用微軟的編程環境建立工程時會讓你選擇是控制台模式還是Windows應用程式。如果選擇控制台的console模式,就會在運行時出現一個黑洞洞的字元強制回應視窗,裡面就有等待輸入一閃一閃的插入符。輸入游標從DOS時代就存在,但是在Win32中賦予了更強大的功能。就是Windows的CMD視窗,其中的輸入焦點就是插入游標:
要注意的是這裡的插入符或插入游標並不是Windows中另外一個“游標”,這裡是指示插入字元的位置,而不是用於滑鼠,手寫輸入等可以定位、移動的游標(Cursor),而是插入符Caret,本文也成為插入游標,注意插入二字,為了方便,以下在本文中也簡稱為游標或插入符,但要注意此游標非彼游標。
為什麼會有插入游標(插入符)?瞭解了這個基本問題,就成功了一半了。我們知道電腦可以通過鍵盤來輸入各種字元和控制符,那麼自然就存在一個問題,輸入的字元應該放到螢幕的什麼位置?這個就是游標產生的原因,游標實際上就是一個字元插入標識。簡單的說就是我們想把字元輸入到哪個位置,就先把插入符設定到那裡,設定時其實就是告訴電腦輸入的字元你給我在哪裡顯示!從這個我們也可以推斷,插入符在同一時刻只能有一個。
要使用游標,首先得建立一個游標,建立游標的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 terminalstatic TCHAR *pTextMatrix; //VT character bufferstatic int nCharWidth, nCharHeight; //the width and height of characetersstatic int nCaretPosX, nCaretPosY; //the current position of caretstatic int nVTWidth, nVTHeight; //the width and height of virtual terminal windowstatic int nLineChars, nRowChars; //character number in one line and rowstatic int nCaretOffsetY; //the offset of vertical heightstatic COLORREF TextColor; //text color for terminal window, that is fore colorstatic 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 9876static 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,謝謝合作!
【Windows編程】系列第十篇:文本插入符