用Windows API实现一个简单的文本输入框

用Windows API实现一个简单的文本输入框

转自:http://blog.csdn.net/skyever2100/archive/2008/11/13/3292480.aspx

一、             概述

在Windows Form应用中,Windows界面系统通过消息与应用程序进行交互,每个窗口都有相应的消息处理器,处理各自的用户输入及界面重绘等逻辑。窗口都有自己的类名,需要先把该类名及它对应的消息处理器注册到Windows界面系统中,再根据该类名来创建自己的窗口。

Windows也为我们准备了文本输入框,对于简单的文本输入,这个功能已经很完美了,不过如果我们要做一个功能强大的文本编辑器,就像开发环境的IDE那样,那么从头来写它会更好,可以实现我们想要的任何逻辑。

文本框是这样一个窗口,它响应键盘消息,并实时重绘窗口中的文本,还要响应鼠标消息来移动光标位置。

我尝试着用Windows API来实现了一个简单的单行文本框,它仅有以下几个功能:

1、  响应用户的普通字符输入

2、  可以用光标键及HOME、END键来移动光标

3、  可以用鼠标键来移动光标

4、  可以用BACKSPACE及DELETE键来删除输入的内容

另外,它不具有选择文本的功能及剪切、复制、粘贴等功能,这个文本框是用纯C来写的,不具有对象化的特征,也就是说,没有将代码封装成类,不能在界面上放置两个文本框,这是为了简化代码,只说明它的原理,如果要封装成类,可以采用MFC等类库来编写这个文本框。

在本文的最后,附带了本程序的全部代码,为了书写方便,把所有的代码都放在了一个代码文件中了。

本文本框运行界面如下:

 

 

 

二、             技术要点

1、 注册文本框类并创建文本框窗口

可以使用API函数RegisterClassEx来注册文本框类,如下:

WNDCLASSEX wc;

     ::ZeroMemory(&wc, sizeof(wc));

     wc.cbSize     = sizeof(wc);

     wc.style      = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;   // 指定当窗口尺寸发生变化时重绘窗口,并且响应鼠标双击事件

     wc.hInstance  = _HInstance;

     wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 指定窗口背景颜色为系统颜色“窗口背景”

     wc.lpszClassName = _T("MySimpleTextBox"); // 指定要注册的窗口类名,创建窗口时要以此类名为标识符

     wc.lpfnWndProc     = _TextBoxWndProc; // 处理窗口消息的函数

::RegisterClassEx(&wc);              // 调用API函数注册文本框窗口

在注册文本框类的时候,需要为其指定消息处理过程,就是那个名为_TextBoxWndProc的函数,函数原型如下:

LRESULT CALLBACK _TextBoxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

使用类名“MySimpleTextBox”来注册了一个文本框类,并且为其指定了消息处理过程,下一步就是要使用这个类名来创建一个文本框了,使用CreateWindow可以创建该窗口:

HWND hWnd = ::CreateWindow(__T("MySimpleTextBox"), NULL, WS_CHILDWINDOW | WS_VISIBLE,

left, top, width, height, hParentWnd, NULL, _HInstance, NULL);

其中的left、top为、width、height为文本框的位置及尺寸,_HInstance为父窗口的句柄。

 

2、 绘制文本框及文本

在文本框的消息处理过程中,响应消息WM_PAINT,可以实现对文本的绘制,假设使用默认的字体及字号,则代码如下:

     static PAINTSTRUCT ps;

     static RECT rect;

     HDC hDC = ::BeginPaint(hWnd, &ps);  // 开始绘制操作

     ::GetClientRect(hWnd, &rect);        // 获取客户区的尺寸

     ::DrawEdge(hDC, &rect, EDGE_SUNKEN, BF_RECT);  // 绘制边框,EDGE_SUNKEN表示绘制样式为内嵌样式,BF_RECT表示绘制矩形边框

     int len = ::_tcslen(_String);

::TextOut(hDC, 4, 2, _String, len);

::EndPaint(hWnd, &ps);               // 结束绘制操作

其中,_String为定义的一个全局变量:TCHAR _String[TEXTBOX_MAXLENGTH+1];

其中API函数DrawEdge可以绘制文本框的边缘。

 

3、 光标操作

我们可以自己一绘制闪烁的光标,不过Windows为我们提供了一套和光标有关的API函数,可以省去我们绘制光标的繁琐过程:

CreateCaret(HWND hWnd, HBITMAP hBitmap, int width, int height);

该API函数用于创建一个光标,第一个参数是窗口的句柄,第二个参数是光标的位图,用于定义光标的形状,第三、四个参数为光标的尺寸。

我们通常见到的光标是一个黑色的竖线,在Insert模式下(按了Insert键)为一个黑色的方块,如果是使用这种简单的光标,就把第二个参数设置为NULL就可以了。

