Avalonia跨平台框架列出了以下几种嵌入网页的方式:(来源)
Web Browsers
- CefGlue - .NET/Mono binding for The Chromium Embedded Framework (CEF).
- CefNet - .NET binding for the Chromium Embedded Framework (CEF).
- OutSystems WebView - Fully featured Avalonia WebView Control.
平时工程中常用的网页嵌入框架是CefSharp,因此优先考虑Cef方式。其中CefNet已经许久没有更新,于是决定选用CefGlue.
CefGlue的仓库链接:CefGlue,仓库里包含了WPF和Avalonia两种版本的Demo.
CefGlue支持的平台和架构如下图
引用CefGlue可以在Nuget程序包管理器中直接下载程序包:
直接引用dll的话主要用到这四个,没有用到WPF相关的库:
由于我们项目中需要用到浏览器透明效果,这一功能需要开启离屏渲染,我下载源码对Demo进行调试时发现CefGlue官方代码中离屏渲染的焦点获取有些问题,导致无法使用键盘输入,所以对源码进行了一点修改,只有一行代码(浏览器所在控件没有设置为可获取焦点,加上就可以了):
如果要用输入法,还需要实现一个TextInputMethodClient,并给_control添加TextInputMethodClientRequestedEvent事件的响应,这样Avalonia的输入法管理器InputMethodManager在拿到Client之后才会唤起输入法。现在这个写法是始终在浏览器界面上启用输入法的,输入密码时也是,所以还需要改进。
namespace Xilium.CefGlue.Avalonia.Platform
{
internal class OsrInputMethodClient : TextInputMethodClient
{
private ITextInputMethodImpl _im;
private Visual _presenter;
public override Visual TextViewVisual => _presenter!;
public override bool SupportsPreedit => true;
public override bool SupportsSurroundingText => true;
public override string SurroundingText => "";
public override Rect CursorRectangle => default;
public override TextSelection Selection { get; set; }
public void SetPresenter(Visual? presenter)
{
_presenter = presenter;
if (presenter != null)
{
_im = (TopLevel.GetTopLevel(_presenter) as ITextInputMethodRoot)?.InputMethod;
}
else { _im = null; }
}
}
internal class AvaloniaControl : Common.Platform.IControl
{
// on Windows all browsers will be hosted in the same long-lived window to prevent crashes
// during browser window creation, which could occur if the hosting window was closed
private static IPlatformHandle _hostWindowPlatformHandle;
private IHandleHolder _browserView;
private readonly IAvaloniaList<Visual> _controlVisualChildren;
protected readonly Control _control;
protected readonly OsrInputMethodClient _imClient = new OsrInputMethodClient();
public event Action GotFocus;
public event Action<CefSize> SizeChanged;
public AvaloniaControl(Control control, IAvaloniaList<Visual> visualChildren)
{
_control = control;
_controlVisualChildren = visualChildren;
_control.Focusable = true;
_control.GotFocus += OnGotFocus;
_control.LostFocus += OnLostFocus;
_control.LayoutUpdated += OnLayoutUpdated;
_imClient.SetPresenter(_control);
Control.TextInputMethodClientRequestedEvent.AddClassHandler<Control>((tb, e) =>
{
e.Client = _imClient;
});
}
private void OnLostFocus(object sender, global::Avalonia.Interactivity.RoutedEventArgs e)
{
_imClient.SetPresenter(null);
}
private void OnLayoutUpdated(object sender, EventArgs e)
{
SizeChanged?.Invoke(new CefSize((int)_control.Bounds.Width, (int)_control.Bounds.Height));
}
private void OnGotFocus(object sender, GotFocusEventArgs e)
{
GotFocus?.Invoke();
_imClient.SetPresenter(_control);
}
}
}
以上这些改动可查看这个git
CefGlue提供的Demo引用了内部项目和libcef库本身,为了测试CefGlue独立使用的效果,我仿照Demo单独创建了一个工程,只引用CefGlue的库,工程结构如图:
关键代码修改如下:
public static AppBuilder BuildAvaloniaApp()
{
try
{
LogHelper.Info("APP创建");
Console.WriteLine("DEBUG_CONSOLE: BuildAvaloniaApp");
string defaultFont = "fonts:AppFontFamilies#FZLanTingHeiS-R-GB";
Uri fontName = new Uri("fonts:AppFontFamilies", UriKind.Absolute);
Uri fontSource = new Uri("avares://AvaloniaOnlyApp/Res/Fonts", UriKind.Absolute);
var ap = AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace()
.With(new FontManagerOptions()
{
//使用编译到程序中的字体
DefaultFamilyName = defaultFont,
FontFallbacks = new[] { new FontFallback { FontFamily = new FontFamily(defaultFont) } }
//麒麟系统用下面这个字体设置
//DefaultFamilyName = "Noto Sans CJK SC",
//FontFallbacks =
// [
// new FontFallback { FontFamily = "文泉驿正黑" },
// new FontFallback { FontFamily = "DejaVu Sans" },
// ],
})
.ConfigureFonts(manager=>manager.AddFontCollection(new EmbeddedFontCollection(fontName, fontSource)))
.AfterSetup(_ => CefRuntimeLoader.Initialize(new CefSettings()
{
WindowlessRenderingEnabled = true,
//BackgroundColor = new CefColor(0x00, 0xff, 0xff, 0xff),
Locale = "zh-CN",
RootCachePath = AppDomain.CurrentDomain.BaseDirectory + "CefCache",
LogFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log", "CefLog.log"),
LogSeverity = CefLogSeverity.Debug,
},
flags: new Dictionary<string, string> {
{"--ignore-urlfetcher-cert-requests", "1" },
{"--ignore-certificate-errors", "1" },
{"--disable-web-security", "1" },
{"--no-sandbox","1"},
{"disable-keyring-access","1" },
{"disable-gpu", "1" },
{"disable-gpu-compositing", "1" },
{"enable-begin-frame-scheduling", "1" },
{"disable-gpu-vsync", "1" }
//{"--disable-gpu-sandbox","1" },
}.ToArray(),
customSchemes: new[] {
new CustomScheme()
{
SchemeName = "test",
SchemeHandlerFactory = new CustomSchemeHandler()
}
}));
return ap;
}
catch (Exception ex)
{
LogHelper.Fatal("Program 异常",ex);
throw;
}
}
工程在国产化系统麒麟V10上运行,需要注意两个地方:
- 默认字体问题:Avalonia启动时会自动选择默认字体,但是在麒麟环境下这个DefaultFamilyName获取到的不正确或无法使用,导致程序运行失败,有两种解决方法,一是如代码中注释掉的地方一样,直接将它设置为麒麟系统下指定的中文字体:
//麒麟系统用下面这个字体设置 DefaultFamilyName = "Noto Sans CJK SC", FontFallbacks = [ new FontFallback { FontFamily = "文泉驿正黑" }, new FontFallback { FontFamily = "DejaVu Sans" }, ],
二就是将要用的字体文件作为资源加入到程序中,启动时直接使用自带字体。具体方法可以看这个解决WPF+Avalonia在openKylin系统下默认字体问题 - 踏平扶桑 - 博客园和官方文档如何使用自定义字体 | Avalonia Docs
- Cef启动参数:在Linux系统下运行时,--no-sandbox参数是必须的。为了能正常加载网页还增加了其他参数,具体查看上面代码即可。
代码编译完后点击在VS2022中点击发布,部署模式选择独立,这样就不需要单独安装.net运行环境了。
由于是直接引用的CefGlue的dll,所以它依赖的libcef等库文件需要手动复制到目标文件夹中,具体文件列表如图:
这些依赖库可以从Demo的生成路径中获取。第一个文件夹中是CefGlue的子进程,第二个是chromium语言依赖,第三个是运行环境相关的依赖,里面不需要的平台或架构文件夹可以删除进行精简。
将整个目标文件夹打包到Linux操作系统中就可以运行了。我是直接压缩复制到Linux下再解压运行的,在Windows下直接打DEB等安装包比较麻烦。
运行时需要注意,CefGlue的浏览器子进程Xilium.CefGlue.BrowserProcess需要修改权限(主程序也是一样的,但是子进程不会提示无权限,只会运行到一半直接退出)
运行效果涉及内部数据,不放上来