世态人情,比明月清风更饶有滋味;可作书读,可当戏看。书上的描摹,戏里的扮演,即使栩栩如生,究竟只是文艺作品;人情世态,都是天真自然的流露,往往超出情理之外,新奇得令人震惊,令人骇怪,给人以更深刻的效益,更奇妙的娱乐。惟有身处卑微的人,最有机缘看到世态人情的真相,而不是面对观众的艺术表演。
—— 杨绛
一、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 debugging
LogSeverity = LogSeverity.Verbose,
//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data
CachePath = 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-restart
settings.CefCommandLineArgs.Add("enable-media-stream");
//https://peter.sh/experiments/chromium-command-line-switches/#use-fake-ui-for-media-stream
settings.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 Acceleration
settings.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 proxy
settings.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 null
defaultPage: "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);