我们的程序的执行效果图如下:
【自由移动光标修改编辑文字】
我们这里并没有支持退格键删除,是为了突出主题,以免散注意力。如果你想加入删除的功能,请阅读文章《win32使用退格键BackSpace从后到前的删除字符串的字符》和《win32长按退格键删除全部文字的功能实现》。相信等你加上所有学到的这些知识后,你的程序将会越来越强大了。这个组合就留给你自己来实现了,就和堆积木一样。我们的每一篇文章都详细的讲述了每一个知识点的原理和代码使用,只要你认真学习了,可以轻松完成的。对于键盘的响应,我们这里有上下左右光标方向键和PageUp、PageDown、Home和End键,对于基础的按键响应,就请参考《win32使用Tab键输入制表符或者替换为四个空格》。对于提示当前光标所在的位置的字符功能,可以参考《win32检测键盘的大小写状态并实时显示编辑的大小写状态》。
由上面的引用文章可以看到,我们学习的内容,将会越来越全面,越来越复杂。然而我们将复杂的知识点分成了很多的点来逐个细细分析,所以也不难。最后你只要将他们组装起来即可完成丰富的功能。
那么下面我就来说说本程序涉及到的逻辑点和注意点。
整体来分析一下,我们要达到的效果是:我们的输入使用单行模式。Home回到开头,End回到结尾,PageUp向前移动10个字,PageDown向后移动10个字。键盘光标↑和←可以向左移动一个位置,光标→和↓可以向右移动一个位置。随时输入文字,可以通过指定的光标方向键和其他几个按键来移动光标,然后可以覆盖光标所在的位置的文字,这样实现修改的效果。同时,我们要实时提示当前光标所在的位置的文字。
所以,整体上分为三个部分,即:输入文字、移动光标和显示文字。
输入文字就是将光标所在的位置的数组元素赋值当前按键的字符即可。很简单是吧。当然,修改时的覆盖,还是在光标所在的位置的数组元素中赋值一个字符。所以是一样的。当然,输入一个文字后,自然要将光标的位置右移一个。既然如此,我们就可以轻松的实现这个简单的功能。为了能够在输入文字后立马显示出来,所以要让客户区无效且使客户区的背景擦除。输入文字在WM_CHAR消息中处理。
case WM_CHAR:
{
TCHAR ch=wParam;
txt[iPosInput]=ch;
iPosInput++;
}
InvalidateRect(hwnd,NULL,TRUE);
return 0;
移动光标则更加简单。因为移动光标只是对当前光标的位置进行计算即可。所以,在WM_KEYDOWN消息中,将wParam参数中附带的虚拟键码对比一下,然后更新一下光标的位置即可。按照我们的上面的定义,方向键的步长为一个字符,即向左向右只移动一个字符。PageUp、PageDown则移动10个字符,Home和End则分别移动到字符串的开始和结束位置的后一个位置。
所以我们在每一个case中使用break跳出switch,然后统一做一个规整到正确的范围内,以免溢出数组和超出字符串。对于小于0和大于最大值的检测是为了防止溢出。而对于字符串长度的检测,则是为了逻辑的正确性。大于字符串长度的输入必然都是追加在字符串结尾。
我们还需要注意一个细节,即Home和End的处理。我们是要将Home定位到数组的0的位置,这个好说。然而,要将End定位在哪里呢?是字符串最后一个字符位置,还是字符串最后一个字符的后一个位置呢?当然这个由你自己定了。我们选择是最后一个字符的后一个位置。因为这样的话,就可以直接在末尾直接赋值,就可以追加在字符串结尾。你不要担心这个字符会覆盖结尾的空字符。因为我们初始化将所有字符设置为了空字符。
这样的话,编辑输入文字的功能代码就不受影响。否则的话,你就要将输入文字的逻辑改一下,将位置值加一。这样处理的话,输入文字和定位光标又要重新调整逻辑了。实际上这些就是开发中比较麻烦的地方,需要处理好这些细节。而这些细节就是开发中要提前规划好,提前想全面点。否则写起代码来,就因为考虑不周到而前后翻来覆去,最后代码写烂了。这些是有经验的程序员和新手程序员的分水岭。
那么我们在最后实现显示文字的功能。这个功能倒是简单,不过也有一个细节需要处理。我们只要输出字符串即可显示所有输入的文本,然后根据光标的位置格式化显示一个字符就是当前光标所在的字符。然而在输入的时候,光标会自动向右移动一次,所以你显示当前光标所在的字符时,需要减一,这样才能显示正确。然而当你将光标定位到开头,即使用Home键,则会发现,你显示的竟然是空白。因为定位到开头时光标的位置值为0,再减一就为-1了。这还怎么显示字符,没有崩溃就是好的了。好在格式化字符串时没有报错。不过这样的代码也是很危险的,因为已经越界了,数组溢出了。不要庆幸没有崩溃,这种问题是最恐怖的。因为你很难查的出来。所以在使用数组时,一定要时刻记住溢出的问题,防患于未然,写出安全的不溢出数组的代码。所以我们使用了?:三目操作符来处理了一下。这个是C语言的基础,我就不说了。其他的基本上都很容易懂了。
所有程序的逻辑和细节都分析清楚了,下面是完整的代码:
#include "windows.h"
#include <tchar.h>
#define MAX 1024
static TCHAR cjjjs1[]=_T("提示1:Home回到开头,End回到结尾,PageUp向前移动10个字,PageDown向后移动10个字");
static TCHAR cjjjs2[]=_T("提示2:键盘光标↑和←可以向左移动一个位置,光标→和↓可以向右移动一个位置");
TCHAR txt[MAX]=_T("");
TCHAR tip[100]=_T("");
// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
static int iPosInput=0;
switch (message)
{
case WM_PAINT:
{
hdc = BeginPaint(hwnd,&ps);
//设置等宽字体
SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));
//当前光标位置的字符
wsprintf(tip,_T("当前光标所在的字符:%c"),txt[iPosInput?iPosInput-1:0]);
TextOut(hdc,0,170,tip,lstrlen(tip));
//提示
TextOut(hdc,0,150,cjjjs1,lstrlen(cjjjs1));
TextOut(hdc,0,130,cjjjs2,lstrlen(cjjjs2));
//输出文字内容
TextOut(hdc,0,0,txt,lstrlen(txt));
EndPaint(hwnd,&ps);
}
return 0;
case WM_KEYDOWN:
{
//默认直接覆盖
switch(wParam)
{
case VK_UP://左移一个字
case VK_LEFT:
iPosInput+=-1;
break;
case VK_RIGHT://右移一个字
case VK_DOWN:
iPosInput+=1;
break;
case VK_PRIOR://Page Up键,左移10个字
iPosInput+=-10;
break;
case VK_NEXT://Page Down键,右移10个字
iPosInput+=10;
break;
case VK_HOME://Home键,移动开头
iPosInput=0;
break;
case VK_END://End键,移到结尾最后一个字符位置
iPosInput=lstrlen(txt);
break;
default:
break;
}
//规整文字输入的位置,以免溢出
if (iPosInput<0)
iPosInput=0;
else if (iPosInput>lstrlen(txt))
iPosInput=lstrlen(txt);
else if(iPosInput>MAX)
iPosInput=MAX;
}
InvalidateRect(hwnd,NULL,TRUE);
return 0;
case WM_CHAR:
{
TCHAR ch=wParam;
txt[iPosInput]=ch;
iPosInput++;
}
InvalidateRect(hwnd,NULL,TRUE);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
break;//跳出到默认处理
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
TCHAR ClassName[] = _T("MyClass");
TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
WNDCLASS wndClass;
wndClass.cbClsExtra=0;
wndClass.cbWndExtra=0;
wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.hCursor=LoadCursor(NULL,IDC_HAND);
wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wndClass.hInstance = hInstance;
wndClass.lpfnWndProc = WinProc;
wndClass.lpszClassName = ClassName;
wndClass.lpszMenuName=NULL;
wndClass.style=CS_HREDRAW|CS_VREDRAW;
if(!RegisterClass(&wndClass))return 0;
HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,650,400,NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,SW_SHOWNORMAL);
MSG msg;
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}