NanUI for Winform 使用示例【第一集】——山寨個代碼編輯器


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

 

NanUI for Winform從昨天寫博客發布到現在獲得了和多朋友的關注,首先感謝大家的關注和支持!請看昨天本人的博文《NanUI for Winform發布,讓Winform界面設計擁有無限可能》。

有朋友問到我是否考慮開源NanUI,我想回答是肯定的。但是目前來看NanUI內部還有一些問題並沒有得到解決,因此暫時不會開放源代碼。待這些問題順利解決之后我會第一時間把NanUI的源碼放到GitHub,請稍等片刻。

那么,從今天起我會陸續放出一些NanUI使用的一些小示例和源代碼供感興趣的朋友參考把玩。任何關於NanUI的問題歡迎大家進群(群號:241088256)或留言與我交流。

下面,開始今天的示例

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

山寨個代碼編輯器

去年微軟破天荒的發布了個吊炸天的開源代碼編輯器VS Code,觀看VS Code的目錄結構可知,其實它也是基於CEF來進行開發的,使用的是名為electron的框架。那么,下面我將用NanUI山寨一個簡單的Code編輯器實現對代碼文件的新建、打開和保存操作,取名為NanUI代碼編輯器。

NanUI代碼編輯器使用的核心技術有:

  • Bootstrap
  • CodeMirror
  • NanUI for Winform(我曉得,這句話是廢話)
  • 從分離的資源程序集加載資源

Bootstrap做響應式的頁面吊炸天,雖然我們今天要進行的小示例用不到響應式布局,但是引用進來就當CSS Clear用吧。另外一個CodeMirror作為網頁端最強大的代碼編輯器,這次通過NanUI,我們的Winform也將享受它帶來的強大功能。下面,我將分步講解如何來山寨這個代碼編輯器。

在VS中新建Windows Application項目(后面稱為主項目),然后在項目->屬性->調試中關閉“啟動VS承載進程”選項,因為經過實踐,開啟該選項后無法加載嵌入的網頁資源。同時,開啟“啟用本機代碼調試選項”,因為ChromiumFX使用了PInvoke的方式調用,會有很多莫名其妙的非托管錯誤,例如,我之前就遇到個只要啟動項目就報錯的問題,開啟了本機代碼調試后發現是QQ拼音輸入法鈎子的問題,點個忽略繼續就可以正常調試了。設置好后引用NanUI的庫NetDimenison.NanUI.dll

 

再新建一個類庫項目(后面稱為資源項目),在里面建立文件夾www,文件夾名字沒有要求,隨意就好,但要強調一點,html文件不能在類庫項目的根目錄下,必須建立個文件夾來放置網頁文檔。將bootstrap和codemirror的html、css和js文件等拷貝進www目錄,當然你也可以直接從nuget上下載它們,只是需要把nuget拿到的文件都拖到www里面,形成下面的文件結構。

剔除掉用不着的文件,從項目中排除或直接刪除都行,剩下的需要用的項目都在屬性窗口中把生成操作改成“嵌入的資源”。然后新建個靜態類,名字隨便取,里面新建個方法來暴露資源項目的Assembly。

namespace NanUI.Demo.CodeEditor.Resources
{
    public static class SchemeHelper
    {
        public static System.Reflection.Assembly GetSchemeAssembley()
        {
            return System.Reflection.Assembly.GetExecutingAssembly();
        }
    }
}

新建這個類的作用是方便主項目注冊資源項目里面的程序集,如果用這種方法來注冊資源文件需要在主項目中引用資源項目。另外一個方法,可以直接在主項目中直接使用Assembly.LoadFile加載資源項目,如果項目需要經常更新的話用這個方法可以做到隨時更新資源文件而不用重新安裝整個軟件,具體的用法會在將來的示例中介紹,在此就不多說了。

如此這般,資源項目就弄好了。接下來的工作都將在主項目上進行了。在主項目的main函數里初始化NanUI。

namespace NanUI.Demo.CodeEditor
{
    using NetDimension.NanUI;

