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界面设计与接口开发(收费)。

案例展示

某聊天应用

某公司内部办公系统

posted @ 2016-06-13 00:03 林选臣 阅读(...) 评论(...) 编辑 收藏