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('.') == )
{
ext = ext.Substring();
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 > )
{ 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 Winform发布,让Winform界面设计拥有无限可能
- NanUI for Winform 使用示例【第一集】——山寨个代码编辑器
- NanUI for Winform 使用示例【第二集】——做一个所见即所得的Markdown编辑器
经过了这一个多星期的调整与修复,NanUI for .NET Winform的稳定版已经发布。应广大群友的要求,现已将NanUI的全部代码开源。
GitHub: https://github.com/NetDimension/NanUI
Release: https://github.com/NetDimension/NanUI/releases
如果你喜欢NanUI项目,你可以参与到NanUI的开发中来,当然你也可以更直接了当的支持我的工作,使用支付宝或微信扫描下面二维码请我喝一杯热腾腾的咖啡。
支付宝转账
微信转账
另外,打个广告,承接NanUI界面设计与接口开发(收费)。
案例展示
某聊天应用
某公司内部办公系统
NanUI for Winform 使用示例【第一集】——山寨个代码编辑器的更多相关文章
- NanUI for Winform 使用示例【第二集】——做一个所见即所得的Markdown编辑器
经过了这一个多星期的调整与修复,NanUI for .NET Winform的稳定版已经发布.应广大群友的要求,现已将NanUI的全部代码开源. GitHub: https://github.com/ ...
- NanUI for Winform发布,让Winform界面设计拥有无限可能
如今,尽管WPF.UWP大行其道,大有把Winform打残干废的趋势.但是还是有那么一波顽固不化的老家伙们固守着Winform,其中就包括我. 好吧,既然都说Winform做得软件不如WPF界面美观效 ...
- Windows Phone开发(43):推送通知第一集——Toast推送
原文:Windows Phone开发(43):推送通知第一集--Toast推送 好像有好几天没更新了,抱歉抱歉,最近"光荣"地失业,先是忙于寻找新去处,唉,暂时没有下文.而后又有一 ...
- 天气类API调用的代码示例合集:全国天气预报、实时空气质量数据查询、PM2.5空气质量指数等
以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 全国天气预报:数据来自国家气象局,可根据地名.经纬度GPS.IP查 ...
- 位置信息类API调用的代码示例合集:中国省市区查询、经纬度地址转换、POI检索等
以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 中国省市区查询:2017最新中国省市区地址 经纬度地址转换:经纬度 ...
- 通讯服务类API调用的代码示例合集:短信服务、手机号归属地查询、电信基站查询等
以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 短信服务:通知类和验证码短信,全国三网合一通道,5秒内到达,费用低 ...
- 生活常用类API调用的代码示例合集:邮编查询、今日热门新闻查询、区号查询等
以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 邮编查询:通过邮编查询地名:通过地名查询邮编 今日热门新闻查询:提 ...
- 开发工具类API调用的代码示例合集:六位图片验证码生成、四位图片验证码生成、简单验证码识别等
以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 六位图片验证码生成:包括纯数字.小写字母.大写字母.大小写混合.数 ...
- 出行服务类API调用的代码示例合集:长途汽车查询、车型大全、火车票查询等
以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 长途汽车查询:全国主要城市的长途汽车时刻查询,汽车站查询 车型大全 ...
随机推荐
- ASP.Net WebForm温故知新学习笔记:二、ViewState与UpdatePanel探秘
开篇:经历了上一篇<aspx与服务器控件探秘>后,我们了解了aspx和服务器控件背后的故事.这篇我们开始走进WebForm状态保持的一大法宝—ViewState,对其刨根究底一下.然后,再 ...
- Net作业调度(三) — Quartz.Net进阶
介绍 前面介绍Quartz.Net的基本用法,但在实际应用中,往往有更多的特性需求,比如记录job执行的执行历史,发邮件等. 阅读目录 Quartz.Net插件 TriggerListener,Job ...
- [公告]Senparc.Weixin.MP v14.2.1 升级说明
在Senparc.Weixin.MP v14.2.1中,所有Senparc.Weixin.MP下的Container,命名空间已经从 Senparc.Weixin.MP.CommonAPIs 改为了 ...
- 2013 duilib入门简明教程 -- 自绘控件 (15)
在[2013 duilib入门简明教程 -- 复杂控件介绍 (13)]中虽然介绍了界面设计器上的所有控件,但是还有一些控件并没有被放到界面设计器上,还有一些常用控件duilib并没有提供(比如 ...
- show master/slave status求根溯源
show master/slave status分别是查看主数据库以及副数据库的状态,是一种能查看主从复制运行情况的方式. 这里仅仅讨论linux下的nysql5.7.13版本的执行情况 一.show ...
- 精通visual c++指纹模式识别系统算法及实现
通过学习,掌握以下几个问题: 1.核心算法,并且向GVF衍生: 2.核心库封装的方法 2016年11月16日06:52:51 昨日实现了梯度场和频率场的计算.最大的感觉就是建立基础代码库的重要性. 如 ...
- springboot学习笔记(一)
一.什么是SpringBoot 描述:Spring Boot是Spring社区发布的一个开源项目,旨在帮助开发者快速并且更简单的构建项目.大多数SpringBoot项目只需要很少的配置文件.二.Spr ...
- 编译opengl编程指南第八版示例代码通过
最近在编译opengl编程指南第八版的示例代码,如下 #include <iostream> #include "vgl.h" #include "LoadS ...
- Vue.js学习笔记(4)
分享一段将 json数组数据以 csv格式导出的代码: html: <button class="btn btn-danger" @click='exportData'&g ...
- MVC4做网站后台:用户管理 —用户
这块进行用户管理,可以浏览.查询已注册的用户,修改用户资料,删除用户等.没有做添加用户,不知是否必要.列表页还是使用easyui的datagrid.这个思路跟用户组的方式差不多. 1.接口Interf ...