NanUI for Winform 使用示例【第二集】——做一個所見即所得的Markdown編輯器


【請注意:此文已過期,0.6版NanUI實現方式不同!!!】

 

經過了這一個多星期的調整與修復,NanUI for .NET Winform的穩定版已經發布。應廣大群友的要求,現已將NanUI的全部代碼開源。

GitHub: https://github.com/NetDimension/NanUI

Release: https://github.com/NetDimension/NanUI/releases

這次發布的是一個相對穩定的版本,解決和改善了如下問題:

  • 頁面隨機白屏問題(主要原因是GC自動回收后,造成內存地址不可讀)
  • NanUI編譯版本改為.NET 4.0 Client Profile
  • 托上面那條改進的福,NanUI現在支持Windows XP
  • 不再支持本地CEF運行支持文件,現在支持文件都需要在線下載安裝,當然也可以手動下載離線包安裝,但是不論那種方式,CEF都安裝到一個共享的位置。CEF運行庫只需下載安裝一次,不會多次下載。

歡迎下載把玩,也歡迎進群討論,群號241088256。

下面,進入我們的正題,使用NanUI以及手邊的各種開源庫制作一個所見即所得的Markdown編輯器。

NanUI系列目錄

NanUI for Winform 使用示例【第二集】

做一個所見即所得的Markdown編輯器

在本集中,使用了如下開源技術來方便的組建我們的“所見即所得Markdown編輯器”:

  • bootstrap
  • codeMirror
  • jquery
  • jquery.splitter.js
  • markdown-js
  • github-markdown.css

利用Nuget,獲取上列的各種庫不是難題。如效果圖所示,我們可以方便的利用這些開源庫來設計出心儀的頁面。在此着重講解網頁前端和后台C#通信的技術。后面的文章里,凡是HTML、CSS和JS的內容我將稱他們為“前端”、涉及C#編程的地方我會稱他們為“后端”。

如圖所示,軟件將要與C#后端交互的幾個地方有:

  • 代碼編輯框
  • 新建文件按鈕
  • 打開文件按鈕
  • 保存文件按鈕

