简介:这是一份VS2010中EDIT控件使用方法的教程资源包,面向刚接触Windows桌面应用开发、或希望系统梳理文本控件用法的读者,重点解决界面中文本输入与校验、事件联动、多行交互等常见需求。教程共梳理九种实用方法,从控件的创建与属性设置、各类事件处理、文本内容的动态读写,到输入内容的数字限制、字符数限制、多行编辑与只读属性,再到光标与滚动条控制、错误提示,以及基于GDI的高级自定义,构成由浅入深的学习路径。资源包共52个文件、约40.82MB,以C++源码、头文件和工程/资源文件为主,含编译生成的exe与pdb,以及sdf、ipch等VS工程辅助数据,可在VS2010中直接打开、编译和调试。其中的HelpEdit示例工程可运行,配合ReadMe说明与调试信息,能对照源码逐段理解九种方法在实际程序中的调用位置与效果,方便后续在自己的对话框项目中复用。目前已有609人学习使用,适合需要快速上手EDIT控件、完善Windows界面交互逻辑的开发人员。 做Windows桌面开发,有一类控件你几乎躲不开——EDIT控件。不管是填用户名、输验证码,还是做日志查看器、简易文本编辑器,屏幕上那块可以打字的地方,十有八九就是EDIT控件。我在用Win32 API和MFC做界面时,它是我用得最熟的控件之一,也是新手最容易踩坑的地方。这篇博客就围绕EDIT控件的使用方法,把创建、取文本、设置状态、事件处理这套流程串一遍,希望能帮你快速上手。
1. EDIT控件是什么,解决什么问题
1.1 你每天都在用它,只是没注意
EDIT控件本质上是Windows提供给应用程序的一个标准窗口类,程序里管它叫“编辑控件”。它干的事情很单纯:接收用户的键盘输入,显示一段可编辑的文本,然后让程序把你的输入读取走。你平时用微信聊天窗口输入文字、在浏览器地址栏里敲网址、在Excel的单元格里填数字,本质上都是在操作一个编辑控件。
开发聊天软件时,输入框是你产品的门面;写配置工具时,IP地址、端口号、用户名这些参数,最终都要靠EDIT控件来接收。我早年做工业上位机时,很多参数都是通过一堆EDIT控件填进去的,比如温度上限、电机转速、报警延时。你要是把EDIT控件用得不熟,后面的数据处理、校验逻辑全都得抓瞎。
1.2 不同技术栈里的EDIT控件
需要注意,EDIT控件并不只是Win32/MFC里的概念。Qt里叫QLineEdit和QTextEdit,C# WinForms里叫TextBox,WPF里叫TextBox和RichTextBox,HTML里对应的是input和textarea。虽然名字不同,但核心逻辑是一样的:要么单行输入,要么多行输入,要么带点高级格式,要么纯文本。
我建议新人在学的时候,先盯住Win32原生的EDIT控件打基本功。因为它是祖宗辈的东西,消息、样式、通知这三板斧摸透了,再去看QLineEdit、TextBox这些封装层,会觉得特别通透。封装层无非是把底层消息包了一层糖,让你少写几行API调用而已。
2. 创建EDIT控件的两种常用姿势
2.1 在对话框资源里拖一个出来
最快的上手方式是在对话框模板里直接放一个EDIT控件。用Visual Studio打开.rc资源文件,在工具箱里拖一个Edit Control到对话框上,然后给它起个ID,比如IDC_INPUT_NAME。这样程序启动后,系统会自动根据资源描述创建控件,你不需要手动写创建代码。
这种方式适合表单类界面,因为你只需要关心控件ID和消息映射就行。比如你在对话框的初始化函数里可以用GetDlgItem拿到控件句柄,或者直接用GetDlgItemText和SetDlgItemText去读写内容,不需要单独保存控件指针。
// 从对话框里读用户输入的姓名 TCHAR name[64] = {0}; GetDlgItemText(hDlg, IDC_INPUT_NAME, name, 64); // 把结果写回某个显示区域 SetDlgItemText(hDlg, IDC_TIP, TEXT("欢迎你,"));2.2 纯代码动态创建一个EDIT控件
如果你不是做对话框,而是想在某个窗口上动态生成编辑框,那就得用CreateWindowEx来创建。这种方式更灵活,也更能让你看清EDIT控件的本质:它就是一个窗口类名为EDIT的子窗口。
// 假设hWndParent是父窗口句柄 HWND hEdit = CreateWindowEx( WS_EX_CLIENTEDGE, // 扩展样式,带边框的那个立体凹陷效果 TEXT("EDIT"), // 窗口类名,系统内置 TEXT(""), // 初始文本 WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL, 10, 10, 200, 24, // 位置和大小 hWndParent, (HMENU)IDC_INPUT_NAME, // 控件ID,用于后续识别 GetModuleHandle(NULL), NULL );创建完之后,记得用ShowWindow让控件显示出来,并在父窗口的WM_SIZE里重新布局,否则控件大小不会跟着窗口变。我早期经常忘掉WS_VISIBLE,结果找半天不知道控件去哪了,这就是一个典型的坑。
3. 核心玩法:文本读写和状态控制
3.1 读写文本,别把API搞混
EDIT控件最基本的操作就是读文本和写文本。Win32里最常用的两对函数是GetWindowText/SetWindowText和GetDlgItemText/SetDlgItemText。前者需要知道控件句柄,后者直接用对话框句柄和控件ID。
很多新手会混淆这两个API。简单理解:GetWindowText可以读任意标准控件或窗口标题,GetDlgItemText是专门在对话框氛围内按ID去取控件文本。实际开发中,如果已经在某个窗口回调里拿到了控件句柄,用GetWindowText更方便;如果正在处理对话框消息,用GetDlgItemText更顺手。
// 方式一:通过句柄 TCHAR buf[256]; GetWindowText(hEdit, buf, 256); SetWindowText(hEdit, TEXT("hello")); // 方式二:通过对话框ID GetDlgItemText(hDlg, IDC_EDIT_INPUT, buf, 256); SetDlgItemText(hDlg, IDC_EDIT_INPUT, TEXT("hello"));要注意缓冲区大小。这个大小参数是字符数,不是字节数。在Unicode工程下,一个TCHAR是2字节,256就是可以容纳255个字符加一个终止符。分配缓冲区时别只给一个字节就传给API,那样会产生访问越界,严重时会直接把进程搞崩溃。
3.2 让EDIT控件变成密码框、只读框、禁用框
EDIT控件的强大之处在于,改几个样式就能切换形态,而且这些样式可以在创建时指定,也可以后期用SetWindowLong动态改。
- 密码框:样式
ES_PASSWORD。创建时加上,就能把输入内容显示成圆点。后期通过SendMessage(hEdit, EM_SETPASSWORDCHAR, (WPARAM)'*', 0)可以设置密码替代字符,设置成0就取消密码模式。 - 只读框:样式
ES_READONLY。加上之后只能看不能改,但文本可以被选中复制,适合做只读展示区。 - 禁用框:调用
EnableWindow(hEdit, FALSE),控件会变灰,也不能获得焦点。禁用和只读的区别是,禁用不接受鼠标操作,只读还可以划选文字。
这些状态的应用场景很典型:比如做登录界面时,密码框肯定要用ES_PASSWORD;做协议查看器时,报文展示区可以用ES_READONLY;程序未初始化时,把输入框禁掉可以避免用户误操作。
// 动态设置密码框 SendMessage(hEdit, EM_SETPASSWORDCHAR, (WPARAM)'*', 0); // 控件会立刻刷新为密码掩码模式 // 动态设置只读 SetWindowLong(hEdit, GWL_STYLE, GetWindowLong(hEdit, GWL_STYLE) | ES_READONLY); // 请注意,动态改只读样式可能需要重绘 InvalidateRect(hEdit, NULL, TRUE);4. 进阶技巧:样式、消息和事件驱动的输入处理
4.1 多行、自动滚动、自动换行的样式组合
单行EDIT控件默认按回车会触发父窗口的默认按钮,适合输入用户名、文件名这种短内容。而做日志窗口或文本编辑器时,你需要多行EDIT控件。多行需要组合这几个样式:
HWND hEdit = CreateWindowEx( WS_EX_CLIENTEDGE, TEXT("EDIT"), TEXT(""), WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN, 10, 10, 400, 300, hParent, (HMENU)IDC_EDIT_LOG, NULL, NULL );ES_MULTILINE:多行编辑。WS_VSCROLL:垂直滚动条。ES_AUTOVSCROLL:输入超过底部时自动向上滚动。ES_WANTRETURN:没有这个样式时,回车默认会当成确认键交给父窗口;加上它后,回车才会在编辑框里换行。
我经常用这个组合做日志窗口,把系统运行过程往里追加文本。追加文本有讲究,不能每次都用SetWindowText覆盖,要先GetWindowTextLength拿到长度,然后SendMessage(EM_SETSEL)把光标移到末尾,再用EM_REPLACESEL插入新文本,这样效率高,也不会闪烁。
// 追加文本 int len = GetWindowTextLength(hEdit); SendMessage(hEdit, EM_SETSEL, len, len); SendMessage(hEdit, EM_REPLACESEL, 0, (LPARAM)logText);4.2 事件通知:什么时候知道用户改了内容、点了哪里
EDIT控件和用户交互之后,会给父窗口发WM_COMMAND通知。你在对话框消息映射或者窗口回调里判断LOWORD(wParam)是不是控件ID,HIWORD(wParam)是不是通知码就行。
最常用的通知码:
EN_CHANGE:文本内容发生变化之后,界面已经刷新。EN_UPDATE:文本即将显示前触发,比EN_CHANGE早。EN_SETFOCUS:控件获得焦点。EN_KILLFOCUS:控件失去焦点。EN_VSCROLL:用户点击了垂直滚动条。EN_MAXTEXT:输入超过了最大长度限制,输入被截断。
实际开发中,我最常用EN_CHANGE做实时校验。比如表单里的金额输入框,用户每敲一个字符,就立刻判断是不是合法数字,不合法就把“保存”按钮置灰。这比用户填完再统一提交校验更友好,体验也好。
case WM_COMMAND: if (LOWORD(wParam) == IDC_INPUT_AMOUNT) { switch (HIWORD(wParam)) { case EN_CHANGE: // 用户改了金额,触发校验 OnAmountChanged(GetDlgItem(hDlg, IDC_INPUT_AMOUNT)); break; } } break;4.3 限制输入类型,别让用户乱来
做参数输入界面时,最烦用户往数字框里输入字母。EDIT控件没有现成的“只能输数字”样式,但你可以在EN_CHANGE里过滤,或者用更生猛的方式:子类化EDIT控件,拦截WM_CHAR消息。
子类化就是自己写一个窗口过程,接管EDIT控件的某些消息。老版本的MFC程序员都会使ClassWizard,而Win32下用SetWindowLongPtr替换窗口过程就行。核心逻辑:如果希望这个框只能输入数字和退格,就在WM_CHAR里判断一下。
WNDPROC oldProc; LRESULT CALLBACK NumberEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_CHAR) { // 允许数字、退格、回车和Ctrl+C/V等组合键 if (!((wParam >= '0' && wParam <= '9') || wParam == VK_BACK || wParam == VK_RETURN || (GetKeyState(VK_CONTROL) < 0 && (wParam == 'c' || wParam == 'v')))) { MessageBeep(0); return 0; } } return CallWindowProc(oldProc, hwnd, msg, wParam, lParam); } // 在创建EDIT控件后调用: oldProc = (WNDPROC)SetWindowLongPtr(hEdit, GWLP_WNDPROC, (LONG_PTR)NumberEditProc);注意,子类化时一定要保存旧的窗口过程,并在最后调用CallWindowProc,否则EDIT控件本身的行为会全部丢失,连绘图都可能出问题。我见过有人直接在子类里返回0,结果控件根本画不出来,还找不到原因。
5. 踩坑实录:常见问题与排查方法
5.1 中文乱码和字符集问题
这是老Windows程序员天天碰的事情。同一个代码在中文系统上好好的,拿到英文系统上一看全是问号,或者别人机器上正常,自己机器上乱码。十有八九是字符集没统一。
现在的Visual Studio新建项目,基本默认使用Unicode字符集。如果你用char数组和GetWindowTextA,在Unicode工程里要么编译报错,要么数据转换出问题。建议统一使用TCHAR宏,以及GetWindowText这样不带A/W后缀的API,让它跟着工程的字符集走。
TCHAR input[128] = {0}; GetDlgItemText(hDlg, IDC_EDIT_NAME, input, 128);如果非要使用char和std::string,那就明确调用GetWindowTextA和SetWindowTextA,并且保证周围所有代码都是ANSI风格,别一半一半混着来。最怕的就是工程设置是Unicode,代码里用了std::string,然后塞进SetWindowTextW,乱码就成了必然。
5.2 文本读不出来或界面不刷新
有时候你明明SetDlgItemText了,界面上却还是旧内容。首先检查控件ID有没有写错,尤其是对话框资源里控件的ID和代码里的宏定义是不是同一个值。再检查是不是在控件还没创建完成时就调用了设置函数,比如在对话框的WM_INITDIALOG之前。
另外,InvalidateRect和UpdateWindow不是每次都必须调用,但如果你修改了ES_READONLY这类样式,或者动态切换了密码星号,最好主动重绘一下,否则某些老旧的显卡驱动会显示异常。
5.3 回车键“认错门”和焦点问题
单行EDIT控件里按回车,默认会把回车消息当作“确认”发给父窗口,于是对话框就关闭或者触发了默认按钮。如果用户填完密码按回车想登录,结果弹窗没了,那多半就是没加ES_WANTRETURN,或者没在WM_COMMAND里拦截IDOK。
想控制回车时先校验、再跳转到下一个输入框,通常做法是在父窗口里处理WM_COMMAND的IDOK,或者直接设置控件焦点到目标EDIT控件:
SetFocus(GetDlgItem(hDlg, IDC_EDIT_PASSWORD));焦点问题也很典型:EN_KILLFOCUS事件里如果又弹出一个MessageBox,极容易造成焦点闪烁甚至控件失序。不要在这种事件里做阻断式弹窗,应该用PostMessage延后处理,或者简单记录一个标志位,等回调走完再弹。
5.4 多行EDIT控件刷新慢和闪烁
如果日志量大,频繁用SetWindowText就是给自己找罪受,这会全量替换文本并触发重绘。正确做法是使用EM_SETSEL加EM_REPLACESEL追加,并且尽量批量更新:攒一批日志再刷一次,不要来一条刷一条。还有,给EDIT控件加上ES_READONLY可以减少不必要的光标闪烁,对日志展示也友好。
如果实在要频繁更新,可以考虑双缓冲或者直接换用自定义控件。不过大部分场景下,只要用追加文本的方式,性能就够了。我曾用这个方式做了一个实时数据流显示窗口,每秒刷新上千行文本,CPU占用一直很低。
6. 一些压箱底的经验
这里分享几个我实际用出来的小技巧,不是官方文档里写得特别醒目的点:
- EDIT控件也可以作为超链接入口:把
ES_READONLY的控件加上自定义颜色,然后在WM_CTLCOLORSTATIC里返回一个蓝色画刷,再处理鼠标点击消息,就能做出一个假超链接。这个技巧做“关于”对话框好用。 - 密码框的掩码可以自定义:除了星号,你还可以把掩码设成小圆点、井号,甚至中文。用
EM_SETPASSWORDCHAR,传一个TCHAR进去就行。 - 获取选中文本:用
SendMessage(hEdit, EM_GETSEL, &start, &end)拿到起止位置,再用GetWindowText取出全文,取中间那段。这是做“查找替换”功能的基础,关键代码不多,但思路要清晰。 - 限制最大输入长度:用
EM_LIMITTEXT,参数是最大字符数。很多新手在WM_COMMAND里反复拦截字符,其实这消息一条就解决了。
EDIT控件看着简单,真要把焦点、通知、样式、字符集这些细节都处理好,还是要有一定积累。我做这套东西踩过很多坑,回头再看,其实无非就是围绕“创建、取文本、控样式、接通知”四个基本功在转。你把这里面的每个点都亲手写一遍,后面再碰到再复杂的界面,心里都会非常有底。
本文还有配套的精品资源,点击获取