【请注意:此文已过期,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 .NET Winform的稳定版已经发布。应广大群友的要求,现已将NanUI的全部代码开源。

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

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


如果你喜欢NanUI项目,你可以参与到NanUI的开发中来,当然你也可以更直接了当的支持我的工作,使用支付宝或微信扫描下面二维码请我喝一杯热腾腾的咖啡。

支付宝转账

微信转账


另外,打个广告,承接NanUI界面设计与接口开发(收费)。

案例展示

某聊天应用

某公司内部办公系统

NanUI for Winform 使用示例【第一集】——山寨个代码编辑器的更多相关文章

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

    经过了这一个多星期的调整与修复,NanUI for .NET Winform的稳定版已经发布.应广大群友的要求,现已将NanUI的全部代码开源. GitHub: https://github.com/ ...

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

    如今,尽管WPF.UWP大行其道,大有把Winform打残干废的趋势.但是还是有那么一波顽固不化的老家伙们固守着Winform,其中就包括我. 好吧,既然都说Winform做得软件不如WPF界面美观效 ...

  3. Windows Phone开发(43):推送通知第一集——Toast推送

    原文:Windows Phone开发(43):推送通知第一集--Toast推送 好像有好几天没更新了,抱歉抱歉,最近"光荣"地失业,先是忙于寻找新去处,唉,暂时没有下文.而后又有一 ...

  4. 天气类API调用的代码示例合集:全国天气预报、实时空气质量数据查询、PM2.5空气质量指数等

    以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 全国天气预报:数据来自国家气象局,可根据地名.经纬度GPS.IP查 ...

  5. 位置信息类API调用的代码示例合集:中国省市区查询、经纬度地址转换、POI检索等

    以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 中国省市区查询:2017最新中国省市区地址 经纬度地址转换:经纬度 ...

  6. 通讯服务类API调用的代码示例合集:短信服务、手机号归属地查询、电信基站查询等

    以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 短信服务:通知类和验证码短信,全国三网合一通道,5秒内到达,费用低 ...

  7. 生活常用类API调用的代码示例合集:邮编查询、今日热门新闻查询、区号查询等

    以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 邮编查询:通过邮编查询地名:通过地名查询邮编 今日热门新闻查询:提 ...

  8. 开发工具类API调用的代码示例合集:六位图片验证码生成、四位图片验证码生成、简单验证码识别等

    以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 六位图片验证码生成:包括纯数字.小写字母.大写字母.大小写混合.数 ...

  9. 出行服务类API调用的代码示例合集:长途汽车查询、车型大全、火车票查询等

    以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 长途汽车查询:全国主要城市的长途汽车时刻查询,汽车站查询 车型大全 ...

随机推荐

  1. 一个新人如何学习在大型系统中添加新功能和Debug

    文章背景: 今年七月份正式入职,公司主营ERP软件,楼主所在的组主要负责二次开发,使用的语言是Java. 什么叫二次开发呢?ERP软件的客户都是企业.而这些企业之间的情况都有所不同,一套标准版本的企业 ...

  2. Ubuntu 16 安装ElasticSearch

    首先安装Java,参见博客:http://www.cnblogs.com/1zhk/p/6056406.html 下载ElasticSearch安装包 curl -L -O https://artif ...

  3. 《Entity Framework 6 Recipes》中文翻译系列 (43) ------ 第八章 POCO之使用POCO加载实体

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-2  使用POCO加载关联实体 问题 你想使用POCO预先加载关联实体. 解决方 ...

  4. KnockoutJS 3.X API 第六章 组件(4) 自定义元素

    自定义元素提供了一种将组件注入视图的方便方法. 本节目录 介绍 例子 传递参数 父组件和子组件之间的通信 传递监控属性的表达式 将标记传递到组件中 控制自定义元素标记名称 注册自定义元素 备注1:将自 ...

  5. Android 文件读写

    一.分类 文件读写作为Android四大数据存储方式之一,又分为内部存储和外部存储两种: (1)内部存储(Internal storage): 总是可用. 文件默认情况存储在/data/data/包名 ...

  6. Android音频开发之AudioTrack实时播放

    前言: 其实在Android中录音可以用MediaRecord录音,操作比较简单.但是不能对音频进行处理.考虑到项目中做的是实时语音只能选择AudioRecord进行录音.然后实时播放也只能采用Aud ...

  7. Objective-C中的Block回调模式

    在前面的博客中提到了Block的概念和使用方法,个人感觉Block最爽的用法莫过于在回调时用block.感觉比委托回调和目标方法回调用着要顺手,好不好用还得读者亲自用一下才知道.如果 读者之前用过SS ...

  8. Objective-C中的单例模式

    ​    ​单例模式算是设计模式中比较简单的一种吧,设计模式不是只针对某种编程语言,在C++, Java, PHP等其他OOP语言也有设计模式,笔者初接触设计模式是通过<漫谈设计模式>了解 ...

  9. GROUP BY的扩展

    GROUP BY的扩展主要包括ROLLUP,CUBE,GROUPING SETS三种形式. ROLLUP rollup相对于简单的分组合计增加了小计和合计,解释起来会比较抽象,下面我们来看看具体事例. ...

  10. 6-tips-for-managing-property-files-with-spring--转

    原文地址:http://www.summa.com/blog/2009/04/20/6-tips-for-managing-property-files-with-spring What could ...