mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-24 02:14:26 +08:00
531 lines
14 KiB
Markdown
531 lines
14 KiB
Markdown
# 创建第一个插件
|
||
|
||
通过这个教程,你将在 15 分钟内创建一个简单但功能完整的插件。
|
||
|
||
## 学习目标
|
||
|
||
- ✅ 使用模板创建插件项目
|
||
- ✅ 实现插件入口类
|
||
- ✅ 创建一个简单的桌面组件
|
||
- ✅ 注册组件到宿主
|
||
- ✅ 运行和测试插件
|
||
|
||
## 前置准备
|
||
|
||
确保你已经:
|
||
- ✅ 安装了 .NET 10 SDK
|
||
- ✅ 安装了插件模板(参考 [环境准备](01-环境准备.md))
|
||
- ✅ 有一个支持 C# 的 IDE
|
||
|
||
## 步骤 1: 创建项目
|
||
|
||
### 使用模板创建
|
||
|
||
```powershell
|
||
# 创建新插件项目
|
||
dotnet new lmd-plugin -n HelloWorldPlugin
|
||
|
||
# 进入项目目录
|
||
cd HelloWorldPlugin
|
||
|
||
# 还原依赖
|
||
dotnet restore
|
||
```
|
||
|
||
### 项目结构预览
|
||
|
||
```
|
||
HelloWorldPlugin/
|
||
├── HelloWorldPlugin.csproj # 项目文件
|
||
├── Plugin.cs # 插件入口(我们要修改这个)
|
||
├── plugin.json # 插件清单(我们要修改这个)
|
||
├── Components/
|
||
│ └── SampleComponent.cs # 示例组件(我们要修改这个)
|
||
├── Views/
|
||
│ └── SampleComponentView.axaml # 组件视图(我们要修改这个)
|
||
├── ViewModels/
|
||
│ └── SampleComponentViewModel.cs
|
||
├── Assets/
|
||
│ └── icon.png # 插件图标
|
||
└── Settings/
|
||
└── PluginSettingsPage.axaml # 设置页
|
||
```
|
||
|
||
## 步骤 2: 配置插件清单
|
||
|
||
编辑 `plugin.json`,修改基本信息:
|
||
|
||
```json
|
||
{
|
||
"Id": "com.example.helloworldplugin",
|
||
"Name": "Hello World Plugin",
|
||
"Version": "1.0.0",
|
||
"Author": "Your Name",
|
||
"Description": "My first LanMountainDesktop plugin - displays a greeting",
|
||
"MinHostVersion": "1.0.0",
|
||
"SdkVersion": "5.0.0",
|
||
"Dependencies": [],
|
||
"Permissions": [],
|
||
"Icon": "Assets/icon.png",
|
||
"Homepage": "https://github.com/yourusername/helloworldplugin"
|
||
}
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
- **Id**: 插件唯一标识符,建议使用反向域名格式
|
||
- **Name**: 用户看到的插件名称
|
||
- **Version**: 插件版本号(语义化版本)
|
||
- **MinHostVersion**: 最低支持的宿主版本
|
||
- **SdkVersion**: 使用的 SDK 版本
|
||
|
||
## 步骤 3: 实现插件入口
|
||
|
||
编辑 `Plugin.cs`:
|
||
|
||
```csharp
|
||
using LanMountainDesktop.PluginSdk;
|
||
using LanMountainDesktop.Shared.Contracts;
|
||
using HelloWorldPlugin.Components;
|
||
using HelloWorldPlugin.Views;
|
||
using HelloWorldPlugin.ViewModels;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
namespace HelloWorldPlugin;
|
||
|
||
/// <summary>
|
||
/// Hello World 插件入口
|
||
/// </summary>
|
||
public class Plugin : IPlugin
|
||
{
|
||
public string Id => "com.example.helloworldplugin";
|
||
public string Name => "Hello World Plugin";
|
||
public string Version => "1.0.0";
|
||
|
||
private IPluginContext? _context;
|
||
|
||
/// <summary>
|
||
/// 插件初始化
|
||
/// </summary>
|
||
public async Task InitializeAsync(IPluginContext context)
|
||
{
|
||
_context = context;
|
||
|
||
// 记录日志
|
||
context.Logger.LogInformation("Hello World Plugin is initializing...");
|
||
|
||
// 获取组件注册表
|
||
var componentRegistry = context.Services
|
||
.GetService<IComponentRegistry>();
|
||
|
||
if (componentRegistry != null)
|
||
{
|
||
// 注册 Hello World 组件
|
||
componentRegistry.RegisterComponent<HelloWorldComponent>(
|
||
componentFactory: () => new HelloWorldComponent(),
|
||
viewFactory: (component) => new HelloWorldComponentView
|
||
{
|
||
DataContext = new HelloWorldComponentViewModel(
|
||
(HelloWorldComponent)component
|
||
)
|
||
}
|
||
);
|
||
|
||
context.Logger.LogInformation(
|
||
"HelloWorldComponent registered successfully"
|
||
);
|
||
}
|
||
|
||
// 异步操作示例(如果需要)
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 插件关闭
|
||
/// </summary>
|
||
public async Task ShutdownAsync()
|
||
{
|
||
_context?.Logger.LogInformation(
|
||
"Hello World Plugin is shutting down..."
|
||
);
|
||
|
||
// 清理资源(如果有)
|
||
await Task.CompletedTask;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 代码说明
|
||
|
||
1. **实现 IPlugin 接口** - 定义插件的基本信息和生命周期
|
||
2. **InitializeAsync** - 插件加载时调用,注册组件和服务
|
||
3. **ShutdownAsync** - 插件卸载时调用,清理资源
|
||
4. **日志记录** - 使用 `context.Logger` 记录日志
|
||
|
||
## 步骤 4: 创建组件类
|
||
|
||
编辑 `Components/SampleComponent.cs`,重命名为 `HelloWorldComponent.cs`:
|
||
|
||
```csharp
|
||
using LanMountainDesktop.PluginSdk.Components;
|
||
using LanMountainDesktop.Shared.Contracts.Components;
|
||
using System.ComponentModel;
|
||
|
||
namespace HelloWorldPlugin.Components;
|
||
|
||
/// <summary>
|
||
/// Hello World 桌面组件
|
||
/// </summary>
|
||
[Component(
|
||
Id = "com.example.helloworldplugin.helloworld",
|
||
Name = "Hello World",
|
||
Description = "Displays a friendly greeting message",
|
||
Category = "Demo",
|
||
Icon = "avares://HelloWorldPlugin/Assets/icon.png"
|
||
)]
|
||
public class HelloWorldComponent : ComponentBase
|
||
{
|
||
public override string Id => "com.example.helloworldplugin.helloworld";
|
||
public override string Name => "Hello World";
|
||
|
||
private string _greeting = "Hello, World!";
|
||
private int _clickCount = 0;
|
||
|
||
/// <summary>
|
||
/// 问候语
|
||
/// </summary>
|
||
public string Greeting
|
||
{
|
||
get => _greeting;
|
||
set => SetProperty(ref _greeting, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 点击次数
|
||
/// </summary>
|
||
public int ClickCount
|
||
{
|
||
get => _clickCount;
|
||
set => SetProperty(ref _clickCount, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 组件初始化
|
||
/// </summary>
|
||
public override Task InitializeAsync()
|
||
{
|
||
// 从设置加载问候语
|
||
Greeting = Settings.GetValue("Greeting", "Hello, World!");
|
||
ClickCount = Settings.GetValue("ClickCount", 0);
|
||
|
||
Logger.LogInformation("HelloWorldComponent initialized");
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 组件更新(定时调用)
|
||
/// </summary>
|
||
public override Task UpdateAsync()
|
||
{
|
||
// 这里可以更新组件数据
|
||
// 例如:从 API 获取数据、更新时间等
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 增加点击次数
|
||
/// </summary>
|
||
public void IncrementClickCount()
|
||
{
|
||
ClickCount++;
|
||
Settings.SetValue("ClickCount", ClickCount);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 组件说明
|
||
|
||
- **ComponentBase** - 所有组件的基类,提供基础功能
|
||
- **属性通知** - 使用 `SetProperty` 自动触发 UI 更新
|
||
- **设置持久化** - 使用 `Settings` 保存和读取配置
|
||
- **InitializeAsync** - 组件创建时调用
|
||
- **UpdateAsync** - 定时调用(默认 1 秒),用于更新数据
|
||
|
||
## 步骤 5: 创建组件视图
|
||
|
||
编辑 `Views/SampleComponentView.axaml`,重命名为 `HelloWorldComponentView.axaml`:
|
||
|
||
```xml
|
||
<UserControl xmlns="https://github.com/avaloniaui"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
xmlns:vm="using:HelloWorldPlugin.ViewModels"
|
||
x:Class="HelloWorldPlugin.Views.HelloWorldComponentView"
|
||
x:DataType="vm:HelloWorldComponentViewModel">
|
||
|
||
<!-- 组件容器 -->
|
||
<Border Background="{DynamicResource CardBackgroundBrush}"
|
||
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
|
||
Padding="20"
|
||
BorderBrush="{DynamicResource CardBorderBrush}"
|
||
BorderThickness="1">
|
||
|
||
<StackPanel Spacing="12">
|
||
|
||
<!-- 标题 -->
|
||
<TextBlock Text="{Binding Component.Name}"
|
||
FontSize="18"
|
||
FontWeight="Bold"
|
||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||
|
||
<!-- 问候语 -->
|
||
<TextBlock Text="{Binding Component.Greeting}"
|
||
FontSize="16"
|
||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||
TextWrapping="Wrap" />
|
||
|
||
<!-- 点击次数 -->
|
||
<TextBlock Foreground="{DynamicResource TextFillColorTertiaryBrush}">
|
||
<Run Text="Clicks: " />
|
||
<Run Text="{Binding Component.ClickCount}" FontWeight="Bold" />
|
||
</TextBlock>
|
||
|
||
<!-- 按钮 -->
|
||
<Button Content="Click Me!"
|
||
Command="{Binding ClickCommand}"
|
||
HorizontalAlignment="Stretch"
|
||
Padding="12,8" />
|
||
|
||
</StackPanel>
|
||
|
||
</Border>
|
||
|
||
</UserControl>
|
||
```
|
||
|
||
编辑对应的代码后台 `HelloWorldComponentView.axaml.cs`:
|
||
|
||
```csharp
|
||
using Avalonia.Controls;
|
||
|
||
namespace HelloWorldPlugin.Views;
|
||
|
||
public partial class HelloWorldComponentView : UserControl
|
||
{
|
||
public HelloWorldComponentView()
|
||
{
|
||
InitializeComponent();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 视图说明
|
||
|
||
- **动态资源** - 使用 `{DynamicResource}` 适配主题
|
||
- **圆角规范** - 使用 `DesignCornerRadiusComponent` 保持一致性
|
||
- **数据绑定** - 使用 `{Binding}` 绑定到 ViewModel
|
||
- **响应式布局** - 使用 `StackPanel` 自动布局
|
||
|
||
## 步骤 6: 创建视图模型
|
||
|
||
编辑 `ViewModels/SampleComponentViewModel.cs`,重命名为 `HelloWorldComponentViewModel.cs`:
|
||
|
||
```csharp
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using HelloWorldPlugin.Components;
|
||
|
||
namespace HelloWorldPlugin.ViewModels;
|
||
|
||
/// <summary>
|
||
/// Hello World 组件视图模型
|
||
/// </summary>
|
||
public partial class HelloWorldComponentViewModel : ObservableObject
|
||
{
|
||
[ObservableProperty]
|
||
private HelloWorldComponent _component;
|
||
|
||
public HelloWorldComponentViewModel(HelloWorldComponent component)
|
||
{
|
||
_component = component;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 点击按钮命令
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void Click()
|
||
{
|
||
Component.IncrementClickCount();
|
||
|
||
// 更新问候语
|
||
var greetings = new[]
|
||
{
|
||
"Hello, World!",
|
||
"你好,世界!",
|
||
"Bonjour le monde!",
|
||
"Hola, Mundo!",
|
||
"Привет, мир!",
|
||
"こんにちは、世界!"
|
||
};
|
||
|
||
var index = Component.ClickCount % greetings.Length;
|
||
Component.Greeting = greetings[index];
|
||
|
||
// 保存问候语
|
||
Component.Settings.SetValue("Greeting", Component.Greeting);
|
||
}
|
||
}
|
||
```
|
||
|
||
### ViewModel 说明
|
||
|
||
- **ObservableObject** - MVVM Toolkit 基类,提供属性通知
|
||
- **[ObservableProperty]** - 自动生成属性和通知代码
|
||
- **[RelayCommand]** - 自动生成命令
|
||
- **业务逻辑** - 处理用户交互和数据更新
|
||
|
||
## 步骤 7: 构建插件
|
||
|
||
```powershell
|
||
# 构建项目
|
||
dotnet build -c Release
|
||
|
||
# 查看输出
|
||
dir bin\Release\net10.0\
|
||
```
|
||
|
||
输出目录应包含:
|
||
- `HelloWorldPlugin.dll` - 主程序集
|
||
- `plugin.json` - 插件清单
|
||
- `Assets/` - 资源文件
|
||
- 依赖的 DLL 文件
|
||
|
||
## 步骤 8: 安装和测试
|
||
|
||
### 方法 1: 复制到插件目录
|
||
|
||
```powershell
|
||
# 创建插件目录
|
||
$pluginDir = "$env:LOCALAPPDATA\LanMountainDesktop\plugins\HelloWorldPlugin"
|
||
New-Item -ItemType Directory -Path $pluginDir -Force
|
||
|
||
# 复制文件
|
||
Copy-Item -Path "bin\Release\net10.0\*" -Destination $pluginDir -Recurse -Force
|
||
|
||
# 启动宿主应用
|
||
# (从开始菜单或安装目录启动)
|
||
```
|
||
|
||
### 方法 2: 使用符号链接(开发模式)
|
||
|
||
```powershell
|
||
# 需要管理员权限
|
||
$pluginDir = "$env:LOCALAPPDATA\LanMountainDesktop\plugins\HelloWorldPlugin"
|
||
$buildDir = "$(pwd)\bin\Release\net10.0"
|
||
|
||
New-Item -ItemType SymbolicLink -Path $pluginDir -Target $buildDir -Force
|
||
```
|
||
|
||
### 验证插件加载
|
||
|
||
1. 启动阑山桌面
|
||
2. 查看日志文件:
|
||
```powershell
|
||
Get-Content "$env:LOCALAPPDATA\LanMountainDesktop\logs\latest.log" -Tail 50
|
||
```
|
||
3. 应该看到类似输出:
|
||
```
|
||
[INFO] Hello World Plugin is initializing...
|
||
[INFO] HelloWorldComponent registered successfully
|
||
```
|
||
|
||
### 添加组件到桌面
|
||
|
||
1. 右键点击桌面空白处
|
||
2. 选择"添加组件"
|
||
3. 找到"Hello World"组件
|
||
4. 点击添加
|
||
5. 组件应该出现在桌面上
|
||
|
||
### 测试功能
|
||
|
||
1. 点击"Click Me!"按钮
|
||
2. 观察点击次数增加
|
||
3. 观察问候语在不同语言间切换
|
||
4. 重启应用,验证设置持久化
|
||
|
||
## 步骤 9: 调试插件
|
||
|
||
### 附加调试器
|
||
|
||
1. 启动阑山桌面应用
|
||
2. 在 Visual Studio 中:
|
||
- 选择"调试" → "附加到进程"
|
||
- 找到 `LanMountainDesktop.exe`
|
||
- 点击"附加"
|
||
3. 在插件代码中设置断点
|
||
4. 触发相应功能(如点击按钮)
|
||
|
||
### 查看日志
|
||
|
||
```powershell
|
||
# 实时查看日志
|
||
Get-Content "$env:LOCALAPPDATA\LanMountainDesktop\logs\latest.log" -Wait -Tail 50
|
||
```
|
||
|
||
### 常见问题
|
||
|
||
#### 插件没有加载
|
||
|
||
**检查清单**:
|
||
1. `plugin.json` 存在且格式正确
|
||
2. 插件 DLL 文件存在
|
||
3. 查看日志中的错误信息
|
||
4. 确认插件 ID 唯一
|
||
|
||
#### 组件没有显示
|
||
|
||
**检查清单**:
|
||
1. 组件已正确注册
|
||
2. 组件 ID 唯一
|
||
3. 视图文件正确编译为 AvaloniaResource
|
||
4. DataContext 正确设置
|
||
|
||
#### 按钮点击没有响应
|
||
|
||
**检查清单**:
|
||
1. Command 绑定正确
|
||
2. ViewModel 方法存在
|
||
3. 查看日志中的异常信息
|
||
|
||
## 下一步
|
||
|
||
恭喜!你已经创建了第一个插件。接下来可以:
|
||
|
||
- [插件生命周期](../02-核心概念/01-插件生命周期.md) - 深入理解插件运行机制
|
||
- [组件系统详解](../02-核心概念/02-组件系统.md) - 学习更多组件功能
|
||
- [设置系统](../02-核心概念/03-设置系统.md) - 添加设置页面
|
||
- [实战案例](../04-实战案例/01-天气组件.md) - 学习完整的实战案例
|
||
|
||
## 完整代码仓库
|
||
|
||
本教程的完整代码可以在以下位置找到:
|
||
|
||
- GitHub: https://github.com/HelloWRC/LanMountainDesktop.SamplePlugin
|
||
|
||
## 小结
|
||
|
||
在这个教程中,你学会了:
|
||
|
||
- ✅ 使用模板创建插件项目
|
||
- ✅ 配置插件清单
|
||
- ✅ 实现插件入口类
|
||
- ✅ 创建桌面组件(模型、视图、视图模型)
|
||
- ✅ 注册组件到宿主
|
||
- ✅ 使用设置系统持久化数据
|
||
- ✅ 构建、安装和测试插件
|
||
- ✅ 调试插件代码
|
||
|
||
这是一个完整但简单的插件,展示了插件开发的基本流程。在实际项目中,你可以添加更多功能,如网络请求、定时任务、复杂 UI 等。
|