精华内容
下载资源
问答
  • 文本编辑框

    千次阅读 2009-04-20 14:05:00
    在界面编程中,文本编辑框是使用频率最高的控件之一,为了方便操作,MFC提供了CEdit类来管理文本编辑框。 创建文本编译框有两种方式: 第一种、在对话框模式下,编辑资源文件,撰写创建对话框的脚本,可在表示...

        在界面编程中,文本编辑框是使用频率最高的控件之一,为了方便操作,MFC提供了CEdit类来管理文本编辑框。
        创建文本编译框有两种方式:
        第一种、在对话框模式下,编辑资源文件,撰写创建对话框的脚本,可在表示对话框脚本的一对标识符BEGIN和END中间插入形如
        EDITTEXT        IDC_MYEDIT,81,112,71,14,ES_CENTER
    的脚本。其中EDITTEXT表示插入了一个文本编辑框,IDC_MYEDIT是文本编辑框的ID号,81,112,71,14表示文本编辑框相对对话框的位置;ES_CENTER是对话框的属性,该熟悉表示文本水平居中对齐,可用“|”符号将若干熟悉联合。
        从工具箱中拖入一个文本编辑框控件,实际上是通过可视化过程,由IDE解析鼠标操作来编写脚本,在属性对话框中设置各种属性,也是在编写这些脚本。
        通过动态数据交换可以将CEdit类的一个对象与文本编辑框控件绑定在一起,DDX_Control(pDX, IDC_MYEDIT,m_MyEdit),m_MyEdit就是CEdit类的对象。
        第二种、构造一个CEdit类的对象,利用CEdit::Create()创建一个文本编辑框,调用CEdit的成员函数可以设置文本编辑框的各种属性。

        文本编辑框常用的操作:
        1.GetWindowText(),获得文本编辑框中输入的内容。
        2.SetLimitText(),设置文本编辑框能够输入最大字符数。
        3.SetReadOnly(),设置文本编辑框为不可编辑。
        4.GetSel(),获得当前选中文本编辑框中内容的起始和结束位置。
        5.SetSel(),设置光标所在的位置,或者选中部分文本。
        6.SetPasswordChar(),如果传入参数为非零的字符,则文本编辑框中显示以该非零字符代替,例如传入一个'*',用户输入的所有内容都以'*'显示,这就是密码模式,也是默认值,可以使用其他的字符代替。如果取消密码模式,只需要传入0.
        7.Undo(),撤销之前的操作,在调用此函数之前,请先调用CanUndo判断此操作是否允许被撤销。
        其他一些属性的设置,使用SetWindowLongPtr函数。
        int nWindowLong = GetWindowLongPtr(m_hWnd,GWL_STYLE);
        SetWindowLongPtr(m_hWnd,GWL_STYLE,nWindowLong | nStyle);
        其中m_hWnd是文本编辑框的句柄,nStyle是属性值的组合,这些属性值包括:
        1.ES_MULTILINE 多行文本编辑框
        2.ES_WANTRETURN 允许回车换行
        3.ES_NUMBER 只允许输入数字
       
        修改文本编辑框边框的颜色是一件比较困难的事情,MFC提供的CEdit类只提供了对文本编辑框的功能操作,外观设置是MFC控件管理类的共同弱点,没有办法,我们只能自己重载CEdit类来修改边框颜色。
        1、创建CEdit的派生类CDrawEdit
        class CDrawEdit : public CEdit
        {
            DECLARE_DYNAMIC(CDrawEdit)

        public:
            CDrawEdit();
            virtual ~CDrawEdit();
        protected:
            DECLARE_MESSAGE_MAP()
        };
        2、设置边框颜色
        因为文本编辑框的边框是在非客户区域,所以要重载OnNcPaint()函数。
        void CDrawEdit::OnNcPaint()
        {
            CRect rc;
            GetWindowRect(&rc);
            CRect rcWindow(0,0,rc.Width(),rc.Height());

            CBrush brushFrame;
            brushFrame.CreateSolidBrush(RGB(255,0,0));

            CWindowDC dc(this);
            dc.FrameRect(&rcWindow,&brushFrame);
        }
        可以定义一个成员变量来保存画刷的颜色值,定义一个成员函数设置此值,这样就可以通过函数来灵活的这是各种边框颜色。

        文本颜色和文本字体以及文本编辑框的背景色可以在父类的OnCtlColor中设置
        HBRUSH CMFCDlgDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
        {
            HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

            if (pWnd->GetDlgCtrlID() == IDC_MYEDIT)
            {
                pDC->SetTextColor(RGB(0,0,255));    //设置字体颜色
                pDC->SelectObject(&m_Font);            //设置字体样式

                //设置背景色
                pDC->SetBkMode(TRANSPARENT);
                return CreateSolidBrush(RGB(0,255,0));
            }

            return hbr;
        }

     

        如果在OnCtlColor函数中添加了对静态文本框的设置,有可能会影响到文本框的效果。

    展开全文
  • 两个el-upload,在富文本编辑框选择插入图片,调用了上传封面的回调方法,然后图片返回到上面封面的位置,而没有返回到下面内容中 ![图片说明](https://img-ask.csdn.net/upload/202002/29/1582964984_711970.png) ...
  • 仿微博富文本编辑框

    千次阅读 2016-11-07 14:17:48
    没图我说个毛?https://github.com/aishang5wpj/RichEditText/raw/master/screenshot/screenshot.gif’ height=’480px’/>首先要说明的是:不管是话题、at还是poi,都应该被...结合以上,该富文本编辑框的主要功能如

    没图我说个毛?

    首先要说明的是:不管是话题、at还是poi,都应该被看成一个最小子项,即不可在中间插入文字,也不可对话题、at或者poi中的任何文字进行单独的修改或者删除。

    结合以上,该富文本编辑框的主要功能如下:

    • 1、富文本的高亮处理;
    • 2、点击话题、at或者poi时光标会落在旁边合适的位置,光标移动时,如果碰到话题等要直接移动跳过整个话题,而不是仍然逐个文字的移动;
    • 3、删除话题、at或者poi时,先整体选中,再点一次删除则执行真正的删除操作;
    • 4、对于poi这种特殊类型,需要支持图文混排的展示;
    • 5、除了对输入的文字进行展示,同时也要有良好的格式以便将用户输入的内容保存或者传输到服务器。

    下面,就开始逐个分析吧。

    富文本的高亮处理

    这个简单,就是富文本的处理。

    边界判断

    现在的很多输入法的键盘都有上、下、左、右移动光标的功能,比如搜狗输入法:

    所谓边界判断,就是当用户在键盘上移动光标、或者直接用手指点击输入框调整光标的位置时,如果移动时碰到话题等特殊内容时,自动调整光标到旁边合适的位置。

    监听光标位置

    要监听光标的位置,TextView中有一个方法叫onSelectionChanged,完整声明如下:

        protected void onSelectionChanged(final int selStart, final int selEnd) {
        }

    当光标每次的位置发生改变时都会回调这个方法,两个形参即分别代表了光标的开始位置和结束位置。

    而触发onSelectionChanged这个方法的原因基本上有两个:

    • 1、用户手动输入文字,导致文字改变同时光标也向后移;
    • 2、开发者手动调用setSelection()方法移动光标从而导致onSelectionChanged的回调。

    调整光标位置

    上面说到调整光标的位置,setSelection()正好符合我们的需要。该方法在EditText中的完整声明如下:

        public void setSelection(int index) {
        }
    
        public void setSelection(int start, int stop) {
        }

    setSelection()有两个重名方法,上面那个是纯粹的移动光标,下面那个是选中一段文字或者调整选区。

    对于第1种情况处理起来比较简单,我们主要考虑第2种情况。我们来看一个图:

    用户选中中间一段文字之后,如果此时点击键盘上的 “<-“、”->”,此时应该一次性选中富文本,而不是逐个选中每个文字。

    如何判断光标移动到了话题旁边?

    好了,监听以及调整光标位置的事情已经解决了,我怎么知道光标移动到了话题、at或者poi的旁边呢?

    这就涉及到了正则表达式了。不管是话题、at还是poi,我们都要分别给每种富文本定义一个格式,这里用话题来举例。

    话题的格式是:一个空格+一个#+话题内容+一个空格,即” #话题 “(引号内即为一个完整的话题,至于为什么话题不像大家平常见到的那样是两个”#”,我们稍后再讲)。

    定下了话题格式之后,就可以写出正则表达式:” #[^#+] “,表示两个空格中间的字符串是#开头的若干个字符。这里推荐一个在线正则表达式测试给大家,非常好用。

    有了正则表达式,就可以针对话题提供一系列方法:判断字符串中是否包含话题、获取字符串中第一个话题、判断字符串是否以话题开头,等等。

    看到这里,怎么判断光标旁边是否是话题就已经非常简单了:

    • 1、如果光标是往左移,取光标左边的字符串,并判断这个字符串是否以话题结尾;
    • 2、如果光标是往右移,取光标右边的字符串,并判断这个字符串是否以话题开头;

    删除话题

    View类中有个方法是setOnKeyListener(OnKeyListener l),通过它可以设置对设备按键的事件进行监听,我们这里只需要监听KeyEvent.KEYCODE_DEL即可,每当用户按下删除键便判断光标前面的字符串是否是话题,如果是话题则计算该话题的长度并选中之,如果再次点击删除即删除之。

    如果前面的字符串是话题,那怎么判断到底是选中还是删除呢?

    第一次按删除时,光标start和end的值肯定是相同的,因为只有一个光标,这时按下删除键肯定是执行选中话题的操作;

    选中话题之后,光标变成了两个,这时候start和end值变成了两个,这时候按删除键就直接删除话题了。

    所以只需要判断光标start和end的值是否一样即可。

    POI图文混排

    平时做图文混排的方式有很多种,但是这里用的是ImageSpan,就不讲了。

    格式化保存

    这是最主要的问题,也是之前困扰我很长一段时间的问题,其实回过头来看看也并没有我想的那么复杂,因为我们在前面定义话题格式、写话题正则表达式的时候,一个这样的字符串这里有一个话题 #话题 不信你看其实已经包含了很多信息了,所以我们并不需要额外的数据来存储任何其他的信息。

    当时想的东西比较多,这么简单一说可能你们不能体会到上面这段话的意思,也可能我表达的不够好。

    下面开始分析代码吧。

    源码分析

    架构设计

    下面是程序uml类图。

    分别说一下图中接口和各个类的作用:

    IRichParser

    解析器必须实现的接口,规范了一系列方法,分别如下:

    public interface IRichParser {
    
        /**
         * 设置待判断的字符串
         *
         * @param text
         */
        void resetTargetStr(String text);
    
        /**
         * 判断是否是话题的正则表达式
         *
         * @return
         */
        String getRichPattern();
    
        /**
         * 判断字符串中是否包含话题
         *
         * @param str
         * @return
         */
        boolean containsRichItem();
    
        /**
         * 获取字符串中的第一个话题,匹配到的结果已经是格式化之后的了,不需要对匹配结果再次格式化
         *
         * @return
         */
        String getFirstRichItem();
    
        /**
         * 获取字符串中的第一个话题在字符串中的索引
         *
         * @return
         */
        int getFirstRichIndex();
    
        /**
         * 获取字符串中的最后一个话题,匹配到的结果已经是格式化之后的了,不需要对匹配结果再次格式化
         *
         * @return
         */
        String getLastRichItem();
    
        /**
         * 获取字符串中的最后一个话题在字符串中的索引
         *
         * @return
         */
        int getLastRichIndex();
    
        /**
         * 格式化输出
         *
         * @return
         */
        String getRichText(String richStr);
    
        /**
         * 输出富文本的形式
         *
         * @param richStr
         * @return
         */
        SpannableString getRichSpannable(Context context, String richStr);
    }

    注释说的很清楚了,说几个难懂一点的。

    • void resetTargetStr(String text)
      要明白这个方法,先来看看它的使用场景。比如如果我要判断"这个字符串中有一个 #话题 "这样一个字符串中有没有话题,代码是这样写的:
    boolean isContains = new TopicRichParser().resetTargetStr("这个字符串中有一个 #话题 "). containsRichItem();

    可以看到,resetTargetStr()的作用即每次要对字符串进行判断时,则调用这个方法对要判断的mTargetStr进行设置,然后再调用其他的方法进行判断。

    那为什么不在TopicRichParser类中直接写一个静态方法呢,比如这样:

    boolean isContains = TopicRichParser.containsRichItem("这个字符串中有一个 #话题 ");

    看,多么省事。

    乍一看好像挺对,因为我一开始也是这么写的,这么写也没错,目前有话题、at和poi这3种,你可能只需要写3个if else就可以判断完:

    String targetStr = "这个字符串中有一个 #话题 ";
    boolean isContains = false;
    if(TopicRichParser.containsRichItem(targetStr)){
    
        isContains = true;
    }else ir( AtRichParser.containsRichItem(targetStr)){
    
        isContains = true;
    }else if(PoiRichParser.containsRichItem(targetStr))
    
        isContains = true;
    }

    甚至可以写成这样:

    String targetStr = "这个字符串中有一个 #话题 ";
    boolean isContains = TopicRichParser.containsRichItem(targetStr)
                                && AtRichParser.containsRichItem(targetStr)
                                && PoiRichParser.containsRichItem(targetStr) ;

    对于目前的需求,这种写法是没有问题的,因为现在的富文本样式只有3中,如果以后变成5种、8种甚至更多呢?你需要在每个方法里面重复写这样相似的代码,但是如果有一个接口统一定义了这些方法,然后让子类分别实现各自不同的部分,就可以不用写这么多重复的代码了。

    具体细节讲到RichParserManager时即可明白了。

    AbstractRichParser

    实现了IRichParser接口,并重写了所有子类共同的方法,但是对于需要不同实现的方法仍然保持没有实现的状态。具体情况如下:

    • 已实现部分:resetTargetStrcontainsRichItemgetFirstRichItemgetFirstRichIndexgetLastRichItemgetLastRichIndex

    • 未实现部分:getRichPatterngetRichTextgetRichSpannable

    TopicRichParserAtRichParserPoiRichParser

    均继承自AbstractRichParser,并实现了父类所有虚方法(getRichPatterngetRichTextgetRichSpannable),负责提供判断富文本的正则表达式、输出富文本字符串、输出富文本。

    RichParserManager:

    上面介绍IRichParser说到:如果富文本样式有很多种时,如果仍然用静态方法+if else这种陈旧落后的方式将导致重复代码太多,最重要的是容易出错,如果某个方法中漏写了某种富文本的判断,则可能导致最终的结果并不准确。
    RichParserManager中是怎么处理的呢?

    public class RichParserManager {
    
        private static RichParserManager mInstance;
        private List<IRichParser> mRichPasers;
    
        private RichParserManager() {
            mRichPasers = new ArrayList<>();
        }
    
        public static RichParserManager getManager() {
            if (null == mInstance) {
                synchronized (RichParserManager.class) {
                    if (null == mInstance) {
                        mInstance = new RichParserManager();
                    }
                }
            }
            return mInstance;
        }
    
        /**
         * @param richParser
         */
        public void registerRichParser(IRichParser richParser) {
    
            //是否需要去重处理?
            mRichPasers.add(richParser);
        }
    
        /**
         * 判断是否包含富文本bean
         *
         * @param str
         * @return
         */
        public boolean containsRichItem(String str) {
    
            if (TextUtils.isEmpty(str)) {
                return false;
            }
            for (IRichParser richItem : mRichPasers) {
                richItem.resetTargetStr(str);
                if (richItem.containsRichItem()) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 获取字符串中的最后一个富文本串
         *
         * @param targetStr
         * @return 最后一个"话题"或者最后一个"@"或者其他,如果没有富文本串,则返回空字符串("")
         */
        public String getLastRichItem(String targetStr) {
            //细节省略
        }
    
        /**
         * 获取字符串中的最后一个富文本串
         *
         * @param targetStr
         * @return 最后一个"话题"或者最后一个"@"或者其他,如果没有富文本串,则返回空字符串("")
         */
        public String getFirstRichItem(String targetStr) {
            //细节省略
        }
    
        /**
         * 是否以富文本开头
         *
         * @param targetStr
         * @return
         */
        public boolean isStartWithRichItem(String targetStr) {
            //细节省略
        }
    
        public boolean isEndWithRichItem(String targetStr) {
            //细节省略
        }
    
        /**
         * 解析字符串中的富文本并返回一个经过格式化的富文本串
         *
         * @param targetStr
         * @return
         */
        public SpannableStringBuilder parseRichItems(Context context, String targetStr) {
            //细节省略
        }
    
        private SpannableString formateRichStr(Context context, String richStr) {
            //细节省略
        }
    }

    RichParserManager采用单例模式对所有IRichParser进行管理,上面的需求中只有3中富文本,假设现在添加一种“音乐”类型的富文本,则只需要定义一个MusicRichParser继承自AbstractRichParser,并实现所有方法,然后调用RichParserManagerregisterRichParser()即可使编辑器支持对音乐类型的富文本解析。

    是不是很简单?说出来你可能不信,实际操作起来简单到我自己都怕。

    举个栗子,如果要判断字符串是否包含富文本,外部只需要用下面的代码即可:

    boolean isContains = RichParserManager.getManager().containsRichItem("这个字符串中有一个 #话题 ");

    是不是非常简单?再来看containsRichItem()的实现:

    public boolean containsRichItem(String str) {
    
            if (TextUtils.isEmpty(str)) {
                return false;
            }
            for (IRichParser richItem : mRichPasers) {
                richItem.resetTargetStr(str);
                if (richItem.containsRichItem()) {
                    return true;
                }
            }
            return false;
        }

    如你所见,正是遍历所有的IRichParser,依次检查是否含有富文本,这样既不会在检查每种富文本的时候都写重复的代码,而且也不会遗漏任何对任何一种富文本的检查(只要你一开始调用过RichParserManager.getInstance().registerRichParser()注册过)。

    其他方法类似,不多提了。

    话题、AT、POI,以及以后任何可能的富文本样式

    假设现在添加一种“音乐”类型的富文本,则只需要定义一个MusicRichParser继承自AbstractRichParser,并实现下列3个方法即可:

    • getRichPattern 提供正则表达

    • getRichText 格式化输出富文本字符串

    • getRichSpannable 将字符串富文本化(图文混排)等

    getRichTextgetRichSpannable有什么区别呢?

    恩,这是个好问题。我们用POI富文本来举例,先来看一个图:

    这是一个poi富文本图文混排之后的结果,poi的正则表达式是&[^&]+,即两个空格中间是一个&开头的字符串。所有符合这种格式的字符串都将被解析成poi。

    所以对于上图中的poi,它的真实字符串其实是&兰溪,而且这也是调用getRichText()得到的结果。getRichText()代码如下:

        @Override
        public String getRichText(String richStr) {
            return String.format(" &%s ", richStr);
        }

    而为什么会输出成上图中图文混排的结果呢?这就要看getRichSpannable()中的实现了。

        public SpannableString getRichSpannable(Context context, String richStr) {
    
            if (TextUtils.isEmpty(richStr)) {
                return new SpannableString("");
            }
            String str = richStr;
            SpannableString spannableString = new SpannableString(str);
            //color spannable
            ForegroundColorSpan highLightSpan = new ForegroundColorSpan(Color.parseColor("#FF6699"));
            spannableString.setSpan(highLightSpan, 0, str.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
            ImageSpan imageSpan = new CenteredImageSpan(context, R.mipmap.poi);
            spannableString.setSpan(imageSpan, 1, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
            return spannableString;
        }

    getRichSpannable()方法中,形参的值一般是getRichText()返回的结果,对于上图,这里的形参即为:&兰溪

    getRichSpannable()的第9~10行,先将富文本字符串颜色高亮。

    第11~12,将&替换成POI富文本的icon。

    看到这里是不是一切都了然了,所以这里的&其实是充当了一个占位符的作用。事实上如果你回过头去看,不管是话题、at还是poi,都有一个占位符。他们分别是#@&,只不过at的保留了占位符,而话题的占位符被我替换成了一个透明图片。

    调用getRichSpannable()得到SpannableString之后,在Edittext中调用setText()就可以将上面所有的更改显示到界面上了。

    为什么要采用这种策略呢?

    对于在EditText中图文混排这件事情本身是不困难的,但是现在要支持对一个特定格式的富文本进行正则表达式的判断以及各种处理,特别是光标的控制,假设没有占位符,直接将图片加在文字前面变成富文本,光标移动的时候计算的文字长度仍然是原来的长度,而显示在屏幕上的富文本长度其实是超过了文字的实际长度,所以就有可能出现光标移动时被文字或图片遮挡等等各种奇怪的bug。

    不知道你们能不能明白,这个需要自己去体会了。

    RichEditText

    上面说了很多,都是针对架构的设计,可能很啰嗦但是如果不讲上面的话下面有的地方可能不好理解,下面讲下RichEditText中的实现吧。

    删除事件

    先来看对删除按键的事件监听:

        public RichEdittext(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            setBackgroundColor(Color.WHITE);
            setOnKeyListener(this);
        }
    
        /**
         * 监听删除按键,执行删除动作
         */
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            //按下键盘时会出发动作,弹起键盘时同样会触发动作
            if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
    
                if (startStrEndWithRichItem() && getSelectionStart() == getSelectionEnd()) {
    
                    int startPos = getSelectionStart();
                    final String startStr = toString().substring(0, startPos);
    
                    //获取话题,并计算话题长度
                    String richItem = RichParserManager.getManager().getLastRichItem(startStr);
                    int lenth = richItem.length();
    
                    clearFocus();
                    requestFocus();
    
                    //方案1: 先选中,不直接删除
                    setSelection(startPos - lenth, startPos);
    
                    v(String.format("del: (%s,%s)", startPos - lenth, startPos));
    
                    //方案2: 直接删除该话题
    //                String temp = startStr.substring(0, startStr.length() - lenth);
    //                setText(temp + toString().substring(startPos, toString().length()));
    //                setSelection(temp.length());
    
                    return true;
                }
            }
            return false;
        }

    代码第5行,开启对按键事件的监听,第8~37行,就是重写的View.OnKeyListener中的回调方法。

    第14行,过滤掉除删除键按下以外的事件。

    第16行,如果光标前面的字符串是以话题结尾,则进行特殊处理。

    第18~19行,截取光标前面的所有字符。

    第21~23行,获取上一步截取的字符串中的第一个富文本,并且得到这个富文本的长度。

    第25~26行,选中该富文本。

    上面是第一种方案,如果不选中富文本直接删除这个话题,如代码28~31。

    第33行,返回true表示已经处理了本次删除事件。

    第16行startStrEndWithRichItem()判断光标前面的字符串是否以话题结尾,源码如下:

        public boolean startStrEndWithRichItem() {
    
            int startPos = getSelectionStart();
            final String startStr = toString().substring(0, startPos);
            if (!RichParserManager.getManager().containsRichItem(startStr)) {
                return false;
            }
    
            String lastTopic = RichParserManager.getManager().getLastRichItem(startStr);
            return startStr.endsWith(lastTopic);
        }

    第3~4行,先截取光标前面的字符串。

    第5~7行,如果字符串中没有富文本,则直接返回false。

    第9行,获取字符串中最后面的一个富文本。

    然后在第10行判断这个字符串中是否以这个富文本结尾即可。

    至此,删除的时候当光标碰到富文本,可以先选中富文本再按一次删除时就删除,也可以直接把富文本删除的这个功能就实现了。

    光标移动

    光标移动的场景主要有4个:

    • 输入文字时
    • 插入、删除文字时;
    • 用键盘上下左右移动光标;
    • 直接点击输入框,改变光标的文字;

    针对所有的光标移动,由于我们只能在光标移动后监听到光标位置的改变,所以我们没有办法在移动光标前决定光标应当移动到哪个位置,只能在光标移动完成后通过对光标移动后的位置进行判断,并重新调整光标到合适的位置

    比如如果光标移动后落在富文本中间,则应当调整光标移动到富文本旁边,所以光标的移动用“调整”、“修正”来形容更加贴切一些。

    对于上面的4种情况,我们依次来分析。

    第1种情况:输入文字时光标自动移动到后一位,不用特殊处理。

    第2种情况:插入文字、富文本时,光标要移动一个富文本的长度;删除富文本时,光标也要移动一个富文本的长度。

    第3种情况又有两类:

    • 光标的开始位置和结束位置相同,移动时只需要判断移动后是否落在富文本中间,如果是则将光标的开始位置和结束位置都移动到富文本旁边;

    • 光标的开始位置和结束位置不同,即此时用户已经选中了一段文字,这时候如果对光标的开始位置和结束位置分布进行上下左右的移动,则要对光标的开始位置和结束位置分别进行处理和移动。

    第4种情况,其实就是第3种情况中的第1类。

    具体代码大致如下:

        @Override
        public void setSelection(int start, int stop) {
            if (0 <= start && stop <= getText().toString().length()) {
    
    //            mNewSelStart = start;
    //            mNewSelEnd = stop;
                super.setSelection(start, stop);
            }
        }
    
        @Override
        protected void onSelectionChanged(int selStart, int selEnd) {
            try {
    
                selectChanged(selStart, selEnd);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        private void selectChanged(int selStart, int selEnd) {
            //调用setText()会导致先触发onSelectionChanged()并且start和end均为0,然后才是正确的start和end的值
            if (0 == selStart && 0 == selEnd
                    //避免下面的setSelection()触发onSelectionChanged()造成死循环
                    || selStart == mNewSelStart && selEnd == mNewSelEnd) {
                mOldSelStart = selStart;
                mOldSelEnd = selEnd;
                return;
            }
            //校准左边光标
            int targetStart = getRecommendSelection(selStart);
            targetStart = targetStart == -1 ? selStart : targetStart;
            //校准右边光标
            int targetEnd = getRecommendSelection(selEnd);
            targetEnd = targetEnd == -1 ? selEnd : targetEnd;
            //保存旧值
            mOldSelStart = selStart;
            mOldSelEnd = selEnd;
            //保存新值
            mNewSelStart = targetStart;
            mNewSelEnd = targetEnd;
            //更新选中区域
            setSelection(targetStart, targetEnd);
        }
    
        /**
         * 掐头去尾,取中间字符串中的富文本
         *
         * @param pos
         * @return 由于富文本无法选中, 所以返回一个合适的位置(返回-1表示不做特殊处理)
         */
        private int getRecommendSelection(int pos) {
    
            String text = toString();
            if (TextUtils.isEmpty(text)) {
                return -1;
            }
    
            //取前面字符串中最后一个富文本
            String startStr = text.substring(0, pos);
            String richStr = RichParserManager.getManager().getLastRichItem(startStr);
            //start默认指向最前
            int start = 0;
            //如果点击的是最前面的话题,则richStr可能为空
            if (!TextUtils.isEmpty(richStr)) {
    
                start = startStr.lastIndexOf(richStr) + richStr.length();
            }
    
            //取后面字符串中第一个富文本
            String endStr = text.substring(pos, text.length());
            richStr = RichParserManager.getManager().getFirstRichItem(endStr);
            //end默认指向最后
            int end = text.length();
            //如果点击的是最后面的话题,则richStr可能为空
            if (!TextUtils.isEmpty(richStr)) {
    
                end = startStr.length() + endStr.indexOf(richStr);
            }
            String middleStr = text.substring(start, end);
            richStr = RichParserManager.getManager().getFirstRichItem(middleStr);
            if (TextUtils.isEmpty(richStr)) {
                return -1;
            }
            //"01 #456# 9",这种话题的start并不是从0,而是2
            start = start + middleStr.indexOf(richStr);
            end = start + richStr.length();
            //将光标移动离当前位置较近的地方
            return (pos - start < end - pos) ? start : end;
        }

    没解决的bug

    上面录制的那张gif看上去是不是很美好,但是事实很残酷,实际操作的时候,还是有两个bug。
    - 程序大部分功能基本上没有问题,但是删除话题有的时候删不了,因为有个先选中再删除的过程,不知道为什么选中的时候老是选中不了,所以造成删除话题时一直跳过。

    解决办法是:去掉先选中再删除的逻辑,当删除时碰到话题,按删除即直接删除整个话题,而不要选中了。

    • 移动选区时,光标没办法跳过整个富文本,光标仍然可以跑到富文本文字中间。

    打断点调试的时候逻辑都是对的,打印log的时候貌似是onSelectChanged方法有多次调用导致对正常判断造成了干扰。囧~

    如果解决了bug我会及时更新的,欢迎大神前来指点。


    (2017.1.19日更新,这两个bug均已解决,非常感谢Panjianan同学提供的思路 )

    项目地址

    https://github.com/aishang5wpj/RichEditText

    展开全文
  • 最近做了一个文章上传的项目,因为考虑到文章内容中有文字样式的需求和图片的插入,就做了一个富文本框的功能,我选择的插件就是vue-quill-editor,下边总结一下我在这里边踩过的坑和解决方案。分为两大部分来讲解,...

    最近做了一个文章上传的项目,因为考虑到文章内容中有文字样式的需求和图片的插入,就做了一个富文本框的功能,我选择的插件就是vue-quill-editor,下边总结一下我在这里边踩过的坑和解决方案。分为两大部分来讲解,使用优化

    一、使用

    1,下载插件 
              npm install vue-quill-editor --save

    2,引用

              在vue的main.js文件中添加这两行代码,就是引用和使用

              import QuillEditor from 'vue-quill-editor'

              Vue.use(QuillEditor)

    3,在插件中使用

         (1)html部分

            <el-form-item label="内容" :label-width="formLabelWidth">
              <quill-editor 
                v-model="content" 
                ref="myQuillEditor" 
                :options="editorOption" 
                @blur="onEditorBlur($event)" @ focus="onEditorFocus($event)" @change="onEditorChange($event)">
              </quill-editor>
            </el-form-item>

          (2)editorOption这个数据是工具栏的定义(图片是便于观看,图片下边附部分你需要的代码)

    工具栏功能的常量,我这个不全,网上随便找,有的是

    const toolbarOptions = [
                  ['bold', 'italic', 'underline', 'strike'],        // toggled buttons
                  ['blockquote', 'code-block'],
                  [{'list': 'ordered'}, {'list': 'bullet'}],
                  [{'script': 'sub'}, {'script': 'super'}],      // superscript/subscript
                  [{'indent': '-1'}, {'indent': '+1'}],          // outdent/indent
                  [{'direction': 'rtl'}],                         // text direction
                  [{'color': []}, {'background': []}],          // dropdown with defaults from theme
                  [{'align': []}],
                  ['image'],
                  ['clean']                                         // remove formatting button
              ]

    工具栏的定义,与html中的数据变量是一致就行

    editorOption: {
                        placeholder: '',
                        theme: 'snow',  // or 'bubble'
                        modules: {
                               toolbar: {
                                      container: toolbarOptions,  // 工具栏
                               }
                       }
               }

    4,内容处理

              quill-editor 得到的内容是由若干个<p></p>标签包裹的字符串,我们可以根据需求自行处理

    二、优化

    在使用过程中,发现了一个问题,这个富文本编辑器对图片的处理是把图片转换成base-64格式的,小小的一张图片就是一组很长的字符串,如果内容中有过多的图片,那就会发生412错误,就是上传的数据量过大,面对这种情况服务器可以设置增大限制上线,但是这种方案就会显的很low,还会对回显有不利影响。我想办法做了优化,把富文本编辑器的图片导入功能直接做了上传,之后我们富文本的内容中直接就变成<p><img src="xxxxxxxx"></p>,这样子大大减少了内容对服务器的损耗。下边直接上代码,具体的功能请自行琢磨

    html代码中增加一个上传的功能

    <el-form-item label="内容">
              <!-- 图片上传组件辅助-->
              <el-upload
                  class="avatar-uploader quill-img"
                  action="这里是你图片上传所访问的后台接口"
                  :show-file-list="false"
                  :on-success="uploadSuccess"
                  >
              </el-upload>
              <!--富文本编辑器组件-->
              <quill-editor 
                  v-model="nowData.content"
                  ref="myQuillEditor"
                  :options="editorOption"
                   @blur="onEditorBlur($event)" @focus="onEditorFocus($event)"@change="onEditorChange($event)">
              </quill-editor>
            </el-form-item>

    data中对image这项功能做重写覆盖

    editorOption: {
              placeholder: '',
              theme: 'snow',  // or 'bubble'
              modules: {
              toolbar: {
                  container: toolbarOptions,  // 工具栏
                  handlers: {
                    'image': function (value) {
                      if (value) {
                        // 触发input框选择图片文件
                        document.querySelector('.quill-img input').click()
                      } else {
                        this.quill.format('image', false);
                      }
                    }
                  }

              }
              }
          }

    methods中给上传成功的回调函数做处理

    uploadSuccess(res) {  //把已经上传的图片显示回富文本编辑框中

            //res返回的格式是{url:"图片的路径"},这个是后台接口返回的
            let quill = this.$refs.myQuillEditor.quill
            quill.focus();
            quill.insertEmbed(quill.getSelection().index, 'image', res.url);

        },

    这回功能就做好了

    展开全文
  • 如何让全屏游戏的文本编辑框支持IME和TSF输入法本文主要解决游戏在全屏状态下文本编辑框无法显示组词窗口和候选词窗口的问题 文章首发:blog.csdn.net/goodboychina/导致这个问题的原因是TSF与IME不兼容,且从Win7...

    如何让全屏游戏的文本编辑框支持IME和TSF输入法

    本文主要解决游戏在全屏状态下文本编辑框无法显示组词窗口和候选词窗口的问题
    文章首发:http://blog.csdn.net/goodboychina/article/details/52067561

    导致这个问题的原因是TSF与IME不兼容,且从Win7开始TSF(高级文字服务)默认是开启状态,并且无法关闭。这个情况在Win8下更严重。
    要解决这个问题要从TSF框架的引入导致的兼容性问题入手。首先先了解下文本编辑框用到的IME相关的API和相关概念。

    • HKL:键盘布局,最初的含义就是单纯的键盘布局,能够将键盘的扫描码转换成设备无关的虚键码。现在HKL的含义更广泛,表示本地化标识符。

    • HKL名字:键盘布局编号,设备码和语言代码组成。前四位是设备码,后四位是语言码。注册表项HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts中列出了所有键盘布局。项名就是HKL名称。

      • “00000804” 表示 中文(简体) - 默认键盘
      • “E0220804” 表示 中文(简体) - 搜狗输入法
      • “E0230804” 表示 中文(简体) – 必应Bing输入法
    • API与消息

      • BOOL WINAPI GetKeyboardLayoutName(_Out_ LPTSTR pwszKLID); 可以获得当前键盘布局或输入法的HKL名字
      • WM_INPUTLANGCHANGE 消息可以获知输入法切换情况
    • 组词窗口 组词窗口显示了当前已经键入的字符序列

      • WM_IME_STARTCOMPOSITION:输入法进入组词模式,程序会收到这个消息。
      • WM_IME_ENDCOMPOSITION:输入法结束组词模式,程序会收到这个消息。
      • WM_IME_COMPOSITION:组词发生改变,程序会收到这个消息
    • 候选词窗口 候选词窗口列出可以选择的字或词

      • WM_IME_NOTIFY wParam==IMN_CHANGECANDIDATE:候选词发生变动
      • WM_IME_NOTIFY wParam==IMN_CLOSECANDIDATE:关闭候选词窗口
      • WM_IME_NOTIFY wParam==IMN_OPENCANDIDATE:打开候选词窗口

    TSF对输入框架重新进行了设计,基于COM组件实现,部分原有的消息与API已不能正常工作

    • ITfTextInputProcessor:TIP 文字输入处理器
    • ITfInputProcessorProfiles:管理系统范围内的TIP
    • ITfInputProcessorProfileMgr:管理程序当前的TIP
    • ITfUIElementSink:接口类,实现此接口可以监听组词窗口和候选词窗口相关事件
    • ITfInputProcessorProfileActivationSink:接口类,实现此接口可以得知TIP的切换

    程序需要作调整,能够同时支持TIP和IME

    获取输入法名字示例代码

    /* 获取HKL的显示名字
     */
    bool GetLayoutName(const wchar_t* kl, wchar_t* nm )
    {
        long lRet;
        HKEY hKey;
        static wchar_t tchData[64];
        DWORD dwSize;
        wchar_t keypath[200];
    
        wsprintfW(keypath, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s", kl);
    
        lRet = RegOpenKeyExW(
            HKEY_LOCAL_MACHINE,
            keypath,
            0,
            KEY_QUERY_VALUE,
            &hKey
            );
    
        if (lRet == ERROR_SUCCESS)
        {
            dwSize = sizeof(tchData);
            lRet = RegQueryValueExW(
                hKey,
                L"Layout Text",
                NULL,
                NULL,
                (LPBYTE)tchData,
                &dwSize
                );
        }
    
        RegCloseKey(hKey);
    
        if (lRet == ERROR_SUCCESS && wcslen(nm) < 64)
        {
            wcscpy(nm, tchData);
            return true;
        }
    
        return false;
    }
    
    /* 获取当前输入法名字
     */
    wchar_t* TsfApp::GetCurrentIMEName()
    {
        static wchar_t _LastTipName[64];
    
        ZeroMemory(_LastTipName, sizeof(_LastTipName));
    
        TF_INPUTPROCESSORPROFILE tip;
        _pProfileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &tip);
    
        if (tip.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR)
        {
            BSTR bstrImeName = NULL;
            m_pProfiles->GetLanguageProfileDescription(tip.clsid, tip.langid, tip.guidProfile, &bstrImeName);
            if (wcslen(bstrImeName) < 64)
                wcscpy(_LastTipName, bstrImeName);
    
            SysFreeString(bstrImeName);
        }
        else if (tip.dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT)
        {
            static wchar_t klnm[KL_NAMELENGTH];
            if (GetKeyboardLayoutNameW(klnm))
            {
                GetLayoutName(klnm, _LastTipName);
            }
        }
    
        return _LastTipName;
    }

    TIP相关消息处理

    struct TsfApp : public ITfUIElementSink, public ITfInputProcessorProfileActivationSink
    {
        // IUnknown
        STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    
        // ITfUIElementSink
        //   WM_IME_COMPOSITION、WM_IME_NOTIFY消息的替代者
        STDMETHODIMP BeginUIElement(DWORD dwUIElementId, BOOL *pbShow);
        STDMETHODIMP UpdateUIElement(DWORD dwUIElementId);
        STDMETHODIMP EndUIElement(DWORD dwUIElementId);
    
        // ITfInputProcessorProfileActivationSink
        //   WM_INPUTLANGUAGECHANGED消息的替代者
        STDMETHODIMP OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid,
            REFGUID guidProfile, HKL hkl, DWORD dwFlags);
    
        LONG _cRef;
    
        DWORD m_dwUIElementSinkCookie;
        DWORD m_dwAlpnSinkCookie;
    
        ITfThreadMgrEx              *m_pThreadMgrEx;
        ITfInputProcessorProfiles   *m_pProfiles;
        ITfInputProcessorProfileMgr *_pProfileMgr;
    
        BOOL SetupSinks();
        void ReleaseSinks();        
    };
    
    BOOL TsfApp::SetupSinks()
    {
        CoInitialize(NULL);
        HRESULT hr;
    
        hr = CoCreateInstance(CLSID_TF_ThreadMgr,
            NULL,
            CLSCTX_INPROC_SERVER,
            __uuidof(ITfThreadMgrEx),
            (void**)&m_pThreadMgrEx);
    
        if (FAILED(hr))
        {
            return FALSE;
        }
    
        TfClientId cid;
        if (FAILED(m_pThreadMgrEx->ActivateEx(&cid, TF_TMAE_UIELEMENTENABLEDONLY)))
        {
            return FALSE;
        }
    
        ITfSource *srcTm;
        if (SUCCEEDED(hr = m_pThreadMgrEx->QueryInterface(__uuidof(ITfSource), (void **)&srcTm)))
        {
            srcTm->AdviseSink(__uuidof(ITfUIElementSink), (ITfUIElementSink*)this, &m_dwUIElementSinkCookie);
            srcTm->AdviseSink(__uuidof(ITfInputProcessorProfileActivationSink), (ITfInputProcessorProfileActivationSink*)this, &m_dwAlpnSinkCookie);
            srcTm->Release();
        }
    
        hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, IID_ITfInputProcessorProfiles, (LPVOID*)&m_pProfiles);
    
        if (FAILED(hr))
            return FALSE;
    
        m_pProfiles->QueryInterface(IID_ITfInputProcessorProfileMgr, (void **)&_pProfileMgr);
        return S_OK;
    }
    
    STDAPI TsfApp::QueryInterface(REFIID riid, void **ppvObj)
    {
        if (ppvObj == NULL)
            return E_INVALIDARG;
    
        *ppvObj = NULL;
        if (IsEqualIID(riid, IID_IUnknown))
            *ppvObj = reinterpret_cast<IUnknown *>(this);
        else if (IsEqualIID(riid, __uuidof(ITfUIElementSink)))
            *ppvObj = (ITfUIElementSink *)this;
        else if (IsEqualIID(riid, __uuidof(ITfInputProcessorProfileActivationSink)))
            *ppvObj = (ITfInputProcessorProfileActivationSink*)this;
        else if (IsEqualIID(riid, __uuidof(ITfLanguageProfileNotifySink)))
            *ppvObj = (ITfLanguageProfileNotifySink*)this;
    
        if (*ppvObj)
        {
            AddRef();
            return S_OK;
        }
    
        return E_NOINTERFACE;
    }
    
    STDAPI TsfApp::BeginUIElement(DWORD dwUIElementId, BOOL *pbShow)
    {
        /* 设置为FALSE表示隐藏UI */
        *pbShow = FALSE;
        return S_OK;
    }
    
    STDAPI TsfApp::UpdateUIElement(DWORD dwUIElementId)
    {
        ITfUIElementMgr *lpMgr = NULL;
        ITfCandidateListUIElement *lpCandUI = NULL;
        ITfReadingInformationUIElement *lpReading = NULL;
        ITfUIElement *pElement = NULL;
    
        /* 获取候选词或组合词 */
        if(SUCCEEDED(m_pThreadMgrEx->QueryInterface(IID_ITfUIElementMgr, (void**)&lpMgr)))
        {
            if(SUCCEEDED(lpMgr->GetUIElement(dwUIElementId, &pElement)))
            {
                if (SUCCEEDED(pElement->QueryInterface(IID_ITfCandidateListUIElement, (void**)&lpCandUI)))
                {
                    BSTR _sss;
                    lpCandUI->GetString(0, &_sss);
                    SysFreeString(_sss);
                    SAFE_RELEASE(lpCandUI);
                }
    
                if (SUCCEEDED(pElement->QueryInterface(IID_ITfReadingInformationUIElement, (void**)&lpReading)))
                {
                    BSTR _sss;
                    lpReading->GetString(&_sss);
                    SysFreeString(_sss);
    
                    SAFE_RELEASE(lpReading);
                }
    
                SAFE_RELEASE(pElement);
            }
            SAFE_RELEASE(lpMgr);
        }
    
        return S_OK;
    }
    
    STDAPI TsfApp::EndUIElement(DWORD dwUIElementId)
    {
        ITfDocumentMgr *pDocMgr=NULL;
        ITfContext *pContex = NULL;
        ITfContextView *pContexView = NULL;
    
        HWND hActiveHwnd = NULL;
    
        if (SUCCEEDED(m_pThreadMgrEx->GetFocus(&pDocMgr)))
        {
            if (SUCCEEDED(pDocMgr->GetTop(&pContex)))
            {
                if (SUCCEEDED(pContex->GetActiveView(&pContexView)))
                {
                    pContexView->GetWnd(&hActiveHwnd);
                    SAFE_RELEASE(pContexView);
                }
                SAFE_RELEASE(pContex);
            }
            SAFE_RELEASE(pDocMgr);
        }
    
        if (NULL != hActiveHwnd)
        {
            SendMessageW(hActiveHwnd, WM_IME_NOTIFY, IMN_CLOSECANDIDATE, 0);
        }
    
        return S_OK;
    }
    
    STDAPI TsfApp::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid,
        REFGUID guidProfile, HKL hkl, DWORD dwFlags)
    {
        bool bActive = (dwFlags & TF_IPSINK_FLAG_ACTIVE);
        if (!bActive)
            return S_OK;
    
        if (dwProfileType & TF_PROFILETYPE_INPUTPROCESSOR)
        {
            /* 输入法是 TIP */
    
            printf("TIP输入法 [%08X]\n", (unsigned int)hkl);
        }
        else if (dwProfileType & TF_PROFILETYPE_KEYBOARDLAYOUT)
        {
            /* 当前输入法是键盘布局或IME */
    
            printf("HKL/IME %08X\n", (unsigned int)hkl);
        }
        else
        {
            /* 不可能走到这里 */
            printf("dwProfileType unknown!!!\n");
        }
    
        return S_OK;
    }
    
    展开全文
  • 自定义固定头文字的多行文本编辑框.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
  • 文本编辑框之不可编辑

    千次阅读 2018-10-11 09:56:53
    样式:(用于过滤图文中的图片替换url) 前端代码:  &lt;...-- 加载编辑器的容器 --&gt; &lt;div class="form-group"&gt; &lt;label for="container...
  • 在EditText中软键盘的关闭软键盘: TextView etxtIndex_MemberLogin = this.findViewById(R.id.etxtIndex_MemberLogin); etxtIndex_MemberLogin....关于EditText文本编辑框的其他属性可以参照如下: 1、Ed...
  • Slate.js - 革命性的富文本编辑框架

    千次阅读 2017-10-18 07:04:57
    相信很多同学即便没有接触过富文本编辑领域,也一定听说过【富文本编辑是天坑,千万不要碰】的说法——是的,富文本编辑是天坑,但 Slate 能很好地帮助你。下面会介绍富文本编辑的复杂度所在,以及 Slate 的解决方式...
  • 因为用户需要编辑自定义页面,这里就要用到富文本编辑框,可以插入图片插入视频。我选择了vue-quill-editor。然后问题来了,现实需求和引入的框架冲突。 问题引入 vue-quill-editor默认的图片插入方式,是直接将...
  • 现在各手机厂商深度化定制rom,在谷歌原生系统上重新开发了很多功能,比如EditText的长按或双击事件弹出支持在线翻译,搜索等,如果你的项目出于信息安全考虑想禁止这些功能实现的话,你会发现网上现有的方法都...
  • 在开发过程中,遇到需要截取富文本编辑框的前三十个字的需求,瞬间懵了,还好能百度到东西; 重点内容如下,当前的我也是在一个富文本里面编写这个日志尼,想想都是缘分啊 根据id获取对象的html字符...
  • VC6.0编辑框CEdit的文本操作

    千次阅读 2012-03-12 10:11:34
    myedit.Getsel( A,B):是用来获取编辑框中当前选定文本的开始与结束的位置,必须是选定文本的开始与结束位置,并分别存在两个参数中。 myedit.Setsel():是用来选取指定范围内的文本,或者定位插入符的。 Setsel(0,...
  • 易语言超级编辑框彩色文字源码,超级编辑框彩色文字,加入文本
  • 当前市面上有许多可供选择的富文本编辑器,虽然百度的UEditor已在2016年即停止更新且界面风格较老,但是其属性,功能十分强大,所以仍是当前富文本编辑器里最好的选择之一。 现在就开始在ThinkPH...
  • 设置组合文本垂直居中显示,除了重绘之外,还可以直接在其父对话框中通过在 WM_NCCALCSIZE 消息的响应函数中来搞定它,首先可以通过SetItemHeight设置组合高度,然后进行下面的操作: void CComBoxTest1Dlg:...
  • 那么在修改时,只需将保存的这个文本的信息,在编辑框中重现: 问题主要表现在“:1)编辑框的大小,2)编辑框中字体的大小 ;其实一直纠缠在了 编辑框中无法显示文本的字体状态! 1)获得编辑框的大小  因为...
  • 编辑框控件设定字体的时候,字符编码一定要在字符集中,否则无效 GB2312没有包括藏文字符,而GB18030却包括了 2312指的是国家标准号   windows7 大陆中文版,默认字符集应该是GB18030,否则怎么设置为...
  • 1.文件-新建:检查文本中的内容是否为null,不为null,提示是否将当前内容保存,如果需要保存(MessageBoxResult.Yes),则保存。 表用系统自带的类SaveFileDialog,实现文本流的读入读出。 private void ...
  • 要改变其字体首先要CFont::CreateFont创建一个字体,然后用CWnd::SetFont选择此字体,赋给控件。具体的步骤: 1.在include的下面定义一个全局变量:  CFont cfont; 2.把以下这一部分放到OnInitDialog()函数...
  • - 合并两个编辑框、拆分编辑框 在本文,我整理了一些OneNote的使用经验,供读者参考,如果读者有更棒的经验技巧,不妨在文章下方留言。 3. 合并两个编辑框、拆分编辑框 有时候需要将两个编辑框合并到一起,例如...
  • 本文讨论单元格编辑模式下,下拉选择框的通用实现。jqGrid自带下拉选择框编辑类型,只要设置edittype='select'并设置editoptions即可。如果显示文本与值一致则很简单,如果不一致,则需要通过formatter显示文本,...
  • Android 富文本框实现 RichEditText

    万次阅读 多人点赞 2013-08-26 20:38:44
    Android系统自带控件没有富文本框控件,如果想写一封带格式的邮件基本上不可能,EdtiText只有默认一种格式,显示不能滿足要求,!!正好项目需要研究了一下,开发了此控件,现将一些源代码开放一下,希望对有需要此...
  •  表示可用于显示或编辑非格式化文本的控件。   介绍: 1.属性 常用属性 Name 获取或设置控件的名称。 Text 获取或设置与此控件显示的文本。 Multiline 获取或设置一个值,该值指示...
  • 超级编辑框这个组件编辑好文本后在保存文本不能读取该有的颜色和大小效果等等; 通过字符串来改变这个难题; 可以多重字符分割;(例如:右上方编辑框) 用法: 1.转换\编码 ①.给一个编辑好的文件或内容转换成颜色...
  • 自绘编辑框

    千次阅读 热门讨论 2007-08-07 09:35:00
    源文件 编辑框是常用控件之一,同时也是一种完全自实现较为繁琐的控件...通过观察默认的编辑框我们可以很清楚的看到编辑框有三个最基础的部分需要我们绘制,他们分别是3D边框、背景和文字,再进一步分析我们会发现他们
  •  超级编辑框这个组件编辑好文本后在保存文本不能读取该有的颜色和大小效果等等;  通过字符串来改变这个难题;  可以多重字符分割;(例如:右上方编辑框) 用法:  1.转换\编码  ①.给一个编辑好的文件或内容...
  • Java swing 简易文本编辑

    千次阅读 2017-10-09 10:39:12
    swing 简易文本编辑器 swing的控件使用
  • vue使用富文本框

    千次阅读 2020-08-05 14:36:24
    最近的一个vue项目需要用到富文本框,并且有插入图片/修改图片的需求。 需要用到组件: vue-qull-editor //富文本框组件 quill //用于拖拽改变图片大小 quill-image-resize-module //用于拖拽改变图片大小 1 安装: ...
  • android编辑框

    千次阅读 2017-10-07 00:27:04
    提示文本: android:hint="默认提示文本" android:textColorHint="#95A1AA" 获得焦点后全选组件内所有文本内容 当我们点击想当我们的输入框获得焦点后,不是将光标移动到文本的开始或者结尾;而是 获取到...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 147,554
精华内容 59,021
关键字:

文本编辑框选择部分