乐于分享
好东西不私藏

源码 | 使用AI基于AvaloniaUI MVVM 架构开发跨平台三维应用

源码 | 使用AI基于AvaloniaUI MVVM 架构开发跨平台三维应用

摘要

本文介绍如何使用 AvaloniaUI 框架和 MVVM 架构模式开发现代化的跨平台三维 CAD 应用程序。通过 RapidCAX 项目实例,详细讲解视图切换、命令绑定、消息传递、AnyCAD三维渲染集成等核心技术的实现方法。本项目代码使用AI完成,代码完全开源[1]

注: 为了让AI能够精确了解AnyCAD图形平台API,请在项目中引用专为AI设计的API文档API4AI[2]

关键词:AvaloniaUI, MVVM, 三维 CAD, 跨平台,AnyCAD


1. 引言

随着工业软件国产化需求的日益增长,开发自主可控的 CAD/CAE 软件成为重要趋势。AvaloniaUI 作为一个强大的跨平台 UI 框架,结合 AnyCAD 三维图形平台,为开发 Windows、Linux、macOS 多平台支持的三维应用提供了理想的技术栈。

1.1 技术选型

  • • UI 框架: AvaloniaUI 11.x (跨平台 XAML 框架)
  • • MVVM 库: CommunityToolkit.Mvvm (轻量级 MVVM 组件)
  • • 三维引擎: AnyCAD Platform (专业 CAD 平台 SDK)
  • • UI 主题: SukiUI 6.0 (现代化深色主题)
  • • 开发语言: C# .NET 8.0

1.2 项目特点

RapidCAX 是一个典型的三维 CAD 应用框架,具备以下特点:

  • • 纯 MVVM 架构,View 与 ViewModel 完全解耦
  • • 多视图并行显示(二维草图 + 三维模型)
  • • 全局共享三维渲染控件
  • • 支持文件打开、新建、保存等标准操作
  • • 左右分栏可调节布局
  • • 深色主题界面

2. 项目架构设计

2.1 整体架构图

┌─────────────────────────────────────────────┐
MainWindow.axaml (View)
┌─────────────────────────────────────┐
MainViewModel(DataContext)
-ViewMenuItems
-SelectedView
-SwitchViewCommand
-OpenFileCommand
-NewFileCommand
└───────────┬─────────────────────────┘
DataBinding
┌───────────▼─────────────────────────┐
ViewsContainer
-HomeView
-SketchView
-PartView
-RenderControl(AnyCAD)
└─────────────────────────────────────┘
└─────────────────────────────────────────────┘

2.2 目录结构

src/App/
├──ViewModels/
├──MainViewModel.cs           # 主窗口 ViewModel
├──ViewItemViewModel.cs       # 视图项基类
├──HomeViewModel.cs           # 主页视图 ViewModel
├──SketchViewModel.cs         # 二维视图 ViewModel
├──PartViewModel.cs           # 三维视图 ViewModel
├──BomViewModel.cs            # BOM 视图 ViewModel
└──ComponentsViewModel.cs     # 组件视图 ViewModel
├──Views/
├──HomeView.axaml(.cs)# 主页视图
├──SketchView.axaml(.cs)# 二维草图视图
├──PartView.axaml(.cs)# 三维零件视图
└──...
├──Converters/
├──IsSameConverter.cs         # 相等判断转换器
└──IsSameMultiConverter.cs    # 多值相等判断转换器
├──MainWindow.axaml(.cs)# 主窗口
└──App.axaml(.cs)# 应用程序入口

3. 核心功能实现

3.1 ViewModel 继承体系

为了保持代码的可维护性和扩展性,我们建立了清晰的 ViewModel 继承层次:

基类设计 (ViewItemViewModel.cs):

usingCommunityToolkit.Mvvm.ComponentModel;

namespaceRapidCAX.App.ViewModels
{
///<summary>
/// 视图项 ViewModel(包含工具栏按钮和视图内容信息)
///</summary>
publicpartialclassViewItemViewModel:ObservableObject
{
[ObservableProperty]
privatestring _id;

[ObservableProperty]
privatestring _displayName;

[ObservableProperty]
privatestring _iconPath;

[ObservableProperty]
privatestring _toolTip;

[ObservableProperty]
privateobject _content;

publicViewItemViewModel(string id,string displayName,string iconPath,
string toolTip,object content
)

{
Id= id;
DisplayName= displayName;
IconPath= iconPath;
ToolTip= toolTip;
Content= content;
}
}
}