ShowCaret(HWND hWnd);

该API函数用于显示光标,其中并没有指定显示哪个光标的参数,这是因为光标是与当前线程有关的资源,一个线程只能创建一个光标,在该线程中创建的光标,可以在该线程中对它执行其它的操作。

SetCaretPos(int x, int y);

该API函数用于设置光标的位置,当输入字符后或响应光标键时,可以调用该函数重新设置光标的位置。

在调用CrateCaret创建了光标时,它是隐藏状态,要显示它需要调用ShowCaret函数。

HideCaret(HWND hWnd);

该API函数用于隐藏光标。如果两次调用了HideCaret来隐藏光标,也需要调用两次ShowCaret才能显示它。

DestroyCaret(HWND hWnd);

该API函数用于销毁光标。

 

通常来说,我们需要在文本框得到焦点的时候创建并显示光标,而在文本框失去焦点的时候隐藏并销毁光标。

通过处理两个Windows Form消息可以实现上面的逻辑:

处理WM_SETFOCUS消息创建并显示光标:

::CreateCaret(hWnd, (HBITMAP)NULL, 1, TEXTBOX_HEIGHT-5); // 创建光标

::SetCaretPos(x, y);                  // 设置光标位置

::ShowCaret(hWnd);                    // 显示光标

 

处理WM_KILLFOCUS消息隐藏并销毁光标:

::HideCaret(hWnd);                    // 隐藏光标

::DestroyCaret();                     // 销毁光标

在窗口绘制之前,我们需要先隐藏光标,绘制完成之后再显示光标,否则屏幕上将会残留光标的痕迹,但在处理WM_PAINT消息时我们并没有这样做,是因为BeginPaint和EndPaint已经为我们做了这件事情。

 

4、 响应按键消息

Windows有若干与键盘相关的消息,例如:WM_KEYDOWN、WM_KEYUP、WM_CHAR等,我们需要处理WM_CHAR消息在光标的位置显示所输入的字符,消息处理过程的参数wParam即为所输入的字符,显示出字符之后,需要调用SetCaretPos来重新设置光标位置。

如何将字符立即显示出来呢,首先要指定文本框上的无效区域,按照Windows Form的编程约定,当Windows发现某个窗口上出现无效区域时,会向该窗口发送WM_PAINT消息来通知消息处理过程重绘这个区域。

API函数InvalidateRect可以指定窗口的某个区域无效,最简单的办法是让整个窗口都无效,如下:

     RECT rect;

     ::GetClientRect(hWnd, &rect);

     ::InvalidateRect(hWnd, &rect, TRUE);

::UpdateWindow(hWnd);

API函数UpdateWindow在执行时会立即重绘窗口,以便让用户的输入会在界面上及时做出响应。

光标键及HOME、END等键不会产生WM_CHAR消息,我们可以响应WM_KEYDOWN消息来移动光标的位置。

 

5、 响应鼠标消息

我们需要处理鼠标的单击消息WM_LBUTTONDOWN来移动光标,如何知道鼠标点击在哪个字符上呢,也就是如何取得指定位置处的字符呢?Windows API并没有为我们提供现成的功能,好在写一个这样的功能也不复杂,API函数GetTextExtentPoint(HDC hDc, LPCSTR lpString, int count, LPSIZE lpSize)可以获取指定的设置描述表下,指定字符串的尺寸。

基于这样的原理,我们可以逐渐求得每个字符所在的位置,与鼠标单击的位置来对照,便可以计算出鼠标是单击了哪个字符,如下:

     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、 文本缓冲区问题

本示例中为了简单起见,定义了一个固定大小的文本缓冲区,当输入的字符数量到达固定大小时,将忽略字符消息的处理。显然这种处理方式不实用,当文本较少时会造成内存缓存区的浪费,当文本较多时内存缓冲区不能够满足要求,并且插入和删除字符时,都会移动大量的文本,效率也比较慢。

我们需要用变长的字符串来解决字符缓冲区大小这个问题,变长字符串会有许多逻辑,可以用类来封装这些逻辑,例如MFC中的CString类。

 

2、 如何用面向对象的思维来写一个文本框

本简单的文本框显示不具有重用特征,字符串缓冲区、光标位置等数据都定义为全局变量,这导致无法在界面上放置两个文本框。如果采用面向对象的逻辑,应该把这些数据封装在一个类中,之所以没有采用面向对象的方式来写,是因为Windows API本身就是面向过程的,从整体架构上来讲,我们需要实现一套面向对象的开发框架,定义各种窗口共有的基类,在这个基类上派出生各种窗口,例如MFC就是这样做的。

 

3、 文本选择的逻辑

实现这个逻辑的关系在于以下两点:

一是当用户拖拽鼠标或用Shift+光标键等进行选择时,消息处理过程需要对这些鼠标和键盘的消息正确地响应,确定出当前所选择的区域

