Money is not evil by itself.
Its just paper with perceived value to obtain other things we value in other ways.
If not money what is evil you may ask?
Evil is the unquenchable, obsessive and moral bending desire for more.
Evil is the bottomless,soulless and obsessive compulsive pursuit of some pot of gold at the end of some rainbow which doesnt exist.
Evil is having a price tag for your heart and soul in exchange for financial success at any cost.
Evil is trying to buy happiness, again and again until all of those fake, short lived mirages of emotions are gone.
——《Time》 MKJ
一、ACF UI 简介
是什么?
Awesome Chrome Form UI:简称 ACF UI,基于 CefSharp 库进行插件化封装,它提供接口的默认实现(预设)和常用 Attribute 特性(注解),开发者可以开箱即用,无需过多配置即可使用 Web 技术快速构建一个桌面应用。
应用场景:WinForm 嵌入式浏览器解决方案。
客户端嵌入浏览器的框架很多,首选肯定是 Electron,如果有 Rust 基础,可以考虑使用 Tauri 。在 WinForm 框架上,可以选择 WinFormium(曾用名:NanUI),一个经过了 9 年迭代的框架。
如果你想使用 Vue 等前端技术栈构建 Window 桌面应用,并且使用的是 CefSharp 实现,那么你可以考虑使用 ACF UI。
为什么?
该框架的核心是:通过解耦来简化配置,降低开发难度。类似于 SpringBoot 通过注解实现依赖注入和控制反转等功能,ACF UI 提供 Attribute 实现同样的效果,从而提高应用程序的灵活性和可维护性。
荔枝 例子:使用 Attribute
直接使用 CefSharp 导出 DotNet 方法时,除了需要编码 CommonUtil 类,还需要添加 Browser 处理逻辑,
引入 ACF UI 后,导出方法只需要加上 [JavascriptObject] 特性,框架会帮你添加 Browser 处理逻辑,
二、框架主流程 V1.0.0 设计
主流程也非常简单:
- 初始化 CEF 配置
- 初始化基础窗口
- 初始化 Browser 配置
- 关闭 CEF
三个初始化阶段的操作相同,都是通过反射机制扫描 Attribute 进行配置初始化,如果没有自定义实现配置则采用预设值。
三、框架主流程 V1.0.0 实现
1、AwesomeChromeFormUI 插件模块
配置目标平台,指定 x86 或者 x64 都可以,
删除框架生成的 Class1.cs ,新增 BaseForm.cs ,
using System.Windows.Forms;
namespace AwesomeChromeFormUI.ChromiumForms
{
public partial class BaseForm : Form
{
}
}
2、引入 CefSharp 库
CefSharp.WinForms.119.1.20
3、ControlExtensions
在 UI 线程上异步执行 Action,
using System;
using System.Windows.Forms;
namespace AwesomeChromeFormUI.CommonExtensions
{
public static class ControlExtensions
{
/// <summary>
/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
/// </summary>
/// <param name="control">the control for which the update is required</param>
/// <param name="action">action to be performed on the control</param>
public static void InvokeOnUiThreadIfRequired(this Control control, Action action)
{
//If you are planning on using a similar function in your own code then please be sure to
//have a quick read over https://stackoverflow.com/questions/1874728/avoid-calling-invoke-when-the-control-is-disposed
//No action
if (control.Disposing || control.IsDisposed || !control.IsHandleCreated)
{
return;
}
if (control.InvokeRequired)
{
control.BeginInvoke(action);
}
else
{
action.Invoke();
}
}
}
}
4、ConfigurationAttribute 配置特性
规则:ConfigurationAttribute 所标记的类只能被应用一次,并且只能应用于类的定义上,
using System;
namespace AwesomeChromeFormUI.Attributes
{
/// <summary>
/// 自定义 BaseForm 配置注解
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class BaseFormConfigurationAttribute : Attribute
{
}
}
using System;
namespace AwesomeChromeFormUI.Attributes
{
/// <summary>
/// 自定义 Cef 配置注解
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CefConfigurationAttribute : Attribute
{
}
}
using System;
namespace AwesomeChromeFormUI.Attributes
{
/// <summary>
/// 自定义 Browser 配置注解
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class BrowserConfigurationAttribute : Attribute
{
}
}
5、Configuration 配置类封装
规则:提供常见的属性作为字段,并通过无参构造函数为属性赋默认值,
using AwesomeChromeFormUI.Constants;
using System.Drawing;
using System.Windows.Forms;
namespace AwesomeChromeFormUI.Configs
{
public class BaseFormConfiguration
{
/// <summary>
/// 宽度
/// </summary>
public int Width { get; set; }
/// <summary>
/// 高度
/// </summary>
public int Height { get; set; }
/// <summary>
/// 标题文本
/// </summary>
public string Text { get; set; }
/// <summary>
/// Logo
/// </summary>
public Icon Icon { get; set; }
/// <summary>
/// 窗口边框样式
/// </summary>
public FormBorderStyle FormBorderStyle { get; set; }
/// <summary>
/// 启动位置
/// </summary>
public FormStartPosition StartPosition { get; set; }
/// <summary>
/// 窗口状态
/// </summary>
public FormWindowState WindowState { get; set; }
public BaseFormConfiguration()
{
this.Width = BaseFormConstant.DEFAULT_WIDTH;
this.Height = BaseFormConstant.DEFAULT_HEIGHT;
this.Text = BaseFormConstant.DEFAULT_TITLE;
this.Icon = new Icon(BaseFormConstant.DEFAULT_ICON);
this.StartPosition = FormStartPosition.CenterScreen;
this.FormBorderStyle = FormBorderStyle.Sizable;
this.WindowState = FormWindowState.Normal;
}
}
}
using CefSharp;
using CefSharp.SchemeHandler;
using CefSharp.WinForms;
using System;
using System.IO;
namespace AwesomeChromeFormUI.Configs
{
public class CefConfiguration
{
/// <summary>
/// 默认前端文件夹
/// </summary>
private string _defaultFrontendFolderPath;
public CefSettingsBase CefSettings { get; set; }
public bool PerformDependencyCheck { get; set; }
public IBrowserProcessHandler BrowserProcessHandler { get; set; }
public CefConfiguration()
{
CreateDefaultFrontendFolderPath();
// Pseudo code; you probably need more in your CefSettings also.
var settings = new CefSettings()
{