当前位置:C++技术网 > 精选软件 > win32混合中英文字符时准确定位插入符(光标)

win32混合中英文字符时准确定位插入符(光标)

更新时间:2016-01-06 15:29:32浏览次数:1+次

    在文章《win32使用插入符(光标)来辅助编辑文字的功能实现分析》中,我们已经实现了插入符辅助编辑的功能。不过有点遗憾的是,如果输入中文,插入符显示的位置就不对了。
    为了解决插入符针对中文字符显示错误的问题,我们来做一个专门的分析和实现。中国程序员编程,解决不了中文字符的问题,如何面对列祖列宗呀!哈哈哈。

    下面来看看效果图:

中英文字符混排也能够准确的显示插入符(光标)的位置

【中英文字符混排也能够准确的显示插入符(光标)的位置】
    其实本文就是讨论一下如何计算插入符的位置。我们使用中英文字符数分别进行计数,然后根据中英文字符的个数分别计算出中英文字符所占的长度。注意,这段字符是从开头到插入符所在的位置的一段字符。
    也就是说,我们要先知道中英文字符所占的宽度。我们使用的是等宽字体,调用:
SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));
    来设置等宽字体。SYSTEM_FIXED_FONT宏就是指示的获取等宽字体的GDI对象来设置当前的字体。使用等宽字体让我们确定字符宽度就容易多了。中英文大小写字符的宽度都是一样的,中文的字符宽度也是一样的。所以我们分别确定中英文的字符宽度即可。然后得到中英文各自的字符数,然后就可以得到最终的字符串所占的长度,就可以确定插入符正确的位置。
    我们使用GetTextMetrics函数来获取字体的信息,再得到的字体信息结构体变量的成员有一个是平均字体宽度成员tmAveCharWidth,这个就是英文字符的宽度了。而我们中文的字符都比较大,所以使用的是最大字符宽度成员tmMaxCharWidth来存储中文的字符宽度。
    下面就是统计中英文字符的个数的实现了。我们通过ASCII编码表得知,英文字符的编码不会超过127的,所以我们简单的通过这个来区分中英文即可。所以就对光标到字符串开头的这一段字符进行遍历,然后将字符编码与127比较,小于127的为英文字符,否则为中文字符。然后就统计出来了中英文字符的个数,最后用中文字符个数乘以中文字符宽度,然后用英文字符个数乘以英文字符宽度,两者相加就得到了最终插入符的水平位置。
    大功告成!我们只修改了WM_SETFOCUS消息中的一些代码,就完美解决了中英文字符混合时的插入符的准确定位问题。
    下面是完整的代码:
#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 iFontWidth_Eng = tm.tmAveCharWidth;//英文字体宽度
            int iFontWidth_CH = tm.tmMaxCharWidth; //中文字体宽度
            int iCountCh=0,iCountEn=0;
            //统计中英文字符数,用来计算准确的插入符位置
            for(int i=0;i<iPosInput;i++)
            {
                TCHAR ch =txt[i];
                if (ch>127)
                {
                    iCountCh++;
                }
                else
                {
                    iCountEn++;
                }
            }
            int iCaretPos = iCountCh*iFontWidth_CH+iCountEn*iFontWidth_Eng;//当前插入符所在的位置

            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;
}