【请注意:此文已过期,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. Google分布式构建软件之三:分布式执行构建步骤

    注:本文英文原文在google开发者工具组的博客上[需要FQ],以下是我的翻译,欢迎转载,但请尊重作者版权,注名原文地址. 之前两篇文章分别介绍了Google 分布式软件构建系统Blaze相关的为了提 ...

  2. MySQL 正则表达式

    介绍 正则表达式用来描述或者匹配符合规则的字符串.它的用法和like比较相似,但是它又比like更强大,能够实现一些很特殊的规则匹配:正则表达式需要使用REGEXP命令,匹配上返回"1&qu ...

  3. 《Hive编程指南》—— 读后总结

    知识图谱

  4. mybatis的一些小总结

    好长时间没用mybatis了,现在项目忽然用mybatis,用的过程中出现了些问题,虽然解决了,不过这花的时间有些长了.总结用的过程中出现的一些问题 1.mapper.xml 之前一直用的自动生成,现 ...

  5. 路由转发过程的IP及MAC地址变化

    A-----(B1-B2)-----(C1-C2)-------E就假设拓扑图是这个样子吧,B1和B2是路由器B上的两个接口,C1和C2是路由器C上的两个接口,A和E是PC,由主机A向主机E发送数据包 ...

  6. MongoDB 文档的删除操作

    在db中删除数据是十分危险的事,建议使用logic delete,即在doc中增加一个field:IsDeleted,将其设置为1,表示该doc在逻辑上被删除,这种workaround将delete操 ...

  7. 如何转型成为SQL Server DBA

        本篇PPT是我在2015 MVP OPEN Day Comunity Camp上分享的课程.之所以选择这个主题是因为有很多人曾经问过这方面的问题,而与之相关的主题却少之又少,因此我希望将自己的 ...

  8. Python第一天 - 函数

    ---恢复内容开始--- (一)定义一个函数 def 函数名(参数): 函数体 return 返回值 例: def mySum(x , y): return int(x)+int(y)print(my ...

  9. Android自定义Dialog(美化界面)

    前言:在做项目的时候,发现dialog界面太丑陋,从csdn上下载了一份自定义dialog的源码,在他的基础上对界面进行美化...有需要的朋友可以直接拿走 效果图如下: 主要代码: /** * 自定义 ...

  10. EntityFramework 分页问题探讨之 OrderBy

    应用场景 最近被应用程序中页面加载慢的问题所折磨,看似容易的问题,其实并不容易(已经持续两天时间了),经过"侦查",发现了两个"嫌疑犯": EntityFram ...