NanUI for Winform 使用示例【第二集】——做一个所见即所得的Markdown编辑器

经过了这一个多星期的调整与修复,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界面设计与接口开发(收费)。

案例展示

某聊天应用

某公司内部办公系统

时间: 2024-10-29 10:46:21

NanUI for Winform 使用示例【第二集】——做一个所见即所得的Markdown编辑器的相关文章

第二章 做一个简短的C++程序

第二章 做一个简短的C++程序 2.  输出语句的使用 endl要调用flush函数刷新缓冲区,而\n不需要调用 4.iostream与iostream.h的区别 5.  重名问题 释放命名空间的两种方法(1.所有 2局部) using namespace std; using std::endl; 本章总结: 1 //#include<iostream.h> 2 #include<iostream> //标准输入输出流,包含于std中,包含了cin.cout等关键字 3 usin

Winform窗体用对象数组做一个小项目

首先我我们看一下需求:我们要做的是显示员工信息,实现项目经理给员工评分的功能! 首先项目经理是评分的人所以没有用,因为我们自己写,评分的就是我们自己.所以我们要做的是先在vs也就是我们的环境里建一个项目,在建一个Windows窗体应用程序,然后把控件拖好! 左边是查看员工的信息窗体,右边是给员工评分的窗体: 1.我们现在开始代码部分 我们首先需要一个辅助类,在辅助类里我们要做什么呢?我们要写这些东西. public int id;        public int age;        pu

做一个所见即所得的CSS效果

style 也是标签(在非ie内核的浏览器中支持),我们将style设置成 contenteditable的时候,那么那的内容就可以编辑了.仔细的体验下,如果我们将背景修改成红色的.那么只要书写完,立马呈现.哇靠,这很方便我们以后写那种动态的编辑器效果呢,等有时间的话,我再试试.洛宁县幼儿园 提示:你可以随意改变.test_div里的CSS代码,比如把green改成blue,#666什么的. 附上我研究的代码: <!DOCTYPE HTML> <html > <head>

NanUI for Winform发布,让Winform界面设计拥有无限可能

如今,尽管WPF.UWP大行其道,大有把Winform打残干废的趋势.但是还是有那么一波顽固不化的老家伙们固守着Winform,其中就包括我. 好吧,既然都说Winform做得软件不如WPF界面美观效果绚丽,那么我们就找一个方法让Winform也拥有漂亮的界面.DevExpress和ComponentOne都是不错的选择,Telerik虽说是做Asp.net组件出生的,但是他家的UI for Winform做得也很不错.稍等,那问题来了,这些组件收费昂贵不说,而且是年付,起价都得几百美刀.对于我

验证码识别与生成类API调用的代码示例合集:六位图片验证码生成、四位图片验证码生成、简单验证码识别等

以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 六位图片验证码生成:包括纯数字.小写字母.大写字母.大小写混合.数字+小写.数字+大写.数字+大小写等情况. 四位图片验证码生成:包括纯数字.小写字母.大写字母.大小写混合.数字+小写.数字+大写.数字+大小写等情况. 简单验证码识别:验证码类型 : 数字+字母, 纯英文, 纯数字,计算题 英数_验证码识别:纯数字,纯英文,数字+英文 中英数_验证码识别:英文.数

2018 FJUT acm校赛 b题 第二集:以后我就叫你小蛤了

第二集:以后我就叫你小蛤了 TimeLimit:1000MS  MemoryLimit:128MB 64-bit integer IO format:%lld Problem Description "小蛤啊,对了以后我就叫你小蛤了,你给的题完全没有难度啊" "哼,你别得意,让你碰巧做出来了而已,接下来这题你绝对不可能做出来的,你要是做出来了balabalabala...." "行了行了,快说题目吧,我时间宝贵啊!" 小A心急如焚,想尽快知道小C

【第一季:Tomcat 8迷情】 第二集:Tomcat8在的用户安全配置

Tomcat的角色 manager-gui - Access to the HTML interface. manager-status - Access to the "Server Status" page only. manager-script - Access to the tools-friendly plain text interface that is described in this document, and to the "Server Status

Winform and WPF 第二遍双击快捷方式或应用程序打开原来的应用程序而不新建一个实例[进程通信 1]

private void Window_Loaded(object sender, RoutedEventArgs e)         {             Process[] pro = Process.GetProcesses();             int n = pro.Where(p => p.ProcessName.Equals("进程名称")).Count();             if (n > 1)             {      

影视娱乐类API调用的代码示例合集:NBA赛事、电视节目等

以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. NBA赛事:NBA篮球赛事赛程相关信息 电视节目:央视及各地卫视的电视节目时间表,包括本周及下周的电视节目内容 **API Shop(apishop.net)提供多达50款的常用第三方API,可以从github上下载代码示例合集:https://github.com/apishop/All-APIs** 以上接口均包含PHP.Python.C#和Java等四种语言