美文网首页.NET C#dotNET
一个WinForm富文本编辑器控件

一个WinForm富文本编辑器控件

作者: 天兵公园 | 来源:发表于2017-09-17 01:35 被阅读2365次

    WinForm 上的富文本编辑器简直不要太少,虽然有 RichEdit,但是这个鬼极难用而且复杂,在插入图片和表格的时候简直抓狂,还要理解复杂的 RTF 格式。

    我希望有一个文本控件,包括基本的格式设置,图片表格插入等,能够自定义打开文件,保存和插入图片等功能,并且它的依赖项要尽可能少,因为是 WinForm 控件,也不用需要跨 Linux 和 Osx 平台,只用在 Windows 下保持兼容就行。这么一来,似乎并没有好的免费控件可用了 ...

    但是,Js 的这类控件就比比皆是了,有没有办法移到 C# WinForm 上来用呢,答案当然是 YES!

    首先要显示 HTML页面和JS的执行,必须要由 WebBrowser 控件承载,所以我们的整个编辑器都会在 WebBrowser 中呈现。接下来是编辑器控件了,尽量轻量级,最好是美观点,文档全面,接口丰富的,我找到我用过的一款。

    summernote https://github.com/summernote/summernote/

    接下来是编辑器的界面了,创建一个 HTML 页面,呈现编辑器,并设置编译方式为嵌入的资源,将所有的脚本文件内容全部和样式内容写在这个 HTML 页面中,这样一来,页面可能达到惊人的几百 KB,不过这没关系,除了脚本之外,还会有字体资源,关于字体资源如何嵌入在 CSS 中,可以通过下列方式:

    @font-face {
        font-family: "name";
        font-style: normal;
        font-weight: normal;
        src: url(data:font/truetype;charset=utf-8;base64,XXX);
    }
    

    需要注意的是 WebBrowser 是 IE 核心,所以只需要 eot 格式的字体即可。关于如何将字体生出你个 Base64 字符串,猛击 http://www.motobit.com/util/base64-decoder-encoder.asp

    编辑器的事件,我们写成接口,由调用方自行实,分别是保存按钮、打开文件按钮、插入图片按钮,异常等事件。

     public interface IKBrowserEventListener
     {
         void onSaveClicked();
         void onOpenFileClicked();
         void onInsertImageClicked();
         void onError(Exception ex);
     }
    

    如何直接使用 WebBrowser 控件的话,会有一些奇怪的问题,比如阻止脚本错误执行的对话框依旧会执行 ... 可是直接使用 COM+ 接口 IWebBrowser2,需要引用 Microsoft Internet Controls。

    public class KBrowser : System.Windows.Forms.WebBrowser
    {
        private SHDocVw.IWebBrowser2 Iwb2;
    
        public KBrowser()
        {
            NewWindow += KBrowser_NewWindow;
        }
    
        private void KBrowser_NewWindow(object sender, System.ComponentModel.CancelEventArgs e)
        {
            KBrowser kb = sender as KBrowser;
            string url = kb.StatusText;
            Navigate(url);
            e.Cancel = true;
        }
    
        protected override void AttachInterfaces(object nativeActiveXObject)
        {
            Iwb2 = (SHDocVw.IWebBrowser2)nativeActiveXObject;
            Iwb2.Silent = true;
            base.AttachInterfaces(nativeActiveXObject);
        }
    
        protected override void DetachInterfaces()
        {
            Iwb2 = null;
            base.DetachInterfaces();
        } 
    }
    

    接下来编辑器控件可以用用户控件,拖一个 KBrowser即可,Dock 为 Fill 铺满整个控件。它至少拥有下列属性:

    /// <summary>
    /// 编辑器的事件监听器
    /// </summary>
    public IKBrowserEventListener KBrowserEventListener { get; set; }
    /// <summary>
    /// 获取或设置编辑器中Html值
    /// </summary>
    public string Html
    {
        get
        {
            try
            {
                return kBrowser1.Document.InvokeScript("getHtml", null).ToString();
            }
            catch (Exception ex)
            {
                onError(ex);
                return "";
            }
        }
        set
        {
            try
            {
                kBrowser1.Document.InvokeScript("setHtml", new string[] { value });
            }
            catch (Exception ex)
            {
                onError(ex);
            }
        }
    }
    
    

    在这个 Html 的属性中,包括了 JS 和 C# 的互调用代码,这里是在 C# 中调用 JS 的一个方法,并且一个有返回值但无参数,一个有参数但无返回值。

    如果在 JS 里调用 C#,需要将类设置为 ComVisible(true),应用到方法级不知是否也可以,没试过。然后 window.external.XXX() 的方式调用,XXX 是 C# 的方法。有没有参看重载就知道了。

    在编辑器控件 OnLoad 时加载 Html 编辑器,因为是嵌入的资源,所以不是通过 File IO 的方式。

     private void KEditor_Load(object sender, EventArgs e)
     {
         try
         {
             Stream sm = Assembly.GetExecutingAssembly().GetManifestResourceStream("Knote.Widgets.Resources.editor.html");
             byte[] bs = new byte[sm.Length];
             sm.Read(bs, 0, (int)sm.Length);
             sm.Close();
             UTF8Encoding con = new UTF8Encoding();
             string str = con.GetString(bs);
             kBrowser1.DocumentText = str;
         }
         catch (Exception ex)
         {
             onError(ex);
         }
     }
    

    然后添加一些方法供 JS 调用,基本就是上述接口中的方法,调用前一定要判断是否空指针。

    /// <summary>
    /// 保存按钮点击的事件,请不要调用,而是使用监听器
    /// </summary>
    public void onSaveButtonClick()
    {
        if (KBrowserEventListener != null)
            KBrowserEventListener.onSaveClicked();
    }
    
    /// <summary>
    /// 打开文件按钮点击的事件,请不要调用,而是使用监听器
    /// </summary>
    public void onOpenFileButtonClick()
    {
        if (KBrowserEventListener != null)
            KBrowserEventListener.onOpenFileClicked();
    }
    
    /// <summary>
    /// 插入图片按钮点击的事件,请不要调用,而是使用监听器
    /// </summary>
    public void onInsertPictureButtonClick()
    {
        if (KBrowserEventListener != null)
            KBrowserEventListener.onInsertImageClicked();
    }
    

    插入普通文本和插入 HTML 源代码。

    /// <summary>
    /// 插入一个节点,它将由 div 元素包裹
    /// </summary>
    /// <param name="html"></param>
    public void InsertNode(string html)
    {
        try
        {
            kBrowser1.Document.InvokeScript("insertNode", new string[] { html });
        }
        catch (Exception ex)
        {
            onError(ex);
        }
    }
    
    /// <summary>
    /// 插入文本
    /// </summary>
    /// <param name="text"></param>
    public void InsertText(string text)
    {
        try
        {
            kBrowser1.Document.InvokeScript("insertText", new string[] { text });
        }
        catch (Exception ex)
        {
            onError(ex);
        }
    }
    

    为什么是 insertText 和 insertNode,这个是 JS 控件决定的,知道流程后,就可以封装任意编辑器了,最终完成效果如下,并且设计阶段也是所见即所得。

    WinForm 编辑器

    封装后只有一个 DLL,地址 https://github.com/yahch/kwig

    相关文章

      网友评论

      • 前达:作者你好,我又遇到了一个问题,图片进行一系列处理后,比如剪切,细节增强等操作后,插入到编辑器后不能正常显示图片了,未经过处理的图片编辑器能显示出来。

        将保存后的html在chrome打开,chrome能正常显示图片。

        这里不能传图片,不然我传几张不能正常显示的图片能方便你查看问题。

        另外这个编辑器比以前的.net编辑器好用多了。
        前达:我把处理过的和未处理的图片上传到了网上

        问题很奇怪,使用网络链接2张图片在编辑器里面都能正常显示,但是下载到本地之后,引用到编辑器里面后只有未处理过的才能显示。处理过的就不能正常显示了。

        http://www.dalianmaosoft.com/uploads/allimg/4791_1.jpg
        http://www.dalianmaosoft.com/uploads/allimg/4791_2.jpg
        前达:好的,谢谢了。太好了。开源了。
        天兵公园:@前达 没遇到过不太清楚你的问题,你可以从我github 看源码看能不能解决你的问题
      • 曾经a:太谢谢作者了,能帮忙改支持更全的富文本编辑器吗?我这边用的人反馈这个编辑器不好用,麻烦一下作者
        曾经a:@天兵公园 16号字体 希望找个好的编辑器
        天兵公园:@曾经a 需要什么功能,接受付费定制
      • 前达:作者你好,我下载了demo.运行后只显示一个空白的kEditor1,没有按钮,也不能输入,我下载了Summernote,把dist目录下的全部文件放到了Debug目录下,然后运行demo,还是只有一个空白的kEditor1,请问是什么情况?这里好像发不了图片,不知道你能懂吗
        前达:@天兵公园 你使用环境的ie版本是多少?
        前达:@天兵公园 谢谢回复,demo打开正常情况下是最后一张图的样子吗?还是需要做其他的配置?
        天兵公园:不知道是什么问题,我这边是正常的
      • 一直都在_9aaa:楼主,这个可以设置初始文本吗? 弄了好久都没弄好,帮忙一下
        天兵公园:@一直都在_9aaa 3楼4楼评论里的百度盘里就是示例代码
        一直都在_9aaa:@天兵公园 我是一个小白 怎么实现那个接口 可以给代码吗
        天兵公园:实现 IKEditorEventListener 接口,在 OnEditorLoadComplete 事件里调用 kEditor1.Html = "初始化文本";
      • cf97567facbb:不错,但是我想修改上面的操作按钮没法修改,比如我想加个截图功能,希望楼主能提供一些扩展接口
        天兵公园:目前没有精力维护,你可以沿着文中思路自己加上的
      • 小飞_54af:亲,我现在想用你这个编辑器,请问你有纯英文版的吗,比如按钮的一些注释和字体选择,希望是没有汉字
        天兵公园:@小飞_54af 别人付费定制的,暂时不考虑开放接口哦
        小飞_54af:@天兵公园 请问,可否开放编辑语言的接口,
        天兵公园:@小飞_54af 没有英文的,要改语言代码
      • 胡潇洒_dcef:感谢你的分享,关于如何封装此控件
        1 您这边有源码吗?如果有的话,可否发一份给我
        邮箱:2582964739@qq.com
        2 这边使用VS2015运行你的代码,确实可行,但是按照你的demo建立,点击事件均没有响应,是否是WebBrowser支持IE的版本问题?
        天兵公园:@胡潇洒_dcef 暂时没有增加获取纯文本的方法,你可以把html标签过滤得到纯文本,源码暂时不能提供
        胡潇洒_dcef:@天兵公园 按照你的方法,实现了响应,多谢!
        还是想问下是否可以直接得到其添加的文本,而不是整个HTML
        天兵公园:事件需要实现 IKEditorEventListener,参考 https://pan.baidu.com/s/1dE64wLz 密码: umci
      • 不是方的馒头:你好,谢谢你的分享,的确很漂亮的界面,已经成功应用上WINFORM;然后里面的插入图片的功能没有响应,是要怎么处理呢?敬请赐教,谢谢!
        天兵公园:你可以参考 demo 链接: https://pan.baidu.com/s/1dE64wLz 密码: umci
      • 前达:好东西啊。很激动的下载了。发现不支持net3.5,博主,可以支持下3.5吗,谢谢了。
        天兵公园:我编译了 2.0版,支持 2.0至4.7,你试试 。链接: https://pan.baidu.com/s/1eR2xyMY 密码: k2br

      本文标题:一个WinForm富文本编辑器控件

      本文链接:https://www.haomeiwen.com/subject/dbfpsxtx.html