派生示例 (PartViewModel.cs):

usingCommunityToolkit.Mvvm.ComponentModel;

namespaceRapidCAX.App.ViewModels
{
///<summary>
/// 三维设计视图模型
///</summary>
publicpartialclassPartViewModel:ViewItemViewModel
{
publicPartViewModel()
:base("3D","三维",
"M21,16.5C21,16.88 20.79,17.21...",// SVG 路径数据
"三维设计",
newViews.PartView(
))

{
}
}
}

优势分析

  • • ✅ 职责单一:每个 ViewModel 只负责自己的视图
  • • ✅ 易于扩展:添加新视图只需创建新的 ViewModel 类
  • • ✅ 代码清晰:MainViewModel 不再包含冗长的初始化代码
  • • ✅ 便于维护:每个视图的逻辑在独立文件中

3.2 视图切换机制

3.2.1 XAML 绑定设计

<!-- 工具栏区域 -->
<ItemsControlItemsSource="{Binding ViewMenuItems}">
<ItemsControl.ItemTemplate>
<DataTemplateDataType="vm:ViewItemViewModel">
<ButtonClasses="ToolButton"
Command="{Binding $parent[ItemsControl].DataContext.SwitchViewCommand}"
CommandParameter="{Binding Id}"
ToolTip.Tip="{Binding ToolTip}">

<StackPanelOrientation="Vertical"HorizontalAlignment="Center"Spacing="4">
<PathIconData="{Binding IconPath}"Width="20"Height="20"/>
<TextBlockText="{Binding DisplayName}"FontSize="11"/>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

<!-- 视图内容区域 -->
<ItemsControlx:Name="viewsContainer"ItemsSource="{Binding ViewMenuItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControlContent="{Binding Content}">
<ContentControl.IsVisible>
<MultiBindingConverter="{x:Static converters:IsSameMultiConverter.Instance}">
<BindingPath="$parent[ItemsControl].DataContext.SelectedView"/>
<BindingPath="Id"/>
</MultiBinding>
</ContentControl.IsVisible>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

3.2.2 相等判断转换器

publicclassIsSameMultiConverter:IMultiValueConverter
{
publicstaticIsSameMultiConverterInstance{get;}=new();

publicobject?Convert(IList<object?> values,Type targetType,
object? parameter,CultureInfo culture)
{
if(values.Count>=2&& values[0]!=null&& values[1]!=null)
{
return values[0].Equals(values[1]);
}
returnfalse;
}
}

工作原理

  1. 1. 用户点击工具栏按钮 → 触发 SwitchViewCommand
  2. 2. ViewModel 更新 SelectedView 属性
  3. 3. IsSameMultiConverter 比较 SelectedView 与各视图的 Id
  4. 4. 匹配的视图设置 IsVisible = true,其他视图隐藏
  5. 5. 所有视图常驻内存,保持状态

3.3 命令与消息系统

3.3.1 命令定义

publicpartialclassMainViewModel:ObservableObject
{
[RelayCommand]
publicvoidSwitchView(string viewName)
{
SelectedView= viewName;

// 发送视图切换消息
WeakReferenceMessenger.Default.Send(newViewSwitchedMessage(viewName));
}

[RelayCommand]
privatevoidOpenFile()
{
// 发送消息请求打开文件,由 MainWindow 处理实际的对话框
WeakReferenceMessenger.Default.Send(newOpenFileRequestMessage());
}

[RelayCommand]
privatevoidNewFile()
{
// 发送消息请求清空视图
WeakReferenceMessenger.Default.Send(newClearViewerRequestMessage());
AnyCAD.Platform.Application.Instance().ExecuteCommand("New");
}
}

3.3.2 消息接收处理

publicpartialclassMainWindow:Window,
IRecipient<OpenFileRequestMessage>,
IRecipient<ClearViewerRequestMessage>
{
publicMainWindow()
{
InitializeComponent();
this.Loaded+=OnLoaded;

// 注册消息接收器
WeakReferenceMessenger.Default.Register<OpenFileRequestMessage>(this);
WeakReferenceMessenger.Default.Register<ClearViewerRequestMessage>(this);
}

publicasyncvoidReceive(OpenFileRequestMessage message)
{
var files =awaitthis.StorageProvider.OpenFilePickerAsync(
newFilePickerOpenOptions
{
Title="打开 ACAD 文件",
AllowMultiple=false,
FileTypeFilter=new[]
{
newFilePickerFileType("ACAD 文件")
{Patterns=new[]{"*.acad"}},
newFilePickerFileType("所有文件")
{Patterns=new[]{"*.*"}}
}
});

if(files.Count>0)
{
var filePath = files[0].Path.LocalPath;
var doc =DocumentIO.Load(filePath);
if(doc !=null)
{
_RenderControl.ClearAll();
Application.Instance().ShowDocument(doc);
_RenderControl.RequestDraw(EnumUpdateFlags.ZoomToFit);
}
}
}

publicvoidReceive(ClearViewerRequestMessage message)
{
_RenderControl.ClearAll();
}
}

