美文网首页.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 看源码看能不能解决你的问题
  • e8d986bdcbd1:太谢谢作者了,能帮忙改支持更全的富文本编辑器吗?我这边用的人反馈这个编辑器不好用,麻烦一下作者
    e8d986bdcbd1:@天兵公园 16号字体 希望找个好的编辑器
    天兵公园:@曾经a 需要什么功能,接受付费定制
  • 前达:作者你好,我下载了demo.运行后只显示一个空白的kEditor1,没有按钮,也不能输入,我下载了Summernote,把dist目录下的全部文件放到了Debug目录下,然后运行demo,还是只有一个空白的kEditor1,请问是什么情况?这里好像发不了图片,不知道你能懂吗
    前达:@天兵公园 你使用环境的ie版本是多少?
    前达:@天兵公园 谢谢回复,demo打开正常情况下是最后一张图的样子吗?还是需要做其他的配置?
    天兵公园:不知道是什么问题,我这边是正常的
  • 一直都在_9aaa:楼主,这个可以设置初始文本吗? 弄了好久都没弄好,帮忙一下
    天兵公园:@一直都在_9aaa 3楼4楼评论里的百度盘里就是示例代码
    一直都在_9aaa:@天兵公园 我是一个小白 怎么实现那个接口 可以给代码吗
    天兵公园:实现 IKEditorEventListener 接口,在 OnEditorLoadComplete 事件里调用 kEditor1.Html = "初始化文本";
  • cf97567facbb:不错,但是我想修改上面的操作按钮没法修改,比如我想加个截图功能,希望楼主能提供一些扩展接口
    天兵公园:目前没有精力维护,你可以沿着文中思路自己加上的
  • 245a66e0e15b:亲,我现在想用你这个编辑器,请问你有纯英文版的吗,比如按钮的一些注释和字体选择,希望是没有汉字
    天兵公园:@小飞_54af 别人付费定制的,暂时不考虑开放接口哦
    245a66e0e15b:@天兵公园 请问,可否开放编辑语言的接口,
    天兵公园:@小飞_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