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

一、             概述

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

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

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

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

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

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

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

4、  可以用BACKSPACEDELETE键来删除输入的内容

另外,它不具有选择文本的功能及剪切、复制、粘贴等功能,这个文本框是用纯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);

其中的lefttop为、widthheight为文本框的位置及尺寸,_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消息时我们并没有这样做,是因为BeginPaintEndPaint已经为我们做了这件事情。

 

4、 响应按键消息

Windows有若干与键盘相关的消息,例如:WM_KEYDOWNWM_KEYUPWM_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在执行时会立即重绘窗口,以便让用户的输入会在界面上及时做出响应。

光标键及HOMEEND等键不会产生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,会自动把关键字突出显示,如果要做这样一个文本编辑器,就是非常复杂的事情了,不过办法总比问题多,这些有激情的问题会带领我们进入一个广阔的思维空间。

 

7、 最后一个问题

我写文章的水平不咋的,多多见谅。

全部的代码请见《用Windows API实现一个简单的文本输入框(下)》

 

相关推荐
作为Microsoft 32位平台的应用程序编程接口, Win32 API是从事Windows应用程序开发所必备的。 首先对Win32 API函数做完整的概述;然后收录五大类函数: 窗口管理、图形设备接口、系统服务、国际特性以及网络服务; 在附录部分,讲解如何在Visual Basic和Delphi中对其调用。 本书是从事Windows应用程序开发的软件工程师的必备参考手册。 控件与消息函数 共91个函数 硬件与系统函数 共98个函数 设备场景函数 共73个函数 绘图函数 共105个函数 位图、图标和光栅运算函数 共39个函数 菜单函数 共37个函数 文本和字体函数 共41个函数 打印函数 共66个函数 文件处理函数 共118个函数 进程和线程函数 共40个函数 Windows消息函数 共11个函数 网络函数 共14个函数 目 录 第一章 Win32 API概论…………………………………………………………………………1 1.1 为什么使用Win32 API …………………………………………………………………1 1.2 Win32 API简介 …………………………………………………………………………1 1.3 综述………………………………………………………………………………………11 第二章 窗口管理函数(Windows Control Function) ……………………………………13 2.1 易用特性函数(Accessibility Features)…………………………………………13 2.2 按钮函数(Button)……………………………………………………………………20 2.3 插入标记(^)函数(Caret)…………………………………………………………21 2.4 组合框函数(Combo box) ……………………………………………………………24 2.5 通用对话框函数(Common Dialog Box) ……………………………………………25 2.6 标函数(Cursor)………………………………………………………………………36 2.7 对话框函数(Dialog Box)……………………………………………………………40 2.8 编辑控制函数(Edit Control)………………………………………………………54 2.9 图标函数(Icon)………………………………………………………………………54 2.10 键盘加速器函数(Keyboard Accelerator)……………………………………… 61 2.11 键盘输入函数(Keyboard InPut) …………………………………………………63 2.12 列表框函数(List box) ……………………………………………………………75 2.13 菜单函数(Menu) ……………………………………………………………………76 2.14 消息和消息队列函数(Message and Message Queue)……………………………90 2.15 鼠标输入函数(Mouse Input) ……………………………………………………100 2.16 多文档接口函数(Multiple Document Interface) ……………………………103 2.17 资源函数(Resource)………………………………………………………………105 2.18 滚动条函数(Scroll Bar)…………………………………………………………113 2.19 窗口函数(Window)…………………………………………………………………119 2.20 窗口类函数(Window Class)………………………………………………………144 2.21 窗口过程函数(Window Procedure)………………………………………………150 2.22 窗口属性函数(Window Property) ………………………………………………152 第三章 图形设备接口函数(Graphic Device Interface Function) …………………155 3.1 位图函数(Bitmap) …………………………………………………………………155 3.2 笔刷函数(Brush)……………………………………………………………………171 3.3 剪切函数(Clippin
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页