消息流程

用户操作MenuItem.CommandViewModel.Command
WeakReferenceMessenger.Send(Message)
MainWindow.Receive(Message)执行 UI 操作

3.4 三维渲染集成

3.4.1 RenderControl 全局共享

<!-- 右侧固定的三维渲染窗口 -->
<BorderGrid.Column="2"Margin="0">
<anycad:RenderControlx:Name="_RenderControl"
ViewerReady="OnViewerReady"/>

</Border>
privatevoidOnViewerReady()
{
// 设置活动查看器
AnyCAD.Platform.Application.Instance().SetActiveViewer(_RenderControl);
}

3.4.2 关键特性

  • • 单例共享RenderControl 作为单例在整个应用中共享
  • • 原生窗口: 基于 NativeWindowHandle 实现,避免多父容器问题
  • • 状态保持: 视图切换时三维场景状态自动保持
  • • 多视口支持: 可同时显示多个三维视口

3.5 布局系统设计

3.5.1 三层垂直布局

<GridRowDefinitions="Auto, *, Auto">
<!-- 顶部菜单栏 -->
<MenuGrid.Row="0">
<MenuItemHeader="文件">
<MenuItemHeader="新建"Command="{Binding NewFileCommand}"/>
<MenuItemHeader="打开"Command="{Binding OpenFileCommand}"/>
<Separator/>
<MenuItemHeader="退出"/>
</MenuItem>
</Menu>

<!-- 中间工作区 -->
<GridGrid.Row="1"ColumnDefinitions="Auto, 3, *">
<!-- 工具栏 + 视图容器 + 三维渲染 -->
</Grid>

<!-- 底部状态栏 -->
<BorderGrid.Row="2"Background="#2D2D30"Padding="10,5">
<TextBlockText="就绪"Foreground="#CCCCCC"/>
</Border>
</Grid>

3.5.2 左右分栏设计

<GridColumnDefinitions="250, 3, *">
<!-- 左侧:可切换的视图区域 (250px) -->
<BorderGrid.Column="0">
<ItemsControlx:Name="viewsContainer"/>
</Border>

<!-- 分隔条 (可拖动) -->
<GridSplitterGrid.Column="1"
ResizeDirection="Columns"
Width="1"
Background="#3E3E42"/>


<!-- 右侧:固定的三维渲染窗口 (自适应) -->
<BorderGrid.Column="2">
<anycad:RenderControl/>
</Border>
</Grid>

4. 关键技术要点

4.1 纯 MVVM 架构实践

核心原则

  1. 1. ViewModel 不直接引用 View 的具体类型
  2. 2. 所有交互通过命令和消息实现
  3. 3. View 只负责 UI 呈现和用户输入
  4. 4. ViewModel 包含所有业务逻辑

实现效果

// ✅ 正确:ViewModel 发送消息,不关心谁接收
WeakReferenceMessenger.Default.Send(newClearViewerRequestMessage());

// ❌ 错误:ViewModel 直接操作 View
// mainWindow._RenderControl.ClearAll(); // 不要这样做!

4.2 视图生命周期管理

常驻内存策略

  • • 所有视图在初始化时一次性创建
  • • 通过 IsVisible 控制显示/隐藏
  • • 切换视图时保持状态(如缩放比例、选择集)
privatevoidInitializeViewMenuItems()
{
// 所有视图在启动时创建并保持在内存中
ViewMenuItems.Add(newHomeViewModel());
ViewMenuItems.Add(newSketchViewModel());
ViewMenuItems.Add(newPartViewModel());
ViewMenuItems.Add(newBomViewModel());
ViewMenuItems.Add(newComponentsViewModel());
}

4.3 深色主题适配

SukiUI 主题配置

<suki:SukiWindowxmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
Background="#1F1F1F">


<!-- 自定义颜色资源 -->
<suki:SukiWindow.Resources>
<SolidColorBrushx:Key="SukiPrimaryColor"Color="#007ACC"/>
<SolidColorBrushx:Key="SukiAccentColor"Color="#FF9800"/>
</suki:SukiWindow.Resources>
</suki:SukiWindow>

