| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 1918 人关注过本帖
标题:绘制文本编辑框中的光标等的思路
只看楼主 加入收藏
xyzdwf
Rank: 2
等 级:论坛游民
威 望:1
帖 子:52
专家分:10
注 册:2017-1-9
结帖率:88.89%
收藏
已结贴  问题点数:20 回复次数:9 
绘制文本编辑框中的光标等的思路
最近在用gdiplus api绘制文本框,就想一步步来,先封装个能有光标和选中背景的文本显示组件,在此基础上实现文本编辑会好弄点。
另一方面套两层可以实现文字边距,而且计算坐标的时候不用再把边距给计算进去,不容易搞晕。
一开始为了效率考虑就使用二分法来确定文字中的坐标,但由于多字节字符串(汉字占两个字节),导致二分法截止条件设置错误,好几天了也没调试通,
网上搜了不少也根本没找到好的解决办法,今天终于调通了
发在论坛里,以前做过的帮我看看还有没有问题,也给需要的坛友提供点思路,以后自己也好找到
图片附件: 游客没有浏览图片的权限,请 登录注册

打的32位exe也不知道64位系统能不能用
luia.zip (123.48 KB)

程序代码:
/**
  * @brief 获取字符串中某个index的子串占位rect
  * @param ctx ui上下文
  * @param wgt    widget ui组件
  * @param style 文字样式(和draw方法必须相同,否则会不一致)
  * @param indx 字符在字符串中的index
  * @return 占位rect
  */
static gui_rect_t gui_itxt_indx_rect(gui_context_p ctx, gui_widget_p wgt, gui_text_style_t style, int indx)
{
    char* str = wgt->text;
    int num = indx+1; 
    char txt[num+1];
    int j;
    for(j = 0; j<num; j++){
        txt[j] = *(str+j);
    }
    txt[num] = '\0';
    
    gui_rect_t rect = gui_base_measuretext(ctx->context, wgt->px, wgt->py, 65535, wgt->ph, txt, style);
    return rect;
}

/**

 * @brief 获取鼠标在文本中的位置和字符index(用于绘制文字中的光标和选中背景)

 * @param ctx ui 上下文

 * @param wgt widget ui组件

 * @param x 点击坐标x

 * @param y 点击坐标x

 * @param style 文字样式(和draw方法必须相同,否则会不一致)

 * @param indx 用于返回字符在字符串中的index

 * @param rec 占位rect

 */
static void gui_itxt_measure_pos(gui_context_p ctx, gui_widget_p wgt, int x, int y,
    gui_text_style_t style, int* indx, gui_rect_t* rec)
{
    gui_rect_t rect = {0};
    char* str = wgt->text;
    if(!str || strlen(str)<1)return;
    int m = 0;
    int n = strlen(str)-1;
    int i;
    byte bol = 0;//
    while(n - m > 4){//因为是多字节字符的字符串,汉字占两个字节,两个相邻汉字差4个char
        i=0;
        int mid = (m+n)/2;
        while(i <= mid){
            if(*(str+i)<0){
                if(i == mid){i--;break;}//汉字不能完整舍弃
                if(i == mid-1){i++;break;}//汉字刚好占满结束
                i+=2;
            }else{
                if(i == mid)break;
                i++;
            }
        }

        rect = gui_itxt_indx_rect(ctx, wgt, style, i);
        
        if(rect.x+rect.w > x){
            n = i;
        }else if(rect.x+rect.w < x){
            m = i+1;
        }else{//点击位置正好为光标位置
            bol = 1;
            break;
        }
    }
    if(bol){
        rec->x = rect.x;
        rec->y = rect.y;
        rec->w = rect.w-2;//-2为一点差值,根据实际测试得来
        rec->h = rect.h;
        *indx = i;
        return;
    }
    
    while(m <= n){//小于4个char逐char定位
        byte bla = *(str+m) < 0;//同样需要处理汉字
        int rlm = bla?m+1:m;
        rect = gui_itxt_indx_rect(ctx, wgt, style, rlm);
        *indx = rlm;
        if(x < rect.x+rect.w){
            if(m == 0){//第0个字符处理
                int per = rect.w/2;//用于处理电子字符左右半边,定位该字符前后
                if(x <= rect.x + per){
                    rect.w=3;
                    *indx = -1;
                }
            }else{
                gui_rect_t recta = gui_itxt_indx_rect(ctx, wgt, style, m-1);
                int pera = (rect.w - recta.w)/2;//用于处理电子字符左右半边,定位该字符前后
                if(x <= recta.x + recta.w + pera - 2){
                    rect = recta;
                    *indx = m-1;
                }
            }
            break;
        }
        if(bla)m+=2;
        else m++;
    }
    
    rec->x = rect.x;
    rec->y = rect.y;
    rec->w = rect.w-2;//-2为一点差值,根据实际测试得来
    rec->h = rect.h;

}

