当前位置:C++技术网 > 精选软件 > win32实现自由移动光标以插入模式编辑文字以及防止破坏字符数组

win32实现自由移动光标以插入模式编辑文字以及防止破坏字符数组

更新时间:2016-01-05 22:57:24浏览次数:1+次

    在文章《win32自由移动光标编辑修改文字》中,我们已经实现了自由移动光标,然后对光标所在的位置的字符进行修改。然而,能够移动当前编辑的位置,却不能以插入的方式输入文字,只能将当前的字符覆盖,显得有点不太方便。所以,就在本文分析实现以插入模式编辑文字。
    在Word等软件里,都支持插入模式和覆盖模式编辑文档。而默认的就是插入模式,有时候不小心按了“Insert”按键,就切换成了覆盖模式,也让一些编辑好的文字被覆盖了。

    我们为了实现这个效果,也支持这两个模式,即插入模式和覆盖模式。我们也默认为插入模式。你可以通过“Insert”键切换插入模式和覆盖模式。我们可以看到下图:

直接输入了www.cjjjs.com

【直接输入了www.cjjjs.com】

    然后我发现少打了网站名称,所有使用“Home”键将光标返回开头,因为默认的是插入模式,所以直接插入网站名称“C++技术网”。此前我们都是覆盖模式,所以无法完成这个功能,现在有了插入模式,就好说了。下面是插入文字后的效果图:

在www.cjjjs.com前面插入了C++技术网字样

【在www.cjjjs.com前面插入了C++技术网字样】
    那么插入模式与覆盖模式的区别是什么,实现的难点又是什么呢?覆盖模式在编程实现起来非常简单,就是将光标所在的位置的字符替换就可以了。然而插入模式则会在中间多出一个字,那么就表示说,后面的字要向后移动了。那么麻烦的地方就是移动后面的字符了。至于状态的切换,可以看大小写状态切换的文章《win32检测键盘的大小写状态并实时显示编辑的大小写状态》,和切换插入模式一个道理,这里就不说插入模式切换的实现了。
    而对于移动光标的实现,在本文开头就提到了,请先阅读。本文只重点讲述插入模式的实现。在WM_PAINT只是新增了一个插入模式的提示,移动光标没有改动。就是在输入文字的地方,会根据插入模式或者覆盖模式来处理一下字符数组。

    我们在自由移动光标编辑修改文字的文章里,并没有对输入文字做溢出检测。当输入的文字达到最后一个字的时候,就将字符覆盖了最后结尾的空字符,也就让字符数组失去了空字符。这样就出现了一个问题,即在显示字符串的时候,就将其他内存的内容当做这个字符串显示了。而刚好,我们的字符数组紧接的是其他字符数组。这样就将其他字符数组的数据当做我们输入的内容被显示出来了。下面是显示了其他字符数组内容的截图:

显示了其他字符数组内容

【显示了其他字符数组内容】
    提示的字符串里,我故意加了this英文字符,就是方便在内存中观察,因为中文编码看不到文字。在界面上你就可以看到了这个现象。这就是因为字符数组没有用空字符结尾。很多时候,如果后面没有紧接的字符数组,很可能就出现“烫烫烫”的字样。你可以参考文章《经典乱码“烫烫烫”和“屯屯屯” 》和《程序员才懂的笑话:烫烫烫的恶搞用户名,怎么都是烫烫烫烫烫烫烫烫? 》。
    所以本次分析就加入了错误检测的这部分内容。如果只是覆盖模式,那么我们检测只需要对当前光标做检测,防止覆盖最后一个空字符。所以我们使用了检测代码如下:
if (iPosInput==MAX-1)
{
    iPosInput=MAX-2;//防止溢出
}
    当然,这个代码也适用于插入模式。因为插入模式下,光标始终都在结尾处输入,也会出现输入的文字覆盖最后的空字符。插入模式下,还有另外一种情况会出现错误。也就是,当光标在字符串中的某个字符上时,此时插入字符后,后面的字符串就往后移。在移动的过程中,那么最后一个字符可能就移到了结尾的空字符上,这样也破坏了字符串。所以也要加一道检测。代码如下:
int len = lstrlen(txt);
if (len>=MAX-2)
{
    //超出长度的字符,截断处理。必须减2才能防止溢出。
    len=MAX-2;
}

    我们分析一下上面两个检测的原理。

    第一个检测,就是防止光标输入覆盖。每一次输入时,光标都会向后移动一个,所以一个字符输入完毕后,光标可能移动到了最后的空字符上了。那么下次输入时就会覆盖。所以,在输入时就检测,如果光标的位置达到了最后一个位置MAX-1,就将光标设置为最后一个位置的前一个位置MAX-2.这样达到最大长度时,只是修改最后一个字符,而无法继续增加字符串的长度了。

    而第二个检测,则是对字符串长度的检测。如果长度达到了倒数第二个位置,也就意味着移动字符时,最后一个字符将会移到结尾空字符上,所以要防止这个情况的发生。所以到这个的时候,我们将长度变量len修改为字符串最大支持的长度。那么这样,在后面的移动字符过程中,就相当于把最后一个字符给丢弃了。
    而最关键的移动字符串,使用一个循环就可以移动了。我们先用字符串的长度减去光标所在的位置,得到光标后面的字符数,然后移动这么多次即可。移动就是将倒数第三个移到倒数第二个,倒数第四个移到大倒数第三个,依次类推。代码如下:
for (int i=0;i<len-iPosInput;i++)
{
    txt[len-i]=txt[len-i-1];
}
    这不就是我们很熟悉的移动字符串的循环嘛,在这个功能里就用上了。
    下面是完整的源代码:
#include "windows.h"
#include <tchar.h>
#define MAX 20
static TCHAR cjjjs1[]=_T("提示1:Home回到开头,End回到结尾,PageUp向前移动10个字,PageDown向后移动10个字");
static TCHAR cjjjs2[]=_T("提示2:键盘光标↑和←可以向左移动一个位置,光标→和↓可以向右移动一个位置");
TCHAR txt[MAX]=_T("");
TCHAR tip[100]=_T("");
bool bIsInsert=true;//默认插入模式
// - 项目是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("this当前光标所在的字符:%c"),txt[iPosInput?iPosInput-1:0]);
             TextOut(hdc,0,170,tip,lstrlen(tip));

             //提示
             TextOut(hdc,0,130,cjjjs1,lstrlen(cjjjs1));
             TextOut(hdc,0,150,cjjjs2,lstrlen(cjjjs2));

             TCHAR insTip[100]=_T("");
             wsprintf(insTip,_T("当前编辑状态:%s"),bIsInsert?_T("插入模式"):_T("覆盖模式"));
             TextOut(hdc,0,190,insTip,lstrlen(insTip));

             //输出文字内容
             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;
             case VK_INSERT://End键,移到结尾最后一个字符位置
                 bIsInsert=!bIsInsert;
                 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;

                if (bIsInsert)
                {
                    //插入字符
                    //将插入点后面的字符全部后移
                int len = lstrlen(txt);
                if (len>=MAX-2)
                {
                    //超出长度的字符,截断处理。必须减2才能防止溢出。
                    len=MAX-2;
                }
                for (int i=0;i<len-iPosInput;i++)
                {
                    txt[len-i]=txt[len-i-1];
                }
                                }
                if (iPosInput==MAX-1)
                {
                    iPosInput=MAX-2;//防止溢出
                }
                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;
}