当前位置:C++技术网 > 精选软件 > win32使用插入符(光标)来辅助编辑文字的功能实现分析

win32使用插入符(光标)来辅助编辑文字的功能实现分析

更新时间:2016-01-06 14:59:01浏览次数:1+次

    我们已经在文章《win32自由移动光标编辑修改文字》分析实现了自由移动光标和编辑的功能,在文章《支持移动光标后的退格键前向删除文字和Delete键后向删除文字功能实现》中分析实现了退格键和Delete键删除文字的功能。到此一个基本的编辑框就实现了。然而为了更加完美,我们需要支持插入符即光标,就是那种一闪一闪的光标。
    我们之前一直将插入符叫做光标,是从普通用户的角度来称呼的。而对于程序员来说,这种叫法太不专业了。我们将这个一闪一闪的光标叫做插入符。插入符就是用来辅助编辑的,以这种特定的用途来称呼,自然更加准确。在程序员之间交流,使用插入符会更加准确的表达你要说的这个类型的光标。因为鼠标移动的那个箭头也叫作光标哦。

    先来看看我们实现的光标的效果,如下图所示:

使用插入符(光标)来辅助编辑文字

【使用插入符(光标)来辅助编辑文字】
    从图中标注的箭头可以看到文字中间可以看到一个细小的竖线,这就是传说中的插入符了。好了,下面说说如何显示和定位插入符吧。
    插入符是与窗口相关的,而且你要使用,就必须创建一个。在不需要使用的时候,要将插入符销毁掉。一个窗口只能有一个插入符,通过窗口句柄就可以创建插入符和操作插入符了。销毁的时候根本就不需要窗口句柄,函数会自动提取。所以你不会得到插入符的句柄,实际上也不存在这个句柄。你所要的句柄就是窗口的句柄。
    创建插入符调用函数CreateCaret,销毁插入符使用函数DestroyCaret。只有创建了插入符后才可以显示插入符。销毁插入符后就不能够再显示了。显示插入符调用函数ShowCaret。而显示插入符的位置,则需要提前设置好,调用函数SetCaretPos设置插入符的位置。隐藏插入符的函数为HideCaret。这几个函数使用都很简单,我就不说明函数的用法了。要么就没有参数,要么就只有一个窗口句柄参数,要么就只要位置的xy坐标值,创建时使用默认的系统样式的光标,位图句柄设置为NULL即可,其他参数你懂的。
    需要说明的是,因为窗口只需要一个插入符,所以如果要频繁的使用插入符,就不必用到的时候就创建,不用的时候就销毁。这样的效率有点低。我们就在WM_CREATE窗口创建时创建插入符,在窗口销毁WM_DESTROY消息时销毁插入符。在使用时只需要不停地改变插入符的位置、显示插入符或者隐藏插入符。
    插入符的坐标位置则是基于窗口客户区的坐标的,所以我们可以将插入符显示在任何位置。在创建插入符时,最后两个参数是指定插入符的宽度和高度,这个你自己设置下就可以看到效果了。
    对了,需要强调一点,插入符显示在MSDN说要在WM_SETFOCUS消息中使用,在失去焦点时销毁。这只不过就是一个建议而已。实际上我们显示插入符可以在任何消息中完成。只要我们提前创建好了插入符,就可以显示。不过一般单独在WM_SETFOCUS消息中处理,独立起来比较好管理。不过看你程序的需要,如果在其他消息中显示更为合适,那就在其他消息中处理好了。
    然而要用好插入符,就要定位好插入符,否则插入符的位置不对,则会让插入符不仅无法辅助,甚至显得混乱。插入符的位置就和文本的行列数有关。为了简化问题,我们使用了系统等宽字体,这里先只使用英文字母来测试。
    我们要得到字体的宽度信息,使用GetTextMetrics函数获取文本信息,存入文本信息结构体变量中,然后取平均的字符宽度即可。因为我们使用的本身就是等宽字体,宽度都一样的。
    我们就在WM_SETFOCUS消息中显示插入符。先设置等宽字体,然后获取字体的信息,然后计算插入符所在的位置,然后设置插入符位置,然后显示插入符。如此就准确显示插入符的位置,可以帮助用户编辑文字。
    然而在移动光标、输入文字的时候,插入符并没有跟着移动,所以我们在移动光标和输入文字时要发送WM_SETFOCUS消息来更新插入符的位置。使用SendMessage即可完成。WM_SETFOCUS消息也不需要参数,所以两个参数都可以设置为0。
    下面是完整的代码:
#include "windows.h"
#include <tchar.h>
#define MAX 2000
static TCHAR cjjjs1[]=_T("提示1:Home回到开头,End回到结尾,PageUp向前移动10个字,PageDown向后移动10个字");
static TCHAR cjjjs2[]=_T("提示2:键盘光标↑和←可以向左移动一个位置,光标→和↓可以向右移动一个位置");
TCHAR txt[MAX]=_T("");
bool bIsInsert=true;//默认插入模式
// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static int iPosInput=0;
    TEXTMETRIC tm;
    switch (message)
    {
    case WM_CREATE:
        CreateCaret(hwnd, (HBITMAP) NULL, 1,20);
        return 0;
    case WM_SETFOCUS:
        {
            hdc=GetDC(hwnd);
            SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));//等宽字体
            GetTextMetrics(hdc,&tm);
            int iCaretPos = iPosInput*tm.tmAveCharWidth;//当前插入符所在的位置
            SetCaretPos(iCaretPos, 0);//设置插入符的位置
            ShowCaret(hwnd);//显示插入符
            ReleaseDC(hwnd,hdc);
        }
        return 0;
     case WM_PAINT:
         {
             hdc = BeginPaint(hwnd,&ps);
             //设置等宽字体
             SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));
             //提示
             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;
             case VK_DELETE:
                 {
                     //将光标所在的位置删除
                     int len = lstrlen(txt);
                     if (iPosInput==len)return 0;//向后删除,光标在结尾处,也没有字符可删除

                     for (int i=0;i<=len-iPosInput;i++)
                     {
                         txt[iPosInput+i]=txt[iPosInput+1+i];
                     }
                     InvalidateRect(hwnd,NULL,TRUE);
                 }
                 break;
             case VK_BACK:
                 {
                     //将光标前一个字符删除,后面的字符向前移动
                     if (iPosInput==0)return 0;//向前删除,光标在0处没有字符可删除

                     int len = lstrlen(txt);

                     for (int i=0;i<=len-iPosInput;i++)
                     {
                         txt[iPosInput-1+i]=txt[iPosInput+i];
                     }
                     iPosInput--;
                     InvalidateRect(hwnd,NULL,TRUE);
                 }
             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);
         SendMessage(hwnd,WM_SETFOCUS,0,0);//更新插入符的位置
         return 0;
        case WM_CHAR:
            {
                TCHAR ch=wParam;
                if (ch==8)return 0;//屏蔽掉退格符的输入,在WM_KEYDOWN执行删除
                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++;
            }
            SendMessage(hwnd,WM_SETFOCUS,0,0);//更新插入符的位置
            InvalidateRect(hwnd,NULL,TRUE);
            return 0;
     case WM_DESTROY:
         DestroyCaret();
         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;
}