如何使用纯win32函数和C语言实现一个简单的文本编辑器
2017-04-11 23:37
941 查看
一、引言
在学习小甲鱼老师的Win32 SDK编程的教程的时候,当学到了第35课“插入符号”这课的时候,看到了这个源代码,我实在难掩心头的激动之情。是啊!这不就是一个用纯win32函数和C语言实现的一个简单的文本编辑器吗?!
现在的我真的太激动太激动了!跟着教程把代码敲了一遍,还是觉得不够尽兴,还要继续把注释敲上去,觉得还不够尽兴,所以特地来写了一篇博客来好好记录下这份代码。
二、这份优雅的代码所实现的功能
说是简单的文本编辑器,那么基本的功能是要有的:光标可以随处定位,随处可以输入文本
可以响应基本的按键输出字符
可以响应基本的功能性按键移动光标,比如换行、上下左右、home、end、page up和page down等等
可以对输入的文本实现删除的功能
看上去,这些功能说简单也简单,说不简单,实现起来也略显复杂,这里就让我们好好的来赏析这份代码吧!
三、请看代码
现在就来看看这份让我激动了好久的代码:#include <windows.h> // 用来设置指定位置的元素内容的宏 #define BUFFER(x, y) *(pBuffer + y * cxBuffer + x) LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("MyWindows"); 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("这个程序需要在 Windows NT 才能执行!"), szAppName, MB_ICONERROR); return 0; } hwnd = CreateWindow(szAppName, TEXT("文本编辑器"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { // cxChar 字符的平均宽度 // cyChar 字符的平均高度 // cxClient 客户区的宽度 // cyClient 客户区的高度 // cxBuffer 窗口横向最大缓冲区 // cyBuffer 窗口纵向最大缓冲区 // xCaret 输入插入符号的横坐标 // yCaret 输入插入符号的纵坐标 static int cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer, xCaret, yCaret; // pBuffer 存储整个屏幕的内容的缓冲区 static TCHAR *pBuffer = NULL; // 设备环境 HDC hdc; // x是横坐标计数,y是纵坐标技术,i是临时计数 int x, y, i; // 描述客户区绘制的信息 PAINTSTRUCT ps; // 当前设备环境中字体的信息 TEXTMETRIC tm; switch (message) { // 窗口创建时,计算字体的平均宽度和高度 // 得到 cxChar 和 cyChar 的值 case WM_CREATE: hdc = GetDC(hwnd); SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); GetTextMetrics(hdc, &tm); cxChar = tm.tmAveCharWidth; cyChar = tm.tmHeight; ReleaseDC(hwnd, hdc); // 此处木有返回,木有break case WM_SIZE: // 获得客户区的宽度和高度 // 得到 cxClient 和 cyClient 的值 if (message == WM_SIZE) { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); } // 获得横向最大缓存区和纵向最大缓存区的值 // 得到 cxBuffer 和 cyBuffer cxBuffer = max(1, cxClient / cxChar); cyBuffer = max(1, cyClient / cyChar); // 分配足够整个客户区显示的字符缓存区的空间 if (pBuffer != NULL) { free(pBuffer); } pBuffer = (TCHAR *)malloc(cxBuffer * cyBuffer * sizeof(TCHAR)); // 将整个字符缓存区的空间置为空字符 for (y = 0; y < cyBuffer; y++) { for (x = 0; x < cxBuffer; x++) { BUFFER(x, y) = ' '; } } // 将插入符号指向左上角 xCaret = 0; yCaret = 0; // 如果当前窗口获得了输入焦点,则设置输入焦点 if (hwnd == GetFocus()) { // 输入焦点在指定位置 // 位置坐标为 (xCaret, yCaret) SetCaretPos(xCaret * cxChar, yCaret * cyChar); } InvalidateRect(hwnd, NULL, TRUE); return 0; // 创建、设置输入插入符号并且显示 case WM_SETFOCUS: CreateCaret(hwnd, NULL, cxChar, cyChar); SetCaretPos(xCaret * cxChar, yCaret * cyChar); ShowCaret(hwnd); return 0; // 隐藏并摧毁输入插入符号 // 这里的隐藏操作是必要的,只有在 ShowCaret() 与 HideCaret() // 数量一一对应的时候,输入插入符号才会显示出来 case WM_KILLFOCUS: HideCaret(hwnd); DestroyCaret(); return 0; // 处理击键消息 case WM_KEYDOWN: switch (wParam) { // home键 case VK_HOME: xCaret = 0; break; // end键 case VK_END: xCaret = cxBuffer - 1; break; // pg up键 case VK_PRIOR: yCaret = 0; break; // pg dn键 case VK_NEXT: yCaret = cyBuffer - 1; break; // <-键 case VK_LEFT: xCaret = max(xCaret - 1, 0); break; // ->键 case VK_RIGHT: xCaret = min(xCaret + 1, cxBuffer - 1); break; // 上键 case VK_UP: yCaret = max(yCaret - 1, 0); break; // 下键 case VK_DOWN: yCaret = min(yCaret + 1, cyBuffer - 1); break; // del键 case VK_DELETE: // 要删除指定位置的一个字符,即要把后面的字符 // 一个一个挪到前面一个位置上,再将最后一个位置 // 的字符置为空 for (x = xCaret; x < cxBuffer - 1; x++) { BUFFER(x, yCaret) = BUFFER(x + 1, yCaret); } BUFFER(cxBuffer - 1, yCaret) = ' '; HideCaret(hwnd); hdc = GetDC(hwnd); SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); TextOut(hdc, xCaret * cxChar, yCaret * cyChar, &BUFFER(xCaret, yCaret), cxBuffer - xCaret); ReleaseDC(hwnd, hdc); ShowCaret(hwnd); break; } SetCaretPos(xCaret * cxChar, yCaret * cyChar); return 0; // 处理字符消息 case WM_CHAR: // lParam 表示重复次数, wParam 表示字符编码 for (i = 0; i < (int)LOWORD(lParam); i++) { switch (wParam) { // backspace键 case '\b': if (xCaret > 0) { xCaret--; SendMessage(hwnd, WM_KEYDOWN, VK_DELETE, 1); } break; // tab键 case '\t': do { SendMessage(hwnd, WM_CHAR, ' ', 1); } while (xCaret % 8 != 0); break; // enter键 case '\n': if (++yCaret == cyBuffer) { yCaret = 0; } break; // enter键 case '\r': xCaret = 0; if (++yCaret == cyBuffer) { yCaret = 0; } break; // esc键 case '\x1B': // 十六进制的1B,对应的ASCII字符是ESC for (y = 0; y < cyBuffer; y++) { for (x = 0; x < cxBuffer; x++) { BUFFER(x, y) = ' '; } } xCaret = 0; yCaret = 0; InvalidateRect(hwnd, NULL, FALSE); break; // 输出用户按下的键位 default: BUFFER(xCaret, yCaret) = (TCHAR)wParam; HideCaret(hwnd); hdc = GetDC(hwnd); SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); TextOut(hdc, xCaret * cxChar, yCaret * cyChar, &BUFFER(xCaret, yCaret), 1); ReleaseDC(hwnd, hdc); ShowCaret(hwnd); // 本行输完了,则跳转下一行开头显示 if (++xCaret == cxBuffer) { xCaret = 0; if (++yCaret == cyBuffer) { yCaret = 0; } } break; } SetCaretPos(xCaret * cxChar, yCaret * cyChar); return 0; } // 显示所有行的信息,之前都是以每行为单位显示的 case WM_PAINT: hdc = BeginPaint(hwnd, &ps); SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); for (y = 0; y < cyBuffer; y++) { TextOut(hdc, 0, y * cyChar, &BUFFER(0, y), cxBuffer); } EndPaint(hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
注释已经非常非常清晰,这里挑几个重点进行阐述:
程序是如何存储文本信息的呢?
程序将整个界面看做一个二维的空间,看作是一个横纵以字符平均宽度和高度为横纵单位的二维数组,那么只需要将这个二维数组进行实时的数据的更新,就可以实现文本的输入、编辑功能了。
光标的定位是如何实现的呢?
光标的定位是由这么几个函数实现的:
CreateCaret:创建和窗口相关联的插入符号
SetCaretPos:设置窗口内的插入符号的位置
ShowCaret:显示插入符号
HideCaret:隐藏插入符号
DestroyCaret:销毁插入符号
这几个函数完成了光标的实时定位显示和隐藏。这里按照小甲鱼老师说的,一定要让 ShowCaret 函数和 HideCaret 函数成对出现,否则光标是显示不出来的。
文本的删除功能是如何实现的呢?
文本的删除功能比较简单:
当用户删除一个字符的时候,当前位置之后的(当前行)的所有字符都向前移动一个位置,最后一个位置的字符重置为空即可实现删除功能。
功能性按键和字符按键的响应是如何实现的呢?
分别响应 WM_KEYDOWN 和 WM_CHAR 消息来实现的:
功能性的只需要记住当前是个二维数组,定位的光标只需要处理二维上的变化即可;
字符按键需要特殊处理几个特殊按键,其他的可以默认输出一个字符即可,在重绘的过程中,自动会刷新显示各个行的信息。
四、对这份代码爱不释手怎么办
这一份代码真的让我爱不释手,简单、优雅,却又做出来了强大的功能。日后想要添加复杂的功能又都可以自行钻研添加 :)
最后的最后,当然还是要感谢小甲鱼老师!!!
相关文章推荐
- 【远程调用框架】如何实现一个简单的RPC框架(二)实现与使用
- 对于一个频繁使用的短小函数,在C语言中应用什么实现,在C++中应用什么实现?
- 用C语言的可变参函数实现一个简单的printf函数
- <C语言>如何一步一步根据简单的代码联想到更多的功能?(实现输入一个整数,输出比它小包括它本身的所有素数。)
- Python(10)使用python函数实现一个简单的闭包操作
- 【c语言】编写一个函数reverse_string(char * string) 实现:将参数字符串中的字符反向排列。 要求:不能使用C函数库中的字符串操作函数
- 【C语言】编写一个函数实现n^k,使用递归实现。
- 学python(03)—— 如何使用函数实现一个随机字符串里的大小写字符互换
- 使用c语言实现一个简单的易语言
- 用UDP实现可靠文件传输,如何利用UDX创建一个简单的WIN32程序
- C语言:编写一个函数实现n^k,使用递归实现
- 如何使用epoll? 一个C语言的简单例子 - asdfjkl210 - ITeye技术网站
- C语言 编写一个函数实现n^k,使用递归实现
- C语言如何实现一个函数返回另一个函数
- 【C语言】【面试题】【笔试题】编写一个函数实现n^k,使用递归实现
- C语言:编写一个函数实现n^k,使用递归实现
- 【C语言】编写一个函数实现n^k,使用递归实现
- . 有一个一维数组,里面存储整形数据,请写一个函数,将他们按从大到小的顺序排列,要求执行效率高,并说明如何改善执行效率(该函数必须自己实现,不能使用php函数)。
- 如何使用epoll? 一个C语言的简单例子 - asdfjkl210 - ITeye技术网站
- 【C语言】没事可以试试这个小程序,使用文件操作,模拟实现一个简单的文件拷贝工具!