From b21a15376dbd2d274e59c58ae98e44ea46900e48 Mon Sep 17 00:00:00 2001 From: unknown <2564608840@qq.com> Date: Wed, 16 Jul 2025 13:35:17 +0800 Subject: [PATCH] =?UTF-8?q?add:=E6=8F=92=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BuiltIn/SuperLauncher/LauncherButton.cs | 276 ++++ .../BuiltIn/SuperLauncher/LauncherModels.cs | 303 ++++ .../LauncherSettingsControl.xaml | 87 + .../LauncherSettingsControl.xaml.cs | 395 +++++ .../BuiltIn/SuperLauncher/LauncherWindow.xaml | 91 + .../SuperLauncher/LauncherWindow.xaml.cs | 460 ++++++ .../Plugins/BuiltIn/SuperLauncherPlugin.cs | 589 +++++++ Ink Canvas/Helpers/Plugins/IPlugin.cs | 68 + Ink Canvas/Helpers/Plugins/PluginBase.cs | 144 ++ Ink Canvas/Helpers/Plugins/PluginManager.cs | 1462 +++++++++++++++++ Ink Canvas/Helpers/Plugins/PluginTemplate.cs | 276 ++++ Ink Canvas/MainWindow.xaml | 95 +- Ink Canvas/MainWindow.xaml.cs | 64 + Ink Canvas/Windows/PluginSettingsWindow.xaml | 148 ++ .../Windows/PluginSettingsWindow.xaml.cs | 487 ++++++ 15 files changed, 4872 insertions(+), 73 deletions(-) create mode 100644 Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs create mode 100644 Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs create mode 100644 Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml create mode 100644 Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml.cs create mode 100644 Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherWindow.xaml create mode 100644 Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherWindow.xaml.cs create mode 100644 Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs create mode 100644 Ink Canvas/Helpers/Plugins/IPlugin.cs create mode 100644 Ink Canvas/Helpers/Plugins/PluginBase.cs create mode 100644 Ink Canvas/Helpers/Plugins/PluginManager.cs create mode 100644 Ink Canvas/Helpers/Plugins/PluginTemplate.cs create mode 100644 Ink Canvas/Windows/PluginSettingsWindow.xaml create mode 100644 Ink Canvas/Windows/PluginSettingsWindow.xaml.cs diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs new file mode 100644 index 00000000..b8a5ea29 --- /dev/null +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs @@ -0,0 +1,276 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Shapes; +using iNKORE.UI.WPF.Modern.Controls; + +namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher +{ + /// + /// 启动台按钮控件 + /// + public class LauncherButton + { + /// + /// 父插件 + /// + private readonly SuperLauncherPlugin _plugin; + + /// + /// 实际按钮控件 + /// + private readonly SimpleStackPanel _panel; + + /// + /// 获取按钮UI元素 + /// + public UIElement Element => _panel; + + /// + /// 构造函数 + /// + /// 父插件 + public LauncherButton(SuperLauncherPlugin plugin) + { + try + { + _plugin = plugin; + LogHelper.WriteLogToFile("开始创建启动台按钮", LogHelper.LogType.Info); + + // 创建SimpleStackPanel + _panel = new SimpleStackPanel + { + Name = "Launcher_Icon", + Orientation = Orientation.Vertical, + HorizontalAlignment = HorizontalAlignment.Center, + Width = 28, + Margin = new Thickness(0, -2, 0, 0), + Background = Brushes.Transparent + }; + + LogHelper.WriteLogToFile("创建SimpleStackPanel完成", LogHelper.LogType.Info); + + // 添加图标 + var image = CreateIconImage(); + _panel.Children.Add(image); + + // 添加文本 + TextBlock textBlock = new TextBlock + { + Text = "启动台", + Foreground = Brushes.Black, + FontSize = 8, + Margin = new Thickness(0, 1, 0, 0), + TextAlignment = TextAlignment.Center + }; + _panel.Children.Add(textBlock); + + // 设置鼠标事件 + _panel.MouseDown += Panel_MouseDown; + _panel.MouseUp += Panel_MouseUp; + _panel.MouseLeave += Panel_MouseLeave; + + // 右键菜单支持 + _panel.ContextMenu = CreateContextMenu(); + + // 设置工具提示 + _panel.ToolTip = "启动台"; + + LogHelper.WriteLogToFile("启动台按钮创建完成", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"创建启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error); + LogHelper.NewLog(ex); + } + } + + /// + /// 创建右键菜单 + /// + private ContextMenu CreateContextMenu() + { + try + { + // 创建菜单 + ContextMenu menu = new ContextMenu(); + + // 创建位置切换菜单项 + MenuItem positionMenuItem = new MenuItem(); + positionMenuItem.Header = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ? + "移至右侧" : "移至左侧"; + positionMenuItem.Click += (s, e) => + { + // 切换位置 + _plugin.Config.ButtonPosition = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ? + LauncherButtonPosition.Right : LauncherButtonPosition.Left; + + // 更新按钮位置 + _plugin.UpdateButtonPosition(); + + // 保存配置 + _plugin.SaveConfig(); + + LogHelper.WriteLogToFile($"通过右键菜单切换启动台按钮位置为: {_plugin.Config.ButtonPosition}", + LogHelper.LogType.Info); + }; + menu.Items.Add(positionMenuItem); + + // 添加设置菜单项 + MenuItem settingsMenuItem = new MenuItem(); + settingsMenuItem.Header = "打开设置"; + settingsMenuItem.Click += (s, e) => + { + // 打开插件设置窗口 + var mainWindow = Application.Current.MainWindow; + if (mainWindow != null) + { + try + { + // 使用反射调用主窗口的ShowPluginSettings方法 + var method = mainWindow.GetType().GetMethod("ShowPluginSettings"); + if (method != null) + { + method.Invoke(mainWindow, null); + LogHelper.WriteLogToFile("已打开插件设置窗口", LogHelper.LogType.Info); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"打开插件设置窗口失败: {ex.Message}", LogHelper.LogType.Error); + } + } + }; + menu.Items.Add(settingsMenuItem); + + return menu; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"创建右键菜单时出错: {ex.Message}", LogHelper.LogType.Error); + return null; + } + } + + /// + /// 获取实际的UI元素 + /// + [Obsolete("使用Element属性代替")] + public UIElement GetUIElement() + { + return _panel; + } + + /// + /// 创建图标图像 + /// + private Image CreateIconImage() + { + try + { + // 创建图像 + Image image = new Image + { + Height = 17, + Margin = new Thickness(0, 3, 0, 0) + }; + + // 设置位图缩放模式 + RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality); + + // 创建绘图图像 + DrawingImage drawingImage = new DrawingImage(); + DrawingGroup drawingGroup = new DrawingGroup(); + drawingGroup.ClipGeometry = Geometry.Parse("M0,0 V24 H24 V0 H0 Z"); + + // 使用提供的应用网格图标 + GeometryDrawing geometryDrawing = new GeometryDrawing + { + Brush = new SolidColorBrush(Color.FromRgb(0x1B, 0x1B, 0x1B)), + Geometry = Geometry.Parse("F0 M24,24z M0,0z M4.41721,4.29873C4.35178,4.29873,4.29873,4.35178,4.29873,4.41721L4.29873,9.15646C4.29873,9.22189,4.35178,9.27494,4.41721,9.27494L9.15646,9.27494C9.22189,9.27494,9.27494,9.22189,9.27494,9.15646L9.27494,4.41721C9.27494,4.35178,9.22189,4.29873,9.15646,4.29873L4.41721,4.29873z M2.64,4.41721C2.64,3.43569,3.43569,2.64,4.41721,2.64L9.15646,2.64C10.138,2.64,10.9337,3.43569,10.9337,4.41721L10.9337,9.15646C10.9337,10.138,10.138,10.9337,9.15646,10.9337L4.41721,10.9337C3.43569,10.9337,2.64,10.138,2.64,9.15646L2.64,4.41721z M14.8435,4.29873C14.7781,4.29873,14.7251,4.35178,14.7251,4.41721L14.7251,9.15646C14.7251,9.22189,14.7781,9.27494,14.8435,9.27494L19.5828,9.27494C19.6482,9.27494,19.7013,9.22189,19.7013,9.15646L19.7013,4.41721C19.7013,4.35178,19.6482,4.29873,19.5828,4.29873L14.8435,4.29873z M13.0663,4.41721C13.0663,3.43569,13.862,2.64,14.8435,2.64L19.5828,2.64C20.5643,2.64,21.36,3.43569,21.36,4.41721L21.36,9.15646C21.36,10.138,20.5643,10.9337,19.5828,10.9337L14.8435,10.9337C13.862,10.9337,13.0663,10.138,13.0663,9.15646L13.0663,4.41721z M14.8435,14.7251C14.7781,14.7251,14.7251,14.7781,14.7251,14.8435L14.7251,19.5828C14.7251,19.6482,14.7781,19.7013,14.8435,19.7013L19.5828,19.7013C19.6482,19.7013,19.7013,19.6482,19.7013,19.5828L19.7013,14.8435C19.7013,14.7781,19.6482,14.7251,19.5828,14.7251L14.8435,14.7251z M13.0663,14.8435C13.0663,13.862,13.862,13.0663,14.8435,13.0663L19.5828,13.0663C20.5643,13.0663,21.36,13.862,21.36,14.8435L21.36,19.5828C21.36,20.5643,20.5643,21.36,19.5828,21.36L14.8435,21.36C13.862,21.36,13.0663,20.5643,13.0663,19.5828L13.0663,14.8435z M4.41721,14.7251C4.35178,14.7251,4.29873,14.7781,4.29873,14.8435L4.29873,19.5828C4.29873,19.6482,4.35178,19.7013,4.41721,19.7013L9.15646,19.7013C9.22189,19.7013,9.27494,19.6482,9.27494,19.5828L9.27494,14.8435C9.27494,14.7781,9.22189,14.7251,9.15646,14.7251L4.41721,14.7251z M2.64,14.8435C2.64,13.862,3.43569,13.0663,4.41721,13.0663L9.15646,13.0663C10.138,13.0663,10.9337,13.862,10.9337,14.8435L10.9337,19.5828C10.9337,20.5643,10.138,21.36,9.15646,21.36L4.41721,21.36C3.43569,21.36,2.64,20.5643,2.64,19.5828L2.64,14.8435z") + }; + + drawingGroup.Children.Add(geometryDrawing); + + // 设置图像源 + drawingImage.Drawing = drawingGroup; + image.Source = drawingImage; + + return image; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"创建图标图像时出错: {ex.Message}", LogHelper.LogType.Error); + LogHelper.NewLog(ex); + + // 返回一个空图像 + return new Image(); + } + } + + /// + /// 鼠标按下事件 + /// + private void Panel_MouseDown(object sender, MouseButtonEventArgs e) + { + try + { + // 提供反馈 + _panel.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0)); + LogHelper.WriteLogToFile("启动台按钮鼠标按下", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动台按钮鼠标按下事件出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 鼠标抬起事件 + /// + private void Panel_MouseUp(object sender, MouseButtonEventArgs e) + { + try + { + // 只有左键点击才显示启动台窗口 + if (e.ChangedButton != MouseButton.Left) + { + return; + } + + // 恢复背景 + _panel.Background = Brushes.Transparent; + LogHelper.WriteLogToFile("启动台按钮鼠标抬起,准备显示启动台窗口", LogHelper.LogType.Info); + + // 获取按钮在屏幕上的位置 + Point buttonPosition = _panel.PointToScreen(new Point(_panel.ActualWidth / 2, 0)); + + // 显示启动台窗口 + _plugin.ShowLauncherWindow(buttonPosition); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动台按钮鼠标抬起事件出错: {ex.Message}", LogHelper.LogType.Error); + LogHelper.NewLog(ex); + } + } + + /// + /// 鼠标离开事件 + /// + private void Panel_MouseLeave(object sender, MouseEventArgs e) + { + try + { + // 恢复背景 + _panel.Background = Brushes.Transparent; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动台按钮鼠标离开事件出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs new file mode 100644 index 00000000..9749f8dc --- /dev/null +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Microsoft.Win32; + +namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher +{ + /// + /// 启动台按钮位置 + /// + public enum LauncherButtonPosition + { + /// + /// 左侧 + /// + Left, + + /// + /// 右侧 + /// + Right + } + + /// + /// 启动台配置 + /// + public class LauncherConfig + { + /// + /// 启动台按钮位置 + /// + public LauncherButtonPosition ButtonPosition { get; set; } = LauncherButtonPosition.Right; + + /// + /// 启动台应用程序列表 + /// + public List Items { get; set; } = new List(); + } + + /// + /// 启动台应用项 + /// + public class LauncherItem + { + /// + /// 应用程序名称 + /// + public string Name { get; set; } + + /// + /// 应用程序路径 + /// + public string Path { get; set; } + + /// + /// 是否可见 + /// + public bool IsVisible { get; set; } = true; + + /// + /// 在启动台中的位置(0-39) + /// + public int Position { get; set; } = -1; + + /// + /// 是否已固定位置 + /// + public bool IsPositionFixed { get; set; } = false; + + /// + /// 图标缓存 + /// + [Newtonsoft.Json.JsonIgnore] + private ImageSource _iconCache; + + /// + /// 获取应用程序图标 + /// + [Newtonsoft.Json.JsonIgnore] + public ImageSource Icon + { + get + { + if (_iconCache != null) + { + return _iconCache; + } + + try + { + if (File.Exists(Path)) + { + // 从文件中获取图标 + Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(Path); + if (icon != null) + { + _iconCache = Imaging.CreateBitmapSourceFromHIcon( + icon.Handle, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + + icon.Dispose(); + return _iconCache; + } + } + else + { + // 从注册表中获取文件类型关联图标 + string extension = System.IO.Path.GetExtension(Path); + if (!string.IsNullOrEmpty(extension)) + { + string fileType = Registry.ClassesRoot.OpenSubKey(extension)?.GetValue(string.Empty) as string; + if (!string.IsNullOrEmpty(fileType)) + { + string iconPath = Registry.ClassesRoot.OpenSubKey(fileType + "\\DefaultIcon")?.GetValue(string.Empty) as string; + if (!string.IsNullOrEmpty(iconPath)) + { + string[] parts = iconPath.Split(','); + string iconFile = parts[0].Trim('"'); + int iconIndex = parts.Length > 1 ? Convert.ToInt32(parts[1]) : 0; + + if (File.Exists(iconFile)) + { + Icon icon = IconExtractor.Extract(iconFile, iconIndex, true); + if (icon != null) + { + _iconCache = Imaging.CreateBitmapSourceFromHIcon( + icon.Handle, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + + icon.Dispose(); + return _iconCache; + } + } + } + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取应用图标时出错: {ex.Message}", LogHelper.LogType.Error); + } + + // 返回默认图标 + return GetDefaultIcon(); + } + } + + /// + /// 获取默认图标 + /// + private ImageSource GetDefaultIcon() + { + try + { + // 对于资源管理器,使用特定图标 + if (Path.EndsWith("explorer.exe", StringComparison.OrdinalIgnoreCase)) + { + string explorerIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_folder_24_regular.png"); + if (File.Exists(explorerIconPath)) + { + Uri uri = new Uri(explorerIconPath); + BitmapImage image = new BitmapImage(uri); + _iconCache = image; + return _iconCache; + } + } + + // 返回一个简单的默认图标 + string iconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-png", "icc.png"); + if (File.Exists(iconPath)) + { + Uri uri = new Uri(iconPath); + BitmapImage image = new BitmapImage(uri); + _iconCache = image; + return _iconCache; + } + + // 如果还是没有找到,尝试使用应用程序图标 + string appIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_apps_24_regular.png"); + if (File.Exists(appIconPath)) + { + Uri uri = new Uri(appIconPath); + BitmapImage image = new BitmapImage(uri); + _iconCache = image; + return _iconCache; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"获取默认图标时出错: {ex.Message}", LogHelper.LogType.Error); + } + + return null; + } + + /// + /// 启动应用程序 + /// + public void Launch() + { + try + { + if (string.IsNullOrEmpty(Path)) + { + LogHelper.WriteLogToFile("无法启动应用程序:路径为空", LogHelper.LogType.Error); + return; + } + + // 检查文件是否存在 + if (!System.IO.File.Exists(Path) && !Path.Contains(":\\")) + { + // 可能是系统命令,如explorer.exe + System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo + { + FileName = Path, + UseShellExecute = true + }; + System.Diagnostics.Process.Start(psi); + } + else + { + // 使用Process.Start启动应用程序 + System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo + { + FileName = Path, + UseShellExecute = true + }; + System.Diagnostics.Process.Start(psi); + } + + LogHelper.WriteLogToFile($"已启动应用程序: {Path}", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动应用程序时出错: {ex.Message}", LogHelper.LogType.Error); + MessageBox.Show($"启动应用程序时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + } + + /// + /// 图标提取工具类 + /// + public static class IconExtractor + { + /// + /// 从文件中提取图标 + /// + /// 文件路径 + /// 图标索引 + /// 是否提取大图标 + /// 提取的图标 + public static Icon Extract(string file, int index, bool largeIcon) + { + try + { + IntPtr large; + IntPtr small; + ExtractIconEx(file, index, out large, out small, 1); + + try + { + return Icon.FromHandle(largeIcon ? large : small); + } + catch + { + return null; + } + finally + { + if (large != IntPtr.Zero) + DestroyIcon(large); + + if (small != IntPtr.Zero) + DestroyIcon(small); + } + } + catch + { + return null; + } + } + + [System.Runtime.InteropServices.DllImport("Shell32.dll", EntryPoint = "ExtractIconEx")] + private static extern int ExtractIconEx( + [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)] string lpszFile, + int nIconIndex, + out IntPtr phiconLarge, + out IntPtr phiconSmall, + int nIcons); + + [System.Runtime.InteropServices.DllImport("User32.dll")] + private static extern int DestroyIcon(IntPtr hIcon); + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml new file mode 100644 index 00000000..63deeaad --- /dev/null +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherWindow.xaml.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherWindow.xaml.cs new file mode 100644 index 00000000..4045a5fb --- /dev/null +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherWindow.xaml.cs @@ -0,0 +1,460 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher +{ + /// + /// LauncherWindow.xaml 的交互逻辑 + /// + public partial class LauncherWindow : Window + { + /// + /// 父插件 + /// + private readonly SuperLauncherPlugin _plugin; + + /// + /// 是否处于固定模式 + /// + private bool _isFixMode = false; + + /// + /// 应用项按钮列表 + /// + private readonly Dictionary _appButtons = new Dictionary(); + + /// + /// 拖拽中的按钮 + /// + private Button _draggingButton; + + /// + /// 拖拽开始位置 + /// + private Point _dragStartPoint; + + /// + /// 构造函数 + /// + public LauncherWindow(SuperLauncherPlugin plugin) + { + InitializeComponent(); + + _plugin = plugin; + + // 加载应用项 + LoadLauncherItems(); + + // 添加鼠标按下事件(用于拖动窗口) + this.MouseDown += (s, e) => + { + if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Pressed) + { + this.DragMove(); + } + }; + + // 根据应用数量调整窗口大小 + AdjustWindowSize(); + } + + /// + /// 加载启动台应用项 + /// + private void LoadLauncherItems() + { + // 清空现有应用项 + AppPanel.Children.Clear(); + _appButtons.Clear(); + + // 获取显示的应用项 + var visibleItems = _plugin.LauncherItems + .Where(item => item.IsVisible) + .OrderBy(item => item.Position) + .ToList(); + + foreach (var item in visibleItems) + { + // 创建应用按钮 + Button appButton = new Button + { + Style = (Style)FindResource("LauncherItemStyle"), + DataContext = item, + Tag = item.Position + }; + + // 添加点击事件 + appButton.Click += AppButton_Click; + + // 在固定模式下,添加拖拽事件 + appButton.PreviewMouseDown += AppButton_PreviewMouseDown; + appButton.PreviewMouseMove += AppButton_PreviewMouseMove; + appButton.PreviewMouseUp += AppButton_PreviewMouseUp; + + // 记录按钮和项目的对应关系 + _appButtons.Add(appButton, item); + + // 添加到面板 + AppPanel.Children.Add(appButton); + } + } + + /// + /// 根据应用数量调整窗口大小 + /// + private void AdjustWindowSize() + { + try + { + // 每行最多显示4个应用 + const int appsPerRow = 4; + + // 计算行数 + int visibleCount = _appButtons.Count; + int rowCount = (int)Math.Ceiling(visibleCount / (double)appsPerRow); + + // 设置窗口宽度(每个应用90像素宽 = 80 + 5*2) + Width = Math.Min(appsPerRow * 90 + 40, 400); // 最大宽度400 + + // 设置窗口高度(每个应用90像素高 = 80 + 5*2) + Height = Math.Min(rowCount * 90 + 60, 600); // 最大高度600,标题栏40 + 边距20 + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"调整启动台窗口大小时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 应用按钮点击事件 + /// + private void AppButton_Click(object sender, RoutedEventArgs e) + { + try + { + if (_isFixMode) return; // 在固定模式下,不响应点击事件 + + if (sender is Button button && _appButtons.TryGetValue(button, out LauncherItem item)) + { + // 获取应用路径和名称,用于后续启动 + string appPath = item.Path; + string appName = item.Name; + + LogHelper.WriteLogToFile($"点击启动应用: {appName}, 路径: {appPath}", LogHelper.LogType.Info); + + // 首先标记窗口正在关闭 + IsClosing = true; + + // 创建一个应用启动任务 + var launchTask = new System.Threading.Tasks.Task(() => + { + try + { + // 等待一段时间,确保窗口关闭流程已经开始 + System.Threading.Thread.Sleep(200); + + // 使用UI线程启动应用 + Application.Current.Dispatcher.Invoke(() => + { + try + { + // 检查应用路径是否存在 + if (System.IO.File.Exists(appPath) || !appPath.Contains(":\\")) + { + // 创建进程启动信息 + var psi = new System.Diagnostics.ProcessStartInfo + { + FileName = appPath, + UseShellExecute = true, + }; + + // 启动应用程序 + var process = System.Diagnostics.Process.Start(psi); + LogHelper.WriteLogToFile($"应用程序 {appName} 已启动", LogHelper.LogType.Info); + } + else + { + LogHelper.WriteLogToFile($"应用路径不存在: {appPath}", LogHelper.LogType.Error); + MessageBox.Show($"找不到应用程序: {appPath}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动应用程序失败: {ex.Message}", LogHelper.LogType.Error); + MessageBox.Show($"启动应用程序失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + }); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用启动任务出错: {ex.Message}", LogHelper.LogType.Error); + } + }); + + // 关闭窗口 + try + { + Dispatcher.BeginInvoke(new Action(() => + { + try { Close(); } catch { } + + // 启动应用程序任务 + launchTask.Start(); + }), System.Windows.Threading.DispatcherPriority.Background); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"关闭窗口或启动任务时出错: {ex.Message}", LogHelper.LogType.Error); + // 如果无法通过UI关闭窗口,直接启动任务 + launchTask.Start(); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用按钮点击事件出错: {ex.Message}", LogHelper.LogType.Error); + try { IsClosing = true; Close(); } catch { } + } + } + + #region 固定模式拖拽事件 + + /// + /// 应用按钮鼠标按下事件 + /// + private void AppButton_PreviewMouseDown(object sender, MouseButtonEventArgs e) + { + if (!_isFixMode) return; + + if (e.ChangedButton == MouseButton.Left && sender is Button button) + { + _draggingButton = button; + _dragStartPoint = e.GetPosition(AppPanel); + button.CaptureMouse(); + button.Opacity = 0.7; + + // 阻止事件冒泡,以避免触发按钮点击 + e.Handled = true; + } + } + + /// + /// 应用按钮鼠标移动事件 + /// + private void AppButton_PreviewMouseMove(object sender, MouseEventArgs e) + { + if (!_isFixMode || _draggingButton == null) return; + + if (e.LeftButton == MouseButtonState.Pressed) + { + Point currentPosition = e.GetPosition(AppPanel); + + // 移动按钮 + System.Windows.Controls.Canvas.SetLeft(_draggingButton, currentPosition.X - _draggingButton.ActualWidth / 2); + System.Windows.Controls.Canvas.SetTop(_draggingButton, currentPosition.Y - _draggingButton.ActualHeight / 2); + + // 将按钮移到最上层 + Panel.SetZIndex(_draggingButton, 100); + + // 阻止事件冒泡 + e.Handled = true; + } + } + + /// + /// 应用按钮鼠标释放事件 + /// + private void AppButton_PreviewMouseUp(object sender, MouseButtonEventArgs e) + { + if (!_isFixMode || _draggingButton == null) return; + + // 释放鼠标捕获 + _draggingButton.ReleaseMouseCapture(); + + // 计算新位置 + Point releasePoint = e.GetPosition(AppPanel); + int newPosition = CalculateGridPosition(releasePoint); + + // 获取当前项目 + LauncherItem currentItem = _appButtons[_draggingButton]; + + // 重新排序 + ReorderItems(currentItem, newPosition); + + // 重新加载应用项 + LoadLauncherItems(); + + // 保存配置 + _plugin.SaveConfig(); + + // 清除拖拽状态 + _draggingButton.Opacity = 1; + Panel.SetZIndex(_draggingButton, 0); + _draggingButton = null; + + // 阻止事件冒泡 + e.Handled = true; + } + + /// + /// 计算网格位置 + /// + private int CalculateGridPosition(Point point) + { + // 计算行和列 + int columnCount = 4; // 每行最多4个应用 + int columnWidth = 90; // 应用宽度(包括边距) + int rowHeight = 90; // 应用高度(包括边距) + + int column = (int)(point.X / columnWidth); + int row = (int)(point.Y / rowHeight); + + // 确保在有效范围内 + column = Math.Max(0, Math.Min(column, columnCount - 1)); + row = Math.Max(0, row); + + // 计算位置索引 + return row * columnCount + column; + } + + /// + /// 重新排序应用项 + /// + private void ReorderItems(LauncherItem item, int newPosition) + { + try + { + // 设置项目为固定位置 + item.IsPositionFixed = true; + + // 如果位置相同,无需调整 + if (item.Position == newPosition) + { + return; + } + + // 获取所有可见项目 + var visibleItems = _plugin.LauncherItems + .Where(i => i.IsVisible) + .OrderBy(i => i.Position) + .ToList(); + + // 移除当前项目 + visibleItems.Remove(item); + + // 查找插入位置 + int insertIndex = 0; + for (int i = 0; i < visibleItems.Count; i++) + { + if (visibleItems[i].Position >= newPosition) + { + insertIndex = i; + break; + } + insertIndex = i + 1; + } + + // 插入项目 + visibleItems.Insert(insertIndex, item); + + // 重新分配位置 + for (int i = 0; i < visibleItems.Count; i++) + { + visibleItems[i].Position = i; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"重新排序应用项时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + #endregion + + #region 窗口事件处理 + + /// + /// 窗口失去焦点事件 + /// + private void Window_Deactivated(object sender, EventArgs e) + { + try + { + // 只有在非固定模式、窗口已加载、未处于关闭状态且IsLoaded=true时关闭窗口 + if (!_isFixMode && IsLoaded && !IsClosing) + { + // 标记为正在关闭 + IsClosing = true; + + // 使用Dispatcher.BeginInvoke而不是直接调用Close,避免冲突 + Dispatcher.BeginInvoke(new Action(() => + { + try + { + // 再次检查窗口状态 + if (IsLoaded && !IsClosing) + { + Close(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"延迟关闭窗口时出错: {ex.Message}", LogHelper.LogType.Error); + } + }), System.Windows.Threading.DispatcherPriority.Background); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"窗口失去焦点关闭时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 窗口是否正在关闭 + /// + private bool IsClosing { get; set; } = false; + + /// + /// 重写OnClosing方法,标记窗口正在关闭 + /// + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) + { + IsClosing = true; + base.OnClosing(e); + } + + /// + /// 关闭按钮点击事件 + /// + private void BtnClose_Click(object sender, RoutedEventArgs e) + { + Close(); + } + + /// + /// 固定模式按钮点击事件 + /// + private void BtnFixMode_Click(object sender, RoutedEventArgs e) + { + // 切换固定模式 + _isFixMode = !_isFixMode; + + // 更新固定模式按钮图标颜色 + FixModeIcon.Fill = _isFixMode ? Brushes.Yellow : Brushes.White; + + // 显示提示 + if (_isFixMode) + { + MessageBox.Show("已进入固定模式,您可以拖动应用图标调整位置。", "提示", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs new file mode 100644 index 00000000..20cc2972 --- /dev/null +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs @@ -0,0 +1,589 @@ +using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Ink_Canvas.Helpers.Plugins.BuiltIn +{ + /// + /// 超级启动台插件 + /// + public class SuperLauncherPlugin : PluginBase + { + #region 插件基本信息 + + public override string Name => "超级启动台"; + + public override string Description => "在浮动栏添加一个启动台按钮,可快速启动常用应用程序。"; + + public override Version Version => new Version(1, 0, 0); + + public override string Author => "ICC CE 团队"; + + public override bool IsBuiltIn => true; + + #endregion + + #region 插件属性和字段 + + /// + /// 启动台配置 + /// + public LauncherConfig Config { get; private set; } + + /// + /// 启动台应用程序列表 + /// + public ObservableCollection LauncherItems { get; private set; } + + /// + /// 启动台按钮 + /// + private LauncherButton _launcherButton; + + /// + /// 启动台窗口 + /// + private LauncherWindow _launcherWindow; + + /// + /// 配置文件路径 + /// + private readonly string _configPath = Path.Combine(App.RootPath, "PluginConfigs", "SuperLauncher.json"); + + /// + /// 标记是否已添加到浮动栏 + /// + private bool _isAddedToFloatingBar = false; + + #endregion + + #region 插件生命周期 + + public override void Initialize() + { + try + { + base.Initialize(); + + // 创建配置目录 + string configDir = Path.Combine(App.RootPath, "PluginConfigs"); + if (!Directory.Exists(configDir)) + { + Directory.CreateDirectory(configDir); + } + + // 加载配置 + LoadConfig(); + + LogHelper.WriteLogToFile("超级启动台插件已初始化", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error); + LogHelper.NewLog(ex); + } + } + + public override void Enable() + { + try + { + if (IsEnabled) return; // 防止重复启用 + + // 创建启动台按钮 + if (_launcherButton == null) + { + _launcherButton = new LauncherButton(this); + LogHelper.WriteLogToFile("超级启动台按钮已创建", LogHelper.LogType.Info); + } + + // 添加启动台按钮到浮动栏 + AddLauncherButtonToFloatingBar(); + + // 设置启用状态 + base.Enable(); + + // 保存插件配置 + SavePluginSettings(); + + LogHelper.WriteLogToFile("超级启动台插件已启用", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error); + LogHelper.NewLog(ex); + } + } + + public override void Disable() + { + try + { + if (!IsEnabled) return; // 防止重复禁用 + + // 从浮动栏移除启动台按钮 + RemoveLauncherButtonFromFloatingBar(); + + // 如果启动台窗口打开,则关闭 + if (_launcherWindow != null && _launcherWindow.IsVisible) + { + _launcherWindow.Close(); + _launcherWindow = null; + } + + // 设置禁用状态 + base.Disable(); + + // 保存插件配置 + SavePluginSettings(); + + LogHelper.WriteLogToFile("超级启动台插件已禁用", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"禁用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error); + LogHelper.NewLog(ex); + } + } + + public override UserControl GetSettingsView() + { + return new LauncherSettingsControl(this); + } + + public override void Cleanup() + { + // 保存配置 + SaveConfig(); + + // 从浮动栏移除启动台按钮 + RemoveLauncherButtonFromFloatingBar(); + + // 如果启动台窗口打开,则关闭 + if (_launcherWindow != null && _launcherWindow.IsVisible) + { + _launcherWindow.Close(); + _launcherWindow = null; + } + + base.Cleanup(); + } + + /// + /// 保存插件设置 + /// + public override void SavePluginSettings() + { + try + { + // 确保配置已加载 + if (Config == null) + { + LoadConfig(); + } + + // 更新其他设置,但不更改插件启用状态 + + // 保存配置 + SaveConfig(); + + LogHelper.WriteLogToFile($"超级启动台插件设置已保存", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存超级启动台插件设置时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + #endregion + + #region 配置管理 + + /// + /// 加载配置 + /// + private void LoadConfig() + { + try + { + if (File.Exists(_configPath)) + { + string json = File.ReadAllText(_configPath); + Config = JsonConvert.DeserializeObject(json) ?? CreateDefaultConfig(); + LauncherItems = new ObservableCollection(Config.Items ?? new List()); + + // 注意:不再根据配置更改插件启用状态 + // 插件状态由PluginManager统一管理 + } + else + { + Config = CreateDefaultConfig(); + LauncherItems = new ObservableCollection(Config.Items); + SaveConfig(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error); + Config = CreateDefaultConfig(); + LauncherItems = new ObservableCollection(Config.Items); + } + } + + /// + /// 保存配置 + /// + public void SaveConfig() + { + try + { + // 同步LauncherItems到Config + Config.Items = new List(LauncherItems); + + // 序列化并保存配置 + string json = JsonConvert.SerializeObject(Config, Formatting.Indented); + File.WriteAllText(_configPath, json); + + LogHelper.WriteLogToFile("超级启动台配置已保存", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 创建默认配置 + /// + private LauncherConfig CreateDefaultConfig() + { + var config = new LauncherConfig + { + ButtonPosition = LauncherButtonPosition.Right, + // 不再使用IsEnabled,插件状态由PluginManager管理 + Items = new List + { + new LauncherItem + { + Name = "资源管理器", + Path = "explorer.exe", + IsVisible = true, + Position = 0 + } + } + }; + + return config; + } + + #endregion + + #region 启动台按钮管理 + + /// + /// 将启动台按钮添加到浮动栏 + /// + private void AddLauncherButtonToFloatingBar() + { + try + { + // 如果已经添加,先移除 + if (_isAddedToFloatingBar) + { + RemoveLauncherButtonFromFloatingBar(); + _isAddedToFloatingBar = false; + } + + // 获取主窗口实例 + var mainWindow = Application.Current.MainWindow; + if (mainWindow == null) + { + LogHelper.WriteLogToFile("未找到主窗口实例,无法添加启动台按钮", LogHelper.LogType.Error); + return; + } + + // 创建启动台按钮 + _launcherButton = new LauncherButton(this); + var buttonElement = _launcherButton.Element; + + // 查找浮动栏 + var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel; + if (floatingBar == null) + { + // 如果直接查找失败,则尝试遍历可视树查找 + Panel floatingBarPanelFromTree = null; + FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree); + floatingBar = floatingBarPanelFromTree; + } + + if (floatingBar == null) + { + LogHelper.WriteLogToFile("未找到浮动栏,无法添加启动台按钮", LogHelper.LogType.Error); + return; + } + + // 添加启动台按钮到浮动栏 + if (Config.ButtonPosition == LauncherButtonPosition.Left) + { + floatingBar.Children.Insert(0, buttonElement); + LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏左侧", LogHelper.LogType.Info); + } + else + { + floatingBar.Children.Add(buttonElement); + LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏右侧", LogHelper.LogType.Info); + } + + _isAddedToFloatingBar = true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"添加启动台按钮到浮动栏时出错: {ex.Message}", LogHelper.LogType.Error); + LogHelper.NewLog(ex); + } + } + + /// + /// 递归查找StackPanelFloatingBar + /// + private void FindStackPanelFloatingBar(DependencyObject parent, ref Panel result) + { + if (parent == null || result != null) return; + + try + { + // 检查当前对象是否为我们要找的面板 + if (parent is Panel panel && panel.Name == "StackPanelFloatingBar") + { + result = panel; + return; + } + + // 获取子元素数量 + int childCount = VisualTreeHelper.GetChildrenCount(parent); + + // 遍历所有子元素 + for (int i = 0; i < childCount; i++) + { + DependencyObject child = VisualTreeHelper.GetChild(parent, i); + FindStackPanelFloatingBar(child, ref result); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"查找StackPanelFloatingBar时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 从浮动栏移除启动台按钮 + /// + private void RemoveLauncherButtonFromFloatingBar() + { + try + { + if (!_isAddedToFloatingBar || _launcherButton == null) + { + return; + } + + // 获取主窗口实例 + var mainWindow = Application.Current.MainWindow; + if (mainWindow == null) + { + LogHelper.WriteLogToFile("未找到主窗口实例,无法移除启动台按钮", LogHelper.LogType.Error); + return; + } + + // 获取按钮元素 + var buttonElement = _launcherButton.Element; + + // 查找浮动栏 + var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel; + if (floatingBar == null) + { + // 如果直接查找失败,则尝试遍历可视树查找 + Panel floatingBarPanelFromTree = null; + FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree); + floatingBar = floatingBarPanelFromTree; + } + + if (floatingBar == null) + { + LogHelper.WriteLogToFile("未找到浮动栏,无法移除启动台按钮", LogHelper.LogType.Error); + return; + } + + // 从浮动栏移除启动台按钮 + if (floatingBar.Children.Contains(buttonElement)) + { + floatingBar.Children.Remove(buttonElement); + LogHelper.WriteLogToFile("启动台按钮已从浮动栏移除", LogHelper.LogType.Info); + } + + _isAddedToFloatingBar = false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"移除启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error); + LogHelper.NewLog(ex); + } + } + + /// + /// 更新启动台按钮位置 + /// + public void UpdateButtonPosition() + { + try + { + // 如果按钮已添加到浮动栏,重新添加以更新位置 + if (_isAddedToFloatingBar) + { + RemoveLauncherButtonFromFloatingBar(); + AddLauncherButtonToFloatingBar(); + LogHelper.WriteLogToFile($"启动台按钮位置已更新为: {Config.ButtonPosition}", LogHelper.LogType.Info); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error); + LogHelper.NewLog(ex); + } + } + + #endregion + + #region 启动台功能 + + /// + /// 显示启动台窗口 + /// + /// 按钮在屏幕上的位置 + public void ShowLauncherWindow(Point buttonPosition) + { + try + { + // 如果窗口已存在,关闭它 + if (_launcherWindow != null && _launcherWindow.IsVisible) + { + _launcherWindow.Close(); + _launcherWindow = null; + return; + } + + // 创建新的启动台窗口 + _launcherWindow = new LauncherWindow(this); + + // 计算窗口位置,使其位于按钮上方 + PositionLauncherWindow(_launcherWindow, buttonPosition); + + // 显示窗口 + _launcherWindow.Show(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"显示启动台窗口时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 设置启动台窗口位置 + /// + /// 启动台窗口 + /// 按钮在屏幕上的位置 + private void PositionLauncherWindow(LauncherWindow window, Point buttonPosition) + { + // 确保窗口已加载 + if (window.ActualWidth == 0 || window.ActualHeight == 0) + { + window.WindowStartupLocation = WindowStartupLocation.CenterScreen; + + // 设置窗口加载完成后的位置 + window.Loaded += (s, e) => + { + // 窗口位于按钮上方居中 + double left = buttonPosition.X - (window.ActualWidth / 2); + double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距 + + // 确保窗口在屏幕内 + left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth)); + top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight)); + + window.Left = left; + window.Top = top; + }; + } + else + { + // 窗口位于按钮上方居中 + double left = buttonPosition.X - (window.ActualWidth / 2); + double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距 + + // 确保窗口在屏幕内 + left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth)); + top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight)); + + window.Left = left; + window.Top = top; + } + } + + /// + /// 添加应用到启动台 + /// + /// 启动台项 + public void AddLauncherItem(LauncherItem item) + { + // 如果项目数量已达上限,则不添加 + if (LauncherItems.Count >= 40) + { + MessageBox.Show("启动台项目数量已达上限(40个)!", "提示", MessageBoxButton.OK, MessageBoxImage.Information); + return; + } + + // 寻找合适的位置 + if (item.Position < 0) + { + item.Position = FindNextAvailablePosition(); + } + + // 添加项目并保存配置 + LauncherItems.Add(item); + SaveConfig(); + } + + /// + /// 查找下一个可用位置 + /// + private int FindNextAvailablePosition() + { + // 获取已使用的位置列表 + var usedPositions = new HashSet(); + foreach (var item in LauncherItems) + { + usedPositions.Add(item.Position); + } + + // 查找第一个可用位置 + for (int i = 0; i < 40; i++) + { + if (!usedPositions.Contains(i)) + { + return i; + } + } + + // 如果所有位置都已使用,则返回0 + return 0; + } + + #endregion + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/IPlugin.cs b/Ink Canvas/Helpers/Plugins/IPlugin.cs new file mode 100644 index 00000000..be55f8af --- /dev/null +++ b/Ink Canvas/Helpers/Plugins/IPlugin.cs @@ -0,0 +1,68 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace Ink_Canvas.Helpers.Plugins +{ + /// + /// 定义插件的基本接口 + /// + public interface IPlugin + { + /// + /// 插件名称 + /// + string Name { get; } + + /// + /// 插件描述 + /// + string Description { get; } + + /// + /// 插件版本 + /// + Version Version { get; } + + /// + /// 插件作者 + /// + string Author { get; } + + /// + /// 是否为内置插件 + /// + bool IsBuiltIn { get; } + + /// + /// 初始化插件 + /// 此方法在插件加载时被调用,用于执行一些初始化工作 + /// + void Initialize(); + + /// + /// 启用插件 + /// 此方法在插件被用户或系统启用时调用,激活插件功能 + /// + void Enable(); + + /// + /// 禁用插件 + /// 此方法在插件被用户或系统禁用时调用,停用插件功能 + /// + void Disable(); + + /// + /// 获取插件设置界面 + /// 此方法返回插件的设置界面控件,用于展示在设置窗口 + /// + /// 插件设置界面 + UserControl GetSettingsView(); + + /// + /// 插件卸载时的清理工作 + /// 此方法在插件被卸载前调用,用于释放资源和执行清理 + /// + void Cleanup(); + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/PluginBase.cs b/Ink Canvas/Helpers/Plugins/PluginBase.cs new file mode 100644 index 00000000..6fee53ab --- /dev/null +++ b/Ink Canvas/Helpers/Plugins/PluginBase.cs @@ -0,0 +1,144 @@ +using System; +using System.Windows.Controls; + +namespace Ink_Canvas.Helpers.Plugins +{ + /// + /// 插件基类,提供基本实现 + /// + public abstract class PluginBase : IPlugin + { + /// + /// 插件状态(私有字段) + /// + private bool _isEnabled = false; + + /// + /// 插件状态(公共属性) + /// + public bool IsEnabled + { + get => _isEnabled; + protected set + { + if (_isEnabled != value) + { + _isEnabled = value; + OnEnabledStateChanged(value); + } + } + } + + /// + /// 插件ID + /// + public string Id { get; protected set; } + + /// + /// 插件路径 + /// + public string PluginPath { get; set; } + + /// + /// 插件名称 + /// + public abstract string Name { get; } + + /// + /// 插件描述 + /// + public abstract string Description { get; } + + /// + /// 插件版本 + /// + public abstract Version Version { get; } + + /// + /// 插件作者 + /// + public abstract string Author { get; } + + /// + /// 是否为内置插件 + /// + public virtual bool IsBuiltIn => false; + + /// + /// 状态变更事件 + /// + public event EventHandler EnabledStateChanged; + + /// + /// 初始化插件 + /// + public virtual void Initialize() + { + Id = GetType().FullName; + LogHelper.WriteLogToFile($"插件 {Name} 已初始化", LogHelper.LogType.Info); + } + + /// + /// 启用插件 + /// + public virtual void Enable() + { + if (!IsEnabled) + { + IsEnabled = true; + LogHelper.WriteLogToFile($"插件 {Name} 已启用", LogHelper.LogType.Info); + } + } + + /// + /// 禁用插件 + /// + public virtual void Disable() + { + if (IsEnabled) + { + IsEnabled = false; + LogHelper.WriteLogToFile($"插件 {Name} 已禁用", LogHelper.LogType.Info); + } + } + + /// + /// 获取插件设置界面 + /// + /// 插件设置界面 + public virtual UserControl GetSettingsView() + { + // 默认返回空设置页面 + return new UserControl(); + } + + /// + /// 插件卸载时的清理工作 + /// + public virtual void Cleanup() + { + LogHelper.WriteLogToFile($"插件 {Name} 已卸载", LogHelper.LogType.Info); + } + + /// + /// 保存插件自身的设置 + /// 注意:此方法仅用于保存插件的特定设置,不应影响插件启用/禁用状态 + /// 插件启用状态由PluginManager统一管理 + /// + public virtual void SavePluginSettings() + { + // 默认实现不做任何事情 + // 子类可以重写此方法,将自身设置保存到配置文件中 + LogHelper.WriteLogToFile($"插件 {Name} 设置已保存", LogHelper.LogType.Event); + } + + /// + /// 触发状态变更事件 + /// + /// 是否启用 + protected virtual void OnEnabledStateChanged(bool isEnabled) + { + EnabledStateChanged?.Invoke(this, isEnabled); + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/PluginManager.cs b/Ink Canvas/Helpers/Plugins/PluginManager.cs new file mode 100644 index 00000000..e76322aa --- /dev/null +++ b/Ink Canvas/Helpers/Plugins/PluginManager.cs @@ -0,0 +1,1462 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; + +namespace Ink_Canvas.Helpers.Plugins +{ + /// + /// 插件管理器,负责插件的加载、卸载和管理 + /// + public class PluginManager + { + private static readonly string PluginsDirectory = Path.Combine(App.RootPath, "Plugins"); + private static readonly string PluginConfigFile = Path.Combine(App.RootPath, "PluginConfig.json"); + private static readonly string PluginConfigBackupFile = Path.Combine(App.RootPath, "PluginConfig.json.bak"); + + private static PluginManager _instance; + private static SemaphoreSlim _configLock = new SemaphoreSlim(1, 1); + + /// + /// 插件管理器单例 + /// + public static PluginManager Instance + { + get + { + if (_instance == null) + { + _instance = new PluginManager(); + } + return _instance; + } + } + + /// + /// 已加载的插件集合 + /// + public ObservableCollection Plugins { get; } = new ObservableCollection(); + + /// + /// 插件配置信息 + /// + public Dictionary PluginStates { get; private set; } = new Dictionary(); + + /// + /// 配置是否已更改但未保存 + /// + private bool _configDirty = false; + + /// + /// 配置自动保存计时器 + /// + private System.Timers.Timer _autoSaveTimer; + + /// + /// 加载的程序集缓存 + /// + private Dictionary _loadedAssemblies = new Dictionary(); + + /// + /// 插件文件哈希缓存,用于热重载检测 + /// + private Dictionary _pluginHashes = new Dictionary(); + + private PluginManager() + { + // 确保插件目录存在 + if (!Directory.Exists(PluginsDirectory)) + { + Directory.CreateDirectory(PluginsDirectory); + } + + // 加载插件配置 + LoadConfig(); + + // 初始化自动保存计时器(3秒) + _autoSaveTimer = new System.Timers.Timer(3000); + _autoSaveTimer.Elapsed += (s, e) => + { + if (_configDirty) + { + SaveConfigAsync().ConfigureAwait(false); + } + }; + _autoSaveTimer.AutoReset = false; + + // 注册插件状态变更事件处理 + AppDomain.CurrentDomain.ProcessExit += (s, e) => + { + // 应用退出时强制保存配置 + if (_configDirty) + { + SaveConfig(); + } + }; + } + + /// + /// 初始化插件系统 + /// + public void Initialize() + { + try + { + LogHelper.WriteLogToFile("开始初始化插件系统", LogHelper.LogType.Info); + + // 加载配置 + LoadConfig(); + LogHelper.WriteLogToFile($"已从配置文件加载 {PluginStates.Count} 个插件状态记录", LogHelper.LogType.Info); + + // 加载内置插件 + LogHelper.WriteLogToFile("正在加载内置插件...", LogHelper.LogType.Info); + LoadBuiltInPlugins(); + + // 加载外部插件 + LogHelper.WriteLogToFile("正在加载外部插件...", LogHelper.LogType.Info); + LoadExternalPlugins(); + + // 启用已配置为启用的插件 + LogHelper.WriteLogToFile("正在应用配置的插件状态...", LogHelper.LogType.Info); + EnableConfiguredPlugins(); + + // 设置定期检查热重载 + StartHotReloadWatcher(); + + // 保存初始化后的配置(可能有新插件) + SaveConfig(); + + LogHelper.WriteLogToFile($"插件系统初始化完成,共加载 {Plugins.Count} 个插件", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化插件系统时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 加载内置插件 + /// + private void LoadBuiltInPlugins() + { + try + { + // 获取当前程序集 + Assembly currentAssembly = Assembly.GetExecutingAssembly(); + + // 查找实现了IPlugin接口的所有类型 + var pluginTypes = currentAssembly.GetTypes() + .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && t.IsClass); + + foreach (var pluginType in pluginTypes) + { + try + { + // 创建插件实例 + IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType); + + // 只处理内置插件 + if (plugin.IsBuiltIn) + { + plugin.Initialize(); + Plugins.Add(plugin); + LogHelper.WriteLogToFile($"已加载内置插件: {plugin.Name} v{plugin.Version}", LogHelper.LogType.Info); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载内置插件 {pluginType.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载内置插件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 加载外部插件 + /// + private void LoadExternalPlugins() + { + try + { + // 检查插件目录是否存在 + if (!Directory.Exists(PluginsDirectory)) + { + Directory.CreateDirectory(PluginsDirectory); + return; + } + + // 获取所有插件文件 + var pluginFiles = Directory.GetFiles(PluginsDirectory, "*.iccpp", SearchOption.TopDirectoryOnly); + + foreach (var pluginFile in pluginFiles) + { + LoadExternalPlugin(pluginFile); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载外部插件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 加载单个外部插件 + /// + /// 插件文件路径 + /// 加载的插件实例,加载失败则返回null + public IPlugin LoadExternalPlugin(string pluginPath) + { + try + { + // 计算文件哈希 + string fileHash = CalculateFileHash(pluginPath); + _pluginHashes[pluginPath] = fileHash; + + // 加载插件程序集 + Assembly pluginAssembly = LoadPluginAssembly(pluginPath); + if (pluginAssembly == null) return null; + + // 查找实现了IPlugin接口的类型 + var pluginTypes = pluginAssembly.GetTypes() + .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && t.IsClass); + + foreach (var pluginType in pluginTypes) + { + try + { + // 创建插件实例 + IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType); + + // 设置插件路径 + if (plugin is PluginBase pluginBase) + { + pluginBase.PluginPath = pluginPath; + } + + plugin.Initialize(); + Plugins.Add(plugin); + + LogHelper.WriteLogToFile($"已加载外部插件: {plugin.Name} v{plugin.Version} 来自 {Path.GetFileName(pluginPath)}", + LogHelper.LogType.Info); + + return plugin; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"实例化插件 {pluginType.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载插件 {Path.GetFileName(pluginPath)} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + + return null; + } + + /// + /// 加载插件程序集 + /// + /// 插件文件路径 + /// 加载的程序集 + private Assembly LoadPluginAssembly(string pluginPath) + { + try + { + // 检查是否已加载该程序集 + if (_loadedAssemblies.TryGetValue(pluginPath, out var loadedAssembly)) + { + return loadedAssembly; + } + + // 加载程序集 + Assembly pluginAssembly = Assembly.LoadFrom(pluginPath); + _loadedAssemblies[pluginPath] = pluginAssembly; + + return pluginAssembly; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载插件程序集 {Path.GetFileName(pluginPath)} 时出错: {ex.Message}", LogHelper.LogType.Error); + return null; + } + } + + /// + /// 启用已配置为启用的插件 + /// + private void EnableConfiguredPlugins() + { + int enabledCount = 0; + int disabledCount = 0; + int errorCount = 0; + + foreach (var plugin in Plugins) + { + try + { + string pluginTypeName = plugin.GetType().FullName; + + // 检查配置中的插件状态 + if (PluginStates.TryGetValue(pluginTypeName, out bool enabled)) + { + // 获取当前实际状态 + bool currentState = plugin is PluginBase pluginBase && pluginBase.IsEnabled; + + // 如果配置状态与当前状态不一致,则应用配置状态 + if (currentState != enabled) + { + // 注册插件状态变更事件 + if (plugin is PluginBase pb) + { + pb.EnabledStateChanged += Plugin_EnabledStateChanged; + } + + if (enabled) + { + plugin.Enable(); + enabledCount++; + LogHelper.WriteLogToFile($"根据配置启用插件: {plugin.Name}", LogHelper.LogType.Info); + } + else + { + plugin.Disable(); + disabledCount++; + LogHelper.WriteLogToFile($"根据配置禁用插件: {plugin.Name}", LogHelper.LogType.Info); + } + } + else + { + // 状态一致,只注册事件 + if (plugin is PluginBase pb) + { + pb.EnabledStateChanged += Plugin_EnabledStateChanged; + } + } + } + else + { + // 插件不在配置中,添加默认状态(禁用) + PluginStates[pluginTypeName] = false; + _configDirty = true; + + // 注册插件状态变更事件 + if (plugin is PluginBase pb) + { + pb.EnabledStateChanged += Plugin_EnabledStateChanged; + } + + // 如果当前是启用状态,则禁用 + if (plugin is PluginBase pluginBase && pluginBase.IsEnabled) + { + plugin.Disable(); + disabledCount++; + LogHelper.WriteLogToFile($"插件不在配置中,默认禁用: {plugin.Name}", LogHelper.LogType.Info); + } + } + } + catch (Exception ex) + { + errorCount++; + LogHelper.WriteLogToFile($"应用插件 {plugin.Name} 配置时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + // 如果有配置变更,启动自动保存 + if (_configDirty) + { + TriggerAutoSave(); + } + + LogHelper.WriteLogToFile($"已应用插件配置: 启用 {enabledCount} 个,禁用 {disabledCount} 个,错误 {errorCount} 个", LogHelper.LogType.Info); + } + + /// + /// 插件状态变更事件处理 + /// + private void Plugin_EnabledStateChanged(object sender, bool isEnabled) + { + try + { + if (sender is IPlugin plugin) + { + string pluginTypeName = plugin.GetType().FullName; + + // 更新配置状态 + if (!PluginStates.ContainsKey(pluginTypeName) || PluginStates[pluginTypeName] != isEnabled) + { + PluginStates[pluginTypeName] = isEnabled; + _configDirty = true; + + LogHelper.WriteLogToFile($"插件状态变更: {plugin.Name} = {(isEnabled ? "启用" : "禁用")}", LogHelper.LogType.Info); + + // 触发自动保存 + TriggerAutoSave(); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"处理插件状态变更事件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 触发自动保存计时器 + /// + private void TriggerAutoSave() + { + // 重置并启动计时器 + _autoSaveTimer.Stop(); + _autoSaveTimer.Start(); + } + + /// + /// 启动热重载监视器 + /// + private void StartHotReloadWatcher() + { + // 创建定时检查任务 + Task.Run(async () => + { + while (true) + { + try + { + // 每5秒检查一次 + await Task.Delay(5000); + + // 获取所有外部插件 + var externalPlugins = Plugins.OfType() + .Where(p => !p.IsBuiltIn && !string.IsNullOrEmpty(p.PluginPath)) + .ToList(); + + foreach (var plugin in externalPlugins) + { + // 检查插件文件是否存在 + if (!File.Exists(plugin.PluginPath)) + { + continue; + } + + // 计算当前文件哈希 + string currentHash = CalculateFileHash(plugin.PluginPath); + + // 比较哈希值是否变化 + if (_pluginHashes.TryGetValue(plugin.PluginPath, out string oldHash) && + !string.IsNullOrEmpty(oldHash) && + oldHash != currentHash) + { + // 文件已变化,执行热重载 + Application.Current.Dispatcher.Invoke(() => + { + ReloadPlugin(plugin); + }); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"热重载检查出错: {ex.Message}", LogHelper.LogType.Error); + } + } + }); + } + + /// + /// 重新加载插件 + /// + /// 要重新加载的插件 + private void ReloadPlugin(PluginBase plugin) + { + try + { + LogHelper.WriteLogToFile($"开始重载插件: {plugin.Name}", LogHelper.LogType.Info); + + // 记录插件状态和信息 + bool wasEnabled = plugin.IsEnabled; + string pluginPath = plugin.PluginPath; + string pluginTypeName = plugin.GetType().FullName; + + // 记录日志,方便排查问题 + LogHelper.WriteLogToFile($"重载前插件状态 - 类型: {pluginTypeName}, 状态: {(wasEnabled ? "启用" : "禁用")}", LogHelper.LogType.Info); + + // 如果配置中有该插件的状态,记录配置中的状态 + if (PluginStates.TryGetValue(pluginTypeName, out bool currentConfigState)) + { + LogHelper.WriteLogToFile($"配置中插件状态: {(currentConfigState ? "启用" : "禁用")}", LogHelper.LogType.Info); + } + + // 卸载插件,但不从PluginStates中移除状态信息 + UnloadPlugin(plugin); + + // 清除程序集缓存,确保加载最新版本 + _loadedAssemblies.Remove(pluginPath); + + // 重新加载插件 + IPlugin newPlugin = LoadExternalPlugin(pluginPath); + + if (newPlugin != null) + { + // 更新配置中的插件状态 + string newPluginTypeName = newPlugin.GetType().FullName; + + // 如果插件类型名称变化,需要更新配置 + if (newPluginTypeName != pluginTypeName && PluginStates.ContainsKey(pluginTypeName)) + { + bool state = PluginStates[pluginTypeName]; + PluginStates.Remove(pluginTypeName); + PluginStates[newPluginTypeName] = state; + LogHelper.WriteLogToFile($"插件类型名称已变更: {pluginTypeName} -> {newPluginTypeName}, 已更新配置", LogHelper.LogType.Info); + } + + // 应用正确的状态 + bool shouldBeEnabled = false; + if (PluginStates.TryGetValue(newPluginTypeName, out bool storedConfigState)) + { + shouldBeEnabled = storedConfigState; + LogHelper.WriteLogToFile($"从配置获取插件状态: {(shouldBeEnabled ? "启用" : "禁用")}", LogHelper.LogType.Info); + } + else + { + shouldBeEnabled = wasEnabled; + PluginStates[newPluginTypeName] = shouldBeEnabled; + LogHelper.WriteLogToFile($"使用之前的状态: {(shouldBeEnabled ? "启用" : "禁用")}", LogHelper.LogType.Info); + } + + // 获取重载后的实际状态 + bool currentState = newPlugin is PluginBase pluginBaseState && pluginBaseState.IsEnabled; + LogHelper.WriteLogToFile($"重载后实际状态: {(currentState ? "启用" : "禁用")}", LogHelper.LogType.Info); + + // 根据应该启用的状态启用或禁用插件 + if (shouldBeEnabled != currentState) + { + if (shouldBeEnabled) + { + newPlugin.Enable(); + LogHelper.WriteLogToFile($"插件 {newPlugin.Name} 已重载并启用", LogHelper.LogType.Info); + } + else + { + newPlugin.Disable(); + LogHelper.WriteLogToFile($"插件 {newPlugin.Name} 已重载并禁用", LogHelper.LogType.Info); + } + + // 检查状态是否正确应用 + currentState = newPlugin is PluginBase reloadedBase && reloadedBase.IsEnabled; + LogHelper.WriteLogToFile($"应用状态后实际状态: {(currentState ? "启用" : "禁用")}", LogHelper.LogType.Info); + + if (currentState != shouldBeEnabled) + { + LogHelper.WriteLogToFile($"警告: 插件状态应用失败,目标状态: {(shouldBeEnabled ? "启用" : "禁用")}, 实际状态: {(currentState ? "启用" : "禁用")}", LogHelper.LogType.Warning); + } + } + else + { + LogHelper.WriteLogToFile($"插件 {newPlugin.Name} 已重载并保持{(shouldBeEnabled ? "启用" : "禁用")}状态", LogHelper.LogType.Info); + } + + // 保存插件设置 + if (newPlugin is PluginBase pluginBaseInstance) + { + try + { + // 保存插件设置(与启用状态无关) + pluginBaseInstance.SavePluginSettings(); + LogHelper.WriteLogToFile($"已保存插件 {newPlugin.Name} 设置", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存插件 {newPlugin.Name} 设置时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + // 立即保存配置 + LogHelper.WriteLogToFile($"重载后保存插件配置...", LogHelper.LogType.Info); + SaveConfig(); + } + else + { + LogHelper.WriteLogToFile($"插件 {plugin.Name} 重载失败: 无法加载新插件", LogHelper.LogType.Error); + } + + // 更新UI + NotifyUIRefresh(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"重新加载插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 卸载插件 + /// + /// 要卸载的插件 + /// 是否从配置中移除插件状态(默认为false) + public void UnloadPlugin(IPlugin plugin, bool removeFromConfig = false) + { + try + { + // 如果插件已启用,先禁用它 + if (plugin is PluginBase pluginBase && pluginBase.IsEnabled) + { + plugin.Disable(); + } + + // 执行插件清理 + plugin.Cleanup(); + + // 从插件集合中移除 + Plugins.Remove(plugin); + + // 从配置中移除(如果需要) + if (removeFromConfig && plugin.GetType() != null) + { + string pluginTypeName = plugin.GetType().FullName; + if (PluginStates.ContainsKey(pluginTypeName)) + { + PluginStates.Remove(pluginTypeName); + SaveConfig(); + } + } + + LogHelper.WriteLogToFile($"已卸载插件: {plugin.Name}", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"卸载插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 删除插件 + /// + /// 要删除的插件 + /// 删除是否成功 + public bool DeletePlugin(IPlugin plugin) + { + try + { + // 只能删除外部插件 + if (plugin.IsBuiltIn) + { + return false; + } + + // 获取插件路径 + string pluginPath = null; + if (plugin is PluginBase pluginBase) + { + pluginPath = pluginBase.PluginPath; + } + + if (string.IsNullOrEmpty(pluginPath) || !File.Exists(pluginPath)) + { + return false; + } + + // 卸载插件(并从配置中移除状态) + UnloadPlugin(plugin, true); + + // 删除插件文件 + File.Delete(pluginPath); + + // 清理缓存 + _loadedAssemblies.Remove(pluginPath); + _pluginHashes.Remove(pluginPath); + + // 保存配置 + SaveConfig(); + + LogHelper.WriteLogToFile($"已删除插件: {plugin.Name}", LogHelper.LogType.Info); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"删除插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 切换插件启用状态 + /// + /// 目标插件 + /// 是否启用 + public void TogglePlugin(IPlugin plugin, bool enable) + { + try + { + // 检查当前状态是否已经是目标状态 + bool currentState = plugin is PluginBase pluginBase && pluginBase.IsEnabled; + if (currentState == enable) + { + // 已经是目标状态,无需操作 + LogHelper.WriteLogToFile($"插件 {plugin.Name} 已经是 {(enable ? "启用" : "禁用")} 状态,无需切换", LogHelper.LogType.Info); + return; + } + + // 记录插件信息,用于日志 + string pluginName = plugin.Name; + string pluginTypeName = plugin.GetType().FullName; + + LogHelper.WriteLogToFile($"开始切换插件 {pluginName} 状态为: {(enable ? "启用" : "禁用")}", LogHelper.LogType.Info); + + // 首先更新配置状态 + PluginStates[pluginTypeName] = enable; + _configDirty = true; + + // 更新插件状态 + try + { + // 注册事件(无需检查事件是否为null) + if (plugin is PluginBase pb) + { + // 先取消可能已有的订阅,避免重复订阅 + pb.EnabledStateChanged -= Plugin_EnabledStateChanged; + // 重新订阅 + pb.EnabledStateChanged += Plugin_EnabledStateChanged; + } + + // 更新插件状态 + if (enable) + { + plugin.Enable(); + LogHelper.WriteLogToFile($"插件 {pluginName} 已启用", LogHelper.LogType.Info); + } + else + { + // 禁用前先记录是否为内置插件 + bool isBuiltIn = plugin.IsBuiltIn; + LogHelper.WriteLogToFile($"尝试禁用{(isBuiltIn ? "内置" : "外部")}插件 {pluginName}", LogHelper.LogType.Info); + + // 禁用插件 + plugin.Disable(); + + // 禁用后立即检查状态,确保禁用成功 + bool actuallyDisabled = !(plugin is PluginBase pb2 && pb2.IsEnabled); + if (!actuallyDisabled) + { + LogHelper.WriteLogToFile($"警告: 插件 {pluginName} 禁用失败,再次尝试禁用", LogHelper.LogType.Warning); + plugin.Disable(); // 再次尝试禁用 + + // 再次检查 + actuallyDisabled = !(plugin is PluginBase pb3 && pb3.IsEnabled); + if (!actuallyDisabled) + { + LogHelper.WriteLogToFile($"错误: 插件 {pluginName} 禁用失败,强制设置禁用状态", LogHelper.LogType.Error); + // 强制设置状态 + if (plugin is PluginBase pb4) + { + // 使用反射强制设置禁用状态 + var enabledProperty = typeof(PluginBase).GetProperty("IsEnabled"); + if (enabledProperty != null) + { + enabledProperty.SetValue(pb4, false); + LogHelper.WriteLogToFile($"已通过反射强制设置插件 {pluginName} 为禁用状态", LogHelper.LogType.Info); + } + } + } + } + + LogHelper.WriteLogToFile($"插件 {pluginName} 已禁用", LogHelper.LogType.Info); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更改插件 {pluginName} 状态时出错: {ex.Message}", LogHelper.LogType.Error); + } + + // 立即保存配置 + SaveConfigAsync().ConfigureAwait(false); + + // 插件状态切换后,始终进行重载(无论是启用还是禁用) + if (plugin is PluginBase pluginInstance) + { + // 对于内置插件,执行专门的处理 + if (pluginInstance.IsBuiltIn) + { + LogHelper.WriteLogToFile($"处理内置插件 {pluginName} 状态变更", LogHelper.LogType.Info); + + // 对于内置插件,我们需要确保状态正确应用 + bool finalState = pluginInstance.IsEnabled; + bool expectedState = enable; + + if (finalState != expectedState) + { + LogHelper.WriteLogToFile($"内置插件状态不匹配: 当前={finalState}, 期望={expectedState},尝试纠正", LogHelper.LogType.Warning); + + // 再次尝试设置状态 + if (expectedState) + { + plugin.Enable(); + } + else + { + plugin.Disable(); + + // 最后一次检查,如果仍然不匹配,强制设置 + if (pluginInstance.IsEnabled != expectedState) + { + var enabledProperty = typeof(PluginBase).GetProperty("IsEnabled"); + if (enabledProperty != null) + { + enabledProperty.SetValue(pluginInstance, expectedState); + LogHelper.WriteLogToFile($"已通过反射强制设置内置插件 {pluginName} 状态为 {(expectedState ? "启用" : "禁用")}", LogHelper.LogType.Info); + } + } + } + } + + // 通知UI刷新 + NotifyUIRefresh(); + } + else + { + // 外部插件,执行热重载 + try + { + if (!string.IsNullOrEmpty(pluginInstance.PluginPath) && File.Exists(pluginInstance.PluginPath)) + { + LogHelper.WriteLogToFile($"开始重载外部插件 {pluginName}", LogHelper.LogType.Info); + + // 使用调度器确保在UI线程执行热重载 + if (Application.Current != null && Application.Current.Dispatcher != null) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + ReloadPlugin(pluginInstance); + LogHelper.WriteLogToFile($"插件 {pluginName} 已重载以应用{(enable ? "启用" : "禁用")}状态", LogHelper.LogType.Info); + })); + } + else + { + // 当前不在UI线程,直接重载 + ReloadPlugin(pluginInstance); + LogHelper.WriteLogToFile($"插件 {pluginName} 已重载以应用{(enable ? "启用" : "禁用")}状态", LogHelper.LogType.Info); + } + } + else + { + LogHelper.WriteLogToFile($"外部插件 {pluginName} 文件不存在,无法重载", LogHelper.LogType.Warning); + NotifyUIRefresh(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"重载插件 {pluginName} 时出错: {ex.Message}", LogHelper.LogType.Error); + // 出错时也要刷新UI + NotifyUIRefresh(); + } + } + } + else + { + // 通知UI刷新 + NotifyUIRefresh(); + } + + LogHelper.WriteLogToFile($"插件 {pluginName} 状态切换完成", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换插件状态时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 应用插件实时状态 + /// + /// 目标插件 + /// 是否启用 + private void ApplyPluginRealTimeState(IPlugin plugin, bool enable) + { + try + { + // 确保当前实例状态正确 + bool currentState = plugin is PluginBase pluginBase && pluginBase.IsEnabled; + if (currentState != enable) + { + if (enable) + { + plugin.Enable(); + LogHelper.WriteLogToFile($"实时应用: 已启用插件 {plugin.Name}", LogHelper.LogType.Info); + } + else + { + plugin.Disable(); + LogHelper.WriteLogToFile($"实时应用: 已禁用插件 {plugin.Name}", LogHelper.LogType.Info); + } + + // 同步状态到插件自身的配置 + if (plugin is PluginBase pluginSettings) + { + try + { + // 保存插件设置(与启用状态无关) + pluginSettings.SavePluginSettings(); + LogHelper.WriteLogToFile($"实时应用: 已保存插件 {plugin.Name} 设置", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"实时应用: 保存插件 {plugin.Name} 设置时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } + + // 对于外部插件,尝试执行热重载以确保状态立即生效 + if (plugin is PluginBase externalPlugin && !externalPlugin.IsBuiltIn) + { + string pluginPath = externalPlugin.PluginPath; + if (!string.IsNullOrEmpty(pluginPath) && File.Exists(pluginPath)) + { + // 记录插件类型名称,用于后续状态检查 + string pluginTypeName = plugin.GetType().FullName; + bool targetState = enable; + + // 使用调度器确保在UI线程执行热重载 + if (Application.Current != null && Application.Current.Dispatcher != null) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + try + { + // 热重载前再次确认配置状态正确 + if (PluginStates.TryGetValue(pluginTypeName, out bool storedStateUi) && storedStateUi != targetState) + { + LogHelper.WriteLogToFile($"热重载前发现状态不一致,修正配置: {plugin.Name}, 配置={storedStateUi}, 目标={targetState}", LogHelper.LogType.Warning); + PluginStates[pluginTypeName] = targetState; + SaveConfig(); + } + + // 执行热重载 + ReloadPlugin(externalPlugin); + LogHelper.WriteLogToFile($"插件 {plugin.Name} 已成功热重载以应用实时状态", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"热重载插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + })); + } + else + { + // 当前不在UI线程,直接重载 + // 热重载前再次确认配置状态正确 + if (PluginStates.TryGetValue(pluginTypeName, out bool storedStateNonUi) && storedStateNonUi != targetState) + { + LogHelper.WriteLogToFile($"热重载前发现状态不一致,修正配置: {plugin.Name}, 配置={storedStateNonUi}, 目标={targetState}", LogHelper.LogType.Warning); + PluginStates[pluginTypeName] = targetState; + SaveConfig(); + } + + ReloadPlugin(externalPlugin); + } + } + } + + LogHelper.WriteLogToFile($"插件 {plugin.Name} 实时状态已应用: {(enable ? "启用" : "禁用")}", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用插件实时状态时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 通知UI刷新 + /// + private void NotifyUIRefresh() + { + try + { + // 通知UI刷新 + if (Application.Current != null && Application.Current.Dispatcher != null) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => { + // 通知任何可能打开的插件设置窗口刷新 + foreach (Window window in Application.Current.Windows) + { + if (window is Windows.PluginSettingsWindow pluginWindow) + { + pluginWindow.RefreshPluginList(); + break; + } + } + })); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"通知UI刷新时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 加载插件配置 + /// + private void LoadConfig() + { + const int maxRetries = 3; // 最大重试次数 + const int retryDelayMs = 300; // 重试延迟时间(毫秒) + + LogHelper.WriteLogToFile($"开始从配置文件加载插件状态: {PluginConfigFile}", LogHelper.LogType.Info); + + // 确保至少有一个默认配置 + Dictionary defaultConfig = new Dictionary(); + + // 尝试获取配置锁 + _configLock.Wait(); + + try + { + for (int attempt = 1; attempt <= maxRetries; attempt++) + { + try + { + if (File.Exists(PluginConfigFile)) + { + string json; + // 使用共享读取模式,允许其他进程同时读取但不允许写入 + using (FileStream fs = new FileStream(PluginConfigFile, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (StreamReader reader = new StreamReader(fs)) + { + json = reader.ReadToEnd(); + } + + var loadedStates = Newtonsoft.Json.JsonConvert.DeserializeObject>(json); + + if (loadedStates != null && loadedStates.Count > 0) + { + PluginStates = loadedStates; + _configDirty = false; // 重置脏标记 + LogHelper.WriteLogToFile($"成功从配置文件加载了 {PluginStates.Count} 个插件状态", LogHelper.LogType.Info); + return; // 成功加载,提前退出 + } + else + { + LogHelper.WriteLogToFile("配置文件解析为空,尝试使用备份", LogHelper.LogType.Warning); + // 尝试加载备份 + if (File.Exists(PluginConfigBackupFile)) + { + try + { + string backupJson = File.ReadAllText(PluginConfigBackupFile); + var backupStates = Newtonsoft.Json.JsonConvert.DeserializeObject>(backupJson); + + if (backupStates != null && backupStates.Count > 0) + { + PluginStates = backupStates; + _configDirty = true; // 从备份加载,需要重新保存主配置 + LogHelper.WriteLogToFile($"已从备份恢复 {PluginStates.Count} 个插件状态", LogHelper.LogType.Info); + return; // 成功从备份加载,提前退出 + } + } + catch (Exception backupEx) + { + LogHelper.WriteLogToFile($"从备份恢复配置失败: {backupEx.Message}", LogHelper.LogType.Error); + } + } + + // 备份也失败,使用默认配置 + PluginStates = defaultConfig; + _configDirty = true; + } + } + else + { + LogHelper.WriteLogToFile($"配置文件不存在,尝试使用备份: {PluginConfigFile}", LogHelper.LogType.Warning); + + // 尝试加载备份 + if (File.Exists(PluginConfigBackupFile)) + { + try + { + string backupJson = File.ReadAllText(PluginConfigBackupFile); + var backupStates = Newtonsoft.Json.JsonConvert.DeserializeObject>(backupJson); + + if (backupStates != null && backupStates.Count > 0) + { + PluginStates = backupStates; + _configDirty = true; // 从备份加载,需要重新保存主配置 + LogHelper.WriteLogToFile($"已从备份恢复 {PluginStates.Count} 个插件状态", LogHelper.LogType.Info); + return; // 成功从备份加载,提前退出 + } + } + catch (Exception backupEx) + { + LogHelper.WriteLogToFile($"从备份恢复配置失败: {backupEx.Message}", LogHelper.LogType.Error); + } + } + + PluginStates = defaultConfig; + _configDirty = true; + LogHelper.WriteLogToFile("使用默认空配置", LogHelper.LogType.Warning); + } + + // 没有成功加载或使用备份,使用默认配置 + break; + } + catch (Exception ex) + { + if (attempt < maxRetries) + { + LogHelper.WriteLogToFile($"加载配置失败 (尝试 {attempt}/{maxRetries}): {ex.Message},将在 {retryDelayMs}ms 后重试", LogHelper.LogType.Warning); + System.Threading.Thread.Sleep(retryDelayMs); + } + else + { + LogHelper.WriteLogToFile($"加载插件配置失败,已达最大重试次数 ({maxRetries}): {ex.Message}", LogHelper.LogType.Error); + + // 最终失败,使用默认配置 + PluginStates = defaultConfig; + _configDirty = true; + } + } + } + } + finally + { + // 释放配置锁 + _configLock.Release(); + } + } + + /// + /// 异步保存插件配置 + /// + public async Task SaveConfigAsync() + { + // 如果配置没有变化,无需保存 + if (!_configDirty) + { + return; + } + + // 尝试获取配置锁(异步) + if (!await _configLock.WaitAsync(0)) + { + // 已有保存操作在进行中,触发自动保存延迟 + TriggerAutoSave(); + return; + } + + try + { + // 创建配置任务 + await Task.Run(() => SaveConfig()); + } + finally + { + // 释放配置锁 + _configLock.Release(); + } + } + + /// + /// 保存插件配置 + /// + public void SaveConfig() + { + // 如果配置没有变化,无需保存 + if (!_configDirty) + { + return; + } + + const int maxRetries = 3; // 最大重试次数 + const int retryDelayMs = 500; // 重试延迟时间(毫秒) + + try + { + LogHelper.WriteLogToFile($"开始保存插件配置到: {PluginConfigFile}", LogHelper.LogType.Info); + + // 生成JSON数据 + string json = Newtonsoft.Json.JsonConvert.SerializeObject(PluginStates, Newtonsoft.Json.Formatting.Indented); + string tempFile = PluginConfigFile + ".temp"; // 临时文件路径 + + // 确保目录存在 + string configDir = Path.GetDirectoryName(PluginConfigFile); + if (!Directory.Exists(configDir)) + { + Directory.CreateDirectory(configDir); + LogHelper.WriteLogToFile($"创建配置目录: {configDir}", LogHelper.LogType.Info); + } + + // 先备份当前配置 + try + { + if (File.Exists(PluginConfigFile)) + { + File.Copy(PluginConfigFile, PluginConfigBackupFile, true); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"备份配置文件失败: {ex.Message}", LogHelper.LogType.Warning); + } + + for (int attempt = 1; attempt <= maxRetries; attempt++) + { + try + { + // 直接写入目标文件 + File.WriteAllText(PluginConfigFile, json); + + // 验证写入是否成功 + if (File.Exists(PluginConfigFile)) + { + // 重置脏标记 + _configDirty = false; + LogHelper.WriteLogToFile($"插件配置已成功保存到磁盘: {PluginConfigFile}, 共 {PluginStates.Count} 个插件状态", LogHelper.LogType.Info); + return; + } + } + catch (Exception ex) + { + if (attempt < maxRetries) + { + LogHelper.WriteLogToFile($"保存配置失败 (尝试 {attempt}/{maxRetries}): {ex.Message},将在 {retryDelayMs}ms 后重试", LogHelper.LogType.Warning); + System.Threading.Thread.Sleep(retryDelayMs); + } + else + { + LogHelper.WriteLogToFile($"保存插件配置失败,已达最大重试次数 ({maxRetries}): {ex.Message}", LogHelper.LogType.Error); + + // 尝试使用临时文件方式 + try + { + // 删除可能存在的旧临时文件 + if (File.Exists(tempFile)) + { + File.Delete(tempFile); + } + + // 写入临时文件 + File.WriteAllText(tempFile, json); + + // 如果目标文件存在,先删除 + if (File.Exists(PluginConfigFile)) + { + File.Delete(PluginConfigFile); + } + + // 重命名临时文件 + File.Move(tempFile, PluginConfigFile); + + // 重置脏标记 + _configDirty = false; + LogHelper.WriteLogToFile($"使用临时文件方式成功保存配置: {PluginConfigFile}", LogHelper.LogType.Info); + return; + } + catch (Exception fallbackEx) + { + LogHelper.WriteLogToFile($"临时文件保存方式也失败: {fallbackEx.Message}", LogHelper.LogType.Error); + } + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存插件配置时发生未处理异常: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 计算文件哈希 + /// + /// 文件路径 + /// 文件哈希值 + private string CalculateFileHash(string filePath) + { + try + { + using (var md5 = MD5.Create()) + using (var stream = File.OpenRead(filePath)) + { + byte[] hash = md5.ComputeHash(stream); + return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"计算文件哈希值时出错: {ex.Message}", LogHelper.LogType.Error); + return string.Empty; + } + } + + /// + /// 从配置文件重新加载所有插件状态并应用 + /// + public void ReloadPluginsFromConfig() + { + try + { + LogHelper.WriteLogToFile("开始从配置文件重新加载插件状态", LogHelper.LogType.Info); + + // 保存当前配置状态,以便在加载失败时回滚 + Dictionary previousStates = new Dictionary(PluginStates); + + // 重新加载配置文件 + LoadConfig(); + + // 如果配置文件加载失败,PluginStates可能为空,这时使用之前的状态 + if (PluginStates == null || PluginStates.Count == 0) + { + LogHelper.WriteLogToFile("加载的配置为空,恢复到之前的状态", LogHelper.LogType.Warning); + PluginStates = previousStates; + return; + } + + LogHelper.WriteLogToFile($"已加载 {PluginStates.Count} 个插件状态,开始应用...", LogHelper.LogType.Info); + + // 对比配置,查找变更的插件 + foreach (var plugin in Plugins.ToList()) // 创建副本进行遍历,避免集合修改异常 + { + string pluginTypeName = plugin.GetType().FullName; + + // 检查插件在配置中是否存在 + if (PluginStates.TryGetValue(pluginTypeName, out bool shouldBeEnabled)) + { + bool currentlyEnabled = plugin is PluginBase pluginBase && pluginBase.IsEnabled; + + // 如果状态需要变更 + if (currentlyEnabled != shouldBeEnabled) + { + LogHelper.WriteLogToFile($"应用插件 {plugin.Name} 的配置状态: {(shouldBeEnabled ? "启用" : "禁用")}", LogHelper.LogType.Info); + + if (shouldBeEnabled) + { + try + { + plugin.Enable(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启用插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + else + { + try + { + // 记录禁用信息,特别是内置插件 + bool isBuiltIn = plugin.IsBuiltIn; + LogHelper.WriteLogToFile($"尝试禁用{(isBuiltIn ? "内置" : "外部")}插件 {plugin.Name}", LogHelper.LogType.Info); + + // 禁用插件 + plugin.Disable(); + + // 对于内置插件,特别检查禁用状态 + if (isBuiltIn && plugin is PluginBase builtInPluginBase) + { + if (builtInPluginBase.IsEnabled) + { + LogHelper.WriteLogToFile($"内置插件 {plugin.Name} 禁用失败,尝试强制禁用", LogHelper.LogType.Warning); + // 强制设置禁用状态 + var enabledProperty = typeof(PluginBase).GetProperty("IsEnabled"); + if (enabledProperty != null) + { + enabledProperty.SetValue(builtInPluginBase, false); + LogHelper.WriteLogToFile($"已通过反射强制禁用内置插件 {plugin.Name}", LogHelper.LogType.Info); + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"禁用插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + // 如果是外部插件,执行重载 + if (!plugin.IsBuiltIn && plugin is PluginBase externalPlugin && !string.IsNullOrEmpty(externalPlugin.PluginPath)) + { + try + { + ReloadPlugin(externalPlugin); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"重载外部插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } + } + else + { + // 插件不在配置中,将其添加为禁用状态 + PluginStates[pluginTypeName] = false; + LogHelper.WriteLogToFile($"插件 {plugin.Name} 不在配置中,默认设置为禁用状态", LogHelper.LogType.Info); + + // 如果当前是启用状态,则禁用它 + if (plugin is PluginBase pluginBase && pluginBase.IsEnabled) + { + try + { + bool isBuiltIn = plugin.IsBuiltIn; + LogHelper.WriteLogToFile($"尝试禁用未配置的{(isBuiltIn ? "内置" : "外部")}插件 {plugin.Name}", LogHelper.LogType.Info); + + plugin.Disable(); + + // 对于内置插件,特别检查禁用状态 + if (isBuiltIn && pluginBase.IsEnabled) + { + LogHelper.WriteLogToFile($"未配置的内置插件 {plugin.Name} 禁用失败,尝试强制禁用", LogHelper.LogType.Warning); + // 强制设置禁用状态 + var enabledProperty = typeof(PluginBase).GetProperty("IsEnabled"); + if (enabledProperty != null) + { + enabledProperty.SetValue(pluginBase, false); + LogHelper.WriteLogToFile($"已通过反射强制禁用未配置的内置插件 {plugin.Name}", LogHelper.LogType.Info); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"禁用未配置插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } + } + + // 保存更新后的配置 + SaveConfig(); + + // 通知UI更新 + if (Application.Current != null && Application.Current.Dispatcher != null) + { + Application.Current.Dispatcher.Invoke(() => { + // 通知任何可能打开的插件设置窗口刷新 + foreach (Window window in Application.Current.Windows) + { + if (window is Windows.PluginSettingsWindow pluginWindow) + { + pluginWindow.RefreshPluginList(); + } + } + }); + } + + LogHelper.WriteLogToFile("插件状态已从配置文件重新加载完成", LogHelper.LogType.Info); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从配置文件重新加载插件状态时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/PluginTemplate.cs b/Ink Canvas/Helpers/Plugins/PluginTemplate.cs new file mode 100644 index 00000000..72f4c846 --- /dev/null +++ b/Ink Canvas/Helpers/Plugins/PluginTemplate.cs @@ -0,0 +1,276 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace Ink_Canvas.Helpers.Plugins +{ + /// + /// 插件模板,用于开发者参考 + /// 注意:实际开发时,请将此类移到单独的程序集中 + /// + public class PluginTemplate : PluginBase + { + #region 插件基本信息 + + /// + /// 插件名称 + /// + public override string Name => "插件模板"; + + /// + /// 插件描述 + /// + public override string Description => "这是一个插件开发模板,用于开发者参考。"; + + /// + /// 插件版本 + /// + public override Version Version => new Version(1, 0, 0); + + /// + /// 插件作者 + /// + public override string Author => "Your Name"; + + /// + /// 是否为内置插件(外部插件请返回false) + /// + public override bool IsBuiltIn => false; + + #endregion + + #region 插件生命周期 + + /// + /// 插件初始化 + /// 在这里进行插件的初始化工作,如加载配置、注册事件等 + /// + public override void Initialize() + { + // 先调用基类方法,这样会设置插件ID和记录日志 + base.Initialize(); + + // TODO: 在这里进行插件初始化工作 + + // 示例:记录初始化信息 + LogHelper.WriteLogToFile($"插件 {Name} 开始初始化", LogHelper.LogType.Info); + + // 示例:加载配置 + LoadConfig(); + + // 示例:注册自定义事件 + // MainWindow.Instance.SomeEvent += OnSomeEvent; + + LogHelper.WriteLogToFile($"插件 {Name} 初始化完成", LogHelper.LogType.Info); + } + + /// + /// 启用插件 + /// 在这里激活插件功能 + /// + public override void Enable() + { + // 先调用基类方法,这样会设置插件状态和记录日志 + base.Enable(); + + // TODO: 在这里启用插件功能 + + LogHelper.WriteLogToFile($"插件 {Name} 已启用", LogHelper.LogType.Info); + } + + /// + /// 禁用插件 + /// 在这里停用插件功能 + /// + public override void Disable() + { + // 先调用基类方法,这样会设置插件状态和记录日志 + base.Disable(); + + // TODO: 在这里禁用插件功能 + + LogHelper.WriteLogToFile($"插件 {Name} 已禁用", LogHelper.LogType.Info); + } + + /// + /// 清理资源 + /// 在插件卸载时调用,清理资源 + /// + public override void Cleanup() + { + // TODO: 在这里清理插件资源 + + // 示例:取消注册事件 + // MainWindow.Instance.SomeEvent -= OnSomeEvent; + + // 示例:保存配置 + SaveConfig(); + + // 最后调用基类方法 + base.Cleanup(); + } + + #endregion + + #region 插件配置 + + /// + /// 加载插件配置 + /// + 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(json); + // } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载插件配置时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 保存插件配置 + /// + 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 插件设置界面 + + /// + /// 获取插件设置界面 + /// + /// 插件设置界面 + public override UserControl GetSettingsView() + { + // 创建插件设置界面 + return new PluginTemplateSettingsControl(); + } + + #endregion + + #region 插件功能方法 + + // TODO: 在这里添加插件的具体功能方法 + + /// + /// 示例方法:执行一些功能 + /// + 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 + } + + /// + /// 插件设置控件 + /// + 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); + + // 设置控件内容 + this.Content = panel; + } + } +} \ No newline at end of file diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 7f9a583c..9f43e127 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -113,6 +113,9 @@ + + + @@ -197,6 +200,11 @@ + + - - - + + + + + + + 通过插件扩展InkCanvas的功能。您可以启用或禁用插件,或加载自定义插件。 + +