    static class Program
    {
        /// <summary>
        /// 應用程序的主入口點。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            UIStartupManager.UseSharedFramework = true;
            if (UIStartupManager.InitializeChromium())
            {
                //初始化成功,加載程序集內嵌的資源到運行時中
                UIStartupManager.RegisterEmbeddedScheme(Resources.SchemeHelper.GetSchemeAssembley());

                

                //啟動主窗體
                Application.Run(new EditorForm());
            }

            
        }
    }
}

其中,Resources.SchemeHelper.GetSchemeAssembley()方法就是從資源項目里面把Assembly加載過來。之后新建編輯器的主窗體EditorForm。EditorForm除了簡單的設置外,需另外添加幾個方法來控制文件的新建、打開、保存等操作。

namespace NanUI.Demo.CodeEditor
{
    using NetDimension.NanUI;

    public partial class EditorForm : HtmlUIForm
    {

        internal bool isClean = true;
        internal bool isNew = true;

        internal string currentFilePath = string.Empty;


        public EditorForm()
            : base("embedded://www/main.html")
        {
            InitializeComponent();

            UI.GlobalObject.Add("hostEditor", new JsCodeEditorObject(this));
        }

        void SetEditorMode()
        {
            if (!string.IsNullOrEmpty(currentFilePath))
            {
                var fileInfo = new System.IO.FileInfo(currentFilePath);

                var ext = fileInfo.Extension;

                if (ext.IndexOf('.') == 0)
                {
                    ext = ext.Substring(1);
                    UI.ExecuteJavascript($"CodeEditor.changeCodeScheme('{ext}');");
                }
            }
        }

        //設置編輯器標題邏輯
        void SetEditorTitle()
        {
            if (isNew || string.IsNullOrEmpty(currentFilePath))
            {
                UI.ExecuteJavascript($"CodeEditor.setTitle('新建');");
            }
            else
            {
                var fileInfo = new System.IO.FileInfo(currentFilePath);
                UI.ExecuteJavascript($"CodeEditor.setTitle('{fileInfo.Name}');");
            }
        }
        //保存文件邏輯
        internal bool SaveFile()
        {
            var result = false;
            UI.EvaluateJavascript(@"CodeEditor.getContent()", (value, ex) =>
            {
                if (ex == null)
                {
                    var content = value.IsString ? value.StringValue : null;

                    if (content != null)
                    {

                        if (isNew)
                        {
                            var saveFileDialog = new SaveFileDialog()
                            {
                                AddExtension = true,
                                Filter = "支持的文件|*.txt;*.js;*.cs;*.html;*.htm;*.css;*.h;*.cpp;*.php;*.xml;*.vb",
                                OverwritePrompt = true
                            };
                            if (saveFileDialog.ShowDialog(this) == DialogResult.OK)
                            {
                                currentFilePath = saveFileDialog.FileName;
                                result = true;

                            }
                        }

                        if (result)
                        {
                            System.IO.File.WriteAllText(currentFilePath, content, Encoding.UTF8);
                            isClean = true;
                            SetEditorMode();
                            SetEditorTitle();
                        }

                        

                    }
                }
            });

            return result;
        }
        //新建文件邏輯
        internal void NewFile()
        {
            var continueFlag = true;

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

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

            if (!continueFlag)
            {
                return;
            }

            isNew = true;
            isClean = true;


            UI.ExecuteJavascript(@"CodeEditor.setNew()");
            SetEditorTitle();



        }
        //打開文件邏輯
        internal string OpenFile()
        {
            var continueFlag = true;

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

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

            if (!continueFlag)
            {
                return null;
            }

            var content = string.Empty;

            var openDialog = new OpenFileDialog()
            {
                AddExtension = true,
                Filter = "支持的文件|*.txt;*.js;*.cs;*.html;*.htm;*.css;*.h;*.cpp;*.php;*.xml;*.vb"
            };

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

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

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

                isNew = false;

                SetEditorMode();
                SetEditorTitle();
            }
            else
            {
                content = null;
            }

            return content;
        }
    }
}

 

下面重點來了,以下內容將涉及C#操作網頁內JS代碼以及JS調用C#的方法等等,核心就是中間對象JsCodeEditorObject。該對象繼承自ChromiumFX的JSObject對象,對JS有了解的朋友不會對JS的Object對象感到陌生,這里的JSObject對象和JS的Object對象其實很相似,也是包含了數據和方法的一個集合。如下所示,在其中注冊了多個Function,這些Function都是能夠被網頁端js調用的。

