add:插件系统

This commit is contained in:
2026-04-05 14:06:49 +08:00
parent 069a478559
commit 1fca17d557
31 changed files with 3361 additions and 1438 deletions
@@ -1,92 +0,0 @@
using System.Windows.Controls;
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
/// 增强的插件基类,提供对插件服务的访问和基本实现
/// </summary>
public abstract class EnhancedPluginBase : PluginBase, IEnhancedPlugin
{
/// <summary>
/// 插件服务实例
/// </summary>
public IPluginService PluginService { get; private set; }
/// <summary>
/// 构造函数
/// </summary>
protected EnhancedPluginBase()
{
PluginService = PluginServiceManager.Instance;
}
/// <summary>
/// 插件启动时调用,在Initialize之后
/// </summary>
public virtual void OnStartup()
{
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
}
/// <summary>
/// 插件关闭时调用,在Cleanup之前
/// </summary>
public virtual void OnShutdown()
{
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
}
/// <summary>
/// 获取插件的菜单项
/// </summary>
/// <returns>菜单项集合</returns>
public virtual MenuItem[] GetMenuItems()
{
return new MenuItem[0];
}
/// <summary>
/// 获取插件的工具栏按钮
/// </summary>
/// <returns>工具栏按钮集合</returns>
public virtual Button[] GetToolbarButtons()
{
return new Button[0];
}
/// <summary>
/// 获取插件的状态栏信息
/// </summary>
/// <returns>状态栏信息</returns>
public virtual string GetStatusBarInfo()
{
return $"{Name} v{Version} - {(IsEnabled ? "" : "")}";
}
/// <summary>
/// 插件配置变更时调用
/// </summary>
public virtual void OnConfigurationChanged()
{
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
}
/// <summary>
/// 重写初始化方法,调用OnStartup
/// </summary>
public override void Initialize()
{
base.Initialize();
OnStartup();
}
/// <summary>
/// 重写清理方法,调用OnShutdown
/// </summary>
public override void Cleanup()
{
OnShutdown();
base.Cleanup();
}
}
}
@@ -1,241 +0,0 @@
using System.Windows.Controls;
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
/// 增强的插件基类 V2,提供对三个专门服务接口的访问
/// 插件开发者可以根据需要选择性地使用这些服务
/// </summary>
public abstract class EnhancedPluginBaseV2 : PluginBase, IEnhancedPlugin
{
/// <summary>
/// 获取服务实例
/// </summary>
public IGetService GetService { get; private set; }
/// <summary>
/// 窗口服务实例
/// </summary>
public IWindowService WindowService { get; private set; }
/// <summary>
/// 操作服务实例
/// </summary>
public IActionService ActionService { get; private set; }
/// <summary>
/// 插件服务实例(兼容性)
/// </summary>
public IPluginService PluginService { get; private set; }
/// <summary>
/// 构造函数
/// </summary>
protected EnhancedPluginBaseV2()
{
// 初始化所有服务实例
PluginService = PluginServiceManager.Instance;
GetService = PluginServiceManager.Instance;
WindowService = PluginServiceManager.Instance;
ActionService = PluginServiceManager.Instance;
}
/// <summary>
/// 插件启动时调用,在Initialize之后
/// </summary>
public virtual void OnStartup()
{
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
}
/// <summary>
/// 插件关闭时调用,在Cleanup之前
/// </summary>
public virtual void OnShutdown()
{
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
}
/// <summary>
/// 获取插件的菜单项
/// </summary>
/// <returns>菜单项集合</returns>
public virtual MenuItem[] GetMenuItems()
{
return new MenuItem[0];
}
/// <summary>
/// 获取插件的工具栏按钮
/// </summary>
/// <returns>工具栏按钮集合</returns>
public virtual Button[] GetToolbarButtons()
{
return new Button[0];
}
/// <summary>
/// 获取插件的状态栏信息
/// </summary>
/// <returns>状态栏信息</returns>
public virtual string GetStatusBarInfo()
{
return $"{Name} v{Version} - {(IsEnabled ? "" : "")}";
}
/// <summary>
/// 插件配置变更时调用
/// </summary>
public virtual void OnConfigurationChanged()
{
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
}
#region 便
/// <summary>
/// 显示通知消息
/// </summary>
/// <param name="message">消息内容</param>
/// <param name="type">消息类型</param>
protected void ShowNotification(string message, NotificationType type = NotificationType.Info)
{
WindowService.ShowNotification(message, type);
}
/// <summary>
/// 显示确认对话框
/// </summary>
/// <param name="message">消息内容</param>
/// <param name="title">标题</param>
/// <returns>用户选择结果</returns>
protected bool ShowConfirmDialog(string message, string title = "确认")
{
return WindowService.ShowConfirmDialog(message, title);
}
/// <summary>
/// 显示输入对话框
/// </summary>
/// <param name="message">提示消息</param>
/// <param name="title">标题</param>
/// <param name="defaultValue">默认值</param>
/// <returns>用户输入内容</returns>
protected string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
{
return WindowService.ShowInputDialog(message, title, defaultValue);
}
/// <summary>
/// 获取系统设置
/// </summary>
/// <typeparam name="T">设置类型</typeparam>
/// <param name="key">设置键</param>
/// <param name="defaultValue">默认值</param>
/// <returns>设置值</returns>
protected T GetSetting<T>(string key, T defaultValue = default(T))
{
return GetService.GetSetting(key, defaultValue);
}
/// <summary>
/// 设置系统设置
/// </summary>
/// <typeparam name="T">设置类型</typeparam>
/// <param name="key">设置键</param>
/// <param name="value">设置值</param>
protected void SetSetting<T>(string key, T value)
{
ActionService.SetSetting(key, value);
}
/// <summary>
/// 保存设置
/// </summary>
protected void SaveSettings()
{
ActionService.SaveSettings();
}
/// <summary>
/// 清除当前画布
/// </summary>
protected void ClearCanvas()
{
ActionService.ClearCanvas();
}
/// <summary>
/// 撤销操作
/// </summary>
protected void Undo()
{
ActionService.Undo();
}
/// <summary>
/// 重做操作
/// </summary>
protected void Redo()
{
ActionService.Redo();
}
/// <summary>
/// 检查是否可以撤销
/// </summary>
protected bool CanUndo => GetService.CanUndo;
/// <summary>
/// 检查是否可以重做
/// </summary>
protected bool CanRedo => GetService.CanRedo;
/// <summary>
/// 获取当前绘制模式
/// </summary>
protected int CurrentDrawingMode => GetService.CurrentDrawingMode;
/// <summary>
/// 设置绘制模式
/// </summary>
/// <param name="mode">绘制模式</param>
protected void SetDrawingMode(int mode)
{
ActionService.SetDrawingMode(mode);
}
/// <summary>
/// 注册事件处理器
/// </summary>
/// <param name="eventName">事件名称</param>
/// <param name="handler">事件处理器</param>
protected void RegisterEventHandler(string eventName, System.EventHandler handler)
{
ActionService.RegisterEventHandler(eventName, handler);
}
/// <summary>
/// 注销事件处理器
/// </summary>
/// <param name="eventName">事件名称</param>
/// <param name="handler">事件处理器</param>
protected void UnregisterEventHandler(string eventName, System.EventHandler handler)
{
ActionService.UnregisterEventHandler(eventName, handler);
}
/// <summary>
/// 触发事件
/// </summary>
/// <param name="eventName">事件名称</param>
/// <param name="sender">事件发送者</param>
/// <param name="args">事件参数</param>
protected void TriggerEvent(string eventName, object sender, System.EventArgs args)
{
ActionService.TriggerEvent(eventName, sender, args);
}
#endregion
}
}
@@ -14,6 +14,8 @@ namespace Ink_Canvas.Helpers.Plugins
private readonly Version _pluginVersion;
private bool _isInitialized;
public override string PluginStateKey => "ICCPP:" + (_pluginPath ?? string.Empty);
/// <summary>
/// 创建 ICCPP 插件适配器
/// </summary>
@@ -1,48 +0,0 @@
using System.Windows.Controls;
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
/// 增强的插件接口,提供对插件服务的访问
/// </summary>
public interface IEnhancedPlugin : IPlugin
{
/// <summary>
/// 获取插件服务实例
/// </summary>
IPluginService PluginService { get; }
/// <summary>
/// 插件启动时调用,在Initialize之后
/// </summary>
void OnStartup();
/// <summary>
/// 插件关闭时调用,在Cleanup之前
/// </summary>
void OnShutdown();
/// <summary>
/// 获取插件的菜单项
/// </summary>
/// <returns>菜单项集合</returns>
MenuItem[] GetMenuItems();
/// <summary>
/// 获取插件的工具栏按钮
/// </summary>
/// <returns>工具栏按钮集合</returns>
Button[] GetToolbarButtons();
/// <summary>
/// 获取插件的状态栏信息
/// </summary>
/// <returns>状态栏信息</returns>
string GetStatusBarInfo();
/// <summary>
/// 插件配置变更时调用
/// </summary>
void OnConfigurationChanged();
}
}
+2 -2
View File
@@ -20,12 +20,12 @@ namespace Ink_Canvas.Helpers.Plugins
/// <summary>
/// 获取当前画布
/// </summary>
InkCanvas CurrentCanvas { get; }
global::System.Windows.Controls.InkCanvas CurrentCanvas { get; }
/// <summary>
/// 获取所有画布页面
/// </summary>
List<Canvas> AllCanvasPages { get; }
List<global::System.Windows.Controls.Canvas> AllCanvasPages { get; }
/// <summary>
/// 获取当前页面索引
+5
View File
@@ -34,6 +34,11 @@ namespace Ink_Canvas.Helpers.Plugins
/// </summary>
public string Id { get; protected set; }
/// <summary>
/// 写入 <see cref="PluginManager"/> 配置时使用的稳定键(默认同类型全名;多实例类型如 SDK 目录插件应重写)。
/// </summary>
public virtual string PluginStateKey => GetType().FullName;
/// <summary>
/// 插件路径
/// </summary>
@@ -1,273 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
/// 插件配置管理器,允许插件管理自己的配置
/// </summary>
public class PluginConfigurationManager
{
private static readonly string PluginConfigDirectory = Path.Combine(App.RootPath, "PluginConfigs");
private static readonly Dictionary<string, Dictionary<string, object>> _pluginConfigs = new Dictionary<string, Dictionary<string, object>>();
private static readonly object _lockObject = new object();
static PluginConfigurationManager()
{
// 确保配置目录存在
if (!Directory.Exists(PluginConfigDirectory))
{
Directory.CreateDirectory(PluginConfigDirectory);
}
}
/// <summary>
/// 获取插件配置值
/// </summary>
/// <typeparam name="T">配置值类型</typeparam>
/// <param name="pluginName">插件名称</param>
/// <param name="key">配置键</param>
/// <param name="defaultValue">默认值</param>
/// <returns>配置值</returns>
public static T GetConfiguration<T>(string pluginName, string key, T defaultValue = default(T))
{
lock (_lockObject)
{
try
{
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
{
if (pluginConfig.TryGetValue(key, out var value))
{
if (value is T typedValue)
{
return typedValue;
}
// 尝试类型转换
try
{
return (T)Convert.ChangeType(value, typeof(T));
}
catch
{
return defaultValue;
}
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"获取插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
}
return defaultValue;
}
}
/// <summary>
/// 设置插件配置值
/// </summary>
/// <typeparam name="T">配置值类型</typeparam>
/// <param name="pluginName">插件名称</param>
/// <param name="key">配置键</param>
/// <param name="value">配置值</param>
public static void SetConfiguration<T>(string pluginName, string key, T value)
{
lock (_lockObject)
{
try
{
if (!_pluginConfigs.ContainsKey(pluginName))
{
_pluginConfigs[pluginName] = new Dictionary<string, object>();
}
_pluginConfigs[pluginName][key] = value;
// 异步保存配置
Task.Run(() => SavePluginConfiguration(pluginName));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"设置插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 删除插件配置
/// </summary>
/// <param name="pluginName">插件名称</param>
/// <param name="key">配置键</param>
public static void RemoveConfiguration(string pluginName, string key)
{
lock (_lockObject)
{
try
{
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
{
if (pluginConfig.Remove(key))
{
// 异步保存配置
Task.Run(() => SavePluginConfiguration(pluginName));
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"删除插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 获取插件的所有配置
/// </summary>
/// <param name="pluginName">插件名称</param>
/// <returns>配置字典</returns>
public static Dictionary<string, object> GetAllConfigurations(string pluginName)
{
lock (_lockObject)
{
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
{
return new Dictionary<string, object>(pluginConfig);
}
return new Dictionary<string, object>();
}
}
/// <summary>
/// 清除插件的所有配置
/// </summary>
/// <param name="pluginName">插件名称</param>
public static void ClearAllConfigurations(string pluginName)
{
lock (_lockObject)
{
try
{
if (_pluginConfigs.Remove(pluginName))
{
// 删除配置文件
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
if (File.Exists(configFile))
{
File.Delete(configFile);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清除插件 {pluginName} 所有配置时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 加载插件配置
/// </summary>
/// <param name="pluginName">插件名称</param>
public static void LoadPluginConfiguration(string pluginName)
{
try
{
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
if (File.Exists(configFile))
{
string json = File.ReadAllText(configFile);
var config = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
lock (_lockObject)
{
_pluginConfigs[pluginName] = config ?? new Dictionary<string, object>();
}
LogHelper.WriteLogToFile($"已加载插件 {pluginName} 的配置");
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"加载插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 保存插件配置
/// </summary>
/// <param name="pluginName">插件名称</param>
private static void SavePluginConfiguration(string pluginName)
{
try
{
Dictionary<string, object> pluginConfig;
lock (_lockObject)
{
if (!_pluginConfigs.TryGetValue(pluginName, out pluginConfig))
{
return;
}
}
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
string json = JsonConvert.SerializeObject(pluginConfig, Formatting.Indented);
File.WriteAllText(configFile, json);
LogHelper.WriteLogToFile($"已保存插件 {pluginName} 的配置");
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"保存插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 加载所有插件的配置
/// </summary>
public static void LoadAllPluginConfigurations()
{
try
{
if (Directory.Exists(PluginConfigDirectory))
{
string[] configFiles = Directory.GetFiles(PluginConfigDirectory, "*.json");
foreach (string configFile in configFiles)
{
string pluginName = Path.GetFileNameWithoutExtension(configFile);
LoadPluginConfiguration(pluginName);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"加载所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 保存所有插件的配置
/// </summary>
public static void SaveAllPluginConfigurations()
{
try
{
lock (_lockObject)
{
foreach (string pluginName in _pluginConfigs.Keys)
{
SavePluginConfiguration(pluginName);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"保存所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
}
}
+171 -8
View File
@@ -1,4 +1,6 @@
using Ink_Canvas.Windows;
using InkCanvasForClass.PluginHost;
using InkCanvasForClass.PluginSdk;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@@ -71,6 +73,23 @@ namespace Ink_Canvas.Helpers.Plugins
/// </summary>
private Dictionary<string, string> _pluginHashes = new Dictionary<string, string>();
/// <summary>
/// SDK 插件程序集(按主 DLL 路径缓存)
/// </summary>
private readonly Dictionary<string, Assembly> _sdkAssembliesByPath =
new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// 已加载的 SDK 插件核心实例(键为插件目录名)
/// </summary>
private readonly Dictionary<string, IInkCanvasPlugin> _sdkCoreByFolderId =
new Dictionary<string, IInkCanvasPlugin>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// SDK 插件登记的菜单 / 工具栏 / 设置页,供主窗口挂载。
/// </summary>
public CollectingPluginRegistry ExtensionRegistry { get; } = new CollectingPluginRegistry();
private PluginManager()
{
// 确保插件目录存在
@@ -102,6 +121,60 @@ namespace Ink_Canvas.Helpers.Plugins
};
}
private static string GetPluginStateKey(IPlugin plugin)
{
if (plugin is PluginBase pluginBase)
{
return pluginBase.PluginStateKey;
}
return plugin.GetType().FullName;
}
public IInkCanvasPlugin GetSdkPluginInstance(string folderId)
{
if (string.IsNullOrEmpty(folderId))
{
return null;
}
return _sdkCoreByFolderId.TryGetValue(folderId, out var core) ? core : null;
}
public IReadOnlyList<IInkCanvasPlugin> GetAllSdkPluginInstances()
{
return _sdkCoreByFolderId.Values.ToList();
}
public IInkCanvasPlugin GetSdkPluginByName(string name)
{
return _sdkCoreByFolderId.Values.FirstOrDefault(p => p.Name == name);
}
public void SetSdkPluginEnabledByName(string name, bool enable)
{
var adapter = Plugins.OfType<SdkPluginAdapter>().FirstOrDefault(a =>
a.Name == name || string.Equals(a.FolderId, name, StringComparison.OrdinalIgnoreCase));
if (adapter == null)
{
return;
}
TogglePlugin(adapter, enable);
}
public void UnloadSdkPluginByName(string name)
{
var adapter = Plugins.OfType<SdkPluginAdapter>().FirstOrDefault(a =>
a.Name == name || string.Equals(a.FolderId, name, StringComparison.OrdinalIgnoreCase));
if (adapter == null)
{
return;
}
UnloadPlugin(adapter, true);
}
/// <summary>
/// 初始化插件系统
/// </summary>
@@ -123,6 +196,10 @@ namespace Ink_Canvas.Helpers.Plugins
LogHelper.WriteLogToFile("正在加载外部插件...");
LoadExternalPlugins();
// 加载 Plugins 子目录中的 SDK 插件(IInkCanvasPlugin
LogHelper.WriteLogToFile("正在加载 SDK 插件(子目录)...");
LoadSdkPluginsFromSubfolders();
// 启用已配置为启用的插件
LogHelper.WriteLogToFile("正在应用配置的插件状态...");
EnableConfiguredPlugins();
@@ -214,6 +291,87 @@ namespace Ink_Canvas.Helpers.Plugins
}
}
/// <summary>
/// 扫描 <see cref="PluginsDirectory"/> 下一层子目录,加载实现 <see cref="IInkCanvasPlugin"/> 的 SDK 插件。
/// </summary>
private void LoadSdkPluginsFromSubfolders()
{
try
{
if (!Directory.Exists(PluginsDirectory))
{
return;
}
foreach (var pluginDir in Directory.GetDirectories(PluginsDirectory))
{
var folderId = Path.GetFileName(pluginDir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
if (string.IsNullOrEmpty(folderId))
{
continue;
}
var dllFiles = Directory.GetFiles(pluginDir, "*.dll", SearchOption.AllDirectories);
var mainDll = dllFiles.FirstOrDefault(f =>
f.IndexOf($"{Path.DirectorySeparatorChar}lib{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) < 0 &&
f.IndexOf($"{Path.AltDirectorySeparatorChar}lib{Path.AltDirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) < 0 &&
f.IndexOf($"{Path.DirectorySeparatorChar}runtimes{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) < 0 &&
f.IndexOf($"{Path.AltDirectorySeparatorChar}runtimes{Path.AltDirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) < 0);
if (mainDll == null)
{
continue;
}
try
{
if (!_sdkAssembliesByPath.TryGetValue(mainDll, out var assembly))
{
assembly = Assembly.LoadFrom(mainDll);
_sdkAssembliesByPath[mainDll] = assembly;
}
Type pluginType = null;
try
{
pluginType = assembly.GetTypes()
.FirstOrDefault(t =>
typeof(IInkCanvasPlugin).IsAssignableFrom(t) &&
!t.IsAbstract &&
!t.IsInterface &&
t.IsClass);
}
catch (ReflectionTypeLoadException ex)
{
LogHelper.WriteLogToFile($"枚举 SDK 插件类型失败 {folderId}: {ex.Message}", LogHelper.LogType.Error);
}
if (pluginType == null)
{
continue;
}
var core = (IInkCanvasPlugin)Activator.CreateInstance(pluginType);
_sdkCoreByFolderId[folderId] = core;
var adapter = new SdkPluginAdapter(folderId, core, mainDll);
adapter.Initialize();
Plugins.Add(adapter);
LogHelper.WriteLogToFile($"已加载 SDK 插件(子目录): {core.Name} — {folderId}");
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"加载 SDK 插件目录 {folderId} 失败: {ex.Message}", LogHelper.LogType.Error);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"扫描 SDK 插件子目录失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 加载单个外部插件
/// </summary>
@@ -349,7 +507,7 @@ namespace Ink_Canvas.Helpers.Plugins
{
try
{
string pluginTypeName = plugin.GetType().FullName;
string pluginTypeName = GetPluginStateKey(plugin);
// 检查配置中的插件状态
if (PluginStates.TryGetValue(pluginTypeName, out bool enabled))
@@ -434,7 +592,7 @@ namespace Ink_Canvas.Helpers.Plugins
{
if (sender is IPlugin plugin)
{
string pluginTypeName = plugin.GetType().FullName;
string pluginTypeName = GetPluginStateKey(plugin);
// 更新配置状态
if (!PluginStates.ContainsKey(pluginTypeName) || PluginStates[pluginTypeName] != isEnabled)
@@ -537,7 +695,7 @@ namespace Ink_Canvas.Helpers.Plugins
// 保存插件的当前状态
bool wasEnabled = plugin.IsEnabled;
string pluginTypeName = plugin.GetType().FullName;
string pluginTypeName = GetPluginStateKey(plugin);
// 卸载插件
UnloadPlugin(plugin);
@@ -564,7 +722,7 @@ namespace Ink_Canvas.Helpers.Plugins
}
// 更新配置(如果类型名称变化)
string newPluginTypeName = newPlugin.GetType().FullName;
string newPluginTypeName = GetPluginStateKey(newPlugin);
if (pluginTypeName != newPluginTypeName && PluginStates.ContainsKey(pluginTypeName))
{
bool state = PluginStates[pluginTypeName];
@@ -614,10 +772,15 @@ namespace Ink_Canvas.Helpers.Plugins
// 从插件集合中移除
Plugins.Remove(plugin);
if (plugin is SdkPluginAdapter sdkAdapter)
{
_sdkCoreByFolderId.Remove(sdkAdapter.FolderId);
}
// 从配置中移除(如果需要)
if (removeFromConfig && plugin.GetType() != null)
{
string pluginTypeName = plugin.GetType().FullName;
string pluginTypeName = GetPluginStateKey(plugin);
if (PluginStates.ContainsKey(pluginTypeName))
{
PluginStates.Remove(pluginTypeName);
@@ -706,7 +869,7 @@ namespace Ink_Canvas.Helpers.Plugins
// 记录插件信息,用于日志
string pluginName = plugin.Name;
string pluginTypeName = plugin.GetType().FullName;
string pluginTypeName = GetPluginStateKey(plugin);
LogHelper.WriteLogToFile($"开始切换插件 {pluginName} 状态为: {(enable ? "" : "")}");
@@ -919,7 +1082,7 @@ namespace Ink_Canvas.Helpers.Plugins
if (!string.IsNullOrEmpty(pluginPath) && File.Exists(pluginPath))
{
// 记录插件类型名称,用于后续状态检查
string pluginTypeName = plugin.GetType().FullName;
string pluginTypeName = GetPluginStateKey(plugin);
bool targetState = enable;
// 使用调度器确保在UI线程执行热重载
@@ -1322,7 +1485,7 @@ namespace Ink_Canvas.Helpers.Plugins
// 对比配置,查找变更的插件
foreach (var plugin in Plugins.ToList()) // 创建副本进行遍历,避免集合修改异常
{
string pluginTypeName = plugin.GetType().FullName;
string pluginTypeName = GetPluginStateKey(plugin);
// 检查插件在配置中是否存在
if (PluginStates.TryGetValue(pluginTypeName, out bool shouldBeEnabled))
@@ -0,0 +1,25 @@
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
/// 在加载任何 SDK 插件之前初始化宿主上下文(实现 <see cref="IPluginContext"/> 与 <see cref="IPluginService"/>)。
/// </summary>
public static class PluginRuntime
{
private static PluginSdkHostContext _context;
public static PluginSdkHostContext SdkContext => _context;
/// <summary>与 <see cref="SdkContext"/> 相同实例,便于旧代码通过 <see cref="PluginServiceManager"/> 访问。</summary>
public static IPluginService Services => SdkContext != null ? (IPluginService)SdkContext : null;
public static void Initialize(MainWindow mainWindow)
{
if (_context == null)
{
_context = new PluginSdkHostContext();
}
_context.SetMainWindow(mainWindow);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,509 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
/// 插件服务管理器,实现IPluginService接口,提供对软件内部功能的访问
/// 兼容旧代码的薄门面:与 <see cref="PluginSdkHostContext"/> 为同一实例,实现 <see cref="IPluginService"/>。
/// </summary>
public class PluginServiceManager : IPluginService
public static class PluginServiceManager
{
private static PluginServiceManager _instance;
private MainWindow _mainWindow;
private Dictionary<string, EventHandler> _eventHandlers;
/// <summary>
/// 单例实例
/// </summary>
public static PluginServiceManager Instance
public static IPluginService Instance
{
get
{
if (_instance == null)
var ctx = PluginRuntime.SdkContext;
if (ctx == null)
{
_instance = new PluginServiceManager();
throw new InvalidOperationException("插件宿主尚未初始化:请先调用 PluginRuntime.Initialize(MainWindow)。");
}
return _instance;
return (IPluginService)ctx;
}
}
private PluginServiceManager()
{
_eventHandlers = new Dictionary<string, EventHandler>();
}
/// <summary>
/// 设置主窗口引用
/// </summary>
/// <param name="mainWindow">主窗口实例</param>
public void SetMainWindow(MainWindow mainWindow)
{
_mainWindow = mainWindow;
}
#region UI访问
public Window MainWindow => _mainWindow;
public InkCanvas CurrentCanvas => null; // 暂时返回null,避免访问权限问题
public List<Canvas> AllCanvasPages => new List<Canvas>(); // 暂时返回空列表
public int CurrentPageIndex => 0; // 暂时返回0
public int TotalPageCount => 0; // 暂时返回0
public FrameworkElement FloatingToolBar => _mainWindow?.ViewboxFloatingBar;
public FrameworkElement LeftPanel => _mainWindow?.BlackboardLeftSide;
public FrameworkElement RightPanel => _mainWindow?.BlackboardRightSide;
public FrameworkElement TopPanel => _mainWindow?.BorderTools;
public FrameworkElement BottomPanel => _mainWindow?.BorderSettings;
#endregion
#region
public int CurrentDrawingMode => 0; // 暂时返回0
public double CurrentInkWidth => 2.5; // 暂时返回默认值
public Color CurrentInkColor => Colors.Black; // 暂时返回默认值
public double CurrentHighlighterWidth => 20.0; // 暂时返回默认值
public int CurrentEraserSize => 2; // 暂时返回默认值
public int CurrentEraserType => 0; // 暂时返回默认值
public int CurrentEraserShape => 0; // 暂时返回默认值
public double CurrentInkAlpha => 255.0; // 暂时返回默认值
public int CurrentInkStyle => 0; // 暂时返回默认值
public string CurrentBackgroundColor => "#162924"; // 暂时返回默认值
#endregion
#region
public bool IsDarkTheme => false; // 暂时返回默认值
public bool IsWhiteboardMode => false; // 暂时返回默认值
public bool IsPPTMode => false; // 暂时返回默认值
public bool IsFullScreenMode => false; // 暂时返回默认值
public bool IsCanvasMode => true; // 暂时返回默认值
public bool IsSelectionMode => false; // 暂时返回默认值
public bool IsEraserMode => false; // 暂时返回默认值
public bool IsShapeDrawingMode => false; // 暂时返回默认值
public bool IsHighlighterMode => false; // 暂时返回默认值
#endregion
#region IGetService
public bool CanUndo => false; // 暂时返回默认值
public bool CanRedo => false; // 暂时返回默认值
public T GetSetting<T>(string key, T defaultValue = default(T))
{
// 暂时不实现,避免访问权限问题
return defaultValue;
}
public List<IPlugin> GetAllPlugins()
{
return new List<IPlugin>(PluginManager.Instance.Plugins);
}
public IPlugin GetPlugin(string pluginName)
{
return PluginManager.Instance.Plugins.FirstOrDefault(p => p.Name == pluginName);
}
#endregion
#region IWindowService
public void ShowSettingsWindow()
{
// 暂时不实现,避免访问权限问题
}
public void HideSettingsWindow()
{
// 暂时不实现,避免访问权限问题
}
public void ShowPluginSettingsWindow()
{
// 暂时不实现,避免访问权限问题
}
public void HidePluginSettingsWindow()
{
// 暂时不实现,避免访问权限问题
}
public void ShowHelpWindow()
{
// 暂时不实现,避免访问权限问题
}
public void HideHelpWindow()
{
// 暂时不实现,避免访问权限问题
}
public void ShowAboutWindow()
{
// 暂时不实现,避免访问权限问题
}
public void HideAboutWindow()
{
// 暂时不实现,避免访问权限问题
}
public void ShowNotification(string message, NotificationType type = NotificationType.Info)
{
// 暂时不实现,避免访问权限问题
}
public bool ShowConfirmDialog(string message, string title = "确认")
{
// 暂时不实现,避免访问权限问题
return false;
}
public string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
{
// 暂时不实现,避免访问权限问题
return defaultValue;
}
public void SetFullScreen(bool isFullScreen)
{
// 暂时不实现,避免访问权限问题
}
public void SetTopMost(bool isTopMost)
{
// 暂时不实现,避免访问权限问题
}
public void SetWindowVisibility(bool isVisible)
{
// 暂时不实现,避免访问权限问题
}
public void MinimizeWindow()
{
// 暂时不实现,避免访问权限问题
}
public void MaximizeWindow()
{
// 暂时不实现,避免访问权限问题
}
public void RestoreWindow()
{
// 暂时不实现,避免访问权限问题
}
public void CloseWindow()
{
// 暂时不实现,避免访问权限问题
}
public void SetWindowPosition(double x, double y)
{
// 暂时不实现,避免访问权限问题
}
public void SetWindowSize(double width, double height)
{
// 暂时不实现,避免访问权限问题
}
public (double x, double y) GetWindowPosition()
{
// 暂时不实现,避免访问权限问题
return (0, 0);
}
public (double width, double height) GetWindowSize()
{
// 暂时不实现,避免访问权限问题
return (800, 600);
}
#endregion
#region IActionService
public void ClearCanvas()
{
// 暂时不实现,避免访问权限问题
}
public void ClearAllCanvases()
{
// 暂时不实现,避免访问权限问题
}
public void AddNewPage()
{
// 暂时不实现,避免访问权限问题
}
public void DeleteCurrentPage()
{
// 暂时不实现,避免访问权限问题
}
public void SwitchToPage(int pageIndex)
{
// 暂时不实现,避免访问权限问题
}
public void NextPage()
{
// 暂时不实现,避免访问权限问题
}
public void PreviousPage()
{
// 暂时不实现,避免访问权限问题
}
public void SetDrawingMode(int mode)
{
// 暂时不实现,避免访问权限问题
}
public void SetInkWidth(double width)
{
// 暂时不实现,避免访问权限问题
}
public void SetInkColor(Color color)
{
// 暂时不实现,避免访问权限问题
}
public void SetHighlighterWidth(double width)
{
// 暂时不实现,避免访问权限问题
}
public void SetEraserSize(int size)
{
// 暂时不实现,避免访问权限问题
}
public void SetEraserType(int type)
{
// 暂时不实现,避免访问权限问题
}
public void SetEraserShape(int shape)
{
// 暂时不实现,避免访问权限问题
}
public void SetInkAlpha(double alpha)
{
// 暂时不实现,避免访问权限问题
}
public void SetInkStyle(int style)
{
// 暂时不实现,避免访问权限问题
}
public void SetBackgroundColor(string color)
{
// 暂时不实现,避免访问权限问题
}
public void SaveCanvas(string filePath)
{
// 暂时不实现,避免访问权限问题
}
public void LoadCanvas(string filePath)
{
// 暂时不实现,避免访问权限问题
}
public void ExportAsImage(string filePath, string format)
{
// 暂时不实现,避免访问权限问题
}
public void ExportAsPDF(string filePath)
{
// 暂时不实现,避免访问权限问题
}
public void Undo()
{
// 暂时不实现,避免访问权限问题
}
public void Redo()
{
// 暂时不实现,避免访问权限问题
}
public void SelectAll()
{
// 暂时不实现,避免访问权限问题
}
public void DeselectAll()
{
// 暂时不实现,避免访问权限问题
}
public void DeleteSelected()
{
// 暂时不实现,避免访问权限问题
}
public void CopySelected()
{
// 暂时不实现,避免访问权限问题
}
public void CutSelected()
{
// 暂时不实现,避免访问权限问题
}
public void Paste()
{
// 暂时不实现,避免访问权限问题
}
public void SetSetting<T>(string key, T value)
{
// 暂时不实现,避免访问权限问题
}
public void SaveSettings()
{
// 暂时不实现,避免访问权限问题
}
public void LoadSettings()
{
// 暂时不实现,避免访问权限问题
}
public void ResetSettings()
{
// 暂时不实现,避免访问权限问题
}
public void EnablePlugin(string pluginName)
{
var plugin = GetPlugin(pluginName);
if (plugin != null)
{
PluginManager.Instance.TogglePlugin(plugin, true);
}
}
public void DisablePlugin(string pluginName)
{
var plugin = GetPlugin(pluginName);
if (plugin != null)
{
PluginManager.Instance.TogglePlugin(plugin, false);
}
}
public void UnloadPlugin(string pluginName)
{
var plugin = GetPlugin(pluginName);
if (plugin != null)
{
PluginManager.Instance.UnloadPlugin(plugin);
}
}
public void RegisterEventHandler(string eventName, EventHandler handler)
{
if (!_eventHandlers.ContainsKey(eventName))
{
_eventHandlers[eventName] = handler;
}
else
{
_eventHandlers[eventName] += handler;
}
}
public void UnregisterEventHandler(string eventName, EventHandler handler)
{
if (_eventHandlers.ContainsKey(eventName))
{
_eventHandlers[eventName] -= handler;
}
}
public void TriggerEvent(string eventName, object sender, EventArgs args)
{
if (_eventHandlers.ContainsKey(eventName))
{
_eventHandlers[eventName]?.Invoke(sender, args);
}
}
public void RestartApplication()
{
// 暂时不实现,避免访问权限问题
}
public void ExitApplication()
{
// 暂时不实现,避免访问权限问题
}
public void CheckForUpdates()
{
// 暂时不实现,避免访问权限问题
}
public void OpenHelpDocument()
{
// 暂时不实现,避免访问权限问题
}
public void OpenAboutPage()
{
// 暂时不实现,避免访问权限问题
}
#endregion
}
}
}
@@ -1,276 +0,0 @@
using System;
using System.Windows;
using System.Windows.Controls;
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
/// 插件模板,用于开发者参考
/// 注意:实际开发时,请将此类移到单独的程序集中
/// </summary>
public class PluginTemplate : PluginBase
{
#region
/// <summary>
/// 插件名称
/// </summary>
public override string Name => "插件模板";
/// <summary>
/// 插件描述
/// </summary>
public override string Description => "这是一个插件开发模板,用于开发者参考。";
/// <summary>
/// 插件版本
/// </summary>
public override Version Version => new Version(1, 0, 0);
/// <summary>
/// 插件作者
/// </summary>
public override string Author => "Your Name";
/// <summary>
/// 是否为内置插件(外部插件请返回false)
/// </summary>
public override bool IsBuiltIn => false;
#endregion
#region
/// <summary>
/// 插件初始化
/// 在这里进行插件的初始化工作,如加载配置、注册事件等
/// </summary>
public override void Initialize()
{
// 先调用基类方法,这样会设置插件ID和记录日志
base.Initialize();
// TODO: 在这里进行插件初始化工作
// 示例:记录初始化信息
LogHelper.WriteLogToFile($"插件 {Name} 开始初始化");
// 示例:加载配置
LoadConfig();
// 示例:注册自定义事件
// MainWindow.Instance.SomeEvent += OnSomeEvent;
LogHelper.WriteLogToFile($"插件 {Name} 初始化完成");
}
/// <summary>
/// 启用插件
/// 在这里激活插件功能
/// </summary>
public override void Enable()
{
// 先调用基类方法,这样会设置插件状态和记录日志
base.Enable();
// TODO: 在这里启用插件功能
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
}
/// <summary>
/// 禁用插件
/// 在这里停用插件功能
/// </summary>
public override void Disable()
{
// 先调用基类方法,这样会设置插件状态和记录日志
base.Disable();
// TODO: 在这里禁用插件功能
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
}
/// <summary>
/// 清理资源
/// 在插件卸载时调用,清理资源
/// </summary>
public override void Cleanup()
{
// TODO: 在这里清理插件资源
// 示例:取消注册事件
// MainWindow.Instance.SomeEvent -= OnSomeEvent;
// 示例:保存配置
SaveConfig();
// 最后调用基类方法
base.Cleanup();
}
#endregion
#region
/// <summary>
/// 加载插件配置
/// </summary>
private void LoadConfig()
{
try
{
// TODO: 从文件或其他位置加载配置
// 示例:
// string configPath = Path.Combine(App.RootPath, "PluginConfigs", "YourPluginName.json");
// if (File.Exists(configPath))
// {
// string json = File.ReadAllText(configPath);
// YourConfig = Newtonsoft.Json.JsonConvert.DeserializeObject<YourConfigClass>(json);
// }
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"加载插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 保存插件配置
/// </summary>
private void SaveConfig()
{
try
{
// TODO: 保存配置到文件或其他位置
// 示例:
// string configDir = Path.Combine(App.RootPath, "PluginConfigs");
// if (!Directory.Exists(configDir))
// {
// Directory.CreateDirectory(configDir);
// }
// string configPath = Path.Combine(configDir, "YourPluginName.json");
// string json = Newtonsoft.Json.JsonConvert.SerializeObject(YourConfig, Newtonsoft.Json.Formatting.Indented);
// File.WriteAllText(configPath, json);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"保存插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
#region
/// <summary>
/// 获取插件设置界面
/// </summary>
/// <returns>插件设置界面</returns>
public override UserControl GetSettingsView()
{
// 创建插件设置界面
return new PluginTemplateSettingsControl();
}
#endregion
#region
// TODO: 在这里添加插件的具体功能方法
/// <summary>
/// 示例方法:执行一些功能
/// </summary>
public void DoSomething()
{
if (!IsEnabled) return;
try
{
// TODO: 实现你的功能
MessageBox.Show("插件功能执行示例", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"执行插件功能时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
}
/// <summary>
/// 插件设置控件
/// </summary>
public class PluginTemplateSettingsControl : UserControl
{
public PluginTemplateSettingsControl()
{
// 创建设置界面布局
var panel = new StackPanel
{
Margin = new Thickness(10)
};
// 添加标题
panel.Children.Add(new TextBlock
{
Text = "插件模板设置",
FontSize = 16,
FontWeight = FontWeights.Bold,
Margin = new Thickness(0, 0, 0, 10)
});
// 添加说明文字
panel.Children.Add(new TextBlock
{
Text = "这是一个示例设置界面,你可以在这里添加自己的设置控件。",
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(0, 0, 0, 15)
});
// 添加示例设置选项
var checkBox = new CheckBox
{
Content = "启用某项功能",
Margin = new Thickness(0, 0, 0, 10)
};
panel.Children.Add(checkBox);
// 添加文本输入框
panel.Children.Add(new TextBlock
{
Text = "设置项:",
Margin = new Thickness(0, 5, 0, 5)
});
panel.Children.Add(new TextBox
{
Margin = new Thickness(0, 0, 0, 10),
Width = 200,
HorizontalAlignment = HorizontalAlignment.Left
});
// 添加按钮
var button = new Button
{
Content = "保存设置",
Padding = new Thickness(10, 5, 10, 5),
Margin = new Thickness(0, 10, 0, 0),
HorizontalAlignment = HorizontalAlignment.Left
};
button.Click += (sender, e) =>
{
MessageBox.Show("设置已保存!", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
};
panel.Children.Add(button);
// 设置控件内容
Content = panel;
}
}
}
@@ -0,0 +1,133 @@
using System;
using System.Windows.Controls;
using InkCanvasForClass.PluginHost;
using InkCanvasForClass.PluginSdk;
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
/// 将基于 <see cref="IInkCanvasPlugin"/> 的外部插件适配为宿主统一的 <see cref="IPlugin"/>。
/// </summary>
public sealed class SdkPluginAdapter : PluginBase
{
private readonly IInkCanvasPlugin _core;
private readonly string _folderId;
public SdkPluginAdapter(string folderId, IInkCanvasPlugin core, string mainAssemblyPath)
{
_folderId = folderId ?? throw new ArgumentNullException(nameof(folderId));
_core = core ?? throw new ArgumentNullException(nameof(core));
PluginPath = mainAssemblyPath ?? string.Empty;
}
public string FolderId => _folderId;
public IInkCanvasPlugin Core => _core;
public override string PluginStateKey => "SdkFolder:" + _folderId;
public override string Name => _core.Name;
public override string Description => _core.Description;
public override Version Version => _core.Version;
public override string Author => _core.Author;
public override bool IsBuiltIn => false;
public override void Initialize()
{
base.Initialize();
var ctx = PluginRuntime.SdkContext;
if (ctx == null)
{
LogHelper.WriteLogToFile($"SDK 插件 {_core.Name} 初始化失败:宿主上下文未就绪", LogHelper.LogType.Error);
return;
}
_core.Initialize(ctx);
var registry = PluginManager.Instance.ExtensionRegistry;
registry.SetCurrentPluginId(_folderId);
try
{
if (_core is InkCanvasPluginBase pluginBase)
{
pluginBase.RegisterExtensions(registry);
}
}
finally
{
registry.SetCurrentPluginId(string.Empty);
}
}
public override void Enable()
{
if (IsEnabled)
{
return;
}
if (_core is InkCanvasPluginBase b)
{
b.IsEnabled = true;
}
else
{
_core.Start();
}
base.Enable();
}
public override void Disable()
{
if (!IsEnabled)
{
return;
}
if (_core is InkCanvasPluginBase b)
{
b.IsEnabled = false;
}
else
{
_core.Stop();
}
base.Disable();
}
public override UserControl GetSettingsView()
{
return _core.GetSettingsView();
}
public override void Cleanup()
{
try
{
if (_core is InkCanvasPluginBase b)
{
b.IsEnabled = false;
}
else
{
_core.Stop();
}
_core.Cleanup();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"SDK 插件 {_core.Name} Cleanup 出错: {ex.Message}", LogHelper.LogType.Error);
}
base.Cleanup();
}
}
}
+7 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Ink;
@@ -140,6 +140,12 @@ namespace Ink_Canvas.Helpers
OnUndoStateChanged?.Invoke(_currentIndex > -1);
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
}
/// <summary>当前历史是否允许撤销。</summary>
public bool CanUndo => _currentIndex > -1;
/// <summary>当前历史是否允许重做。</summary>
public bool CanRedo => _currentStrokeHistory.Count > 0 && _currentStrokeHistory.Count - _currentIndex - 1 > 0;
}
public class TimeMachineHistory