diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs
index b662a247..0808e85a 100644
--- a/Ink Canvas/App.xaml.cs
+++ b/Ink Canvas/App.xaml.cs
@@ -633,7 +633,28 @@ namespace Ink_Canvas
if (!ret && !e.Args.Contains("-m")) //-m multiple
{
LogHelper.NewLog("Detected existing instance");
- MessageBox.Show("已有一个程序实例正在运行");
+
+ // 检查是否有.icstk文件参数
+ string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(e.Args);
+ if (!string.IsNullOrEmpty(icstkFile))
+ {
+ LogHelper.WriteLogToFile($"检测到已运行实例,尝试通过IPC发送文件: {icstkFile}", LogHelper.LogType.Event);
+
+ // 尝试通过IPC发送文件路径给已运行实例
+ if (FileAssociationManager.TrySendFileToExistingInstance(icstkFile))
+ {
+ LogHelper.WriteLogToFile("文件路径已通过IPC发送给已运行实例", LogHelper.LogType.Event);
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("通过IPC发送文件路径失败", LogHelper.LogType.Warning);
+ }
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("检测到已运行实例,但无文件参数", LogHelper.LogType.Event);
+ }
+
LogHelper.NewLog("Ink Canvas automatically closed");
IsAppExitByUser = true; // 多开时标记为用户主动退出
// 写入退出信号,确保看门狗不会重启
@@ -687,6 +708,29 @@ namespace Ink_Canvas
MainWindow = mainWindow;
mainWindow.Show();
+ // 新增:注册.icstk文件关联
+ try
+ {
+ LogHelper.WriteLogToFile("开始注册.icstk文件关联");
+ FileAssociationManager.RegisterFileAssociation();
+ FileAssociationManager.ShowFileAssociationStatus();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+
+ // 新增:启动IPC监听器
+ try
+ {
+ LogHelper.WriteLogToFile("启动IPC监听器");
+ FileAssociationManager.StartIpcListener();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"启动IPC监听器时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+
// 新增:Office注册表检测
try
{
diff --git a/Ink Canvas/Helpers/FileAssociationManager.cs b/Ink Canvas/Helpers/FileAssociationManager.cs
new file mode 100644
index 00000000..e950e521
--- /dev/null
+++ b/Ink Canvas/Helpers/FileAssociationManager.cs
@@ -0,0 +1,380 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Windows;
+using Microsoft.Win32;
+using System.Threading;
+using System.Text;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// 文件关联管理器,用于注册和处理.icstk文件的关联
+ ///
+ public static class FileAssociationManager
+ {
+ private const string FileExtension = ".icstk";
+ private const string FileTypeName = "InkCanvasStrokesFile";
+ private const string AppName = "Ink Canvas";
+ private const string AppDescription = "Ink Canvas Strokes File";
+
+ // IPC相关常量
+ private const string IpcMutexName = "InkCanvasFileAssociationIpc";
+ private const string IpcEventName = "InkCanvasFileAssociationEvent";
+ private const string IpcFilePrefix = "InkCanvasFileAssociation_";
+ private const int IpcTimeout = 5000; // 5秒超时
+
+ ///
+ /// 注册.icstk文件关联
+ ///
+ public static bool RegisterFileAssociation()
+ {
+ try
+ {
+ string exePath = Process.GetCurrentProcess().MainModule.FileName;
+
+ // 注册文件类型
+ using (RegistryKey fileTypeKey = Registry.ClassesRoot.CreateSubKey(FileTypeName))
+ {
+ fileTypeKey.SetValue("", AppDescription);
+ fileTypeKey.SetValue("FriendlyTypeName", AppDescription);
+
+ // 设置默认图标
+ using (RegistryKey defaultIconKey = fileTypeKey.CreateSubKey("DefaultIcon"))
+ {
+ defaultIconKey.SetValue("", $"\"{exePath}\",0");
+ }
+
+ // 设置打开命令
+ using (RegistryKey shellKey = fileTypeKey.CreateSubKey("shell"))
+ using (RegistryKey openKey = shellKey.CreateSubKey("open"))
+ using (RegistryKey commandKey = openKey.CreateSubKey("command"))
+ {
+ commandKey.SetValue("", $"\"{exePath}\" \"%1\"");
+ }
+ }
+
+ // 注册文件扩展名
+ using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(FileExtension))
+ {
+ extensionKey.SetValue("", FileTypeName);
+ }
+
+ // 刷新系统文件关联缓存
+ RefreshSystemFileAssociations();
+
+ LogHelper.WriteLogToFile($"成功注册{FileExtension}文件关联", LogHelper.LogType.Event);
+ return true;
+ }
+ catch (SecurityException ex)
+ {
+ LogHelper.WriteLogToFile($"注册文件关联时权限不足: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ LogHelper.WriteLogToFile($"注册文件关联时访问被拒绝: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ ///
+ /// 注销.icstk文件关联
+ ///
+ public static bool UnregisterFileAssociation()
+ {
+ try
+ {
+ // 删除文件扩展名关联
+ Registry.ClassesRoot.DeleteSubKeyTree(FileExtension, false);
+
+ // 删除文件类型定义
+ Registry.ClassesRoot.DeleteSubKeyTree(FileTypeName, false);
+
+ // 刷新系统文件关联缓存
+ RefreshSystemFileAssociations();
+
+ LogHelper.WriteLogToFile($"成功注销{FileExtension}文件关联", LogHelper.LogType.Event);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"注销文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ ///
+ /// 检查文件关联是否已注册
+ ///
+ public static bool IsFileAssociationRegistered()
+ {
+ try
+ {
+ using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(FileExtension))
+ {
+ if (extensionKey == null) return false;
+
+ string fileType = extensionKey.GetValue("") as string;
+ if (string.IsNullOrEmpty(fileType)) return false;
+
+ using (RegistryKey fileTypeKey = Registry.ClassesRoot.OpenSubKey(fileType))
+ {
+ if (fileTypeKey == null) return false;
+
+ using (RegistryKey shellKey = fileTypeKey.OpenSubKey("shell\\open\\command"))
+ {
+ if (shellKey == null) return false;
+
+ string command = shellKey.GetValue("") as string;
+ if (string.IsNullOrEmpty(command)) return false;
+
+ // 检查命令是否指向当前应用程序
+ string currentExePath = Process.GetCurrentProcess().MainModule.FileName;
+ return command.Contains(currentExePath);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查文件关联状态时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+
+ return false;
+ }
+
+ ///
+ /// 显示文件关联状态
+ ///
+ public static void ShowFileAssociationStatus()
+ {
+ bool isRegistered = IsFileAssociationRegistered();
+ LogHelper.WriteLogToFile($"{FileExtension}文件关联状态: {(isRegistered ? "已注册" : "未注册")}", LogHelper.LogType.Event);
+ }
+
+ ///
+ /// 刷新系统文件关联缓存
+ ///
+ private static void RefreshSystemFileAssociations()
+ {
+ try
+ {
+ // 通知系统文件关联已更改
+ SHChangeNotify(0x08000000, 0, IntPtr.Zero, IntPtr.Zero);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"刷新文件关联缓存时出错: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ }
+
+ ///
+ /// 处理命令行参数中的文件路径
+ ///
+ /// 命令行参数
+ /// 找到的.icstk文件路径,如果没有找到则返回null
+ public static string GetIcstkFileFromArgs(string[] args)
+ {
+ if (args == null || args.Length == 0) return null;
+
+ foreach (string arg in args)
+ {
+ if (string.IsNullOrEmpty(arg)) continue;
+
+ // 检查是否为.icstk文件
+ if (Path.GetExtension(arg).Equals(FileExtension, StringComparison.OrdinalIgnoreCase))
+ {
+ // 检查文件是否存在
+ if (File.Exists(arg))
+ {
+ LogHelper.WriteLogToFile($"从命令行参数中找到.icstk文件: {arg}", LogHelper.LogType.Event);
+ return arg;
+ }
+ else
+ {
+ LogHelper.WriteLogToFile($"命令行参数中的.icstk文件不存在: {arg}", LogHelper.LogType.Warning);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// 尝试通过IPC将文件路径发送给已运行的实例
+ ///
+ /// 要打开的文件路径
+ /// 是否成功发送
+ public static bool TrySendFileToExistingInstance(string filePath)
+ {
+ try
+ {
+ LogHelper.WriteLogToFile($"尝试通过IPC发送文件路径给已运行实例: {filePath}", LogHelper.LogType.Event);
+
+ // 创建IPC文件
+ string tempDir = Path.GetTempPath();
+ string ipcFileName = IpcFilePrefix + Guid.NewGuid().ToString("N") + ".tmp";
+ string ipcFilePath = Path.Combine(tempDir, ipcFileName);
+
+ // 写入文件路径到IPC文件
+ File.WriteAllText(ipcFilePath, filePath, Encoding.UTF8);
+
+ // 创建事件通知已运行实例
+ using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
+ {
+ ipcEvent.Set();
+ }
+
+ // 等待一段时间让已运行实例处理文件
+ Thread.Sleep(1000);
+
+ // 清理IPC文件
+ try
+ {
+ if (File.Exists(ipcFilePath))
+ {
+ File.Delete(ipcFilePath);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+
+ LogHelper.WriteLogToFile("IPC文件路径发送完成", LogHelper.LogType.Event);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"通过IPC发送文件路径失败: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ ///
+ /// 启动IPC监听器,等待其他实例发送文件路径
+ ///
+ public static void StartIpcListener()
+ {
+ try
+ {
+ Thread ipcThread = new Thread(() =>
+ {
+ try
+ {
+ LogHelper.WriteLogToFile("启动IPC监听器", LogHelper.LogType.Event);
+
+ using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
+ {
+ while (true)
+ {
+ // 等待IPC事件
+ if (ipcEvent.WaitOne(IpcTimeout))
+ {
+ // 处理IPC文件
+ ProcessIpcFiles();
+
+ // 重置事件
+ ipcEvent.Reset();
+ }
+
+ // 检查应用是否还在运行
+ if (Application.Current == null || Application.Current.Dispatcher == null)
+ {
+ break;
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"IPC监听器出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ });
+
+ ipcThread.IsBackground = true;
+ ipcThread.Start();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"启动IPC监听器失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ ///
+ /// 处理IPC文件
+ ///
+ private static void ProcessIpcFiles()
+ {
+ try
+ {
+ string tempDir = Path.GetTempPath();
+ string[] ipcFiles = Directory.GetFiles(tempDir, IpcFilePrefix + "*.tmp");
+
+ foreach (string ipcFile in ipcFiles)
+ {
+ try
+ {
+ // 读取文件路径
+ string filePath = File.ReadAllText(ipcFile, Encoding.UTF8);
+
+ if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
+ {
+ LogHelper.WriteLogToFile($"IPC接收到文件路径: {filePath}", LogHelper.LogType.Event);
+
+ // 在UI线程中处理文件打开
+ Application.Current.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ // 获取主窗口并打开文件
+ if (Application.Current.MainWindow is MainWindow mainWindow)
+ {
+ mainWindow.OpenSingleStrokeFile(filePath);
+ mainWindow.ShowNotification($"已加载墨迹文件: {Path.GetFileName(filePath)}");
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"IPC处理文件打开失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }));
+ }
+
+ // 删除IPC文件
+ File.Delete(ipcFile);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
+
+ // 尝试删除损坏的IPC文件
+ try
+ {
+ if (File.Exists(ipcFile))
+ {
+ File.Delete(ipcFile);
+ }
+ }
+ catch { }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理IPC文件时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ [DllImport("shell32.dll")]
+ private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
+ }
+}
\ No newline at end of file
diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs
index c3ace02e..e3185c6f 100644
--- a/Ink Canvas/MainWindow.xaml.cs
+++ b/Ink Canvas/MainWindow.xaml.cs
@@ -523,6 +523,9 @@ namespace Ink_Canvas
// 初始化墨迹渐隐管理器
InitializeInkFadeManager();
+
+ // 处理命令行参数中的文件路径
+ HandleCommandLineFileOpen();
}
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e)
@@ -2126,6 +2129,43 @@ namespace Ink_Canvas
}
#endregion
+ ///
+ /// 处理命令行参数中的文件路径
+ ///
+ private void HandleCommandLineFileOpen()
+ {
+ try
+ {
+ // 检查启动参数中是否有.icstk文件
+ string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(App.StartArgs);
+
+ if (!string.IsNullOrEmpty(icstkFile))
+ {
+ LogHelper.WriteLogToFile($"检测到命令行参数中的.icstk文件: {icstkFile}", LogHelper.LogType.Event);
+
+ // 延迟执行,确保UI已完全加载
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ // 打开文件
+ OpenSingleStrokeFile(icstkFile);
+ ShowNotification($"已加载墨迹文件: {Path.GetFileName(icstkFile)}");
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"打开命令行参数中的文件失败: {ex.Message}", LogHelper.LogType.Error);
+ ShowNotification("打开墨迹文件失败");
+ }
+ }), DispatcherPriority.Loaded);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理命令行文件打开时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
///
/// 集中管理工具模式切换和快捷键状态更新
/// 避免在每个工具按钮点击时重复刷新快捷键状态
diff --git a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs
index fb242d97..b2b0d2a6 100644
--- a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs
@@ -643,7 +643,7 @@ namespace Ink_Canvas
///
/// 打开单个墨迹文件
///
- private void OpenSingleStrokeFile(string filePath)
+ public void OpenSingleStrokeFile(string filePath)
{
var fileStreamHasNoStroke = false;
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))