搜索更多相关主题的帖子: str 字符 汉字 int style 
2021-09-22 18:22
xyzdwf
Rank: 2
等 级:论坛游民
威 望:1
帖 子:52
专家分:10
注 册:2017-1-9
收藏
得分:0 
gui_base_measuretext内部其实就调用的GdipMeasureString,另外就是设置font,format的代码。除了gdiplus用其他方式绘制如x11等也能用,测量字符串的函数应该都提供
2021-09-22 18:26
我善治鬼
Rank: 5Rank: 5
等 级:贵宾
威 望:17
帖 子:107
专家分:181
注 册:2015-2-16
收藏
得分:20 
GDI


程序代码:

#include <windows.h>

int main()
{
    LPCWCHAR str = L"将要绘制的字符串iiiiiiiAAAAAA";
    UINT64 len = lstrlen(str);
    HWND wnd = GetConsoleWindow();
    HDC dc = GetDC(wnd);
    POINT pt = { 0 };
    LPRECT rt = (LPRECT)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(RECT) * (len + 1));
    if (rt == NULL) return MessageBox(wnd, L"分配内存失败", L"分配内存失败", MB_OK);
    SetTextColor(dc, RGB(0, 128, 128));
    SelectObject(dc, CreatePen(PS_SOLID, 2, RGB(255, 0, 0)));
    SelectObject(dc, CreateFont(30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"微软雅黑"));
    // 计算所有字符得到每个字符在屏幕上的矩形
    for (int i = 0; i < len; i++) {
        RECT ct = { 0 };
        DrawText(dc, str, i + 1, &ct, DT_TOP | DT_CALCRECT);
        SetRect(&rt[i], rt[i].left, ct.top, ct.right, ct.bottom);
        rt[i + 1].left = ct.right;
    }
    while (1) {
        // 绘制字符串
        TextOut(dc, 0, 0, str, len);
        GetCursorPos(&pt);
        ScreenToClient(wnd, &pt);
        // 如果鼠标在指定字符的矩形内则绘制直线
        for (int i = 0; i < len; i++)
            if (PtInRect(&rt[i], pt) == TRUE) {
                MoveToEx(dc, rt[i].left, rt[i].top, NULL);
                LineTo(dc, rt[i].left, rt[i].bottom);
            }
    }
    return 0;
}


2021-09-22 21:34
xyzdwf
Rank: 2
等 级:论坛游民
威 望:1
帖 子:52
专家分:10
注 册:2017-1-9
收藏
得分:0 
回复 3楼 我善治鬼
为什么gdi不用测量就能搞定,是绘制玩一个字符就返回宽度?
我刚弄的时候想绘制的函数为什么不可以传回调函数进去绘制到某个字符坐标合适就能在回调里绘制光标了。
如果有返回值一个个绘制也挺好效率也挺高
2021-09-22 21:58
我善治鬼
Rank: 5Rank: 5
等 级:贵宾
威 望:17
帖 子:107
专家分:181
注 册:2015-2-16
收藏
得分:0 
回复 4楼 xyzdwf
是这个函数 DrawText(dc, str, i + 1, &ct, DT_TOP | DT_CALCRECT);
使用 参数 DT_CALCRECT 可以得到字符串的绘制矩形, 从而计算每个字符的矩形
我感觉你的循环计算太多了, 效率太低了.
2021-09-22 22:03
xyzdwf
Rank: 2
等 级:论坛游民
威 望:1
帖 子:52
专家分:10
注 册:2017-1-9
收藏
得分:0 
回复 5楼 我善治鬼
是啊,循环自己看着也挺难受,但也没办法,多字节必须先定位子字符串边界,然后再测量。
用了二分法,先把范围缩小到4个char之内,再逐个比较,但是字符串少的时候二分也没啥用。
帮我看看能不能优化下
2021-09-22 22:14
xyzdwf
Rank: 2
等 级:论坛游民
威 望:1
帖 子:52
专家分:10
注 册:2017-1-9
收藏
得分:0 
以下是引用我善治鬼在2021-9-22 22:03:50的发言:

是这个函数 DrawText(dc, str, i + 1, &ct, DT_TOP | DT_CALCRECT);
使用 参数 DT_CALCRECT 可以得到字符串的绘制矩形, 从而计算每个字符的矩形
我感觉你的循环计算太多了, 效率太低了.

