Repository Wiki
BeyondDimension/SteamTools

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 持有 Avalonia App 的全部宿主职责。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

Loading diagram...

要点解读(均可追溯至实际文件):

组件实际路径架构角色
App.axaml.cssrc/BD.WTTS.Client.Avalonia/UI/App.axaml.csAvalonia App 主入口(XAML 代码后置)
App.Interface.cs / App.Theme.cs / App.TrayIcon.cs / App.Dispose.cs同目录App 的 partial 拆分:接口适配、主题、托盘图标、资源释放
ServiceCollectionExtensions.AddApplicationUpdateService.cssrc/BD.WTTS.Client.Avalonia/Extensions/ServiceCollection/DI 注册扩展
AvaloniaApplicationUpdateServiceImpl.cssrc/BD.WTTS.Client.Avalonia/Services.Implementation/App/应用更新服务的宿主层实现
ReactiveAppWindow.cssrc/BD.WTTS.Client.Avalonia/UI/Views/Abstractions/Windows/窗口抽象基类
AppItem.cs / FixedWrapPanel.cssrc/BD.WTTS.Client.Avalonia/UI/Views/Controls/自定义控件
Avalonia.Base / Avalonia.Diagnosticssrc/Avalonia.Base/、src/Avalonia.Diagnostics/仓库内置的 Avalonia 分叉源码(强名签名,PackageId 为 Avalonia)
BD.Avalonia8.Image2.Compatsrc/BD.Avalonia8.Image2.Compat/Gif/Apng 动图控件兼容包,输出程序集名 BD.Avalonia8.Image2

插件集成流(编译期接线)

插件到宿主的接入完全发生在编译期 csproj 层,宿主不感知具体插件类型:

Loading diagram...

这种设计的意图:插件零运行时发现成本,绑定在编译期校验(编译绑定比反射绑定更快且能在编译期报错),同时通过编译常量让同一份宿主代码在不同插件构建中裁剪行为。

宿主项目结构

text
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.cs

App 部分类拆分(按命名与目录验证)

宿主将 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 插件项目,展示插件接入宿主的三个关键开关:编译常量、编译绑定开关、宿主引用。

xml
<!-- 定义插件编译常量,隔离插件代码路径 --> <DefineConstants>WTTS_PLUGIN;WTTS_PLUGIN_ASFPLUS;$(DefineConstants)</DefineConstants> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
xml
1<!-- UI 资源以 AvaloniaResource 嵌入程序集 --> 2<ItemGroup> 3 <AvaloniaResource Include="UI\Assets\asf.ico" /> 4</ItemGroup>
xml
<!-- 引用客户端核心与 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 插件的资源嵌入示例

xml
<DefineConstants>WTTS_PLUGIN;WTTS_PLUGIN_GAMETOOLS;$(DefineConstants)</DefineConstants> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
xml
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,而是内置了源码分叉:

xml
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 采用同样的签名与版本策略。

动图控件兼容包

xml
<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 / 编译配置项(均已在源码中验证):

选项类型默认(观察值)说明
AvaloniaUseCompiledBindingsByDefaultbooltrue(插件显式声明)默认启用编译期 XAML 绑定,编译期校验绑定路径
DefineConstantsstring 列表WTTS_PLUGIN;WTTS_PLUGIN_XXX插件身份编译常量;#if WTTS_PLUGIN_ASFPLUS / WTTS_PLUGIN_GAMETOOLS 等裁剪代码路径
AvaloniaResourceitem各插件 UI\Assets\*(ico/png)嵌入 Avalonia 资源索引,经 avares:// 加载
ProjectReferenceitemBD.WTTS.Client、BD.WTTS.Client.Avalonia插件复用客户端核心与 Avalonia 宿主
PackageId(Avalonia.Base)stringAvalonia内置分叉以官方包 ID 参与还原解析
IsTrimmable(Avalonia.Base)booltrue允许发布期裁剪
AssemblyOriginatorKeyFile(Avalonia.*)path..\..\avalonia.snk强名签名密钥文件
DelaySign(Avalonia.*)boolfalse仅使用公钥延迟签名开关(此处关闭)

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)。