二是如何向用户呈现所选择的文本区域,通常它们具有指定颜色的底色,这牵涉到界面重绘的问题。可以对这部分文本设置好背景色和前景色进行绘制。

 

4、 重绘的效率问题

本示例中每次输入和删除都要重绘整个文本区域,实际上,我们可以判断窗口哪个位置无效了,一般是光标后面的文本无效。在绘制时先取得其无效区域,仅对这一小部分进行绘制,可以提高重绘效率。

 

5、 多行文本的问题

显然,该示例程序只能输入单行文本,如果要输入多行文本,可以响应回车键另起一行,在窗口绘制时,如果遇到回车键,便跳到下一行的最左侧区域进行绘制。

也可以采取每行文本使用一个字符串缓冲区的办法,以防止在大量文本时引起的大量内存移动,这需要定义一个文本管理器的类来处理多个缓冲区的逻辑。

 

6、 其它问题

围绕文本编辑器可以展开若干问题,例如字体、字号、颜色、行间距等,更高级的,像开发环境的IDE,会自动把关键字突出显示,如果要做这样一个文本编辑器,就是非常复杂的事情了,不过办法总比问题多,这些有激情的问题会带领我们进入一个广阔的思维空间。

 

  为了书写方便,把所有的代码都放在了一个代码文件中了。

 

  关于对该代码技术要点的解释,请参见:《用Windows API实现一个简单的文本输入框(上)》

 

  该代码中大部分地方都加了注释,有不妥之处,敬请批评指正:

  1  #include  < tchar.h >
  2 
  3  #include  < windows.h >
  4 
  5   
  6 
  7  HINSTANCE _HInstance;                               //  应用程序句柄
  8 
  9  TCHAR _Title[]  =  _T( " 简单文本框 " );                  //  定义窗口的标题
 10 
 11   
 12 
 13  TCHAR _WindowClass[]  =  _T( " MySimpleTextBoxApp " ); //  主窗口类名
 14 
 15  ATOM _RegisterClass();                              //  注册主窗口类
 16 
 17  HWND _CreateWindow( int  nCmdShow);                   //  创建主窗口
 18 
 19  LRESULT CALLBACK _WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);    //  主窗口消息处理函数
 20 
 21   
 22 
 23  TCHAR _TextBoxClass[]  =  _T( " MySimpleTextBox " );  //  文本框的类名
 24 
 25  ATOM _RegisterTextBoxClass();                       //  注册文本框的类
 26 
 27  HWND _CreateTextBoxWindow(HWND hParentWnd);         //  创建文本框
 28 
 29  LRESULT CALLBACK _TextBoxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);  //  文本框窗口消息处理函数
 30 
 31  void  _DrawText(HDC hDC);                            //  绘制文本
 32 
 33  void  _SetCaretPos(HWND hWnd);                       //  设置光标位置
 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       //  文本框宽度
 48 
 49  #define  TEXTBOX_HEIGHT      20        //  文本框高度
 50 
 51  #define  TEXTBOX_MAXLENGTH   1024  //  文本框中文本的最大长度
 52 
 53   
 54 
 55  TCHAR _String[TEXTBOX_MAXLENGTH  +   1 =  _T( "" );      //  文本
 56 
 57  int     _StringPosition  =  ::_tcslen(_String);         //  光标插入点所在的位置
 58 
 59   
 60 
 61  int  APIENTRY _tWinMain(HINSTANCE hInstance,         //  当前的应用程序句柄
 62 
 63                            HINSTANCE hPrevInstance,  //  前一个应用程序实例的句柄(在Win32上,始终为NULL)
 64 
 65                            LPTSTR lpCmdLine,         //  命令行参数
 66 
 67                             int         nCmdShow      //  窗口的显示样式
 68 
 69                            )
 70 
 71  {
 72 
 73       _HInstance  =  hInstance;
 74 
 75   
 76 
 77       _RegisterClass();                          //  注册窗口类
 78 
 79        if (_CreateWindow(nCmdShow)  ==  NULL)        //  创建窗口
 80 
 81            return  FALSE;
 82 
 83   
 84 
 85       MSG msg;
 86 
 87        while  (::GetMessage( & msg, NULL,  0 0 ))     //  从消息队列中获取消息
 88 
 89       {
 90 
 91           ::TranslateMessage( & msg);             //  转译一些特殊的消息
 92 
 93           ::DispatchMessage( & msg);              //  执行消息处理
 94 
 95       }
 96 
 97   
 98 
 99        return  ( int )msg.wParam;
100 
101  }
102 
103   
104 
105   
106 
107  //  注册应用程序窗口类
108 
109  ATOM _RegisterClass()
110 
111  {
112 
113       WNDCLASSEX wc;
114 
115       ::ZeroMemory( & wc,  sizeof (wc));                  //  作为一步清空,是为了让未赋值的字段的默认值为(或NULL)
116 
117   
118 
119       wc.cbSize      =   sizeof (wc);
120 
121       wc.style       =  CS_HREDRAW  |  CS_VREDRAW;   //  指定当窗口横向和纵向的尺寸发生变化时都会重绘窗口
122 
123       wc.hInstance   =  _HInstance;
124 
125       wc.hbrBackground  =  (HBRUSH)( COLOR_APPWORKSPACE  +   1 );   //  指定主窗口背景为“工作区域”系统颜色
126 
127       wc.lpszClassName  =  _WindowClass;           //  此为要注册的类名,创建窗口时要以此类名为标识符
128 
129       wc.lpfnWndProc      =  _WndProc;                       //  此为处理窗口消息的函数
130 
131   
132 
133        return  ::RegisterClassEx( & wc);                  //  调用API函数注册窗口类
134 
135  }
136 
137   
138 
139  //  创建窗口
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();     //  注册文本框的类
186 
187           hTextBoxWnd  =  _CreateTextBoxWindow(hWnd);  //  创建文本框
188 
189           }  break ;
190 
191   
192 
193        case  WM_ACTIVATE:                 //  当窗口被激活时,将焦点设置在文本框上
194 
195           ::SetFocus(hTextBoxWnd);
196 
197            break ;
198 
199   
200 
201        case  WM_SETCURSOR: {   //  设置光标形状
202 
203            static  HCURSOR hCursor  =  ::LoadCursor(NULL, IDC_ARROW);
204 
205           ::SetCursor(hCursor);
206 
207           }  break ;
208 
209   
210 
211        case  WM_DESTROY:    //  应用程序被关闭
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  //  注册文本框的类
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;    //  指定当窗口尺寸发生变化时重绘窗口,并且响应鼠标双击事件
248 
249       wc.hInstance   =  _HInstance;
250 
251       wc.hbrBackground  =  (HBRUSH)(COLOR_WINDOW  +   1 );  //  指定窗口背景颜色为系统颜色“窗口背景”
252 
253       wc.lpszClassName  =  _TextBoxClass;                   //  指定要注册的窗口类名,创建窗口时要以此类名为标识符
254 
255       wc.lpfnWndProc      =  _TextBoxWndProc;                //  处理窗口消息的函数
256 
257   
258 
259        return  ::RegisterClassEx( & wc);                      //  调用API函数注册文本框窗口
260 
261  }
262 
263   
264 
265   
266 
267  //  创建文本框
268 
269  HWND _CreateTextBoxWindow(HWND hParentWnd)
270 
271  {
272 
273        //  之下代码是为了让文本框显示在父窗口中央,而计算位置
274 
275       RECT parentWndRect;
276 
277       ::GetClientRect(hParentWnd,  & parentWndRect);   //  获取父窗口客户区的位置
278 
279        int  left  =  (parentWndRect.right  -  TEXTBOX_WIDTH)  /   2 , top  =  (parentWndRect.bottom  -  TEXTBOX_HEIGHT)  /   2 ;
280 
281   
282 
283        //  创建文本框
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: {   //  绘制这里之所以加一对大括号,是为了让之下定义的变量局部化
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);         //  获取客户区的尺寸
322 
323           ::DrawEdge(hDC,  & rect, EDGE_SUNKEN, BF_RECT);   //  绘制边框,EDGE_SUNKEN表示绘制样式为内嵌样式,BF_RECT表示绘制矩形边框
324 
325           _DrawText(hDC);                       //  绘制文本
326 
327           ::EndPaint(hWnd,  & ps);                //  结束绘制操作
328 
329   
330 
331           }  break ;
332 
333   
334 
335        case  WM_SETFOCUS: {     //  获得焦点
336 
337           ::CreateCaret(hWnd, (HBITMAP)NULL,  1 , TEXTBOX_HEIGHT - 5 );      //  创建光标
338 
339           _SetCaretPos(hWnd);                             //  设置光标位置
340 
341           ::ShowCaret(hWnd);                    //  显示光标
342 
343           }  break ;
344 
345   
346 
347        case  WM_KILLFOCUS:  //  失去焦点
348 
349           ::HideCaret(hWnd);                    //  隐藏光标
350 
351           ::DestroyCaret();                     //  销毁光标
352 
353            break ;
354 
355   
356 
357        case  WM_SETCURSOR: {   //  设置光标形状
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:  //  左光标键
406 
407                 if (_StringPosition  >   0 )
408 
409                     _StringPosition  -- ;
410 
411                 break ;
412 
413   
414 
415            case  VK_RIGHT:      //  右光标键
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: {   //  鼠标单击,设置光标位置
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  //  设置光标位置
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 

你可能感兴趣的:(用Windows API实现一个简单的文本输入框)