Skip to content

Commit fe86eb4

Browse files
committed
📖 中文文档
1 parent 0749d8d commit fe86eb4

File tree

63 files changed

+3361
-14
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+3361
-14
lines changed

README.zh-CN.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<p align="center">
2+
<img src="./artworks/WinFormiumLogo.png" width="144" />
3+
</p>
4+
<h1 align="center">NanUI 项目</h1>
5+
<p align="center"><strong>用 HTML, CSS 和 JavaScript 轻松构建功能强大的 WinForm 应用程序。</strong></p>
6+
7+
# NanUI
8+
9+
Click [[Here]](https://github.com/XuanchenLin/NanUI) to see the English version.
10+
11+
![GitHub](https://img.shields.io/github/license/XuanchenLin/NanUI)
12+
[![Build](https://github.com/XuanchenLin/NanUI/actions/workflows/main.yml/badge.svg)](https://github.com/XuanchenLin/NanUI/actions/workflows/main.yml)
13+
![Nuget](https://img.shields.io/nuget/v/NetDimension.NanUI)
14+
![Nuget](https://img.shields.io/nuget/dt/NetDimension.NanUI)
15+
16+
## ⭐ 关于
17+
18+
NanUI 是 .NET 平台上的一个开源框架,用于使用 HTML5、CSS3 和 JavaScript 创建 WinForm 应用程序的用户界面。 它基于 [Xilium.CefGlue](https://bitbucket.org/xilium/xilium.cefglue/wiki/Home) 项目,该项目是 [Chromium Embedded Framework (CEF)](https://bitbucket.org/chromiumembedded/cef) 的 .NET 实现。
19+
20+
如果您正在寻找一个用于创建具有现代用户界面的 WinForm 应用程序的框架,NanUI 是一个不错的选择。 您可以使用 HTML、CSS 和 JavaScript 创建用户界面,并使用 C# 编写应用程序的业务逻辑。
21+
22+
**如果您喜欢 👍,请给 NanUI 项目一颗星 ⭐。**
23+
24+
如果这个项目对你有帮助,请考虑资助它。
25+
26+
[![支付宝](https://img.shields.io/badge/%E6%8D%90%E8%B5%A0-%E6%94%AF%E4%BB%98%E5%AE%9D-blue)](docs/assets/qrcode.png)
27+
[![微信](https://img.shields.io/badge/%E6%8D%90%E8%B5%A0-%E5%BE%AE%E4%BF%A1-Green)](docs/assets/qrcode.png)
28+
29+
## ✨ 推荐给对 WebView2 感兴趣的开发者
30+
31+
如果您觉得基于 CEF 的 NanUI 过重,那么您现在就可以尝试 **WinFormedge** 项目。它是一个基于 [WebView2](https://learn.microsoft.com/en-us/microsoft-edge/webview2/) 作为内核的、轻量级的 WinForm 框架。适用于那些不想集成 libcef 的开发者,使用 WinFormege 并配合 Windows 系统自带的 WebView2 将非常有效地降低应用程序发布包的大小。
32+
33+
经过测试使用 .NET 8.0 x64 + WinFormedge 应用打包使用 ZIP 压缩后大小仅 36MB,而使用 NanUI 打包的应用程序压缩后至少 125M。
34+
35+
但需要注意的是使用 WinFormedge 创建的应用程序只能在 Windows 10 和 Windows 11 上运行,而 NanUI 支持 Windows 7 SP1 及更高版本。
36+
37+
- GitHub - [WinFormedge](https://github.com/XuanchenLin/WinFormedge)
38+
- Gitee - [WinFormedge](https://gitee.com/linxuanchen/WinFormedge)
39+
40+
## 🖥️ 环境要求
41+
42+
**开发环境**
43+
44+
- .NET Framework 4.6.2 或更高版本 / .NET 6.0 或更高版本
45+
- Visual Studio 2019 或更高版本(强烈建议使用 VS2022)
46+
47+
**部署环境**
48+
49+
- Microsoft Windows 7 Service Pack 1 或更高版本
50+
- .Net Framework 4.6.2 或更高版本
51+
- .NET 6.0 需要 Windows 7 Service Pack 1 或更高版本
52+
- .NET 7.0/8.0/9.0 需要 Windows 10 或 Windows 11
53+
54+
这是一个 **仅限 Windows** 的框架,所以它目前不能在 Linux 或者 MacOS 环境运行。
55+
56+
支持的最低 Windows 版本是 Windows 7 Service Pack 1,并且 Windows 7 不支持某些功能(例如 DirectComposition 离屏渲染)。
57+
58+
## 🧰 入门
59+
60+
按照以下步骤即可创建一个简单的 NanUI 应用程序:
61+
62+
**1. 通过默认模板创建一个 WinForm 应用程序。**
63+
64+
**2. 安装 NanUI NuGet 包**
65+
66+
打开 NuGet 包管理器来安装或使用 NuGet 包管理器控制台,然后运行以下命令来安装 WinFormium nuget 包:
67+
68+
```powershell
69+
PM> Install-Package NetDimension.NanUI
70+
```
71+
72+
安装 NanUI 所依赖的 Chromium Embedded Framework 依赖项:
73+
74+
```powershell
75+
PM> Install-Package NetDimension.NanUI.Runtime
76+
```
77+
78+
CEF 运行库巨大,再加上众所周知的原因,中国内地玩家请自行设置 NuGet 使用国内镜像。
79+
80+
- **Azure CDN** - https://nuget.cdn.azure.cn/v3/index.json
81+
- **华为云** - https://repo.huaweicloud.com/repository/nuget/v3/index.json
82+
83+
**3. 一个基本的 NanUI 应用程序需要以下代码:**
84+
85+
按如下示例修改 **Program.cs** 文件中的代码:
86+
87+
```csharp
88+
using NetDimension.NanUI;
89+
90+
class Program
91+
{
92+
[STAThread]
93+
static void Main(string[] args)
94+
{
95+
var builder = NanUIApp.CreateBuilder();
96+
97+
builder.UseNanUIApp<MyApp>();
98+
99+
var app = builder.Build();
100+
101+
app.Run();
102+
}
103+
}
104+
```
105+
106+
创建一个类继承 **AppStartup** 来配置应用程序:
107+
108+
```csharp
109+
using NetDimension.NanUI;
110+
111+
class MyAPP : AppStartup
112+
{
113+
protected override MainWindowCreationAction? UseMainWindow(MainWindowOptions opts)
114+
{
115+
// 设置应用程序的主窗体
116+
return opts.UseMainFormium<MyWindow>();
117+
}
118+
119+
protected override void ProgramMain(string[] args)
120+
{
121+
// Main函数中的代码应该在这里,该函数只在主进程中运行。这样可以防止子进程运行一些不正确的初始化代码。
122+
ApplicationConfiguration.Initialize();
123+
}
124+
125+
protected override void ConfigurationChromiumEmbedded(ChromiumEnvironmentBuiler cef)
126+
{
127+
// 在此处配置 Chromium Embedded Framwork
128+
}
129+
130+
protected override void ConfigureServices(IServiceCollection services)
131+
{
132+
// 在这里配置该应用程序的服务
133+
}
134+
}
135+
```
136+
137+
创建一个类实现 **Formium**,用于配置应用程序的主窗口:
138+
139+
```csharp
140+
using NetDimension.NanUI;
141+
using NetDimension.NanUI.Forms;
142+
143+
class MyWindow : Formium
144+
{
145+
public MyWindow()
146+
{
147+
Url = "https://www.google.com";
148+
}
149+
150+
protected override FormStyle ConfigureWindowStyle(WindowStyleBuilder builder)
151+
{
152+
// 此处配置窗口的样式和属性,或留空以使用默认样式
153+
154+
var style = builder.UseSystemForm();
155+
156+
style.TitleBar = false;
157+
158+
style.DefaultAppTitle = "My first WinFomrim app";
159+
160+
return style;
161+
}
162+
}
163+
```
164+
165+
**4. 生成并运行你的第一个 NanUI 应用程序**
166+
167+
## 📖 文档
168+
169+
有关更多信息,请参阅 - [文档](docs/README.md)
170+
171+
## 🤖 示例代码
172+
173+
- [Minimal WinFormium App](./examples/MinimalWinFormiumApp) - 介绍 NanUI 的基本用法。
174+
175+
## 🔗 第三方库引用和工具集
176+
177+
- CEF - [https://bitbucket.org/chromiumembedded/cef](https://bitbucket.org/chromiumembedded/cef)
178+
- Xilium.CefGlue - [https://gitlab.com/xiliumhq/chromiumembedded/cefglue](https://gitlab.com/xiliumhq/chromiumembedded/cefglue)
179+
- Vanara.Library - [https://github.com/dahall/Vanara/](https://github.com/dahall/Vanara/)
180+
- Vortice.Windows - [https://github.com/amerkoleci/Vortice.Windows](https://github.com/amerkoleci/Vortice.Windows)
181+
- SkiaSharp - [https://github.com/mono/SkiaSharp](https://github.com/mono/SkiaSharp)
182+
- React - [https://github.com/facebook/react](https://github.com/facebook/react)
183+
- React-Router - [https://github.com/remix-run/react-router](https://github.com/remix-run/react-router)
184+
- Vite - [https://github.com/vitejs/vite](https://github.com/vitejs/vite)
185+
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# 在 JavaScript 中注册 .NET 映射对象
2+
3+
## 概述
4+
5+
WinFormium 提供给了在前端 JavaScript 环境中注册 .NET 对象的功能。您可以使用 `RegisterJavaScriptObject` 方法来注册一个 .NET 对象的映射,然后在 JavaScript 的 `window.external` 对象中找到并使用您注册的对象。
6+
7+
.NET 映射对象其实就是 `JavaScriptValue` 衍生类 `JavaScriptObject` 的具体实现。您通过创建一个普通的 JavaScript 对象类型,并为该类型绑定 .NET 的属性和方法,然后将该对象注册到 JavaScript 环境中,就可以在 JavaScript 中使用该对象了。
8+
9+
## 创建映射对象
10+
11+
首先,您需要创建一个 `JavaScriptObject` 对象类,该类将作为您在 JavaScript 中注册的对象类型。
12+
13+
```csharp
14+
var obj = new JavaScriptObject
15+
{
16+
{ "name", "linxuanchen" },
17+
{ "age", 38 }
18+
};
19+
```
20+
21+
**字段与属性**
22+
23+
在上面的代码中,我们创建了一个 `JavaScriptObject` 类型的对象,并为其添加了两个**字段**,分别是 `name``age`
24+
25+
下面,我们继续为该对象添加一些**属性**。需要注意`字段``属性`的区别,字段和属性的区别在于,字段是一个变量,而属性是一个方法。在 JavaScript 环境中读写字段和属性从感知上是没有区别的,但字段是对象创建时就固有的数据,因此它不会联动对象内部的方法。而属性正如前面所述,是一个方法,它在读写时都将触发该属性的 `get``set` 方法,因此,如果这个属性与 .NET 对象关联后,那么在 JavaScript 环境中读写该属性时,将会联动 .NET 的方法逻辑。
26+
27+
定义属性使用 `JavaScriptObject``DefineProperty` 方法,该方法接受两个参数,第一个参数是 `Func<JavaScriptValue>` 委托类型,对应属性的 `get` 方法,第二个参数是 `Action<JavaScriptValue>` 委托类型,对应属性的 `set` 方法。
28+
29+
```csharp
30+
// 定义一个只读属性
31+
obj.DefineProperty("now", () => DateTime.Now);
32+
33+
// 定义一个可读写属性
34+
obj.DefineProperty("title", () => AppTitle, v => AppTitle = ((string?)v ?? string.Empty));
35+
```
36+
37+
上面的代码示例中,我们定义了两个属性,一个是只读属性 `now`,一个是可读写属性 `title`。当前端 JavaScript 代码读取 `now` 属性时,将会执行 `() => DateTime.Now` 方法。当前端 JavaScript 代码读取 `title` 属性时,将返回当前 Formium 对象的 `AppTitle` 属性值,而写入 `title` 属性时将会执行 `v => AppTitle = ((string?)v ?? string.Empty)` 方法,这个方法会接收到来自 JavaScript 环境的赋值,并把该值付给 .NET 环境中的 `AppTitle` 属性。
38+
39+
**同步方法**
40+
41+
接下来,我们为该对象添加一个同步方法。`JavaScriptObject``Add` 方法允许您向该对象添加很多数据类型,添加同步方法使用的是 `Add` 方法的一个重载方法 `Add(string,Func<JavaScriptArray,JavaScriptValue>)`。该方法接受二个 `Func<JavaScriptArray,JavaScriptValue>` 委托类型,该委托类型的参数是一个 `JavaScriptArray` 类型的对象,该对象是一个数组,它包含了来自 JavaScript 环境的参数,如果您熟悉 JavaScript 那么这个参数相当于 JS 方法的 `arguments` 参数。该委托类型的返回值是一个 `JavaScriptValue` 类型的对象,该对象是一个返回值,它将返回给 JavaScript 环境。
42+
43+
```csharp
44+
obj.Add("hello", args=>{
45+
var retval = string.Join(",", args.Select(x => x.GetString()));
46+
47+
InvokeOnUIThread(() =>
48+
{
49+
MessageBox.Show(this, $"Caller arguments:{retval}", "JS Value");
50+
});
51+
52+
return "C# Value";
53+
})
54+
```
55+
56+
**异步方法**
57+
58+
下面,我们在为该对象添加一个异步方法。添加异步方法使用的是 `Add` 方法的另一个重载方法 `Add(string, Action<JavaScriptArray,JavaScriptPromise>)`。该方法接受二个 `Action<JavaScriptArray,JavaScriptPromise>` 委托类型,该委托类型的参数是一个 `JavaScriptArray` 类型的对象,该对象是一个数组,它包含了来自 JavaScript 环境的参数,如果您熟悉 JavaScript 那么这个参数相当于 JS 方法的 `arguments` 参数。该委托类型的第二个参数是一个 `JavaScriptPromise` 类型的对象,该对象提供了 `Resolve``Reject` 两个方法,您可以使用这两个方法来返回异步方法的执行结果。
59+
60+
异步方法在 JavaScript 环境执行后将立即返回一个 `Promise` 对象,您可以使用 `then``catch``finally` 方法来获取异步方法的执行结果。当然,如果您更喜欢 try...catch...finally 的写法也是可以接受的。
61+
62+
```csharp
63+
obj.Add("async", async (args, promise) =>
64+
{
65+
var retval = string.Join(",", args.Select(x => x.GetString()));
66+
67+
var rnd = new Random();
68+
69+
var x = rnd.Next(1, 6);
70+
71+
await Task.Delay(x * 500);
72+
73+
if (x == 5)
74+
{
75+
promise.Reject("Rejected by random.");
76+
}
77+
else
78+
{
79+
promise.Resolve(new string(retval.Reverse().ToArray()) ?? "No argument.");
80+
}
81+
});
82+
```
83+
84+
可以看到上面的这个 `async` 方法在委托参数上使用了 async 关键字,这个方法是一个异步方法,它将在异步执行完成后调用 `promise.Resolve``promise.Reject` 方法来返回异步方法的执行结果。在示例中,使用随机数模拟了延迟执行,然后随机返回一个 `Resolve``Reject` 结果。
85+
86+
## 注册映射对象
87+
88+
在创建好映射对象后,我们需要将其注册到 JavaScript 环境中,这样前端 JavaScript 代码才能够访问到该对象。在调用 `RegisterJavaScriptObject` 方法来注册对象前,您需要先执行 `BeginRegisterJavaScriptObject` 方法来指定该对象注册的 frame,该方法返回一个句柄,您需要在注册完成后调用 `EndRegisterJavaScriptObject` 方法来结束注册。
89+
90+
```csharp
91+
var hbrjso = BeginRegisterJavaScriptObject(frame);
92+
93+
RegisterJavaScriptObject(hbrjso, "test", obj);
94+
95+
EndRegisterJavaScriptObject(hbrjso);
96+
```
97+
98+
上面的代码示例中,我们使用 `BeginRegisterJavaScriptObject` 方法来指定了注册的 frame,然后使用 `RegisterJavaScriptObject` 方法来注册了一个名为 `test` 的对象,该对象是我们前面创建的 `JavaScriptObject` 类型的对象。最后,我们使用 `EndRegisterJavaScriptObject` 方法来结束注册。
99+
100+
## JavaScriptObjectWrapper
101+
102+
为了更直观的创建映射对象,WinFormium 提供了 `JavaScriptObjectWrapper` 抽象类,您可以继承该类来创建映射对象。`JavaScriptObjectWrapper` 类提供了以下方法来简化创建映射对象的过程:
103+
104+
| 方法 | 说明 |
105+
| --------------------------------------------------------------------------------- | ---------------------- |
106+
| AddField(string,JavaScriptValue) | 为映射对象添加一个字段 |
107+
| DefineProperty(string,Func&lt;JavaScriptValue&gt;,Action&lt;JavaScriptValue&gt;?) | 为映射对象添加属性 |
108+
| AddSynchronousFunction(string,Func&lt;JavaScriptArray,JavaScriptValue&gt;) | 为映射对象添加同步方法 |
109+
| AddAsynchronousFunction(string,Action&lt;JavaScriptArray,JavaScriptPromise&gt;) | 为映射对象添加异步方法 |
110+
111+
`JavaScriptObjectWrapper` 的注册方式于普通 `JavaScriptObject` 的注册方式一致,`RegisterJavaScriptObject` 提供了一个重载方法来实现对该类型的注册。
112+
113+
## 使用映射对象
114+
115+
在注册完成后,您就可以在前端 JavaScript 代码中使用该对象了。在前端 JavaScript 代码中,您可以使用 `window.external` 对象来使用该对象。
116+
117+
```javascript
118+
console.log(window.external.test.name); // 输出:linxuanchen
119+
120+
console.log(window.external.test.now); // 输出当前时间
121+
122+
console.log(window.external.test.title); // 输出:WinFormium
123+
124+
window.external.test.title = "New Title"; // 设置标题
125+
126+
console.log(window.external.test.title); // 输出:New Title
127+
128+
window.external.test.hello("Hello", "World"); // 调用同步方法,输出:C# Value,并且弹出一个对话框
129+
130+
window.external.test
131+
.async("Hello", "World")
132+
.then((x) => console.log(x))
133+
.catch((err) => console.log(err)); // 调用异步方法,随机延时后如果成功输出:dlroW,olleH,或者输出:Rejected by random.表示随机Reject
134+
```
135+
136+
## 另请参阅
137+
138+
- [概述](./概述.md)

0 commit comments

Comments
 (0)