样式定义

<StyleSelector="Button.ToolButton">
<SetterProperty="Background"Value="Transparent"/>
<SetterProperty="Foreground"Value="#CCCCCC"/>
<SetterProperty="Cursor"Value="Hand"/>
<StyleSelector="^:pointerover">
<SetterProperty="Background"Value="#3E3E42"/>
<SetterProperty="Foreground"Value="#FFFFFF"/>
</Style>
<StyleSelector="^:checked">
<SetterProperty="Background"Value="#007ACC"/>
</Style>
</Style>

4.4 性能优化

优化措施

  1. 1. 视图缓存: 避免重复创建 UserControl
  2. 2. 延迟加载: 大型数据按需加载
  3. 3. 虚拟化列表: ItemsControl 启用虚拟化
  4. 4. 异步操作: 文件操作使用 async/await
publicasyncvoidReceive(OpenFileRequestMessage message)
{
// 异步文件操作,避免阻塞 UI
var files =awaitthis.StorageProvider.OpenFilePickerAsync(options);
// ...
}

5. 常见问题与解决方案

5.1 控件多父容器问题

问题描述

InvalidOperationException:'RenderControl'is already a child of another visual.

解决方案

  • • 使用 NativeControlHost 或 Border 包裹
  • • 确保控件同时只有一个父容器
  • • 通过可见性控制而非从视觉树移除

5.2 消息注册时机

问题描述: 消息已发送但未接收到

正确做法

publicMainWindow()
{
InitializeComponent();

// ✅ 在构造函数中立即注册
WeakReferenceMessenger.Default.Register<OpenFileRequestMessage>(this);
}

5.3 DataContext 生命周期

注意事项

  • • Window 的 DataContext 应在 XAML 中设置
  • • 不要在 code-behind 中重复设置
  • • 确保 ViewModel 先于 View 初始化

6. 扩展与进阶

6.1 添加新视图

步骤

  1. 1. 创建 View: SketchView.axaml(.cs)
  2. 2. 创建 ViewModel: SketchViewModel.cs
  3. 3. 在 MainViewModel.InitializeViewMenuItems() 中注册
ViewMenuItems.Add(newSketchViewModel());

6.2 添加工具栏功能

示例 – 导出功能

[RelayCommand]
privateasyncTaskExport()
{
WeakReferenceMessenger.Default.Send(newExportRequestMessage());
}

// MainWindow 中处理
publicvoidReceive(ExportRequestMessage message)
{
var file =awaitthis.StorageProvider.SaveFilePickerAsync(/* ... */);
// 执行导出逻辑
}

6.3 插件化架构

设计思路

  • • 定义插件接口 IPlugin
  • • 使用 MEF 或自定义容器加载插件
  • • 通过消息机制与主程序通信

7. 总结与展望

7.1 技术优势

  1. 1. 跨平台能力: Windows、Linux、macOS 一套代码
  2. 2. 现代化 UI: Fluent Design、Material Design 支持
  3. 3. 高性能: 硬件加速、虚拟化、异步编程
  4. 4. 易维护: MVVM 架构、依赖注入、单元测试友好

7.2 未来方向

  • • AI 辅助设计

附录

A. 开发环境配置

# .NET SDK 8.0+
dotnet --version

# 安装 Avalonia 模板
dotnet new install Avalonia.Templates

# 克隆项目
git clone https://github.com/anycad/rapidcax.avalonia.git

# 构建运行
cd rapidcax.avalonia/src/App
dotnet run

B. 核心 NuGet 包

<PackageReferenceInclude="Avalonia"Version="11.1.0"/>
<PackageReferenceInclude="CommunityToolkit.Mvvm"Version="8.2.2"/>
<PackageReferenceInclude="SukiUI"Version="6.0.0"/>
<PackageReferenceInclude="AnyCAD.Avalonia.NET"Version="2026.x.xx"/>

C. 参考资源

  • • AvaloniaUI 官方文档:https://docs.avaloniaui.net/
  • • AnyCAD 开发文档:http://www.anycad.cn/
  • • RapidCAX 源码:https://gitee.com/rapidcax/rapidcax.avalonia
  • • API4AI: https://gitee.com/anycad/anycad.faq.git

引用链接

[1] 代码完全开源: https://gitee.com/rapidcax/rapidcax.avalonia
[2] API4AI: gitee.com/anycad/anycad.faq.git