一個WinForm富文本編輯器控件

WinForm 上的富文本編輯器簡直不要太少桶雀,雖然有 RichEdit氧猬,但是這個鬼極難用而且復(fù)雜盅抚,在插入圖片和表格的時候簡直抓狂,還要理解復(fù)雜的 RTF 格式妄均。

我希望有一個文本控件哪自,包括基本的格式設(shè)置壤巷,圖片表格插入等瞧毙,能夠自定義打開文件,保存和插入圖片等功能矩动,并且它的依賴項要盡可能少释漆,因為是 WinForm 控件,也不用需要跨 Linux 和 Osx 平臺示姿,只用在 Windows 下保持兼容就行逊笆。這么一來,似乎并沒有好的免費控件可用了 ...

但是荧琼,Js 的這類控件就比比皆是了差牛,有沒有辦法移到 C# WinForm 上來用呢堰乔,答案當(dāng)然是 YES!

首先要顯示 HTML頁面和JS的執(zhí)行,必須要由 WebBrowser 控件承載侦讨,所以我們的整個編輯器都會在 WebBrowser 中呈現(xiàn)苟翻。接下來是編輯器控件了,盡量輕量級沈条,最好是美觀點诅炉,文檔全面屋厘,接口豐富的月而,我找到我用過的一款父款。

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

接下來是編輯器的界面了,創(chuàng)建一個 HTML 頁面憨攒,呈現(xiàn)編輯器浓恶,并設(shè)置編譯方式為嵌入的資源,將所有的腳本文件內(nèi)容全部和樣式內(nèi)容寫在這個 HTML 頁面中包晰,這樣一來伐憾,頁面可能達(dá)到驚人的幾百 KB,不過這沒關(guān)系树肃,除了腳本之外胸嘴,還會有字體資源,關(guān)于字體資源如何嵌入在 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 格式的字體即可。關(guān)于如何將字體生出你個 Base64 字符串闸婴,猛擊 http://www.motobit.com/util/base64-decoder-encoder.asp

編輯器的事件芍躏,我們寫成接口,由調(diào)用方自行實溺欧,分別是保存按鈕、打開文件按鈕芥牌、插入圖片按鈕聂使,異常等事件。

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

如何直接使用 WebBrowser 控件的話弃理,會有一些奇怪的問題屎蜓,比如阻止腳本錯誤執(zhí)行的對話框依舊會執(zhí)行 ... 可是直接使用 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>
/// 編輯器的事件監(jiān)聽器
/// </summary>
public IKBrowserEventListener KBrowserEventListener { get; set; }
/// <summary>
/// 獲取或設(shè)置編輯器中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# 的互調(diào)用代碼榨婆,這里是在 C# 中調(diào)用 JS 的一個方法,并且一個有返回值但無參數(shù),一個有參數(shù)但無返回值烟央。

如果在 JS 里調(diào)用 C#歪脏,需要將類設(shè)置為 ComVisible(true),應(yīng)用到方法級不知是否也可以钞艇,沒試過。然后 window.external.XXX() 的方式調(diào)用挺物,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 調(diào)用豪娜,基本就是上述接口中的方法绒疗,調(diào)用前一定要判斷是否空指針。

/// <summary>
/// 保存按鈕點擊的事件惕虑,請不要調(diào)用磨镶,而是使用監(jiān)聽器
/// </summary>
public void onSaveButtonClick()
{
    if (KBrowserEventListener != null)
        KBrowserEventListener.onSaveClicked();
}

/// <summary>
/// 打開文件按鈕點擊的事件,請不要調(diào)用伟叛,而是使用監(jiān)聽器
/// </summary>
public void onOpenFileButtonClick()
{
    if (KBrowserEventListener != null)
        KBrowserEventListener.onOpenFileClicked();
}

/// <summary>
/// 插入圖片按鈕點擊的事件脐嫂,請不要調(diào)用账千,而是使用監(jiān)聽器
/// </summary>
public void onInsertPictureButtonClick()
{
    if (KBrowserEventListener != null)
        KBrowserEventListener.onInsertImageClicked();
}

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

/// <summary>
/// 插入一個節(jié)點鞭衩,它將由 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 控件決定的瑞佩,知道流程后坯台,就可以封裝任意編輯器了,最終完成效果如下御雕,并且設(shè)計階段也是所見即所得滥搭。

WinForm 編輯器

封裝后只有一個 DLL瑟匆,地址 https://github.com/yahch/kwig

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疾嗅,隨后出現(xiàn)的幾起案子冕象,更是在濱河造成了極大的恐慌,老刑警劉巖论悴,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膀估,死亡現(xiàn)場離奇詭異耻讽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饼记,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門慰枕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捺僻,“玉大人,你說我怎么就攤上這事匕坯。” “怎么了锹雏?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵礁遵,是天一觀的道長采记。 經(jīng)常有香客問我,道長兼砖,這世上最難降的妖魔是什么既棺? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任丸冕,我火速辦了婚禮,結(jié)果婚禮上胖烛,老公的妹妹穿的比我還像新娘。我一直安慰自己洪己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布逝钥。 她就那樣靜靜地躺著艘款,像睡著了一般沃琅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晌柬,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音澈歉,去河邊找鬼屿衅。 笑死,一個胖子當(dāng)著我的面吹牛涡尘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播响迂,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼考抄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了栓拜?” 一聲冷哼從身側(cè)響起座泳,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎幕与,沒想到半個月后挑势,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡啦鸣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年潮饱,在試婚紗的時候發(fā)現(xiàn)自己被綠了诫给。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片香拉。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖中狂,靈堂內(nèi)的尸體忽然破棺而出凫碌,到底是詐尸還是另有隱情,我是刑警寧澤胃榕,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布盛险,位于F島的核電站,受9級特大地震影響勋又,放射性物質(zhì)發(fā)生泄漏苦掘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一楔壤、第九天 我趴在偏房一處隱蔽的房頂上張望鹤啡。 院中可真熱鬧,春花似錦蹲嚣、人聲如沸递瑰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泣矛。三九已至疲眷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間您朽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工换淆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留哗总,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓倍试,卻偏偏與公主長得像讯屈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子县习,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容