namespace NanUI.Demo.CodeEditor
{
    using Chromium.Remote;
    using NetDimension.NanUI;

    class JsCodeEditorObject : JSObject
    {

        EditorForm parentForm;
        
        internal JsCodeEditorObject(EditorForm parentForm)
        {
            this.parentForm = parentForm;
            
            AddFunction("newFile").Execute += JsCodeEditorObject_ExecuteNew;

            AddFunction("openFile").Execute += JsCodeEditorObject_ExecuteOpen;

            AddFunction("saveFile").Execute += JsCodeEditorObject_ExecuteSave;

            AddFunction("setClean").Execute += JsCodeEditorObject_ExecuteSetClean;
        }





        private void JsCodeEditorObject_ExecuteSetClean(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
        {
            if (e.Arguments.Length > 0)
            {

                parentForm.isClean = e.Arguments.First(p => p.IsBool).BoolValue;
            }
        }

        private void JsCodeEditorObject_ExecuteSave(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
        {
            var result  = parentForm.SaveFile();

            e.SetReturnValue(CfrV8Value.CreateBool(result));

        }

        private void JsCodeEditorObject_ExecuteOpen(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
        {
            var result = parentForm.OpenFile();

            if (result != null)
            {
                e.SetReturnValue(CfrV8Value.CreateString(result));

            }
            else
            {
                e.SetReturnValue(CfrV8Value.CreateNull());
            }
        }

        private void JsCodeEditorObject_ExecuteNew(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
        {
            parentForm.NewFile();
        }
    }
}

如上面代碼中所展示的,這個類注冊了newFile, openFile, saveFile和setClean幾個方法供網頁端js調用。再折回到上面EditorForm的代碼,有這么一行:

UI.GlobalObject.Add("hostEditor", new JsCodeEditorObject(this));

這行代碼的左右就是將我們的中間對象JsCodeEditorObject實例化后傳到瀏覽器的JS環境中,並且取了個新名字叫做“hostEditor”,這樣,我們就能夠在網頁環境中用js執行下面的幾個方法了。

  • hostEditor.newFile
  • hostEditor.openFile
  • hostEditor.saveFile
  • hostEditor.setClean

當網頁端js調用上面這些方法的時候實際上會執行到EditorForm中相應的操作邏輯,如此就實現了js與c#環境的交互。C#與js環境交互與之相比要簡單得多,例如EdiorForm中的SetEditorTitle方法中,通過UI.ExecuteJavascript方法就可以執行web環境中的js代碼或方法。

UI.ExecuteJavascript($"CodeEditor.setTitle('新建');");

如上這行代碼,調用了js的方法CodeEditor.setTitle來設置編輯器的標題。那么,如果需要執行js代碼並返回相應代碼,就得使用UI.EvaluateJavascript方法。該方法第一個參數為js代碼,第二個參數為執行js代碼后的回調,它是一個有兩個參數的action對象,第一個參數為回調的返回值,第二個參數是exception對象,如果js代碼有問題,那么第二個對象exception就包含了錯誤信息,正確執行時,excepiton對象返回null。

UI.EvaluateJavascript(@"CodeEditor.getContent()", (value, ex) =>
{
    if (ex == null)
    {
        var content = value.IsString ? value.StringValue : null; //value對象是CfrV8Value對象,內置了各種數據轉換的方法。
                
        //其他邏輯
    }
});

 

有了上面的代碼要點,大家應該已經清楚C#和JS之間的交互方法和JS於C#之間的交互的方式了。簡而言之,C#與NanUI的js交互,無返回值的用UI.ExecuteJavascript方法,有返回值的用UI.EvaluateJavascript。除了這兩個方法外,可以用UI.AddFunction方法來直接在js環境中注冊C#的方法,方法大家自行研究在此不再闡述。如果js需要與C#之間的交互,通過編寫繼承JSObject的中間對象注冊方法或數據對象,即可實現。

那么,NanUI的示例第一集就這么講完了,如果不明白請留言給我或進群(群號:241088256)交流,感謝大家關注。

 


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