轉(zhuǎn)自:http://blog.csdn.net/skyever2100/archive/2008/11/13/3292480.aspx
一、 概述
在Windows Form應(yīng)用中,Windows界面系統(tǒng)通過消息與應(yīng)用程序進(jìn)行交互,每個(gè)窗口都有相應(yīng)的消息處理器,處理各自的用戶輸入及界面重繪等邏輯。窗口都有自己的類名,需要先把該類名及它對(duì)應(yīng)的消息處理器注冊(cè)到Windows界面系統(tǒng)中,再根據(jù)該類名來創(chuàng)建自己的窗口。
Windows也為我們準(zhǔn)備了文本輸入框,對(duì)于簡(jiǎn)單的文本輸入,這個(gè)功能已經(jīng)很完美了,不過如果我們要做一個(gè)功能強(qiáng)大的文本編輯器,就像開發(fā)環(huán)境的IDE那樣,那么從頭來寫它會(huì)更好,可以實(shí)現(xiàn)我們想要的任何邏輯。
文本框是這樣一個(gè)窗口,它響應(yīng)鍵盤消息,并實(shí)時(shí)重繪窗口中的文本,還要響應(yīng)鼠標(biāo)消息來移動(dòng)光標(biāo)位置。
我嘗試著用Windows API來實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的單行文本框,它僅有以下幾個(gè)功能:
1、 響應(yīng)用戶的普通字符輸入
2、 可以用光標(biāo)鍵及HOME、END鍵來移動(dòng)光標(biāo)
3、 可以用鼠標(biāo)鍵來移動(dòng)光標(biāo)
4、 可以用BACKSPACE及DELETE鍵來刪除輸入的內(nèi)容
另外,它不具有選擇文本的功能及剪切、復(fù)制、粘貼等功能,這個(gè)文本框是用純C來寫的,不具有對(duì)象化的特征,也就是說,沒有將代碼封裝成類,不能在界面上放置兩個(gè)文本框,這是為了簡(jiǎn)化代碼,只說明它的原理,如果要封裝成類,可以采用MFC等類庫(kù)來編寫這個(gè)文本框。
在本文的最后,附帶了本程序的全部代碼,為了書寫方便,把所有的代碼都放在了一個(gè)代碼文件中了。
本文本框運(yùn)行界面如下:
二、 技術(shù)要點(diǎn)
1、 注冊(cè)文本框類并創(chuàng)建文本框窗口
可以使用API函數(shù)RegisterClassEx來注冊(cè)文本框類,如下:
WNDCLASSEX wc;
::ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; // 指定當(dāng)窗口尺寸發(fā)生變化時(shí)重繪窗口,并且響應(yīng)鼠標(biāo)雙擊事件
wc.hInstance = _HInstance;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 指定窗口背景顏色為系統(tǒng)顏色“窗口背景”
wc.lpszClassName = _T("MySimpleTextBox"); // 指定要注冊(cè)的窗口類名,創(chuàng)建窗口時(shí)要以此類名為標(biāo)識(shí)符
wc.lpfnWndProc = _TextBoxWndProc; // 處理窗口消息的函數(shù)
::RegisterClassEx(&wc); // 調(diào)用API函數(shù)注冊(cè)文本框窗口
在注冊(cè)文本框類的時(shí)候,需要為其指定消息處理過程,就是那個(gè)名為_TextBoxWndProc的函數(shù),函數(shù)原型如下:
LRESULT CALLBACK _TextBoxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
使用類名“MySimpleTextBox”來注冊(cè)了一個(gè)文本框類,并且為其指定了消息處理過程,下一步就是要使用這個(gè)類名來創(chuàng)建一個(gè)文本框了,使用CreateWindow可以創(chuàng)建該窗口:
HWND hWnd = ::CreateWindow(__T("MySimpleTextBox"), NULL, WS_CHILDWINDOW | WS_VISIBLE,
left, top, width, height, hParentWnd, NULL, _HInstance, NULL);
其中的left、top為、width、height為文本框的位置及尺寸,_HInstance為父窗口的句柄。
2、 繪制文本框及文本
在文本框的消息處理過程中,響應(yīng)消息WM_PAINT,可以實(shí)現(xiàn)對(duì)文本的繪制,假設(shè)使用默認(rèn)的字體及字號(hào),則代碼如下:
static PAINTSTRUCT ps;
static RECT rect;
HDC hDC = ::BeginPaint(hWnd, &ps); // 開始繪制操作
::GetClientRect(hWnd, &rect); // 獲取客戶區(qū)的尺寸
::DrawEdge(hDC, &rect, EDGE_SUNKEN, BF_RECT); // 繪制邊框,EDGE_SUNKEN表示繪制樣式為內(nèi)嵌樣式,BF_RECT表示繪制矩形邊框
int len = ::_tcslen(_String);
::TextOut(hDC, 4, 2, _String, len);
::EndPaint(hWnd, &ps); // 結(jié)束繪制操作
其中,_String為定義的一個(gè)全局變量:TCHAR _String[TEXTBOX_MAXLENGTH+1];
其中API函數(shù)DrawEdge可以繪制文本框的邊緣。
3、 光標(biāo)操作
我們可以自己一繪制閃爍的光標(biāo),不過Windows為我們提供了一套和光標(biāo)有關(guān)的API函數(shù),可以省去我們繪制光標(biāo)的繁瑣過程:
CreateCaret(HWND hWnd, HBITMAP hBitmap, int width, int height);
該API函數(shù)用于創(chuàng)建一個(gè)光標(biāo),第一個(gè)參數(shù)是窗口的句柄,第二個(gè)參數(shù)是光標(biāo)的位圖,用于定義光標(biāo)的形狀,第三、四個(gè)參數(shù)為光標(biāo)的尺寸。
我們通常見到的光標(biāo)是一個(gè)黑色的豎線,在Insert模式下(按了Insert鍵)為一個(gè)黑色的方塊,如果是使用這種簡(jiǎn)單的光標(biāo),就把第二個(gè)參數(shù)設(shè)置為NULL就可以了。
ShowCaret(HWND hWnd);
該API函數(shù)用于顯示光標(biāo),其中并沒有指定顯示哪個(gè)光標(biāo)的參數(shù),這是因?yàn)楣鈽?biāo)是與當(dāng)前線程有關(guān)的資源,一個(gè)線程只能創(chuàng)建一個(gè)光標(biāo),在該線程中創(chuàng)建的光標(biāo),可以在該線程中對(duì)它執(zhí)行其它的操作。
SetCaretPos(int x, int y);
該API函數(shù)用于設(shè)置光標(biāo)的位置,當(dāng)輸入字符后或響應(yīng)光標(biāo)鍵時(shí),可以調(diào)用該函數(shù)重新設(shè)置光標(biāo)的位置。
在調(diào)用CrateCaret創(chuàng)建了光標(biāo)時(shí),它是隱藏狀態(tài),要顯示它需要調(diào)用ShowCaret函數(shù)。
HideCaret(HWND hWnd);
該API函數(shù)用于隱藏光標(biāo)。如果兩次調(diào)用了HideCaret來隱藏光標(biāo),也需要調(diào)用兩次ShowCaret才能顯示它。
DestroyCaret(HWND hWnd);
該API函數(shù)用于銷毀光標(biāo)。
通常來說,我們需要在文本框得到焦點(diǎn)的時(shí)候創(chuàng)建并顯示光標(biāo),而在文本框失去焦點(diǎn)的時(shí)候隱藏并銷毀光標(biāo)。
通過處理兩個(gè)Windows Form消息可以實(shí)現(xiàn)上面的邏輯:
處理WM_SETFOCUS消息創(chuàng)建并顯示光標(biāo):
::CreateCaret(hWnd, (HBITMAP)NULL, 1, TEXTBOX_HEIGHT-5); // 創(chuàng)建光標(biāo)
::SetCaretPos(x, y); // 設(shè)置光標(biāo)位置
::ShowCaret(hWnd); // 顯示光標(biāo)
處理WM_KILLFOCUS消息隱藏并銷毀光標(biāo):
::HideCaret(hWnd); // 隱藏光標(biāo)
::DestroyCaret(); // 銷毀光標(biāo)
在窗口繪制之前,我們需要先隱藏光標(biāo),繪制完成之后再顯示光標(biāo),否則屏幕上將會(huì)殘留光標(biāo)的痕跡,但在處理WM_PAINT消息時(shí)我們并沒有這樣做,是因?yàn)锽eginPaint和EndPaint已經(jīng)為我們做了這件事情。
4、 響應(yīng)按鍵消息
Windows有若干與鍵盤相關(guān)的消息,例如:WM_KEYDOWN、WM_KEYUP、WM_CHAR等,我們需要處理WM_CHAR消息在光標(biāo)的位置顯示所輸入的字符,消息處理過程的參數(shù)wParam即為所輸入的字符,顯示出字符之后,需要調(diào)用SetCaretPos來重新設(shè)置光標(biāo)位置。
如何將字符立即顯示出來呢,首先要指定文本框上的無效區(qū)域,按照Windows Form的編程約定,當(dāng)Windows發(fā)現(xiàn)某個(gè)窗口上出現(xiàn)無效區(qū)域時(shí),會(huì)向該窗口發(fā)送WM_PAINT消息來通知消息處理過程重繪這個(gè)區(qū)域。
API函數(shù)InvalidateRect可以指定窗口的某個(gè)區(qū)域無效,最簡(jiǎn)單的辦法是讓整個(gè)窗口都無效,如下:
RECT rect;
::GetClientRect(hWnd, &rect);
::InvalidateRect(hWnd, &rect, TRUE);
::UpdateWindow(hWnd);
API函數(shù)UpdateWindow在執(zhí)行時(shí)會(huì)立即重繪窗口,以便讓用戶的輸入會(huì)在界面上及時(shí)做出響應(yīng)。
光標(biāo)鍵及HOME、END等鍵不會(huì)產(chǎn)生WM_CHAR消息,我們可以響應(yīng)WM_KEYDOWN消息來移動(dòng)光標(biāo)的位置。
5、 響應(yīng)鼠標(biāo)消息
我們需要處理鼠標(biāo)的單擊消息WM_LBUTTONDOWN來移動(dòng)光標(biāo),如何知道鼠標(biāo)點(diǎn)擊在哪個(gè)字符上呢,也就是如何取得指定位置處的字符呢?Windows API并沒有為我們提供現(xiàn)成的功能,好在寫一個(gè)這樣的功能也不復(fù)雜,API函數(shù)GetTextExtentPoint(HDC hDc, LPCSTR lpString, int count, LPSIZE lpSize)可以獲取指定的設(shè)置描述表下,指定字符串的尺寸。
基于這樣的原理,我們可以逐漸求得每個(gè)字符所在的位置,與鼠標(biāo)單擊的位置來對(duì)照,便可以計(jì)算出鼠標(biāo)是單擊了哪個(gè)字符,如下:
int x = LOWORD(lParam);
HDC hDc = ::GetDC(hWnd);
int strLen = ::_tcslen(_String), strPos = 0;
SIZE size;
for (strPos=0; strPos<strLen; strPos++)
{
::GetTextExtentPoint(hDc, _String, strPos, &size);
if(size.cx + 4 >= x)
break;
}
_StringPosition = strPos;
::GetTextExtentPoint(hDc, _String, strPos, &size);
::SetCaretPos(size.cx + 4, 3);
::ReleaseDC(hWnd, hDc);
三、 遺留問題
1、 文本緩沖區(qū)問題
本示例中為了簡(jiǎn)單起見,定義了一個(gè)固定大小的文本緩沖區(qū),當(dāng)輸入的字符數(shù)量到達(dá)固定大小時(shí),將忽略字符消息的處理。顯然這種處理方式不實(shí)用,當(dāng)文本較少時(shí)會(huì)造成內(nèi)存緩存區(qū)的浪費(fèi),當(dāng)文本較多時(shí)內(nèi)存緩沖區(qū)不能夠滿足要求,并且插入和刪除字符時(shí),都會(huì)移動(dòng)大量的文本,效率也比較慢。
我們需要用變長(zhǎng)的字符串來解決字符緩沖區(qū)大小這個(gè)問題,變長(zhǎng)字符串會(huì)有許多邏輯,可以用類來封裝這些邏輯,例如MFC中的CString類。
2、 如何用面向?qū)ο蟮乃季S來寫一個(gè)文本框
本簡(jiǎn)單的文本框顯示不具有重用特征,字符串緩沖區(qū)、光標(biāo)位置等數(shù)據(jù)都定義為全局變量,這導(dǎo)致無法在界面上放置兩個(gè)文本框。如果采用面向?qū)ο蟮倪壿嫞瑧?yīng)該把這些數(shù)據(jù)封裝在一個(gè)類中,之所以沒有采用面向?qū)ο蟮姆绞絹韺懀且驗(yàn)閃indows API本身就是面向過程的,從整體架構(gòu)上來講,我們需要實(shí)現(xiàn)一套面向?qū)ο蟮拈_發(fā)框架,定義各種窗口共有的基類,在這個(gè)基類上派出生各種窗口,例如MFC就是這樣做的。
3、 文本選擇的邏輯
實(shí)現(xiàn)這個(gè)邏輯的關(guān)系在于以下兩點(diǎn):
一是當(dāng)用戶拖拽鼠標(biāo)或用Shift+光標(biāo)鍵等進(jìn)行選擇時(shí),消息處理過程需要對(duì)這些鼠標(biāo)和鍵盤的消息正確地響應(yīng),確定出當(dāng)前所選擇的區(qū)域
二是如何向用戶呈現(xiàn)所選擇的文本區(qū)域,通常它們具有指定顏色的底色,這牽涉到界面重繪的問題。可以對(duì)這部分文本設(shè)置好背景色和前景色進(jìn)行繪制。
4、 重繪的效率問題
本示例中每次輸入和刪除都要重繪整個(gè)文本區(qū)域,實(shí)際上,我們可以判斷窗口哪個(gè)位置無效了,一般是光標(biāo)后面的文本無效。在繪制時(shí)先取得其無效區(qū)域,僅對(duì)這一小部分進(jìn)行繪制,可以提高重繪效率。
5、 多行文本的問題
顯然,該示例程序只能輸入單行文本,如果要輸入多行文本,可以響應(yīng)回車鍵另起一行,在窗口繪制時(shí),如果遇到回車鍵,便跳到下一行的最左側(cè)區(qū)域進(jìn)行繪制。
也可以采取每行文本使用一個(gè)字符串緩沖區(qū)的辦法,以防止在大量文本時(shí)引起的大量?jī)?nèi)存移動(dòng),這需要定義一個(gè)文本管理器的類來處理多個(gè)緩沖區(qū)的邏輯。
6、 其它問題
圍繞文本編輯器可以展開若干問題,例如字體、字號(hào)、顏色、行間距等,更高級(jí)的,像開發(fā)環(huán)境的IDE,會(huì)自動(dòng)把關(guān)鍵字突出顯示,如果要做這樣一個(gè)文本編輯器,就是非常復(fù)雜的事情了,不過辦法總比問題多,這些有激情的問題會(huì)帶領(lǐng)我們進(jìn)入一個(gè)廣闊的思維空間。
為了書寫方便,把所有的代碼都放在了一個(gè)代碼文件中了。
關(guān)于對(duì)該代碼技術(shù)要點(diǎn)的解釋,請(qǐng)參見:《用Windows API實(shí)現(xiàn)一個(gè)簡(jiǎn)單的文本輸入框(上)》
該代碼中大部分地方都加了注釋,有不妥之處,敬請(qǐng)批評(píng)指正:
1 #include <tchar.h>
2
3 #include <windows.h>
4
5
6
7 HINSTANCE _HInstance; // 應(yīng)用程序句柄
8
9 TCHAR _Title[] = _T("簡(jiǎn)單文本框"); // 定義窗口的標(biāo)題
10
11
12
13 TCHAR _WindowClass[] = _T("MySimpleTextBoxApp");// 主窗口類名
14
15 ATOM _RegisterClass(); // 注冊(cè)主窗口類
16
17 HWND _CreateWindow(int nCmdShow); // 創(chuàng)建主窗口
18
19 LRESULT CALLBACK _WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); // 主窗口消息處理函數(shù)
20
21
22
23 TCHAR _TextBoxClass[] = _T("MySimpleTextBox"); // 文本框的類名
24
25 ATOM _RegisterTextBoxClass(); // 注冊(cè)文本框的類
26
27 HWND _CreateTextBoxWindow(HWND hParentWnd); // 創(chuàng)建文本框
28
29 LRESULT CALLBACK _TextBoxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); // 文本框窗口消息處理函數(shù)
30
31 void _DrawText(HDC hDC); // 繪制文本
32
33 void _SetCaretPos(HWND hWnd); // 設(shè)置光標(biāo)位置
34
35 void _UpdateWindow(HWND hWnd); // 更新窗口
36
37
38
39
40
41 // 一些常量定義
42
43 #define MAINWINDOW_WIDTH 400 // 主窗口寬度
44
45 #define MAINWINDOW_HEIGHT 200 // 主窗口高度
46
47 #define TEXTBOX_WIDTH 300 // 文本框?qū)挾?/span>
48
49 #define TEXTBOX_HEIGHT 20 // 文本框高度
50
51 #define TEXTBOX_MAXLENGTH 1024 // 文本框中文本的最大長(zhǎng)度
52
53
54
55 TCHAR _String[TEXTBOX_MAXLENGTH + 1] = _T(""); // 文本
56
57 int _StringPosition = ::_tcslen(_String); // 光標(biāo)插入點(diǎn)所在的位置
58
59
60
61 int APIENTRY _tWinMain(HINSTANCE hInstance, // 當(dāng)前的應(yīng)用程序句柄
62
63 HINSTANCE hPrevInstance, // 前一個(gè)應(yīng)用程序?qū)嵗木浔ㄔ赪in32上,始終為NULL)
64
65 LPTSTR lpCmdLine, // 命令行參數(shù)
66
67 int nCmdShow // 窗口的顯示樣式
68
69 )
70
71 {
72
73 _HInstance = hInstance;
74
75
76
77 _RegisterClass(); // 注冊(cè)窗口類
78
79 if(_CreateWindow(nCmdShow) == NULL) // 創(chuàng)建窗口
80
81 return FALSE;
82
83
84
85 MSG msg;
86
87 while (::GetMessage(&msg, NULL, 0, 0)) // 從消息隊(duì)列中獲取消息
88
89 {
90
91 ::TranslateMessage(&msg); // 轉(zhuǎn)譯一些特殊的消息
92
93 ::DispatchMessage(&msg); // 執(zhí)行消息處理
94
95 }
96
97
98
99 return (int)msg.wParam;
100
101 }
102
103
104
105
106
107 // 注冊(cè)應(yīng)用程序窗口類
108
109 ATOM _RegisterClass()
110
111 {
112
113 WNDCLASSEX wc;
114
115 ::ZeroMemory(&wc, sizeof(wc)); // 作為一步清空,是為了讓未賦值的字段的默認(rèn)值為(或NULL)
116
117
118
119 wc.cbSize = sizeof(wc);
120
121 wc.style = CS_HREDRAW | CS_VREDRAW; // 指定當(dāng)窗口橫向和縱向的尺寸發(fā)生變化時(shí)都會(huì)重繪窗口
122
123 wc.hInstance = _HInstance;
124
125 wc.hbrBackground = (HBRUSH)( COLOR_APPWORKSPACE + 1); // 指定主窗口背景為“工作區(qū)域”系統(tǒng)顏色
126
127 wc.lpszClassName = _WindowClass; // 此為要注冊(cè)的類名,創(chuàng)建窗口時(shí)要以此類名為標(biāo)識(shí)符
128
129 wc.lpfnWndProc = _WndProc; // 此為處理窗口消息的函數(shù)
130
131
132
133 return ::RegisterClassEx(&wc); // 調(diào)用API函數(shù)注冊(cè)窗口類
134
135 }
136
137
138
139 // 創(chuàng)建窗口
140
141 HWND _CreateWindow(int nCmdShow)
142
143 {
144
145 HWND hWnd = ::CreateWindow(_WindowClass, _Title, WS_OVERLAPPEDWINDOW,
146
147 CW_USEDEFAULT, CW_USEDEFAULT, MAINWINDOW_WIDTH, MAINWINDOW_HEIGHT, NULL, NULL, _HInstance, NULL);
148
149
150
151 if(hWnd == NULL)
152
153 return NULL;
154
155
156
157 ::ShowWindow(hWnd, nCmdShow);
158
159 ::UpdateWindow(hWnd);
160
161
162
163 return hWnd;
164
165 }
166
167
168
169 // 窗口處理過程
170
171 LRESULT CALLBACK _WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
172
173 {
174
175 static HWND hTextBoxWnd;
176
177
178
179 switch (message)
180
181 {
182
183 case WM_CREATE: {
184
185 _RegisterTextBoxClass(); // 注冊(cè)文本框的類
186
187 hTextBoxWnd = _CreateTextBoxWindow(hWnd); // 創(chuàng)建文本框
188
189 } break;
190
191
192
193 case WM_ACTIVATE: // 當(dāng)窗口被激活時(shí),將焦點(diǎn)設(shè)置在文本框上
194
195 ::SetFocus(hTextBoxWnd);
196
197 break;
198
199
200
201 case WM_SETCURSOR: { // 設(shè)置光標(biāo)形狀
202
203 static HCURSOR hCursor = ::LoadCursor(NULL, IDC_ARROW);
204
205 ::SetCursor(hCursor);
206
207 } break;
208
209
210
211 case WM_DESTROY: // 應(yīng)用程序被關(guān)閉
212
213 ::PostQuitMessage(0);
214
215 break;
216
217
218
219 default:
220
221 return ::DefWindowProc(hWnd, message, wParam, lParam);
222
223 }
224
225
226
227 return (LRESULT)0;
228
229 }
230
231
232
233 // 注冊(cè)文本框的類
234
235 ATOM _RegisterTextBoxClass()
236
237 {
238
239 WNDCLASSEX wc;
240
241 ::ZeroMemory(&wc, sizeof(wc));
242
243
244
245 wc.cbSize = sizeof(wc);
246
247 wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; // 指定當(dāng)窗口尺寸發(fā)生變化時(shí)重繪窗口,并且響應(yīng)鼠標(biāo)雙擊事件
248
249 wc.hInstance = _HInstance;
250
251 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 指定窗口背景顏色為系統(tǒng)顏色“窗口背景”
252
253 wc.lpszClassName = _TextBoxClass; // 指定要注冊(cè)的窗口類名,創(chuàng)建窗口時(shí)要以此類名為標(biāo)識(shí)符
254
255 wc.lpfnWndProc = _TextBoxWndProc; // 處理窗口消息的函數(shù)
256
257
258
259 return ::RegisterClassEx(&wc); // 調(diào)用API函數(shù)注冊(cè)文本框窗口
260
261 }
262
263
264
265
266
267 // 創(chuàng)建文本框
268
269 HWND _CreateTextBoxWindow(HWND hParentWnd)
270
271 {
272
273 // 之下代碼是為了讓文本框顯示在父窗口中央,而計(jì)算位置
274
275 RECT parentWndRect;
276
277 ::GetClientRect(hParentWnd, &parentWndRect); // 獲取父窗口客戶區(qū)的位置
278
279 int left = (parentWndRect.right - TEXTBOX_WIDTH) / 2, top = (parentWndRect.bottom - TEXTBOX_HEIGHT) / 2;
280
281
282
283 // 創(chuàng)建文本框
284
285 HWND hWnd = ::CreateWindow(_TextBoxClass, NULL, WS_CHILDWINDOW | WS_VISIBLE,
286
287 left, top, TEXTBOX_WIDTH, TEXTBOX_HEIGHT,
288
289 hParentWnd, NULL, _HInstance, NULL);
290
291
292
293 return hWnd;
294
295 }
296
297
298
299 // 文本框消息的處理過程
300
301 LRESULT CALLBACK _TextBoxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
302
303 {
304
305 switch (message)
306
307 {
308
309 case WM_PAINT: { // 繪制這里之所以加一對(duì)大括號(hào),是為了讓之下定義的變量局部化
310
311
312
313 static PAINTSTRUCT ps;
314
315 static RECT rect;
316
317 HDC hDC = ::BeginPaint(hWnd, &ps); // 開始繪制操作
318
319
320
321 ::GetClientRect(hWnd, &rect); // 獲取客戶區(qū)的尺寸
322
323 ::DrawEdge(hDC, &rect, EDGE_SUNKEN, BF_RECT); // 繪制邊框,EDGE_SUNKEN表示繪制樣式為內(nèi)嵌樣式,BF_RECT表示繪制矩形邊框
324
325 _DrawText(hDC); // 繪制文本
326
327 ::EndPaint(hWnd, &ps); // 結(jié)束繪制操作
328
329
330
331 } break;
332
333
334
335 case WM_SETFOCUS: { // 獲得焦點(diǎn)
336
337 ::CreateCaret(hWnd, (HBITMAP)NULL, 1, TEXTBOX_HEIGHT-5); // 創(chuàng)建光標(biāo)
338
339 _SetCaretPos(hWnd); // 設(shè)置光標(biāo)位置
340
341 ::ShowCaret(hWnd); // 顯示光標(biāo)
342
343 } break;
344
345
346
347 case WM_KILLFOCUS: // 失去焦點(diǎn)
348
349 ::HideCaret(hWnd); // 隱藏光標(biāo)
350
351 ::DestroyCaret(); // 銷毀光標(biāo)
352
353 break;
354
355
356
357 case WM_SETCURSOR: { // 設(shè)置光標(biāo)形狀
358
359 static HCURSOR hCursor = ::LoadCursor(NULL, IDC_IBEAM);
360
361 ::SetCursor(hCursor);
362
363 } break;
364
365
366
367 case WM_CHAR: { // 字符消息
368
369 TCHAR code = (TCHAR)wParam;
370
371 int len = ::_tcslen(_String);
372
373 if(code < (TCHAR)' ' || len >= TEXTBOX_MAXLENGTH)
374
375 return 0;
376
377
378
379 ::MoveMemory(_String + _StringPosition + 1, _String + _StringPosition, (len - _StringPosition + 1) * sizeof(TCHAR));
380
381 _String[_StringPosition ++] = code;
382
383
384
385 _UpdateWindow(hWnd);
386
387 _SetCaretPos(hWnd);
388
389
390
391 } break;
392
393
394
395 case WM_KEYDOWN: { // 鍵按下消息
396
397 TCHAR code = (TCHAR)wParam;
398
399
400
401 switch (code)
402
403 {
404
405 case VK_LEFT: // 左光標(biāo)鍵
406
407 if(_StringPosition > 0)
408
409 _StringPosition --;
410
411 break;
412
413
414
415 case VK_RIGHT: // 右光標(biāo)鍵
416
417 if(_StringPosition < (int)::_tcslen(_String))
418
419 _StringPosition ++;
420
421 break;
422
423
424
425 case VK_HOME: // HOME 鍵
426
427 _StringPosition = 0;
428
429 break;
430
431
432
433 case VK_END: // END 鍵
434
435 _StringPosition = ::_tcslen(_String);
436
437 break;
438
439
440
441 case VK_BACK: // 退格鍵
442
443 if(_StringPosition > 0)
444
445 {
446
447 ::MoveMemory(_String + _StringPosition - 1, _String + _StringPosition, (::_tcslen(_String)-_StringPosition + 1) * sizeof(TCHAR));
448
449 _StringPosition --;
450
451 _UpdateWindow(hWnd);
452
453 }
454
455 break;
456
457
458
459 case VK_DELETE: { // 刪除鍵
460
461 int len = ::_tcslen(_String);
462
463 if(_StringPosition < len)
464
465 {
466
467 ::MoveMemory(_String + _StringPosition, _String + _StringPosition + 1, (::_tcslen(_String) - _StringPosition + 1) * sizeof(TCHAR));
468
469 _UpdateWindow(hWnd);
470
471 }
472
473
474
475 } break;
476
477
478
479 }
480
481
482
483 _SetCaretPos(hWnd);
484
485
486
487 } break;
488
489
490
491 case WM_LBUTTONDOWN: { // 鼠標(biāo)單擊,設(shè)置光標(biāo)位置
492
493 int x = LOWORD(lParam);
494
495 HDC hDc = ::GetDC(hWnd);
496
497
498
499 int strLen = ::_tcslen(_String), strPos = 0;
500
501 SIZE size;
502
503
504
505 for (strPos=0; strPos<strLen; strPos++)
506
507 {
508
509 ::GetTextExtentPoint(hDc, _String, strPos, &size);
510
511
512
513 if(size.cx + 4 >= x)
514
515 break;
516
517 }
518
519
520
521 _StringPosition = strPos;
522
523 ::GetTextExtentPoint(hDc, _String, strPos, &size);
524
525 ::SetCaretPos(size.cx + 4, 3);
526
527
528
529 ::ReleaseDC(hWnd, hDc);
530
531
532
533 } break;
534
535
536
537 default:
538
539 return ::DefWindowProc(hWnd, message, wParam, lParam);
540
541 }
542
543
544
545 return (LRESULT)0;
546
547 }
548
549
550
551 // 更新窗口
552
553 void _UpdateWindow(HWND hWnd)
554
555 {
556
557 RECT rect;
558
559 ::GetClientRect(hWnd, &rect);
560
561 ::InvalidateRect(hWnd, &rect, TRUE);
562
563 ::UpdateWindow(hWnd);
564
565 }
566
567
568
569 // 繪制文本
570
571 void _DrawText(HDC hDC)
572
573 {
574
575 int len = ::_tcslen(_String);
576
577 ::TextOut(hDC, 4, 2, _String, len);
578
579 }
580
581
582
583 // 設(shè)置光標(biāo)位置
584
585 void _SetCaretPos(HWND hWnd)
586
587 {
588
589 HDC hDC = ::GetDC(hWnd);
590
591
592
593 SIZE size;
594
595 ::GetTextExtentPoint(hDC, _String, _StringPosition, &size);
596
597 ::SetCaretPos(4 + size.cx, 3);
598
599
600
601 ::ReleaseDC(hWnd, hDC);
602
603
604
605 }
606
607