ACPF UI 框架设计与基础实现
世态人情,比明月清风更饶有滋味;可作书读,可当戏看。书上的描摹,戏里的扮演,即使栩栩如生,究竟只是文艺作品;人情世态,都是天真自然的流露,往往超出情理之外,新奇得令人震惊,令人骇怪,给人以更深刻的效益,更奇妙的娱乐。惟有身处卑微的人,最有机缘看到世态人情的真相,而不是面对观众的艺术表演。
—— 杨绛
一、ACPF UI 简介
Awesome Chrome Presentation Foundation UI:简称 ACPF UI,基于 CefSharp 库进行插件化封装,它提供接口的默认实现(预设)和常用 Attribute 特性(注解),开发者可以开箱即用,无需过多配置即可使用 Web 技术快速构建一个桌面应用。
应用场景:WPF 嵌入式浏览器解决方案。
该框架的核心是:通过解耦来简化配置,降低开发难度。例如,类似于 SpringBoot 通过注解实现依赖注入和控制反转等功能,ACPF UI 提供 Attribute 实现同样的效果,从而提高应用程序的灵活性和可维护性。
如果您想使用 Vue 等前端技术栈构建 WPF 桌面应用,并且使用的是 CefSharp 实现,那么您可以考虑使用 ACPF UI。
如果该框架并不能为您提供解决方案,请考虑使用其他成熟框架例如 Electron/Tauri/WinFormium 等。
二、ACPF 插件模块
安装 CefSharp.Wpf ,这里使用的版本是 119.1.20,
三、Attribute 特性
JavascriptObjectAttribute 用于导出 .net 方法为 js 对象,其他 ConfigurationAttribute 用于注入对应的配置项,
1、JavascriptObjectAttribute
using System;namespace AwesomeChromePresentationFoundationUI.Attributes
{/// <summary>/// 该注解代表将类作为 JavascriptObject 导出/// </summary>[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]public class JavascriptObjectAttribute : Attribute{/// <summary>/// 名字,通常为驼峰命名/// </summary>public string Name { get; set; }}
}
2、BrowserConfigurationAttribute
using System;namespace AwesomeChromePresentationFoundationUI.Attributes
{/// <summary>/// 自定义 Browser 配置注解/// </summary>[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]public class BrowserConfigurationAttribute : Attribute{}
}
3、CefConfigurationAttribute
using System;namespace AwesomeChromePresentationFoundationUI.Attributes
{/// <summary>/// 自定义 Cef 配置注解/// </summary>[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]public class CefConfigurationAttribute : Attribute{}
}
4、MainViewConfigurationAttribute
using System;namespace AwesomeChromePresentationFoundationUI.Attributes
{/// <summary>/// 自定义 BaseForm 配置注解/// </summary>[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]public class MainViewConfigurationAttribute : Attribute{}
}
四、Reflections 反射
AttributeUtil 用于扫描自定义 Attribute 的类,
1、AttributeUtil
using System;
using System.Linq;
using System.Reflection;namespace AwesomeChromePresentationFoundationUI.Reflections
{public class AttributeUtil{/// <summary>/// 找到第一个带注解的类/// </summary>/// <typeparam name="T"></typeparam>/// <returns></returns>public static Type FindFirstAnnotatedClass<T>() where T : Attribute{return AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.IsClass && t.GetCustomAttribute<T>() != null).FirstOrDefault();}/// <summary>/// 找到第一个带注解的类/// </summary>/// <typeparam name="T"></typeparam>/// <typeparam name="S">继承自S类</typeparam>/// <returns></returns>public static Type FindFirstAnnotatedClass<T, S>() where T : Attribute{return AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.IsClass && t.GetCustomAttribute<T>() != null && typeof(S).IsAssignableFrom(t)).FirstOrDefault();}}
}
五、Interfaces 接口
提供接口,引导自定义接口实现,
1、IBrowserConfiger
using AwesomeChromePresentationFoundationUI.Configs;namespace AwesomeChromePresentationFoundationUI.Interfaces
{public interface IBrowserConfiger{BrowserConfiguration CreateCustomBrowserConfiguration();}
}
2、ICefConfiger
using AwesomeChromePresentationFoundationUI.Configs;namespace AwesomeChromePresentationFoundationUI.Interfaces
{public interface ICefConfiger{CefConfiguration CreateCustomCefConfiguration();}
}
3、IMainViewConfiger
using AwesomeChromePresentationFoundationUI.Configs;namespace AwesomeChromePresentationFoundationUI.Interfaces
{public interface IMainViewConfiger{MainViewConfiguration CreateCustomBaseFormConfiguration();}
}
4、IConfigurationExecuter
namespace AwesomeChromePresentationFoundationUI.Interfaces
{public interface IConfigurationExecuter{void Execute();}
}
5、CefConfigurationExecuter
using AwesomeChromePresentationFoundationUI.Configs;
using CefSharp;namespace AwesomeChromePresentationFoundationUI.Interfaces.Implements
{public class CefConfigurationExecuter : IConfigurationExecuter{public void Execute(){CefConfiguration configuration = DefaultICefConfiger.CreateCustomCefConfiguration();Cef.Initialize(configuration.CefSettings,performDependencyCheck: configuration.PerformDependencyCheck,browserProcessHandler: configuration.BrowserProcessHandler);}}
}
六、Configs 配置
提取一些必要配置项并设置默认值,
1、BrowserConfiguration
using AwesomeChromePresentationFoundationUI.Constants;namespace AwesomeChromePresentationFoundationUI.Configs
{public class BrowserConfiguration{/// <summary>/// 默认编码/// </summary>public string DefaultEncoding { get; set; }/// <summary>/// 加载 URL/// </summary>public string HomeUrl { get; set; }public BrowserConfiguration(){this.DefaultEncoding = SystemConstant.BROWSER_DEFAULT_ENCODING;this.HomeUrl = SystemConstant.BROWSER_DEFAULT_LOAD_URL;}}
}
2、CefConfiguration
using CefSharp;
using CefSharp.SchemeHandler;
using CefSharp.Wpf;
using System;
using System.IO;namespace AwesomeChromePresentationFoundationUI.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(){Locale = "zh-CN",// Increase the log severity so CEF outputs detailed information, useful for debuggingLogSeverity = LogSeverity.Verbose,//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist dataCachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache")};//Example of setting a command line argument//Enables WebRTC// - CEF Doesn't currently support permissions on a per browser basis see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access// - CEF Doesn't currently support displaying a UI for media access permissions////NOTE: WebRTC Device Id's aren't persisted as they are in Chrome see https://bitbucket.org/chromiumembedded/cef/issues/2064/persist-webrtc-deviceids-across-restartsettings.CefCommandLineArgs.Add("enable-media-stream");//https://peter.sh/experiments/chromium-command-line-switches/#use-fake-ui-for-media-streamsettings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");//For screen sharing add (see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access#comment-58677180)settings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");//Disable GPU Accelerationsettings.CefCommandLineArgs.Add("disable-gpu");// Don't use a proxy server, always make direct connections. Overrides any other proxy server flags that are passed.// Slightly improves Cef initialize time as it won't attempt to resolve a proxysettings.CefCommandLineArgs.Add("no-proxy-server");settings.RegisterScheme(new CefCustomScheme{SchemeName = "http",DomainName = "yushanma.acpf",SchemeHandlerFactory = new FolderSchemeHandlerFactory(rootFolder: this._defaultFrontendFolderPath,hostName: "yushanma.acpf", //Optional param no hostname/domain checking if nulldefaultPage: "index.html") //Optional param will default to index.html});this.CefSettings = settings;this.PerformDependencyCheck = true;this.BrowserProcessHandler = null;}/// <summary>/// 创建默认前端文件夹/// </summary>private void CreateDefaultFrontendFolderPath(){string frontendFolderPath = Path.Combine(Directory.GetCurrentDirectory(), "Frontend");if (!Directory.Exists(frontendFolderPath)){Directory.CreateDirectory(frontendFolderPath);