看样子问题还是在ANSI多字节字符串,导致字符串处理时代码复杂而且低效
看了csdn一位大哥的博客决定全换成unidoce宽字符https://blog.
@我善治鬼 我想把ANSI windows窗口换成unicode窗口,但是把窗口创建函数个窗口类换成W结尾的报了一堆错,大哥有没有示例给一个,万分感谢
图片附件: 游客没有浏览图片的权限,请 登录注册
2021-09-23 10:45
xyzdwf
Rank: 2
等 级:论坛游民
威 望:1
帖 子:52
专家分:10
注 册:2017-1-9
收藏
得分:0 
以下是引用xyzdwf在2021-9-23 10:45:19的发言:
看样子问题还是在ANSI多字节字符串,导致字符串处理时代码复杂而且低效
看了csdn一位大哥的博客决定全换成unidoce宽字符https://blog.
@我善治鬼 我想把ANSI windows窗口换成unicode窗口,但是把窗口创建函数个窗口类换成W结尾的报了一堆错,大哥有没有示例给一个,万分感谢

已经切换成unicode窗口了,主要是以下几个换成W结尾:

WNDCLASSEXW
RegisterClassExW
CreateWindowExW
UnregisterClassW

其中WNDCLASSEXW的lpszClassName属性换成wchar_t*, CreateWindowExW的窗口标题也换成wchar_t*就行了

还有一个地方就是文字输入WM_CHAR接收到的都是unicode宽字符


2021-09-23 11:05
xyzdwf
Rank: 2
等 级:论坛游民
威 望:1
帖 子:52
专家分:10
注 册:2017-1-9
收藏
得分:0 
按理说unicode窗口输入文字发的wparam应该是unicode字符,但是乱码,最后还是用ANSI窗口输入用MultiByteToWideChar转wchat_t
用wchar_t就方便好多,少了个循环效率高了点
图片附件: 游客没有浏览图片的权限,请 登录注册

luia.zip (123.4 KB)

程序代码:

 /**
  * @brief 获取某个index的字符串占位rect
  * @param ctx ui上下文
  * @param wgt    widget ui组件
  * @param style 文字样式(和draw方法必须相同,否则会不一致)
  * @param indx 字符在字符串中的index
  * @return 占位rect
  */
static gui_rect_t gui_itxt_indx_rect(gui_context_p ctx, gui_widget_p wgt, gui_text_style_t style, int indx)
{
    // indx+1 为子串长度,不用再弄子串的
    gui_rect_t rect = gui_base_measuretext(ctx->context, wgt->px, wgt->py, 65535, wgt->ph, txt, indx+1, style);
    return rect;
}

/**

 * @brief 获取鼠标在文本中的位置和字符index(用于绘制文字中的光标和选中背景)

 * @param ctx ui 上下文

 * @param wgt widget ui组件

 * @param x 点击坐标x

 * @param y 点击坐标x

 * @param style 文字样式(和draw方法必须相同,否则会不一致)

 * @param indx 用于返回字符在字符串中的index

 * @param rec 占位rect

 */
static gui_rect_t gui_itxt_measure_pos(gui_context_p ctx, gui_widget_p wgt, int x, int y, 
    gui_text_style_t style,    int* indx)
{
    gui_rect_t rect = {0};
    wchar_t* str = wgt->text;
    if(!str)return rect;
    int len = wcslen(str);
    if(len<1)return rect;
    int m = 0;
    int n = len-1;
    while(m != n){
        int mid = (m+n)/2;
        
        rect = gui_itxt_indx_rect(ctx, wgt, style, mid);
        
        if(rect.x+rect.w > x){
            n = mid;
        }else if(rect.x+rect.w < x){
            m = mid+1;
        }else{//点击位置正好为光标位置
            *indx = mid;
            return rect;
        }
    }

    rect = gui_itxt_indx_rect(ctx, wgt, style, m);
    *indx = m;
    if(x < rect.x+rect.w){
        if(m == 0){//第0个字符处理
            int per = rect.w/2;//用于处理字符左右半边,定位该字符前后
            if(x <= rect.x + per){
                rect.w=1;
                *indx = -1;
            }
        }else{
            gui_rect_t recta = gui_itxt_indx_rect(ctx, wgt, style, m-1);
            int pera = (rect.w - recta.w)/2;//用于处理字符左右半边,定位该字符前后
            if(x <= recta.x + recta.w + pera){
                rect = recta;
                *indx = m-1;
            }
        }
    }

    return rect;
}


[此贴子已经被作者于2021-9-24 18:22编辑过]

2021-09-23 14:25
xyzdwf
Rank: 2
等 级:论坛游民
威 望:1
帖 子:52
专家分:10
注 册:2017-1-9
收藏
得分:0 
以前用文本框总觉得这东西就是最简单的一个组件了,要是自己亲手来绘制一个真是费劲,各种坐标,偏移量,还要处理好多快捷键,自己造个轮子可能收获更多
搞了好几天文本显示组件才初见眉目,功能:鼠标选中、方向键、ctrl+A、ctrl+C、shift + ←/→
图片附件: 游客没有浏览图片的权限,请 登录注册

luia.zip (125.05 KB)



[此贴子已经被作者于2021-9-27 18:29编辑过]

2021-09-27 18:23
快速回复:绘制文本编辑框中的光标等的思路
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.036944 second(s), 9 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved