add:插件
屎山
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using H.NotifyIcon;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Plugins;
|
||||
using Ink_Canvas.Properties;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using Microsoft.Win32;
|
||||
@@ -1075,6 +1076,18 @@ namespace Ink_Canvas
|
||||
var mainWindow = new MainWindow();
|
||||
MainWindow = mainWindow;
|
||||
|
||||
// 注册 InkCanvas 服务供插件使用
|
||||
try
|
||||
{
|
||||
var inkCanvasService = new Plugins.InkCanvasService(mainWindow);
|
||||
Plugins.PluginManager.Instance.RegisterService<Plugins.IInkCanvasService>(inkCanvasService);
|
||||
LogHelper.WriteLogToFile("InkCanvasService registered for plugins");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Failed to register InkCanvasService: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 主窗口加载完成后关闭启动画面
|
||||
mainWindow.Loaded += (s, args) =>
|
||||
{
|
||||
@@ -1161,6 +1174,18 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"初始化上传帮助类时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 加载插件
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("开始加载插件");
|
||||
await PluginManager.Instance.LoadAllAsync();
|
||||
LogHelper.WriteLogToFile(string.Format("插件加载完成,共加载 {0} 个插件", PluginManager.Instance.Plugins.Count));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile(string.Format("加载插件时出错: {0}", ex.Message), LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
@@ -1437,6 +1462,18 @@ namespace Ink_Canvas
|
||||
|
||||
private void App_Exit(object sender, ExitEventArgs e)
|
||||
{
|
||||
// 卸载所有插件
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("正在卸载插件...");
|
||||
PluginManager.Instance.UnloadAll();
|
||||
LogHelper.WriteLogToFile("插件卸载完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"卸载插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 仅在软件内主动退出时关闭看门狗,并写入退出信号
|
||||
try
|
||||
{
|
||||
|
||||
@@ -141,7 +141,12 @@
|
||||
<PackageReference Include="AForge.Video.DirectShow" Version="2.2.5" />
|
||||
<PackageReference Include="AForge.Imaging" Version="2.2.5" />
|
||||
<PackageReference Include="AForge.Math" Version="2.2.5" />
|
||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageReference Include="WebDav.Client" Version="2.9.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(MSBuildRuntimeType)' == 'Full'">
|
||||
<COMReference Include="IWshRuntimeLibrary">
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using Ink_Canvas.Plugins;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Plugins
|
||||
{
|
||||
public class InkCanvasService : IInkCanvasService
|
||||
{
|
||||
private readonly MainWindow _mainWindow;
|
||||
|
||||
public InkCanvasService(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
public void OpenWhiteboard()
|
||||
{
|
||||
if (_mainWindow != null)
|
||||
{
|
||||
_mainWindow.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.UnFoldFloatingBar_MouseUp(null, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error opening whiteboard: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseWhiteboard()
|
||||
{
|
||||
if (_mainWindow != null)
|
||||
{
|
||||
_mainWindow.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.FoldFloatingBar_MouseUp(null, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error closing whiteboard: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OpenWhiteboardAsync(int delayMilliseconds = 0)
|
||||
{
|
||||
if (delayMilliseconds > 0)
|
||||
{
|
||||
await Task.Delay(delayMilliseconds);
|
||||
}
|
||||
OpenWhiteboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Plugins
|
||||
{
|
||||
public class PluginManager : IPluginHost
|
||||
{
|
||||
private static PluginManager _instance;
|
||||
public static PluginManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new PluginManager();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
|
||||
private readonly string _pluginsDirectory;
|
||||
private readonly List<PluginInfo> _plugins = new List<PluginInfo>();
|
||||
private readonly Dictionary<string, AssemblyLoadContext> _assemblyContexts = new Dictionary<string, AssemblyLoadContext>();
|
||||
|
||||
public IReadOnlyList<PluginInfo> Plugins
|
||||
{
|
||||
get { return _plugins.AsReadOnly(); }
|
||||
}
|
||||
|
||||
public event EventHandler<PluginInfo> PluginLoaded;
|
||||
public event EventHandler<PluginInfo> PluginUnloaded;
|
||||
public event EventHandler<string> LogMessage;
|
||||
|
||||
private PluginManager()
|
||||
{
|
||||
_pluginsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
||||
|
||||
if (!Directory.Exists(_pluginsDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(_pluginsDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(_pluginsDirectory))
|
||||
{
|
||||
Log("Plugins directory does not exist, skipping plugin loading");
|
||||
return;
|
||||
}
|
||||
|
||||
Log(string.Format("Loading plugins from: {0}", _pluginsDirectory));
|
||||
|
||||
var pluginFiles = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
var topLevelFiles = Directory.GetFiles(_pluginsDirectory, "*.dll", SearchOption.TopDirectoryOnly);
|
||||
pluginFiles.AddRange(topLevelFiles);
|
||||
Log(string.Format("Found {0} top-level plugin files", topLevelFiles.Length));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError(string.Format("Error getting top-level plugin files: {0}", ex.Message));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var subDirectories = Directory.GetDirectories(_pluginsDirectory);
|
||||
foreach (var subDir in subDirectories)
|
||||
{
|
||||
try
|
||||
{
|
||||
var subDirFiles = Directory.GetFiles(subDir, "*.dll", SearchOption.TopDirectoryOnly);
|
||||
pluginFiles.AddRange(subDirFiles);
|
||||
Log(string.Format("Found {0} plugin files in directory: {1}", subDirFiles.Length, Path.GetFileName(subDir)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError(string.Format("Error scanning subdirectory {0}: {1}", Path.GetFileName(subDir), ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError(string.Format("Error getting subdirectories: {0}", ex.Message));
|
||||
}
|
||||
|
||||
Log(string.Format("Found total {0} potential plugin files", pluginFiles.Count));
|
||||
|
||||
foreach (var pluginFile in pluginFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadPluginAsync(pluginFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError(string.Format("Failed to load plugin from {0}", Path.GetFileName(pluginFile)), ex);
|
||||
}
|
||||
}
|
||||
|
||||
_plugins.Sort((a, b) => a.Order.CompareTo(b.Order));
|
||||
Log(string.Format("Plugin loading complete. Loaded {0} plugins", _plugins.Count));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("Failed to load plugins", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadPluginAsync(string pluginFile)
|
||||
{
|
||||
var fileName = Path.GetFileName(pluginFile);
|
||||
Log(string.Format("Loading plugin: {0}", fileName));
|
||||
|
||||
var alc = new PluginAssemblyLoadContext(pluginFile, isCollectible: true);
|
||||
|
||||
try
|
||||
{
|
||||
var assembly = alc.LoadFromAssemblyPath(pluginFile);
|
||||
var pluginTypes = assembly.GetTypes()
|
||||
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && t.IsClass);
|
||||
|
||||
foreach (var pluginType in pluginTypes)
|
||||
{
|
||||
var pluginInstance = Activator.CreateInstance(pluginType) as IPlugin;
|
||||
if (pluginInstance == null) continue;
|
||||
|
||||
var pluginInfo = new PluginInfo
|
||||
{
|
||||
Id = pluginInstance.Id,
|
||||
Name = pluginInstance.Name,
|
||||
Version = pluginInstance.Version,
|
||||
Description = pluginInstance.Description,
|
||||
Author = pluginInstance.Author,
|
||||
Order = pluginInstance.Order,
|
||||
Instance = pluginInstance,
|
||||
IsLoaded = true
|
||||
};
|
||||
|
||||
_plugins.Add(pluginInfo);
|
||||
_assemblyContexts[pluginInfo.Id] = alc;
|
||||
|
||||
try
|
||||
{
|
||||
pluginInstance.Initialize(this);
|
||||
Log(string.Format("Plugin loaded: {0} v{1} by {2}", pluginInfo.Name, pluginInfo.Version, pluginInfo.Author));
|
||||
OnPluginLoaded(pluginInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError(string.Format("Failed to initialize plugin {0}", pluginInfo.Name), ex);
|
||||
_plugins.Remove(pluginInfo);
|
||||
_assemblyContexts.Remove(pluginInfo.Id);
|
||||
alc.Unload();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
alc.Unload();
|
||||
throw;
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void UnloadPlugin(PluginInfo plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
plugin.Instance.Shutdown();
|
||||
_plugins.Remove(plugin);
|
||||
plugin.IsLoaded = false;
|
||||
|
||||
if (_assemblyContexts.TryGetValue(plugin.Id, out var alc))
|
||||
{
|
||||
_assemblyContexts.Remove(plugin.Id);
|
||||
alc.Unload();
|
||||
}
|
||||
|
||||
Log(string.Format("Plugin unloaded: {0}", plugin.Name));
|
||||
OnPluginUnloaded(plugin);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError(string.Format("Failed to unload plugin {0}", plugin.Name), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnloadAll()
|
||||
{
|
||||
foreach (var plugin in _plugins.ToList())
|
||||
{
|
||||
UnloadPlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(string message)
|
||||
{
|
||||
OnLogMessage(message);
|
||||
System.Diagnostics.Debug.WriteLine(string.Format("[Plugin] {0}", message));
|
||||
}
|
||||
|
||||
public void LogError(string message, Exception ex = null)
|
||||
{
|
||||
var fullMessage = ex != null ? string.Format("{0}: {1}", message, ex.Message) : message;
|
||||
OnLogMessage(string.Format("ERROR: {0}", fullMessage));
|
||||
System.Diagnostics.Debug.WriteLine(string.Format("[Plugin ERROR] {0}", fullMessage));
|
||||
if (ex != null)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public T GetService<T>() where T : class
|
||||
{
|
||||
if (_services.TryGetValue(typeof(T), out var service))
|
||||
{
|
||||
return service as T;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void RegisterService<T>(T service) where T : class
|
||||
{
|
||||
_services[typeof(T)] = service;
|
||||
}
|
||||
|
||||
protected virtual void OnPluginLoaded(PluginInfo pluginInfo)
|
||||
{
|
||||
var handler = PluginLoaded;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, pluginInfo);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnPluginUnloaded(PluginInfo pluginInfo)
|
||||
{
|
||||
var handler = PluginUnloaded;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, pluginInfo);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnLogMessage(string message)
|
||||
{
|
||||
var handler = LogMessage;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, message);
|
||||
}
|
||||
}
|
||||
|
||||
private class PluginAssemblyLoadContext : AssemblyLoadContext
|
||||
{
|
||||
private readonly AssemblyDependencyResolver _resolver;
|
||||
|
||||
public PluginAssemblyLoadContext(string pluginPath, bool isCollectible)
|
||||
: base(string.Format("PluginContext_{0}", Path.GetFileNameWithoutExtension(pluginPath)), isCollectible)
|
||||
{
|
||||
_resolver = new AssemblyDependencyResolver(pluginPath);
|
||||
}
|
||||
|
||||
protected override Assembly Load(AssemblyName assemblyName)
|
||||
{
|
||||
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
if (assemblyPath != null)
|
||||
{
|
||||
return LoadFromAssemblyPath(assemblyPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<ui:Page
|
||||
x:Class="Ink_Canvas.Windows.SettingsViews.Pages.PluginPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
Title="插件"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="24">
|
||||
<TextBlock Text="🎨 插件管理" Style="{DynamicResource TitleTextBlockStyle}" Margin="0,0,0,16" />
|
||||
|
||||
<Border x:Name="StatusBorder" Background="{DynamicResource InfoAcrylicFillColorDefaultBrush}" Padding="12" CornerRadius="8" Margin="0,0,0,20">
|
||||
<TextBlock x:Name="PluginCountText" Text="正在加载插件..." Foreground="{DynamicResource InfoTextFillColorPrimaryBrush}" />
|
||||
</Border>
|
||||
|
||||
<StackPanel x:Name="PluginContainer" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</ui:Page>
|
||||
@@ -0,0 +1,120 @@
|
||||
using Ink_Canvas.Plugins;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||
{
|
||||
public partial class PluginPage : iNKORE.UI.WPF.Modern.Controls.Page
|
||||
{
|
||||
public PluginPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += PluginPage_Loaded;
|
||||
}
|
||||
|
||||
private void PluginPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LoadPlugins();
|
||||
}
|
||||
|
||||
public void LoadPlugins()
|
||||
{
|
||||
try
|
||||
{
|
||||
var pluginManager = PluginManager.Instance;
|
||||
var plugins = pluginManager.Plugins;
|
||||
|
||||
PluginCountText.Text = string.Format("已加载 {0} 个插件", plugins.Count);
|
||||
|
||||
if (plugins.Count == 0)
|
||||
{
|
||||
PluginContainer.Children.Clear();
|
||||
var noPluginText = new TextBlock
|
||||
{
|
||||
Text = "没有找到插件,请将插件文件放置在 Plugins 目录中",
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Foreground = Brushes.Gray,
|
||||
Margin = new Thickness(0, 10, 0, 0)
|
||||
};
|
||||
PluginContainer.Children.Add(noPluginText);
|
||||
return;
|
||||
}
|
||||
|
||||
PluginContainer.Children.Clear();
|
||||
|
||||
foreach (var pluginInfo in plugins)
|
||||
{
|
||||
var pluginCard = CreatePluginCard(pluginInfo);
|
||||
PluginContainer.Children.Add(pluginCard);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCountText.Text = string.Format("加载插件时出错:{0}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private Border CreatePluginCard(PluginInfo pluginInfo)
|
||||
{
|
||||
var card = new Border
|
||||
{
|
||||
Background = Brushes.White,
|
||||
BorderBrush = Brushes.LightGray,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
Padding = new Thickness(15)
|
||||
};
|
||||
|
||||
var stackPanel = new StackPanel();
|
||||
|
||||
var titlePanel = new DockPanel { LastChildFill = true };
|
||||
|
||||
var nameText = new TextBlock
|
||||
{
|
||||
Text = pluginInfo.Name,
|
||||
FontSize = 16,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Foreground = Brushes.Black,
|
||||
Margin = new Thickness(0, 0, 10, 0)
|
||||
};
|
||||
DockPanel.SetDock(nameText, Dock.Left);
|
||||
|
||||
var versionText = new TextBlock
|
||||
{
|
||||
Text = string.Format("v{0}", pluginInfo.Version),
|
||||
FontSize = 12,
|
||||
Foreground = Brushes.Gray,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
|
||||
titlePanel.Children.Add(nameText);
|
||||
titlePanel.Children.Add(versionText);
|
||||
|
||||
var descriptionText = new TextBlock
|
||||
{
|
||||
Text = pluginInfo.Description,
|
||||
FontSize = 12,
|
||||
Foreground = Brushes.DarkGray,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
};
|
||||
|
||||
var authorText = new TextBlock
|
||||
{
|
||||
Text = string.Format("作者:{0}", pluginInfo.Author),
|
||||
FontSize = 11,
|
||||
Foreground = Brushes.Gray
|
||||
};
|
||||
|
||||
stackPanel.Children.Add(titlePanel);
|
||||
stackPanel.Children.Add(descriptionText);
|
||||
stackPanel.Children.Add(authorText);
|
||||
|
||||
card.Child = stackPanel;
|
||||
return card;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<ui:Page
|
||||
x:Class="Ink_Canvas.Windows.SettingsViews.Pages.PluginSettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
Title="插件设置"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<ContentControl x:Name="PluginSettingsContent" Margin="24" />
|
||||
</Grid>
|
||||
</ui:Page>
|
||||
@@ -0,0 +1,71 @@
|
||||
using Ink_Canvas.Plugins;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||
{
|
||||
public partial class PluginSettingsPage : iNKORE.UI.WPF.Modern.Controls.Page
|
||||
{
|
||||
private static PluginInfo _currentPlugin;
|
||||
|
||||
public static PluginInfo CurrentPlugin
|
||||
{
|
||||
get { return _currentPlugin; }
|
||||
set
|
||||
{
|
||||
_currentPlugin = value;
|
||||
}
|
||||
}
|
||||
|
||||
public PluginSettingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += PluginSettingsPage_Loaded;
|
||||
}
|
||||
|
||||
private void PluginSettingsPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LoadPluginSettings();
|
||||
}
|
||||
|
||||
public void LoadPluginSettings()
|
||||
{
|
||||
if (_currentPlugin == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var settingsView = _currentPlugin.Instance.GetSettingsView();
|
||||
if (settingsView != null && settingsView is UIElement uiElement)
|
||||
{
|
||||
var parent = VisualTreeHelper.GetParent(uiElement);
|
||||
if (parent != null)
|
||||
{
|
||||
if (parent is Decorator decorator)
|
||||
{
|
||||
decorator.Child = null;
|
||||
}
|
||||
else if (parent is ContentControl contentControl)
|
||||
{
|
||||
contentControl.Content = null;
|
||||
}
|
||||
else if (parent is Panel panel)
|
||||
{
|
||||
panel.Children.Remove(uiElement);
|
||||
}
|
||||
}
|
||||
|
||||
PluginSettingsContent.Content = uiElement;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(string.Format("加载插件设置时出错: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,6 +228,15 @@
|
||||
</ui:NavigationViewItem.MenuItems>
|
||||
</ui:NavigationViewItem>
|
||||
<ui:NavigationViewItemHeader Content="插件设置"/>
|
||||
<ui:NavigationViewItem
|
||||
x:Name="PluginItem"
|
||||
Content="插件"
|
||||
Tag="PluginPage"
|
||||
ToolTipService.ToolTip="插件管理">
|
||||
<ui:NavigationViewItem.Icon>
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Puzzle}"/>
|
||||
</ui:NavigationViewItem.Icon>
|
||||
</ui:NavigationViewItem>
|
||||
</ui:NavigationView.MenuItems>
|
||||
|
||||
<ui:NavigationView.FooterMenuItems>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Media;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using Screen = System.Windows.Forms.Screen;
|
||||
|
||||
@@ -15,6 +16,7 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
private readonly Dictionary<string, Type> _pageTypes;
|
||||
private readonly Dictionary<string, object> _pages = new Dictionary<string, object>();
|
||||
private readonly Dictionary<string, Ink_Canvas.Plugins.PluginInfo> _pluginPages = new Dictionary<string, Ink_Canvas.Plugins.PluginInfo>();
|
||||
|
||||
// 保存窗口原始位置和大小
|
||||
private double _originalLeft;
|
||||
@@ -44,7 +46,9 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
{ "FontsPage", typeof(FontsPage) },
|
||||
{ "StartupPage", typeof(StartupPage) },
|
||||
{ "AboutPage", typeof(AboutPage) },
|
||||
{ "Settings", typeof(SettingsPage) }
|
||||
{ "Settings", typeof(SettingsPage) },
|
||||
{ "PluginPage", typeof(PluginPage) },
|
||||
{ "PluginSettingsPage", typeof(PluginSettingsPage) }
|
||||
};
|
||||
|
||||
// 默认选中首页
|
||||
@@ -65,6 +69,7 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
SetMaxSizeAndCenter();
|
||||
RegisterDpiChangedListener();
|
||||
LoadPluginSettingsPages();
|
||||
};
|
||||
|
||||
// 窗口关闭时释放资源
|
||||
@@ -258,6 +263,12 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
string tag = selectedItem.Tag as string;
|
||||
if (!string.IsNullOrEmpty(tag) && _pageTypes.ContainsKey(tag))
|
||||
{
|
||||
// 如果是插件设置页面,设置当前插件
|
||||
if (_pluginPages.TryGetValue(tag, out var plugin))
|
||||
{
|
||||
PluginSettingsPage.CurrentPlugin = plugin;
|
||||
}
|
||||
|
||||
// 避免重复导航到当前页面
|
||||
if (rootFrame.SourcePageType != _pageTypes[tag])
|
||||
{
|
||||
@@ -458,6 +469,44 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private void LoadPluginSettingsPages()
|
||||
{
|
||||
try
|
||||
{
|
||||
var pluginManager = Ink_Canvas.Plugins.PluginManager.Instance;
|
||||
var plugins = pluginManager.Plugins;
|
||||
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
var settingsView = plugin.Instance.GetSettingsView();
|
||||
if (settingsView != null)
|
||||
{
|
||||
var pageTag = string.Format("PluginSettings_{0}", plugin.Id);
|
||||
|
||||
_pageTypes[pageTag] = typeof(PluginSettingsPage);
|
||||
_pluginPages[pageTag] = plugin;
|
||||
|
||||
var navItem = new NavigationViewItem
|
||||
{
|
||||
Content = string.Format("{0} 设置", plugin.Name),
|
||||
Tag = pageTag
|
||||
};
|
||||
|
||||
navItem.Icon = new FontIcon
|
||||
{
|
||||
Glyph = "\uE713"
|
||||
};
|
||||
|
||||
NavigationViewControl.MenuItems.Add(navItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(string.Format("加载插件设置页面时出错: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,16 @@
|
||||
"MdXaml.Plugins": "1.27.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.0.0, )",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.International.Converters.PinYinConverter": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.0, )",
|
||||
@@ -179,6 +189,16 @@
|
||||
"System.CodeDom": "10.0.5"
|
||||
}
|
||||
},
|
||||
"System.Private.Uri": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.3.2, )",
|
||||
"resolved": "4.3.2",
|
||||
"contentHash": "o1+7RJnu3Ik3PazR7Z7tJhjPdE000Eq2KGLLWhqJJKXj04wrS8lwb1OFtDF9jzXXADhUuZNJZlPc98uwwqmpFA==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.1",
|
||||
"Microsoft.NETCore.Targets": "1.1.3"
|
||||
}
|
||||
},
|
||||
"WebDav.Client": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.9.0, )",
|
||||
@@ -221,11 +241,21 @@
|
||||
"resolved": "1.27.0",
|
||||
"contentHash": "We7LtBdoukRg9mqTfa1f5n8z/GQPMKBRj3URk9DiMuqzIHkW1lTgK5njVPSScxsRt4YzW22423tSnLWNm2MJKg=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg=="
|
||||
},
|
||||
"Microsoft.NETCore.Platforms": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ=="
|
||||
},
|
||||
"Microsoft.NETCore.Targets": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.3",
|
||||
"contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ=="
|
||||
},
|
||||
"Microsoft.Win32.Registry": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
@@ -308,6 +338,9 @@
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
|
||||
},
|
||||
"inkcanvas.pluginsdk": {
|
||||
"type": "Project"
|
||||
}
|
||||
},
|
||||
"net6.0-windows10.0.19041/win-arm64": {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Plugins
|
||||
{
|
||||
public interface IInkCanvasService
|
||||
{
|
||||
void OpenWhiteboard();
|
||||
void CloseWhiteboard();
|
||||
Task OpenWhiteboardAsync(int delayMilliseconds = 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Ink_Canvas.Plugins
|
||||
{
|
||||
public interface IPlugin
|
||||
{
|
||||
string Id { get; }
|
||||
string Name { get; }
|
||||
string Version { get; }
|
||||
string Description { get; }
|
||||
string Author { get; }
|
||||
int Order { get; }
|
||||
|
||||
void Initialize(IPluginHost host);
|
||||
void Shutdown();
|
||||
object GetMainView();
|
||||
object GetSettingsView();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Ink_Canvas.Plugins
|
||||
{
|
||||
public interface IPluginHost
|
||||
{
|
||||
void Log(string message);
|
||||
void LogError(string message, Exception ex = null);
|
||||
T GetService<T>() where T : class;
|
||||
void RegisterService<T>(T service) where T : class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<RootNamespace>Ink_Canvas.Plugins</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
|
||||
namespace Ink_Canvas.Plugins
|
||||
{
|
||||
public abstract class PluginBase : IPlugin
|
||||
{
|
||||
protected IPluginHost Host { get; private set; }
|
||||
|
||||
public abstract string Id { get; }
|
||||
public abstract string Name { get; }
|
||||
public abstract string Version { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract string Author { get; }
|
||||
public abstract int Order { get; }
|
||||
|
||||
public virtual void Initialize(IPluginHost host)
|
||||
{
|
||||
Host = host;
|
||||
}
|
||||
|
||||
public virtual void Shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual object GetMainView()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual object GetSettingsView()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void Log(string message)
|
||||
{
|
||||
if (Host != null)
|
||||
{
|
||||
Host.Log(message);
|
||||
}
|
||||
}
|
||||
|
||||
protected void LogError(string message, Exception ex = null)
|
||||
{
|
||||
if (Host != null)
|
||||
{
|
||||
Host.LogError(message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected T GetService<T>() where T : class
|
||||
{
|
||||
if (Host != null)
|
||||
{
|
||||
return Host.GetService<T>();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Ink_Canvas.Plugins
|
||||
{
|
||||
public class PluginInfo
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Author { get; set; }
|
||||
public int Order { get; set; }
|
||||
public IPlugin Instance { get; set; }
|
||||
public bool IsLoaded { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user