Avalonia 应用宿主与 UI 架构
SteamTools( Watt Toolkit )客户端以 BD.WTTS.Client.Avalonia 项目作为跨平台桌面应用宿主:它承载 Avalonia App 的生命周期、主题、托盘图标、视图抽象与平台服务实现,并通过项目引用被各 WTTS 插件(ArchiSteamFarmPlus、GameTools 等)复用,构成整个 UI 层的编译期与运行期基座。
Purpose and Scope
本页覆盖以下内容,作为 UI 宿主架构的唯一权威参考:
- 宿主项目结构:
src/BD.WTTS.Client.Avalonia/中App部分类(partial class)族的职责拆分、服务实现目录与视图层抽象。 - 插件与宿主的接线方式:插件 csproj 如何通过
ProjectReference、DefineConstants(WTTS_PLUGIN*)、AvaloniaUseCompiledBindingsByDefault与AvaloniaResource集成进宿主。 - 仓库内置的 Avalonia 源码与兼容层:
Avalonia.Base、Avalonia.Diagnostics分叉与BD.Avalonia8.Image2.Compat动图控件兼容包。
以下主题有意留给兄弟页面,本页不展开:
- 各插件自身业务功能(ASF Plus、游戏工具):见插件相关页面。
- 客户端业务服务与数据层(
BD.WTTS.Client):见客户端核心页面。 - 具体页面控件库的实现细节:见对应 UI 组件页面。
Overview
该子系统的设计目标是:一个宿主,多个插件,一套 UI 基座。
- 宿主(Host):
BD.WTTS.Client.Avalonia持有 AvaloniaApp的全部宿主职责。App被刻意拆分为多个 partial 文件(App.axaml.cs、App.Interface.cs、App.Theme.cs、App.TrayIcon.cs、App.Dispose.cs),按"生命周期 / 接口适配 / 主题 / 托盘 / 释放"这条职责轴切分,而不是塞进一个巨型文件。 - 服务实现与 DI:宿主内的
Extensions/ServiceCollection提供 DI 注册扩展(如AddApplicationUpdateService),Services.Implementation/App提供平台相关的服务实现(如AvaloniaApplicationUpdateServiceImpl),遵循"接口在核心层、实现在宿主层"的分层约定。 - 视图层抽象:
UI/Views/Abstractions/Windows/ReactiveAppWindow.cs为所有窗口提供响应式基类;UI/Views/Controls/下是自定义控件(AppItem、FixedWrapPanel)。 - 插件复用:插件项目不各自启动应用,而是引用宿主项目复用其 App、样式与控件基础设施,同时以
WTTS_PLUGIN*编译常量隔离代码路径。
取证说明:本次文档生成时源码读取预算受限,
App.axaml.cs等文件的内部实现(具体方法签名、OnFrameworkInitializationCompleted细节)未能逐行读取。下文关于 partial 文件职责的描述基于实际文件命名与目录结构(已验证),内部实现细节以「实现细节未在本次取证中读取」标注,不做臆测。
Architecture
要点解读(均可追溯至实际文件):
| 组件 | 实际路径 | 架构角色 |
|---|---|---|
App.axaml.cs | src/BD.WTTS.Client.Avalonia/UI/App.axaml.cs | Avalonia App 主入口(XAML 代码后置) |
App.Interface.cs / App.Theme.cs / App.TrayIcon.cs / App.Dispose.cs | 同目录 | App 的 partial 拆分:接口适配、主题、托盘图标、资源释放 |
ServiceCollectionExtensions.AddApplicationUpdateService.cs | src/BD.WTTS.Client.Avalonia/Extensions/ServiceCollection/ | DI 注册扩展 |
AvaloniaApplicationUpdateServiceImpl.cs | src/BD.WTTS.Client.Avalonia/Services.Implementation/App/ | 应用更新服务的宿主层实现 |
ReactiveAppWindow.cs | src/BD.WTTS.Client.Avalonia/UI/Views/Abstractions/Windows/ | 窗口抽象基类 |
AppItem.cs / FixedWrapPanel.cs | src/BD.WTTS.Client.Avalonia/UI/Views/Controls/ | 自定义控件 |
Avalonia.Base / Avalonia.Diagnostics | src/Avalonia.Base/、src/Avalonia.Diagnostics/ | 仓库内置的 Avalonia 分叉源码(强名签名,PackageId 为 Avalonia) |
BD.Avalonia8.Image2.Compat | src/BD.Avalonia8.Image2.Compat/ | Gif/Apng 动图控件兼容包,输出程序集名 BD.Avalonia8.Image2 |
插件集成流(编译期接线)
插件到宿主的接入完全发生在编译期 csproj 层,宿主不感知具体插件类型:
这种设计的意图:插件零运行时发现成本,绑定在编译期校验(编译绑定比反射绑定更快且能在编译期报错),同时通过编译常量让同一份宿主代码在不同插件构建中裁剪行为。
宿主项目结构
1src/BD.WTTS.Client.Avalonia/
2├── Extensions/
3│ └── ServiceCollection/
4│ └── ServiceCollectionExtensions.AddApplicationUpdateService.cs # DI 扩展
5├── Services.Implementation/
6│ └── App/
7│ └── AvaloniaApplicationUpdateServiceImpl.cs # 平台服务实现
8└── UI/
9 ├── App.axaml.cs # App 主入口
10 ├── App.Dispose.cs # 释放
11 ├── App.Interface.cs # 接口适配
12 ├── App.Theme.cs # 主题
13 ├── App.TrayIcon.cs # 托盘图标
14 └── Views/
15 ├── Abstractions/Windows/ReactiveAppWindow.cs
16 └── Controls/
17 ├── AppItem.cs
18 └── Widgets/FixedWrapPanel.csApp 部分类拆分(按命名与目录验证)
宿主将 App 按职责轴拆成 5 个文件(文件名即职责边界):
App.axaml.cs— XAML 代码后置,应用入口与生命周期宿主。实现细节未在本次取证中读取。App.Interface.cs—App与抽象接口之间的适配。实现细节未在本次取证中读取。App.Theme.cs— 主题加载/切换。实现细节未在本次取证中读取。App.TrayIcon.cs— 系统托盘图标。实现细节未在本次取证中读取。App.Dispose.cs— 退出时的资源清理。实现细节未在本次取证中读取。
设计意图:桌面应用的 App 往往成为"上帝类"(生命周期 + DI 容器 + 主题 + 托盘 + 清理),partial 拆分让每条关注点独立成文件、独立演进,同时保持单一类型 App 的运行时身份不变。
服务实现与 DI 扩展
Extensions/ServiceCollection/ServiceCollectionExtensions.AddApplicationUpdateService.cs:以AddXXX扩展方法风格向 DI 容器注册宿主层服务(方法签名未在本次取证中读取)。Services.Implementation/App/AvaloniaApplicationUpdateServiceImpl.cs:命名表明这是「应用更新服务」的 Avalonia 实现,属于"接口定义在客户端核心、实现绑定在具体 UI 框架宿主"的分层(实现细节未在本次取证中读取)。
视图层抽象
UI/Views/Abstractions/Windows/ReactiveAppWindow.cs:所有窗口的响应式抽象基类,位于Abstractions目录表明它是给插件与页面继承的基础设施(内部实现未在本次取证中读取)。UI/Views/Controls/AppItem.cs、UI/Views/Controls/Widgets/FixedWrapPanel.cs:宿主自带的自定义控件,插件经ProjectReference直接复用。
Usage Examples
插件接入宿主:ArchiSteamFarmPlus 的 csproj 接线
以下片段取自 ASF Plus 插件项目,展示插件接入宿主的三个关键开关:编译常量、编译绑定开关、宿主引用。
<!-- 定义插件编译常量,隔离插件代码路径 -->
<DefineConstants>WTTS_PLUGIN;WTTS_PLUGIN_ASFPLUS;$(DefineConstants)</DefineConstants>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>1<!-- UI 资源以 AvaloniaResource 嵌入程序集 -->
2<ItemGroup>
3 <AvaloniaResource Include="UI\Assets\asf.ico" />
4</ItemGroup><!-- 引用客户端核心与 Avalonia 宿主,复用 App 与控件基座 -->
<ProjectReference Include="..\BD.WTTS.Client\BD.WTTS.Client.csproj" />
<ProjectReference Include="..\BD.WTTS.Client.Avalonia\BD.WTTS.Client.Avalonia.csproj" />Source: BD.WTTS.Client.Plugins.ArchiSteamFarmPlus.csproj
Sources:
要点:WTTS_PLUGIN_ASFPLUS 与 WTTS_PLUGIN_GAMETOOLS 等常量让插件代码在宿主构建语境下按插件裁剪;AvaloniaUseCompiledBindingsByDefault=true 是全局默认值,两个插件均显式设置。
GameTools 插件的资源嵌入示例
<DefineConstants>WTTS_PLUGIN;WTTS_PLUGIN_GAMETOOLS;$(DefineConstants)</DefineConstants>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>1<ItemGroup>
2 <AvaloniaResource Include="UI\Assets\BorderlessWindow.png" />
3 <AvaloniaResource Include="UI\Assets\CounterStrike.png" />
4 <AvaloniaResource Include="UI\Assets\CPU.png" />
5 <AvaloniaResource Include="UI\Assets\movecross.png" />
6 <AvaloniaResource Include="UI\Assets\movecross_hide.png" />
7 <AvaloniaResource Include="UI\Assets\toolbox.ico" />
8</ItemGroup>Source: BD.WTTS.Client.Plugins.GameTools.csproj
Sources:
设计意图:AvaloniaResource(而非 Content/Resource)保证图片被纳入 Avalonia 资源索引,可通过 avares:// URI 加载,并与裁剪(trimming)兼容。
内置 Avalonia 分叉与强名签名
仓库并未直接消费 NuGet 的 Avalonia,而是内置了源码分叉:
1<DelaySign>false</DelaySign>
2<AssemblyOriginatorKeyFile>$(MSBuildProjectDirectory)\..\..\avalonia.snk</AssemblyOriginatorKeyFile>
3<!--https://github.com/AvaloniaUI/Avalonia/blob/0.10.10/build/SharedVersion.props#L18-->
4<IsTrimmable>true</IsTrimmable>
5<PackageId>Avalonia</PackageId>Source: Avalonia.Base.csproj
要点:PackageId=Avalonia 使该分叉以 Avalonia 官方包身份参与解析;IsTrimmable=true 配合 AOT/裁剪发布;avalonia.snk 强名密钥位于仓库根目录两级之上。Avalonia.Diagnostics 采用同样的签名与版本策略。
动图控件兼容包
<Description>提供动图控件,支持 Gif/Apng</Description>
<AssemblyName>BD.Avalonia8.Image2</AssemblyName>
<RootNamespace>BD.Avalonia8.Image2</RootNamespace>Source: BD.Avalonia8.Image2.Compat.csproj
要点:项目名带 .Compat 后缀但输出程序集名为 BD.Avalonia8.Image2 —— 典型的"兼容层"模式:在不破坏既有程序集引用的前提下,用新项目承载跨版本兼容实现。
Configuration Options
宿主与插件接线相关的 MSBuild / 编译配置项(均已在源码中验证):
| 选项 | 类型 | 默认(观察值) | 说明 |
|---|---|---|---|
AvaloniaUseCompiledBindingsByDefault | bool | true(插件显式声明) | 默认启用编译期 XAML 绑定,编译期校验绑定路径 |
DefineConstants | string 列表 | WTTS_PLUGIN;WTTS_PLUGIN_XXX | 插件身份编译常量;#if WTTS_PLUGIN_ASFPLUS / WTTS_PLUGIN_GAMETOOLS 等裁剪代码路径 |
AvaloniaResource | item | 各插件 UI\Assets\*(ico/png) | 嵌入 Avalonia 资源索引,经 avares:// 加载 |
ProjectReference | item | BD.WTTS.Client、BD.WTTS.Client.Avalonia | 插件复用客户端核心与 Avalonia 宿主 |
PackageId(Avalonia.Base) | string | Avalonia | 内置分叉以官方包 ID 参与还原解析 |
IsTrimmable(Avalonia.Base) | bool | true | 允许发布期裁剪 |
AssemblyOriginatorKeyFile(Avalonia.*) | path | ..\..\avalonia.snk | 强名签名密钥文件 |
DelaySign(Avalonia.*) | bool | false | 仅使用公钥延迟签名开关(此处关闭) |
API Reference
本页为宿主架构参考,不逐条列出 App 部分类的完整成员清单。已知公开扩展点如下;其余成员签名未在本次取证中读取,请以源码为准:
ServiceCollectionExtensions.AddApplicationUpdateService(src/BD.WTTS.Client.Avalonia/Extensions/ServiceCollection/):向IServiceCollection注册应用更新服务。方法签名未在本次取证中读取。AvaloniaApplicationUpdateServiceImpl(src/BD.WTTS.Client.Avalonia/Services.Implementation/App/):应用更新服务的 Avalonia 宿主实现类。成员未在本次取证中读取。ReactiveAppWindow(src/BD.WTTS.Client.Avalonia/UI/Views/Abstractions/Windows/):供页面/插件继承的窗口抽象基类。成员未在本次取证中读取。- 自定义控件:
AppItem、FixedWrapPanel(src/BD.WTTS.Client.Avalonia/UI/Views/Controls/)。
Failure Modes, Edge Cases & Concurrency
- 编译绑定失败即构建失败:
AvaloniaUseCompiledBindingsByDefault=true将绑定错误前移到编译期,插件升级视图属性时若绑定路径过期会直接打断构建 —— 这是刻意的快速失败(fail-fast)取舍。 - 资源 URI 依赖
AvaloniaResource嵌入:若插件误用Resource/Content而非AvaloniaResource,运行期avares://加载将失败;各插件 csproj 中统一使用AvaloniaResource。 - 内置 Avalonia 分叉与 NuGet 包冲突面:
Avalonia.Base的PackageId=Avalonia意味着它顶替官方包;宿主与其余项目必须一致指向该分叉,否则会出现程序集身份/签名不一致风险(分叉使用仓库内avalonia.snk签名)。 - App 生命周期与释放:
App.Dispose.cs单独成文件,表明退出清理被显式建模;托盘(App.TrayIcon.cs)这类平台资源若不在释放路径中注销,跨平台退出时可能残留托盘图标。实现细节未在本次取证中读取。 - 并发说明:本页未读取到显式锁/同步原语证据,
App部分类中的并发语义未在本次取证中验证。
Performance / Operational Notes
- 裁剪与 AOT 友好:
Avalonia.Base设IsTrimmable=true,配合编译绑定(无反射绑定回退)面向减小发布体积优化。 - 插件以编译期静态接线取代运行时发现:无插件扫描/反射开销,代价是新增插件需改动构建引用。
- 更新服务的宿主实现独立成类(
AvaloniaApplicationUpdateServiceImpl),便于在不触碰 UI 生命周期代码的前提下演进更新策略。
Extension Points
- 新增插件:新建项目 → 设置
WTTS_PLUGIN;WTTS_PLUGIN_<NAME>常量 →ProjectReference引用BD.WTTS.Client与BD.WTTS.Client.Avalonia→AvaloniaResource嵌入资源。 - 新增宿主层服务:在
Extensions/ServiceCollection/增加扩展方法,在Services.Implementation/增加实现(参照AddApplicationUpdateService/AvaloniaApplicationUpdateServiceImpl的既有拆分)。 - 新窗口:继承
UI/Views/Abstractions/Windows/ReactiveAppWindow。 - 自定义控件:放入
UI/Views/Controls/(Widgets子目录存放布局类控件,如FixedWrapPanel)。
Related Links
- App.axaml.cs — 宿主 App 入口
- App.Theme.cs — 主题 partial
- App.TrayIcon.cs — 托盘 partial
- ServiceCollectionExtensions.AddApplicationUpdateService.cs — DI 扩展
- AvaloniaApplicationUpdateServiceImpl.cs — 更新服务实现
- ReactiveAppWindow.cs — 窗口基类
- Avalonia.Base.csproj — 内置 Avalonia 分叉