diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt
index cca5748a..2756467a 100644
--- a/AutomaticUpdateVersionControl.txt
+++ b/AutomaticUpdateVersionControl.txt
@@ -1 +1 @@
-1.7.17.0
+1.7.18.0
diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs
index 51ea0265..54c1d425 100644
--- a/Ink Canvas/App.xaml.cs
+++ b/Ink Canvas/App.xaml.cs
@@ -31,7 +31,7 @@ namespace Ink_Canvas
Mutex mutex;
public static string[] StartArgs;
- public static string RootPath = Environment.GetEnvironmentVariable("APPDATA") + "\\Ink Canvas\\";
+ public static string RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
// 新增:标记是否通过--board参数启动
public static bool StartWithBoardMode = false;
@@ -46,7 +46,7 @@ namespace Ink_Canvas
// 新增:退出信号文件路径
private static string watchdogExitSignalFile = Path.Combine(Path.GetTempPath(), "icc_watchdog_exit_" + Process.GetCurrentProcess().Id + ".flag");
// 新增:崩溃日志文件路径
- private static string crashLogFile = Path.Combine(Environment.GetEnvironmentVariable("APPDATA"), "Ink Canvas", "crash_logs");
+ private static string crashLogFile = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Crashes");
// 新增:进程ID
private static int currentProcessId = Process.GetCurrentProcess().Id;
// 新增:应用启动时间
@@ -107,7 +107,6 @@ namespace Ink_Canvas
if (isWindows7)
{
- LogHelper.WriteLogToFile("检测到Windows 7系统,配置TLS协议支持");
// 启用所有TLS版本以支持Windows 7
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
@@ -117,17 +116,14 @@ namespace Ink_Canvas
ServicePointManager.Expect100Continue = false;
ServicePointManager.UseNagleAlgorithm = false;
- LogHelper.WriteLogToFile("TLS协议配置完成,已启用TLS 1.2/1.1/1.0支持");
}
else
{
// 对于更新的Windows版本,不进行任何TLS配置,使用系统默认设置
- LogHelper.WriteLogToFile($"检测到Windows版本: {osVersion.VersionString},使用系统默认TLS配置");
}
}
- catch (Exception ex)
+ catch (Exception)
{
- LogHelper.WriteLogToFile($"配置TLS协议时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
@@ -346,6 +342,25 @@ namespace Ink_Canvas
try
{
var exception = e.ExceptionObject as Exception;
+
+ if (exception is InvalidOperationException invalidOpEx)
+ {
+ string exceptionMessage = invalidOpEx.Message ?? "";
+ string exceptionStackTrace = invalidOpEx.StackTrace ?? "";
+
+ if (exceptionMessage.Contains("调用线程无法访问此对象") ||
+ exceptionMessage.Contains("because another thread owns it") ||
+ exceptionStackTrace.Contains("DynamicRenderer") ||
+ exceptionStackTrace.Contains("CompositionTarget.get_RootVisual"))
+ {
+ LogHelper.WriteLogToFile(
+ $"检测到DynamicRenderer线程访问异常: {invalidOpEx.Message}",
+ LogHelper.LogType.Warning
+ );
+ return;
+ }
+ }
+
string errorMessage = exception?.ToString() ?? "未知异常";
lastErrorMessage = errorMessage;
@@ -361,8 +376,11 @@ namespace Ink_Canvas
// 尝试在最后时刻记录错误
try
{
+ string timeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
+ ? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
+ : DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
File.AppendAllText(
- Path.Combine(crashLogFile, $"critical_error_{DateTime.Now:yyyyMMdd_HHmmss}.log"),
+ Path.Combine(crashLogFile, $"Crash_{timeStr}.txt"),
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 记录未处理异常时发生错误: {ex.Message}\r\n"
);
}
@@ -500,7 +518,10 @@ namespace Ink_Canvas
Directory.CreateDirectory(crashLogFile);
}
- string logFileName = Path.Combine(crashLogFile, $"crash_{DateTime.Now:yyyyMMdd}.log");
+ string appStartTimeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
+ ? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
+ : DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
+ string logFileName = Path.Combine(crashLogFile, $"Crash_{appStartTimeStr}.txt");
// 收集系统状态信息
string memoryUsage = (Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024)) + " MB";
@@ -549,6 +570,31 @@ namespace Ink_Canvas
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
+ // 检查是否是DynamicRenderer线程访问UI对象的已知问题
+ if (e.Exception is InvalidOperationException invalidOpEx)
+ {
+ string exceptionMessage = invalidOpEx.Message ?? "";
+ string exceptionStackTrace = invalidOpEx.StackTrace ?? "";
+
+ // 检查是否是DynamicRenderer相关的线程访问问题
+ if (exceptionMessage.Contains("调用线程无法访问此对象") ||
+ exceptionMessage.Contains("because another thread owns it") ||
+ exceptionStackTrace.Contains("DynamicRenderer") ||
+ exceptionStackTrace.Contains("CompositionTarget.get_RootVisual"))
+ {
+ // 这是WPF InkCanvas的已知问题,DynamicRenderer的后台线程尝试访问UI对象
+ // 这个异常不会影响应用程序功能,可以安全地忽略
+ LogHelper.WriteLogToFile(
+ $"检测到DynamicRenderer线程访问异常(已安全处理): {invalidOpEx.Message}",
+ LogHelper.LogType.Warning
+ );
+
+ // 标记为已处理,不显示错误消息,不触发重启
+ e.Handled = true;
+ return;
+ }
+ }
+
Ink_Canvas.MainWindow.ShowNewMessage("抱歉,出现未预期的异常,可能导致 InkCanvasForClass 运行不稳定。\n建议保存墨迹后重启应用。");
LogHelper.NewLog(e.Exception.ToString());
diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs
index e30061e1..87f44273 100644
--- a/Ink Canvas/AssemblyInfo.cs
+++ b/Ink Canvas/AssemblyInfo.cs
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.7.17.2")]
-[assembly: AssemblyFileVersion("1.7.17.2")]
+[assembly: AssemblyVersion("1.7.18.0")]
+[assembly: AssemblyFileVersion("1.7.18.0")]
diff --git a/Ink Canvas/Windows/QuickDrawFloatingButton.xaml b/Ink Canvas/Controls/QuickDrawFloatingButtonControl.xaml
similarity index 86%
rename from Ink Canvas/Windows/QuickDrawFloatingButton.xaml
rename to Ink Canvas/Controls/QuickDrawFloatingButtonControl.xaml
index 07786911..37a17947 100644
--- a/Ink Canvas/Windows/QuickDrawFloatingButton.xaml
+++ b/Ink Canvas/Controls/QuickDrawFloatingButtonControl.xaml
@@ -1,22 +1,19 @@
-
-
-
+
+
+
-
+
-
+
+
diff --git a/Ink Canvas/Controls/QuickDrawFloatingButtonControl.xaml.cs b/Ink Canvas/Controls/QuickDrawFloatingButtonControl.xaml.cs
new file mode 100644
index 00000000..c09e3f3b
--- /dev/null
+++ b/Ink Canvas/Controls/QuickDrawFloatingButtonControl.xaml.cs
@@ -0,0 +1,149 @@
+using Ink_Canvas.Windows;
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Threading;
+using MouseEventArgs = System.Windows.Input.MouseEventArgs;
+using HorizontalAlignment = System.Windows.HorizontalAlignment;
+using VerticalAlignment = System.Windows.VerticalAlignment;
+
+namespace Ink_Canvas.Controls
+{
+ ///
+ /// 快抽悬浮按钮控件
+ ///
+ public partial class QuickDrawFloatingButtonControl : UserControl
+ {
+ private bool _isDragging = false;
+ private Point _dragStartPoint;
+ private Point _controlStartPoint;
+
+ public QuickDrawFloatingButtonControl()
+ {
+ InitializeComponent();
+ }
+
+ ///
+ /// 快抽按钮点击事件
+ ///
+ private void FloatingButton_Click(object sender, MouseButtonEventArgs e)
+ {
+ try
+ {
+ // 如果正在拖动,不触发点击事件
+ if (_isDragging) return;
+
+ // 打开快抽窗口
+ var quickDrawWindow = new QuickDrawWindow();
+ quickDrawWindow.ShowDialog();
+ }
+ catch (Exception ex)
+ {
+ Helpers.LogHelper.WriteLogToFile($"打开快抽窗口失败: {ex.Message}", Helpers.LogHelper.LogType.Error);
+ }
+ }
+
+ ///
+ /// 拖动区域鼠标按下事件
+ ///
+ private void DragArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ _isDragging = false;
+
+ // 记录鼠标在屏幕上的初始位置
+ _dragStartPoint = this.PointToScreen(e.GetPosition(this));
+
+ // 记录控件的初始位置
+ var parent = this.Parent as FrameworkElement;
+ if (parent != null)
+ {
+ var transform = this.TransformToVisual(parent);
+ var currentPos = transform.Transform(new Point(0, 0));
+ _controlStartPoint = currentPos;
+ }
+ else
+ {
+ var currentMargin = this.Margin;
+ _controlStartPoint = new Point(
+ double.IsNaN(currentMargin.Left) ? 0 : currentMargin.Left,
+ double.IsNaN(currentMargin.Top) ? 0 : currentMargin.Top);
+ }
+
+ ((UIElement)sender).CaptureMouse();
+ e.Handled = true;
+ }
+
+ ///
+ /// 拖动区域鼠标移动事件
+ ///
+ private void DragArea_MouseMove(object sender, MouseEventArgs e)
+ {
+ if (e.LeftButton == MouseButtonState.Pressed && ((UIElement)sender).IsMouseCaptured)
+ {
+ // 获取鼠标在屏幕上的当前位置
+ Point currentScreenPoint = this.PointToScreen(e.GetPosition(this));
+ Vector diff = currentScreenPoint - _dragStartPoint;
+
+ if (!_isDragging && (Math.Abs(diff.X) > 3 || Math.Abs(diff.Y) > 3))
+ {
+ _isDragging = true;
+ // 切换到绝对定位模式
+ this.HorizontalAlignment = HorizontalAlignment.Left;
+ this.VerticalAlignment = VerticalAlignment.Top;
+ }
+
+ if (_isDragging)
+ {
+ // 计算新位置
+ var parent = this.Parent as FrameworkElement;
+ if (parent != null)
+ {
+ // 计算屏幕坐标相对于父容器的位置
+ var parentPoint = parent.PointFromScreen(currentScreenPoint);
+ var startParentPoint = parent.PointFromScreen(_dragStartPoint);
+
+ // 计算相对于初始位置的偏移
+ double offsetX = parentPoint.X - startParentPoint.X;
+ double offsetY = parentPoint.Y - startParentPoint.Y;
+
+ // 新位置 = 初始位置 + 偏移
+ double newLeft = _controlStartPoint.X + offsetX;
+ double newTop = _controlStartPoint.Y + offsetY;
+
+ // 限制在父容器范围内
+ newLeft = Math.Max(0, Math.Min(newLeft, parent.ActualWidth - this.ActualWidth));
+ newTop = Math.Max(0, Math.Min(newTop, parent.ActualHeight - this.ActualHeight));
+
+ // 更新Margin
+ this.Margin = new Thickness(newLeft, newTop, 0, 0);
+ }
+ }
+ }
+ }
+
+ ///
+ /// 拖动区域鼠标释放事件
+ ///
+ private void DragArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ if (((UIElement)sender).IsMouseCaptured)
+ {
+ ((UIElement)sender).ReleaseMouseCapture();
+ }
+
+ if (_isDragging)
+ {
+ Dispatcher.BeginInvoke(new Action(() => { _isDragging = false; }),
+ DispatcherPriority.Background);
+ }
+ else
+ {
+ _isDragging = false;
+ }
+
+ e.Handled = true;
+ }
+ }
+}
+
diff --git a/Ink Canvas/Helpers/CameraService.cs b/Ink Canvas/Helpers/CameraService.cs
index 491c9fef..a8868e29 100644
--- a/Ink Canvas/Helpers/CameraService.cs
+++ b/Ink Canvas/Helpers/CameraService.cs
@@ -281,8 +281,16 @@ namespace Ink_Canvas.Helpers
// 应用旋转
Bitmap rotatedFrame = ApplyRotation(sourceFrame);
- // 应用分辨率调整
- _currentFrame = ResizeImage(rotatedFrame, _resolutionWidth, _resolutionHeight);
+ int targetWidth = _resolutionWidth;
+ int targetHeight = _resolutionHeight;
+
+ if (_rotationAngle == 1 || _rotationAngle == 3)
+ {
+ targetWidth = _resolutionHeight;
+ targetHeight = _resolutionWidth;
+ }
+
+ _currentFrame = ResizeImageWithAspectRatio(rotatedFrame, targetWidth, targetHeight);
rotatedFrame?.Dispose();
}
@@ -357,6 +365,33 @@ namespace Ink_Canvas.Helpers
return rotated;
}
+ ///
+ /// 调整图像大小
+ ///
+ private Bitmap ResizeImageWithAspectRatio(Bitmap source, int targetWidth, int targetHeight)
+ {
+ if (source.Width == targetWidth && source.Height == targetHeight)
+ return new Bitmap(source);
+
+ double scaleX = (double)targetWidth / source.Width;
+ double scaleY = (double)targetHeight / source.Height;
+ double scale = Math.Min(scaleX, scaleY);
+
+ // 计算实际尺寸
+ int actualWidth = (int)(source.Width * scale);
+ int actualHeight = (int)(source.Height * scale);
+
+ var resized = new Bitmap(actualWidth, actualHeight, PixelFormat.Format24bppRgb);
+ using (var graphics = Graphics.FromImage(resized))
+ {
+ graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
+ graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
+ graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
+ graphics.DrawImage(source, 0, 0, actualWidth, actualHeight);
+ }
+ return resized;
+ }
+
///
/// 调整图像大小
///
diff --git a/Ink Canvas/Helpers/DlassApiClient.cs b/Ink Canvas/Helpers/DlassApiClient.cs
index d77bed80..94146ef1 100644
--- a/Ink Canvas/Helpers/DlassApiClient.cs
+++ b/Ink Canvas/Helpers/DlassApiClient.cs
@@ -331,15 +331,15 @@ namespace Ink_Canvas.Helpers
return false;
}
}
- catch (HttpRequestException httpEx)
+ catch (HttpRequestException)
{
return false;
}
- catch (TaskCanceledException timeoutEx)
+ catch (TaskCanceledException)
{
return false;
}
- catch (Exception ex)
+ catch (Exception)
{
return false;
}
diff --git a/Ink Canvas/Helpers/DlassNoteUploader.cs b/Ink Canvas/Helpers/DlassNoteUploader.cs
index 4265c2bc..f1374eaa 100644
--- a/Ink Canvas/Helpers/DlassNoteUploader.cs
+++ b/Ink Canvas/Helpers/DlassNoteUploader.cs
@@ -19,6 +19,22 @@ namespace Ink_Canvas.Helpers
private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA";
private const int BATCH_SIZE = 10; // 批量上传大小
private const int MAX_RETRY_COUNT = 3; // 最大重试次数
+ private const string QUEUE_FILE_NAME = "DlassUploadQueue.json";
+
+ ///
+ /// 上传队列项
+ ///
+ private class UploadQueueItemData
+ {
+ [JsonProperty("file_path")]
+ public string FilePath { get; set; }
+
+ [JsonProperty("retry_count")]
+ public int RetryCount { get; set; }
+
+ [JsonProperty("added_time")]
+ public DateTime AddedTime { get; set; }
+ }
///
/// 上传队列项
@@ -39,6 +55,205 @@ namespace Ink_Canvas.Helpers
///
private static readonly SemaphoreSlim _queueProcessingLock = new SemaphoreSlim(1, 1);
+ ///
+ /// 队列保存锁,防止并发保存
+ ///
+ private static readonly SemaphoreSlim _queueSaveLock = new SemaphoreSlim(1, 1);
+
+ ///
+ /// 是否已初始化队列
+ ///
+ private static bool _isQueueInitialized = false;
+
+ ///
+ /// 获取队列文件路径
+ ///
+ private static string GetQueueFilePath()
+ {
+ var configsDir = Path.Combine(App.RootPath, "Configs");
+ if (!Directory.Exists(configsDir))
+ {
+ Directory.CreateDirectory(configsDir);
+ }
+ return Path.Combine(configsDir, QUEUE_FILE_NAME);
+ }
+
+ ///
+ /// 初始化上传队列
+ ///
+ public static void InitializeQueue()
+ {
+ if (_isQueueInitialized)
+ {
+ return;
+ }
+
+ try
+ {
+ var queueFilePath = GetQueueFilePath();
+ if (!File.Exists(queueFilePath))
+ {
+ _isQueueInitialized = true;
+ return;
+ }
+
+ var jsonContent = File.ReadAllText(queueFilePath);
+ if (string.IsNullOrWhiteSpace(jsonContent))
+ {
+ _isQueueInitialized = true;
+ return;
+ }
+
+ var queueData = JsonConvert.DeserializeObject>(jsonContent);
+ if (queueData == null || queueData.Count == 0)
+ {
+ _isQueueInitialized = true;
+ return;
+ }
+
+ int restoredCount = 0;
+ int skippedCount = 0;
+
+ foreach (var item in queueData)
+ {
+ // 验证文件是否存在
+ if (!File.Exists(item.FilePath))
+ {
+ skippedCount++;
+ continue;
+ }
+
+ // 验证文件格式和大小
+ var fileExtension = Path.GetExtension(item.FilePath).ToLower();
+ if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".zip")
+ {
+ skippedCount++;
+ continue;
+ }
+
+ try
+ {
+ var fileInfo = new FileInfo(item.FilePath);
+ long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
+ if (fileInfo.Length > maxSize)
+ {
+ skippedCount++;
+ continue;
+ }
+ }
+ catch
+ {
+ skippedCount++;
+ continue;
+ }
+
+ // 恢复队列项
+ _uploadQueue.Enqueue(new UploadQueueItem
+ {
+ FilePath = item.FilePath,
+ RetryCount = item.RetryCount
+ });
+ restoredCount++;
+ }
+
+ _isQueueInitialized = true;
+
+ if (restoredCount > 0)
+ {
+ LogHelper.WriteLogToFile($"已恢复上传队列:{restoredCount}个文件,跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
+ // 如果恢复了队列,触发处理
+ _ = ProcessUploadQueueAsync();
+ }
+ else if (skippedCount > 0)
+ {
+ LogHelper.WriteLogToFile($"队列恢复完成:跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"恢复上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
+ _isQueueInitialized = true; // 即使出错也标记为已初始化,避免重复尝试
+ }
+ }
+
+ ///
+ /// 保存队列到文件
+ ///
+ private static async Task SaveQueueToFileAsync()
+ {
+ if (!await _queueSaveLock.WaitAsync(1000)) // 最多等待1秒
+ {
+ return; // 如果无法获取锁,跳过保存(避免阻塞)
+ }
+
+ try
+ {
+ var queueData = new List();
+
+ // 将队列转换为可序列化的格式
+ foreach (var item in _uploadQueue)
+ {
+ queueData.Add(new UploadQueueItemData
+ {
+ FilePath = item.FilePath,
+ RetryCount = item.RetryCount,
+ AddedTime = DateTime.Now
+ });
+ }
+
+ var queueFilePath = GetQueueFilePath();
+
+ // 如果队列为空,清空文件
+ if (queueData.Count == 0)
+ {
+ ClearQueueFile();
+ return;
+ }
+
+ var jsonContent = JsonConvert.SerializeObject(queueData, Formatting.Indented);
+
+ // 使用临时文件写入,然后替换,确保原子性
+ var tempFilePath = queueFilePath + ".tmp";
+ File.WriteAllText(tempFilePath, jsonContent);
+
+ // 如果原文件存在,先删除
+ if (File.Exists(queueFilePath))
+ {
+ File.Delete(queueFilePath);
+ }
+
+ // 重命名临时文件
+ File.Move(tempFilePath, queueFilePath);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"保存上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ finally
+ {
+ _queueSaveLock.Release();
+ }
+ }
+
+ ///
+ /// 清空队列文件
+ ///
+ private static void ClearQueueFile()
+ {
+ try
+ {
+ var queueFilePath = GetQueueFilePath();
+ if (File.Exists(queueFilePath))
+ {
+ File.WriteAllText(queueFilePath, "[]");
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"清空队列文件时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
///
/// 上传笔记响应模型
///
@@ -100,9 +315,9 @@ namespace Ink_Canvas.Helpers
}
///
- /// 异步上传笔记文件到Dlass(支持PNG和ICSTK格式)
+ /// 异步上传笔记文件到Dlass(支持PNG、ICSTK和ZIP格式)
///
- /// 文件路径(支持PNG和ICSTK)
+ /// 文件路径(支持PNG、ICSTK和ZIP)
/// 是否成功加入队列(不等待实际上传完成)
public static async Task UploadNoteFileAsync(string filePath)
{
@@ -122,15 +337,16 @@ namespace Ink_Canvas.Helpers
}
var fileExtension = Path.GetExtension(filePath).ToLower();
- if (fileExtension != ".png" && fileExtension != ".icstk")
+ if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".zip")
{
return false;
}
var fileInfo = new FileInfo(filePath);
- if (fileInfo.Length > 10 * 1024 * 1024)
+ long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
+ if (fileInfo.Length > maxSize)
{
- LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过10MB限制", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过{maxSize / 1024 / 1024}MB限制", LogHelper.LogType.Error);
return false;
}
@@ -171,6 +387,9 @@ namespace Ink_Canvas.Helpers
RetryCount = retryCount
});
+ // 异步保存队列到文件
+ _ = Task.Run(async () => await SaveQueueToFileAsync());
+
// 如果队列达到批量大小,触发批量上传
if (_uploadQueue.Count >= BATCH_SIZE)
{
@@ -219,6 +438,11 @@ namespace Ink_Canvas.Helpers
if (string.IsNullOrEmpty(selectedClassName))
{
LogHelper.WriteLogToFile("上传失败:未选择班级", LogHelper.LogType.Error);
+ // 将文件重新加入队列
+ foreach (var item in filesToUpload)
+ {
+ EnqueueFile(item.FilePath, item.RetryCount);
+ }
return;
}
@@ -226,6 +450,11 @@ namespace Ink_Canvas.Helpers
if (string.IsNullOrEmpty(userToken))
{
LogHelper.WriteLogToFile("上传失败:未设置用户Token", LogHelper.LogType.Error);
+ // 将文件重新加入队列
+ foreach (var item in filesToUpload)
+ {
+ EnqueueFile(item.FilePath, item.RetryCount);
+ }
return;
}
@@ -246,6 +475,11 @@ namespace Ink_Canvas.Helpers
if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
{
LogHelper.WriteLogToFile("上传失败:无法获取白板信息", LogHelper.LogType.Error);
+ // 将文件重新加入队列
+ foreach (var item in filesToUpload)
+ {
+ EnqueueFile(item.FilePath, item.RetryCount);
+ }
return;
}
@@ -255,6 +489,11 @@ namespace Ink_Canvas.Helpers
if (sharedWhiteboard == null || string.IsNullOrEmpty(sharedWhiteboard.BoardId) || string.IsNullOrEmpty(sharedWhiteboard.SecretKey))
{
LogHelper.WriteLogToFile($"上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
+ // 将文件重新加入队列
+ foreach (var item in filesToUpload)
+ {
+ EnqueueFile(item.FilePath, item.RetryCount);
+ }
return;
}
}
@@ -262,6 +501,11 @@ namespace Ink_Canvas.Helpers
catch (Exception ex)
{
LogHelper.WriteLogToFile($"批量上传获取白板信息时出错: {ex.Message}", LogHelper.LogType.Error);
+ // 将文件重新加入队列
+ foreach (var item in filesToUpload)
+ {
+ EnqueueFile(item.FilePath, item.RetryCount);
+ }
return;
}
@@ -317,6 +561,9 @@ namespace Ink_Canvas.Helpers
});
await Task.WhenAll(uploadTasks);
+ // 上传完成后保存队列状态
+ await SaveQueueToFileAsync();
+
// 如果队列达到批量大小,继续处理
if (_uploadQueue.Count >= BATCH_SIZE)
{
@@ -348,16 +595,17 @@ namespace Ink_Canvas.Helpers
// 检查文件扩展名
var fileExtension = Path.GetExtension(filePath).ToLower();
- if (fileExtension != ".png" && fileExtension != ".icstk")
+ if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".zip")
{
return false;
}
- // 检查文件大小(最大10MB)
+ // 检查文件大小(最大10MB,ZIP文件可能更大,允许50MB)
var fileInfo = new FileInfo(filePath);
- if (fileInfo.Length > 10 * 1024 * 1024)
+ long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
+ if (fileInfo.Length > maxSize)
{
- LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过10MB限制", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过{maxSize / 1024 / 1024}MB限制", LogHelper.LogType.Error);
return false;
}
@@ -417,9 +665,24 @@ namespace Ink_Canvas.Helpers
// 准备上传参数
var fileName = Path.GetFileNameWithoutExtension(filePath);
var title = fileName;
- var fileType = fileExtension == ".icstk" ? "墨迹文件" : "笔记";
+ string fileType;
+ string tags;
+ if (fileExtension == ".zip")
+ {
+ fileType = "多页面墨迹压缩包";
+ tags = "自动上传,多页面,zip,压缩包";
+ }
+ else if (fileExtension == ".icstk")
+ {
+ fileType = "墨迹文件";
+ tags = "自动上传,墨迹,icstk";
+ }
+ else
+ {
+ fileType = "笔记";
+ tags = "自动上传,笔记,png";
+ }
var description = $"自动上传的{fileType} - {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
- var tags = fileExtension == ".icstk" ? "自动上传,墨迹,icstk" : "自动上传,笔记,png";
// 创建API客户端并上传文件
using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
@@ -466,7 +729,7 @@ namespace Ink_Canvas.Helpers
// 检查文件扩展名
var fileExtension = Path.GetExtension(filePath).ToLower();
- if (fileExtension != ".png" && fileExtension != ".icstk")
+ if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".zip")
{
return false; // 文件格式错误,不可重试
}
@@ -475,7 +738,8 @@ namespace Ink_Canvas.Helpers
try
{
var fileInfo = new FileInfo(filePath);
- if (fileInfo.Length > 10 * 1024 * 1024)
+ long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
+ if (fileInfo.Length > maxSize)
{
return false; // 文件过大,不可重试
}
@@ -491,3 +755,4 @@ namespace Ink_Canvas.Helpers
}
}
+
diff --git a/Ink Canvas/Helpers/GlobalHotkeyManager.cs b/Ink Canvas/Helpers/GlobalHotkeyManager.cs
index 11fea5a5..cd974d6e 100644
--- a/Ink Canvas/Helpers/GlobalHotkeyManager.cs
+++ b/Ink Canvas/Helpers/GlobalHotkeyManager.cs
@@ -78,6 +78,16 @@ namespace Ink_Canvas.Helpers
{
UnregisterHotkey(hotkeyName);
}
+ else
+ {
+ try
+ {
+ HotkeyManager.Current.Remove(hotkeyName);
+ }
+ catch
+ {
+ }
+ }
// 创建快捷键信息
var hotkeyInfo = new HotkeyInfo
@@ -112,9 +122,8 @@ namespace Ink_Canvas.Helpers
return true;
}
- catch (Exception ex)
+ catch (Exception)
{
- LogHelper.WriteLogToFile($"注册全局快捷键 {hotkeyName} 失败: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
diff --git a/Ink Canvas/Helpers/MultiPPTInkManager.cs b/Ink Canvas/Helpers/MultiPPTInkManager.cs
deleted file mode 100644
index 02d70da2..00000000
--- a/Ink Canvas/Helpers/MultiPPTInkManager.cs
+++ /dev/null
@@ -1,813 +0,0 @@
-using Microsoft.Office.Interop.PowerPoint;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Security.Cryptography;
-using System.Text;
-using System.Windows.Ink;
-
-namespace Ink_Canvas.Helpers
-{
- ///
- /// 多PPT墨迹管理器 - 支持多个PPT窗口分别管理墨迹
- ///
- public class MultiPPTInkManager : IDisposable
- {
- #region Properties
- public bool IsAutoSaveEnabled { get; set; } = true;
- public string AutoSaveLocation { get; set; } = "";
- public PPTManager PPTManager { get; set; }
- #endregion
-
- #region Private Fields
- private readonly Dictionary _presentationManagers;
- private readonly Dictionary _presentationInfos;
- private readonly object _lockObject = new object();
- private bool _disposed;
- private string _currentActivePresentationId = "";
-
- // 墨迹备份机制
- private readonly Dictionary> _strokeBackups;
- private DateTime _lastBackupTime = DateTime.MinValue;
- private const int BackupIntervalMinutes = 2; // 每2分钟备份一次
- #endregion
-
- #region Constructor
- public MultiPPTInkManager()
- {
- _presentationManagers = new Dictionary();
- _presentationInfos = new Dictionary();
- _strokeBackups = new Dictionary>();
- }
- #endregion
-
- #region Public Methods
- ///
- /// 初始化新的演示文稿
- ///
- public void InitializePresentation(Presentation presentation)
- {
- if (presentation == null) return;
-
- lock (_lockObject)
- {
- try
- {
- var presentationId = GeneratePresentationId(presentation);
-
- // 如果已存在该演示文稿的管理器,先清理
- if (_presentationManagers.ContainsKey(presentationId))
- {
- _presentationManagers[presentationId].Dispose();
- _presentationManagers.Remove(presentationId);
- }
-
- // 创建新的墨迹管理器
- var inkManager = new PPTInkManager();
- inkManager.IsAutoSaveEnabled = IsAutoSaveEnabled;
- inkManager.AutoSaveLocation = AutoSaveLocation;
- inkManager.InitializePresentation(presentation);
-
- // 保存管理器和演示文稿信息
- _presentationManagers[presentationId] = inkManager;
- _presentationInfos[presentationId] = new PresentationInfo
- {
- Id = presentationId,
- Name = presentation.Name,
- FullName = presentation.FullName,
- SlideCount = presentation.Slides.Count,
- CreatedTime = DateTime.Now,
- LastAccessTime = DateTime.Now
- };
-
- // 设置为当前活跃的演示文稿
- _currentActivePresentationId = presentationId;
-
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"初始化多PPT墨迹管理失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 切换到指定的演示文稿
- ///
- public bool SwitchToPresentation(Presentation presentation)
- {
- if (presentation == null) return false;
-
- lock (_lockObject)
- {
- try
- {
- var presentationId = GeneratePresentationId(presentation);
-
- if (_presentationManagers.ContainsKey(presentationId))
- {
- // 如果切换的是不同的演示文稿,先保存当前活跃演示文稿的墨迹
- if (!string.IsNullOrEmpty(_currentActivePresentationId) &&
- _currentActivePresentationId != presentationId)
- {
- var currentManager = GetCurrentManager();
- if (currentManager != null)
- {
- // 获取当前活跃的演示文稿并保存墨迹
- var currentPresentation = GetCurrentActivePresentation();
- if (currentPresentation != null)
- {
- try
- {
- currentManager.SaveAllStrokesToFile(currentPresentation);
- LogHelper.WriteLogToFile($"已保存当前演示文稿墨迹: {currentPresentation.Name}", LogHelper.LogType.Trace);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"保存当前演示文稿墨迹失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
- }
-
- _currentActivePresentationId = presentationId;
-
- // 更新最后访问时间
- if (_presentationInfos.ContainsKey(presentationId))
- {
- _presentationInfos[presentationId].LastAccessTime = DateTime.Now;
- }
-
- if (_currentActivePresentationId != presentationId)
- {
- LogHelper.WriteLogToFile($"已切换到演示文稿: {presentation.Name}", LogHelper.LogType.Trace);
- }
- return true;
- }
- else
- {
- // 如果不存在,尝试初始化
- InitializePresentation(presentation);
- return true;
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"切换到演示文稿失败: {ex}", LogHelper.LogType.Error);
- return false;
- }
- }
- }
-
- ///
- /// 保存当前页面的墨迹
- ///
- public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes)
- {
- if (slideIndex <= 0 || strokes == null) return;
-
- lock (_lockObject)
- {
- try
- {
- var manager = GetCurrentManager();
- if (manager != null)
- {
- // 保存到管理器
- manager.SaveCurrentSlideStrokes(slideIndex, strokes);
-
- // 只有在保存成功后才创建备份
- if (!string.IsNullOrEmpty(_currentActivePresentationId))
- {
- CreateStrokeBackup(_currentActivePresentationId, slideIndex, strokes);
- }
-
- // 检查是否需要执行定期备份
- CheckAndPerformBackup();
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"保存当前页面墨迹失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 强制保存指定页面的墨迹(忽略锁定状态)
- ///
- public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes)
- {
- if (slideIndex <= 0 || strokes == null) return;
-
- lock (_lockObject)
- {
- try
- {
- var manager = GetCurrentManager();
- if (manager != null)
- {
- manager.ForceSaveSlideStrokes(slideIndex, strokes);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"强制保存页面墨迹失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 加载指定页面的墨迹
- ///
- public StrokeCollection LoadSlideStrokes(int slideIndex)
- {
- if (slideIndex <= 0) return new StrokeCollection();
-
- lock (_lockObject)
- {
- try
- {
- var manager = GetCurrentManager();
- if (manager != null)
- {
- var strokes = manager.LoadSlideStrokes(slideIndex);
-
- // 如果从管理器加载失败,尝试从备份恢复
- if (strokes == null || strokes.Count == 0)
- {
- if (!string.IsNullOrEmpty(_currentActivePresentationId))
- {
- strokes = RestoreStrokeFromBackup(_currentActivePresentationId, slideIndex);
- }
- }
-
- return strokes ?? new StrokeCollection();
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"加载页面墨迹失败: {ex}", LogHelper.LogType.Error);
-
- // 尝试从备份恢复
- if (!string.IsNullOrEmpty(_currentActivePresentationId))
- {
- return RestoreStrokeFromBackup(_currentActivePresentationId, slideIndex);
- }
- }
- }
-
- return new StrokeCollection();
- }
-
- ///
- /// 切换到指定页面并加载墨迹
- ///
- public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null)
- {
- lock (_lockObject)
- {
- try
- {
- var manager = GetCurrentManager();
- if (manager != null)
- {
- return manager.SwitchToSlide(slideIndex, currentStrokes);
- }
- else
- {
- LogHelper.WriteLogToFile($"无法获取当前墨迹管理器,页面切换失败: {slideIndex}", LogHelper.LogType.Warning);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"切换页面墨迹失败: {ex}", LogHelper.LogType.Error);
- }
- }
-
- return new StrokeCollection();
- }
-
- ///
- /// 保存所有墨迹到文件
- ///
- public void SaveAllStrokesToFile(Presentation presentation)
- {
- if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
-
- lock (_lockObject)
- {
- try
- {
- var presentationId = GeneratePresentationId(presentation);
- if (_presentationManagers.ContainsKey(presentationId))
- {
- _presentationManagers[presentationId].SaveAllStrokesToFile(presentation);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"保存所有墨迹到文件失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 从文件加载已保存的墨迹
- ///
- public void LoadSavedStrokes(Presentation presentation)
- {
- if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
-
- lock (_lockObject)
- {
- try
- {
- var presentationId = GeneratePresentationId(presentation);
- if (_presentationManagers.ContainsKey(presentationId))
- {
- _presentationManagers[presentationId].LoadSavedStrokes();
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 清除指定演示文稿的所有墨迹
- ///
- public void ClearPresentationStrokes(Presentation presentation)
- {
- if (presentation == null) return;
-
- lock (_lockObject)
- {
- try
- {
- var presentationId = GeneratePresentationId(presentation);
- if (_presentationManagers.ContainsKey(presentationId))
- {
- _presentationManagers[presentationId].ClearAllStrokes();
- LogHelper.WriteLogToFile($"已清除演示文稿墨迹: {presentation.Name}", LogHelper.LogType.Trace);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"清除演示文稿墨迹失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 清除所有演示文稿的墨迹
- ///
- public void ClearAllStrokes()
- {
- lock (_lockObject)
- {
- try
- {
- foreach (var manager in _presentationManagers.Values)
- {
- manager?.ClearAllStrokes();
- }
- LogHelper.WriteLogToFile("已清除所有演示文稿墨迹", LogHelper.LogType.Trace);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"清除所有墨迹失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 翻页后锁定墨迹写入
- ///
- public void LockInkForSlide(int slideIndex)
- {
- lock (_lockObject)
- {
- try
- {
- var manager = GetCurrentManager();
- if (manager != null)
- {
- manager.LockInkForSlide(slideIndex);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"锁定墨迹写入失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 检查是否可以写入墨迹
- ///
- public bool CanWriteInk(int currentSlideIndex)
- {
- lock (_lockObject)
- {
- try
- {
- var manager = GetCurrentManager();
- if (manager != null)
- {
- return manager.CanWriteInk(currentSlideIndex);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"检查墨迹写入权限失败: {ex}", LogHelper.LogType.Error);
- }
- }
-
- return false;
- }
-
- ///
- /// 重置当前演示文稿的墨迹锁定状态
- ///
- public void ResetCurrentPresentationLockState()
- {
- lock (_lockObject)
- {
- try
- {
- var manager = GetCurrentManager();
- if (manager != null)
- {
- manager.ResetLockState();
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"重置墨迹锁定状态失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 移除演示文稿管理器
- ///
- public void RemovePresentation(Presentation presentation)
- {
- if (presentation == null) return;
-
- lock (_lockObject)
- {
- try
- {
- var presentationId = GeneratePresentationId(presentation);
-
- if (_presentationManagers.ContainsKey(presentationId))
- {
- // 保存墨迹到文件
- _presentationManagers[presentationId].SaveAllStrokesToFile(presentation);
-
- // 释放资源
- _presentationManagers[presentationId].Dispose();
- _presentationManagers.Remove(presentationId);
- }
-
- if (_presentationInfos.ContainsKey(presentationId))
- {
- _presentationInfos.Remove(presentationId);
- }
-
- // 如果移除的是当前活跃的演示文稿,重置活跃ID
- if (_currentActivePresentationId == presentationId)
- {
- _currentActivePresentationId = "";
- }
-
- }
- catch (COMException comEx)
- {
- var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
- {
- }
- }
- catch (Exception)
- {
- }
- }
- }
-
- ///
- /// 获取当前管理的演示文稿数量
- ///
- public int GetPresentationCount()
- {
- lock (_lockObject)
- {
- return _presentationManagers.Count;
- }
- }
-
- ///
- /// 获取所有演示文稿信息
- ///
- public List GetAllPresentationInfos()
- {
- lock (_lockObject)
- {
- return _presentationInfos.Values.ToList();
- }
- }
-
- ///
- /// 清理长时间未访问的演示文稿管理器
- ///
- public void CleanupInactivePresentations(TimeSpan inactiveThreshold)
- {
- lock (_lockObject)
- {
- try
- {
- var inactiveIds = new List();
- var cutoffTime = DateTime.Now - inactiveThreshold;
-
- foreach (var info in _presentationInfos.Values)
- {
- if (info.LastAccessTime < cutoffTime && info.Id != _currentActivePresentationId)
- {
- inactiveIds.Add(info.Id);
- }
- }
-
- foreach (var id in inactiveIds)
- {
- if (_presentationManagers.ContainsKey(id))
- {
- _presentationManagers[id].Dispose();
- _presentationManagers.Remove(id);
- }
- _presentationInfos.Remove(id);
-
- // 清理备份数据
- if (_strokeBackups.ContainsKey(id))
- {
- _strokeBackups.Remove(id);
- }
-
- LogHelper.WriteLogToFile($"已清理非活跃演示文稿: {id}", LogHelper.LogType.Trace);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"清理非活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
-
- ///
- /// 创建墨迹备份
- ///
- private void CreateStrokeBackup(string presentationId, int slideIndex, StrokeCollection strokes)
- {
- try
- {
- if (strokes == null || strokes.Count == 0) return;
-
- if (!_strokeBackups.ContainsKey(presentationId))
- {
- _strokeBackups[presentationId] = new Dictionary();
- }
-
- // 释放旧的备份
- if (_strokeBackups[presentationId].ContainsKey(slideIndex))
- {
- _strokeBackups[presentationId][slideIndex] = null;
- }
-
- // 创建新的备份
- _strokeBackups[presentationId][slideIndex] = strokes.Clone();
-
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"创建墨迹备份失败: {ex}", LogHelper.LogType.Error);
- }
- }
-
- ///
- /// 从备份恢复墨迹
- ///
- private StrokeCollection RestoreStrokeFromBackup(string presentationId, int slideIndex)
- {
- try
- {
- if (_strokeBackups.ContainsKey(presentationId) &&
- _strokeBackups[presentationId].ContainsKey(slideIndex))
- {
- var backup = _strokeBackups[presentationId][slideIndex];
- if (backup != null)
- {
- LogHelper.WriteLogToFile($"从备份恢复第{slideIndex}页墨迹", LogHelper.LogType.Trace);
- return backup.Clone();
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"从备份恢复墨迹失败: {ex}", LogHelper.LogType.Error);
- }
-
- return new StrokeCollection();
- }
-
- ///
- /// 检查并执行定期备份
- ///
- private void CheckAndPerformBackup()
- {
- try
- {
- var now = DateTime.Now;
-
- // 检查是否需要执行备份
- if (now - _lastBackupTime < TimeSpan.FromMinutes(BackupIntervalMinutes))
- {
- return;
- }
-
- // 备份当前活跃演示文稿的所有墨迹
- if (!string.IsNullOrEmpty(_currentActivePresentationId) &&
- _presentationManagers.ContainsKey(_currentActivePresentationId))
- {
- var manager = _presentationManagers[_currentActivePresentationId];
- if (manager != null)
- {
- // 这里可以添加更详细的备份逻辑
- }
- }
-
- _lastBackupTime = now;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"定期备份检查失败: {ex}", LogHelper.LogType.Error);
- }
- }
- #endregion
-
- #region Private Methods
- private PPTInkManager GetCurrentManager()
- {
- if (string.IsNullOrEmpty(_currentActivePresentationId) ||
- !_presentationManagers.ContainsKey(_currentActivePresentationId))
- {
- return null;
- }
-
- return _presentationManagers[_currentActivePresentationId];
- }
-
- private Presentation GetCurrentActivePresentation()
- {
- try
- {
- // 通过PPTManager获取当前活跃的演示文稿
- return PPTManager?.GetCurrentActivePresentation();
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
- return null;
- }
- }
-
- private string GeneratePresentationId(Presentation presentation)
- {
- try
- {
- // 检查COM对象是否仍然有效
- if (presentation == null)
- {
- return $"invalid_{DateTime.Now.Ticks}";
- }
-
- var presentationPath = presentation.FullName;
- var fileHash = GetFileHash(presentationPath);
- var processId = GetProcessId(presentation);
- return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}_{processId}";
- }
- catch (COMException comEx)
- {
- var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
- {
- return $"disconnected_{DateTime.Now.Ticks}";
- }
- return $"error_{DateTime.Now.Ticks}";
- }
- catch (Exception)
- {
- return $"unknown_{DateTime.Now.Ticks}";
- }
- }
-
- private string GetFileHash(string filePath)
- {
- try
- {
- if (string.IsNullOrEmpty(filePath)) return "unknown";
-
- using (var md5 = MD5.Create())
- {
- byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(filePath));
- return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 8);
- }
- }
- catch (Exception)
- {
- // 所有异常都静默处理,避免日志噪音
- return "error";
- }
- }
-
- private string GetProcessId(Presentation presentation)
- {
- try
- {
- // 尝试获取PowerPoint应用程序的进程ID
- if (presentation.Application != null)
- {
- // 通过COM对象获取进程信息
- var hwnd = presentation.Application.HWND;
- if (hwnd != 0)
- {
- return hwnd.ToString();
- }
- }
- return "unknown";
- }
- catch (COMException comEx)
- {
- // COM对象已失效,这是正常情况,完全静默处理
- var hr = (uint)comEx.HResult;
- if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
- {
- return "disconnected";
- }
- return "error";
- }
- catch (Exception)
- {
- return "error";
- }
- }
- #endregion
-
- #region Dispose
- public void Dispose()
- {
- if (!_disposed)
- {
- lock (_lockObject)
- {
- // 释放所有管理器
- foreach (var manager in _presentationManagers.Values)
- {
- manager?.Dispose();
- }
- _presentationManagers.Clear();
- _presentationInfos.Clear();
-
- // 清理备份数据
- foreach (var backupDict in _strokeBackups.Values)
- {
- foreach (var backup in backupDict.Values)
- {
- backup?.Clear();
- }
- backupDict.Clear();
- }
- _strokeBackups.Clear();
- }
- _disposed = true;
- }
- }
- #endregion
- }
-
- ///
- /// 演示文稿信息
- ///
- public class PresentationInfo
- {
- public string Id { get; set; }
- public string Name { get; set; }
- public string FullName { get; set; }
- public int SlideCount { get; set; }
- public DateTime CreatedTime { get; set; }
- public DateTime LastAccessTime { get; set; }
- }
-}
diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml
index beaa21df..3a6ecac9 100644
--- a/Ink Canvas/MainWindow.xaml
+++ b/Ink Canvas/MainWindow.xaml
@@ -6,6 +6,8 @@
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
xmlns:c="clr-namespace:Ink_Canvas.Converter"
xmlns:Controls="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
+ xmlns:controls="clr-namespace:Ink_Canvas.Controls"
+ xmlns:Windows="clr-namespace:Ink_Canvas.Windows"
mc:Ignorable="d"
AllowsTransparency="True"
WindowStyle="None"
@@ -3370,7 +3372,7 @@
+
+
+