Files
LanMountainDesktop/docs/01-插件开发/01-快速开始/02-创建第一个插件.md
2026-06-08 12:18:58 +08:00

14 KiB
Raw Permalink Blame History

创建第一个插件

通过这个教程,你将在 15 分钟内创建一个简单但功能完整的插件。

学习目标

  • 使用模板创建插件项目
  • 实现插件入口类
  • 创建一个简单的桌面组件
  • 注册组件到宿主
  • 运行和测试插件

前置准备

确保你已经:

  • 安装了 .NET 10 SDK
  • 安装了插件模板(参考 环境准备
  • 有一个支持 C# 的 IDE

步骤 1: 创建项目

使用模板创建

# 创建新插件项目
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,修改基本信息:

{
  "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

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

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

<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

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

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: 构建插件

# 构建项目
dotnet build -c Release

# 查看输出
dir bin\Release\net10.0\

输出目录应包含:

  • HelloWorldPlugin.dll - 主程序集
  • plugin.json - 插件清单
  • Assets/ - 资源文件
  • 依赖的 DLL 文件

步骤 8: 安装和测试

方法 1: 复制到插件目录

# 创建插件目录
$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: 使用符号链接(开发模式)

# 需要管理员权限
$pluginDir = "$env:LOCALAPPDATA\LanMountainDesktop\plugins\HelloWorldPlugin"
$buildDir = "$(pwd)\bin\Release\net10.0"

New-Item -ItemType SymbolicLink -Path $pluginDir -Target $buildDir -Force

验证插件加载

  1. 启动阑山桌面
  2. 查看日志文件:
    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. 触发相应功能(如点击按钮)

查看日志

# 实时查看日志
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. 查看日志中的异常信息

下一步

恭喜!你已经创建了第一个插件。接下来可以:

完整代码仓库

本教程的完整代码可以在以下位置找到:

小结

在这个教程中,你学会了:

  • 使用模板创建插件项目
  • 配置插件清单
  • 实现插件入口类
  • 创建桌面组件(模型、视图、视图模型)
  • 注册组件到宿主
  • 使用设置系统持久化数据
  • 构建、安装和测试插件
  • 调试插件代码

这是一个完整但简单的插件,展示了插件开发的基本流程。在实际项目中,你可以添加更多功能,如网络请求、定时任务、复杂 UI 等。