在C#后端,建立HostEditor類來處理由前端發送回來的按鈕事件。該類繼承自基類JSObject,這個類負責與CEF的V8環境處理各種數據和對象。

 1 class HostEditor:JSObject
 2 {
 3     frmMain MainFrame;
 4     internal HostEditor(frmMain main)
 5     {
 6         MainFrame = main;
 7 
 8         AddFunction("setCleanState").Execute += SetCleanState;
 9 
10         AddFunction("newFile").Execute += NewFile;
11 
12         AddFunction("openFile").Execute += OpenFile;
13 
14         AddFunction("saveFile").Execute += SaveFile;
15     }
16 
17     private void SaveFile(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
18     {
19         var contents = e.Arguments.FirstOrDefault(p => p.IsString);
20         var result = false;
21         if (contents != null)
22         {
23             result = MainFrame.SaveFile(contents.StringValue);
24 
25 
26         }
27 
28         if (result)
29         {
30             e.SetReturnValue(this.GetCfrObject(new
31             {
32                 success = true,
33                 fileName = MainFrame.CurrentFile.Name
34             }));
35         }
36         else
37         {
38             e.SetReturnValue(this.GetCfrObject(new
39             {
40                 success = false
41             }));
42         }
43     }
44 
45     private void OpenFile(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
46     {
47         var contents = e.Arguments.FirstOrDefault(p => p.IsString);
48         string result = null;
49         if (contents != null)
50         {
51             result = MainFrame.OpenFile(contents.StringValue);
52         }
53 
54         if (!string.IsNullOrEmpty(result))
55         {
56             e.SetReturnValue(this.GetCfrObject(new
57             {
58                 success = true,
59                 fileName = MainFrame.CurrentFile.Name,
60                 contents = result
61             }));
62 
63         }
64         else
65         {
66             e.SetReturnValue(this.GetCfrObject(new
67             {
68                 success = false
69             }));
70         }
71     }
72 
73     private void NewFile(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
74     {
75         var contents = e.Arguments.FirstOrDefault(p => p.IsString);
76         var result = false;
77         result = MainFrame.NewFile(contents.StringValue);
78 
79         e.SetReturnValue(CfrV8Value.CreateBool(result));
80     }
81 
82     private void SetCleanState(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
83     {
84         if(e.Arguments.Length>0 && e.Arguments[0].IsBool)
85         {
86             MainFrame.isClean = e.Arguments[0].BoolValue;
87         }
88     }
89 }

在主窗體的構造函數中,將上面的HostEditor類注冊到NanUI的JS環境中,並命名為hostEditor,這樣在前端的JS中就可以調用hostEditor對象以及對象中內置的C#方法了:

GlobalObject.Add("hostEditor", new HostEditor(this));

在JS環境中hostEditor對象提供了以下幾個方法來實現對當前代碼編輯器里的內容進行新增、打開和保存的操作。

  • hostEditor.newFile(string)
  • hostEditor.openFile(string)
  • hostEditor.saveFile(string)
  • hostEditor.setCleanState(bool)

同時,將HostEditor中需要用到的新建文件、保存文件、打開文件等操作的方法放在主窗體中,方便前端JS調用。

        /// <summary>
        /// 標記文檔是否被修改
        /// </summary>
        internal bool isClean = true;
        /// <summary>
        /// 當前文檔的存儲路徑,如果為空則說明該文檔是新文檔
        /// </summary>
        internal string currentFilePath = string.Empty;

        /// <summary>
        /// 當前文檔的FileInfo
        /// </summary>
        internal System.IO.FileInfo CurrentFile
        {
            get
            {
                return new System.IO.FileInfo(currentFilePath);
            }
        }

        /// <summary>
        /// 獲得一個標識當前文檔是否為新建文檔
        /// </summary>
        private bool IsNewFile
        {
            get
            {
                return string.IsNullOrEmpty(currentFilePath);
            }
        }
        /// <summary>
        /// 新建文件
        /// </summary>
        /// <param name="contents">當前文檔中的內容</param>
        /// <returns>如果新建成功則返回true</returns>
        internal bool NewFile(string contents)
        {
            var continueFlag = true;


            if (!isClean)
            {
                var ret = MessageBox.Show(this, "文件已經更改,是否保存下先?", "提示", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

                if (ret == DialogResult.Yes)
                {
                    if (!SaveFile(contents))
                    {
                        continueFlag = false;
                    }
                }
                else if (ret == DialogResult.Cancel)
                {
                    continueFlag = false;
                }

            }

            if (!continueFlag)
            {
                return false;
            }

            currentFilePath = null;
            isClean = true;


            return true;
        }
        /// <summary>
        /// 打開文檔
        /// </summary>
        /// <param name="contents">當前文檔中的內容</param>
        /// <returns>如果新建成功則返回打開文檔的內容</returns>
        internal string OpenFile(string contents)
        {
            var continueFlag = true;
            if (!isClean)
            {
                var ret = MessageBox.Show(this, "文件已經更改,是否保存下先?", "提示", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

                if (ret == DialogResult.Yes)
                {
                    if (!SaveFile(contents))
                    {
                        continueFlag = false;
                    }
                }
                else if (ret == DialogResult.Cancel)
                {
                    continueFlag = false;
                }

            }

            if (!continueFlag)
            {
                return null;
            }

            var content = string.Empty;

            var openDialog = new OpenFileDialog()
            {
                AddExtension = true,
                Filter = "Markdown文件|*.md"
            };

            if (openDialog.ShowDialog() == DialogResult.OK)
            {
                currentFilePath = openDialog.FileName;

                var fileInfo = new System.IO.FileInfo(currentFilePath);

                content = System.IO.File.ReadAllText(fileInfo.FullName);

            }
            else
            {
                content = null;
            }

            return content;


        }
        /// <summary>
        /// 保存文檔
        /// </summary>
        /// <param name="contents">當前文檔中的內容</param>
        /// <returns>如果保存成功則返回true</returns>
        internal bool SaveFile(string contents) {

            if (!IsNewFile) {
                if (isClean) return true;


                System.IO.File.WriteAllText(currentFilePath, contents, Encoding.UTF8);
                isClean = true;
                return true;
            }


            var saveFileDialog = new SaveFileDialog()
            {
                AddExtension = true,
                Filter = "Markdown文件|*.md",
                OverwritePrompt = true
            };

            if (saveFileDialog.ShowDialog(this) == DialogResult.OK)
            {
                currentFilePath = saveFileDialog.FileName;

                System.IO.File.WriteAllText(currentFilePath, contents, Encoding.UTF8);

                isClean = true;

                return true;
            }

            return false;

        }

 

如此這般,一個所見即所得的Markdown編輯器就制作完成了。有了這個小工具編輯GitHub的README文檔就不會那么痛苦了。有興趣的朋友可以自行到GitHub下載代碼來把玩。

那么,NanUI的第二集示例就這么講完了。最后還是歡迎大家留言,或者進群討論。當然能在github夠提供pull request是最好的。

 

附件

MarkdownDotNet.zip - 編譯好的Markdown編輯器,歡迎下載體驗,代碼已上傳到GitHub

 


NanUI for .NET Winform系列目錄


經過了這一個多星期的調整與修復,NanUI for .NET Winform的穩定版已經發布。應廣大群友的要求,現已將NanUI的全部代碼開源。

GitHub: https://github.com/NetDimension/NanUI

Release: https://github.com/NetDimension/NanUI/releases


 

 如果你喜歡NanUI項目,你可以參與到NanUI的開發中來,當然你也可以更直接了當的支持我的工作,使用支付寶或微信掃描下面二維碼請我喝一杯熱騰騰的咖啡。

支付寶轉賬

微信轉賬


 

另外,打個廣告,承接NanUI界面設計與接口開發(收費)。

案例展示

某聊天應用

某公司內部辦公系統


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM