From 729b544675f083704273545fb4a44c49cdba53d1 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Mon, 28 Jul 2025 22:36:02 +0800
Subject: [PATCH 01/22] =?UTF-8?q?improve:PPT=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/MainWindow_cs/MW_PPT.cs | 46 +++---------------------------
1 file changed, 4 insertions(+), 42 deletions(-)
diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs
index d3376635..834f04d8 100644
--- a/Ink Canvas/MainWindow_cs/MW_PPT.cs
+++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs
@@ -799,7 +799,7 @@ namespace Ink_Canvas {
uint processId;
GetWindowThreadProcessId((IntPtr)pptApplication.HWND, out processId);
wpsProcess = Process.GetProcessById((int)processId);
- LogHelper.WriteLogToFile($"通过路径检测到WPS进程: {processId}", LogHelper.LogType.Trace);
+ //LogHelper.WriteLogToFile($"通过路径检测到WPS进程: {processId}", LogHelper.LogType.Trace);
}
// 方法2:通过前台窗口检测
@@ -809,7 +809,7 @@ namespace Ink_Canvas {
if (foregroundWpsWindow != null)
{
wpsProcess = Process.GetProcessById((int)foregroundWpsWindow.ProcessId);
- LogHelper.WriteLogToFile($"通过前台窗口检测到WPS进程: {foregroundWpsWindow.ProcessId}", LogHelper.LogType.Trace);
+ //LogHelper.WriteLogToFile($"通过前台窗口检测到WPS进程: {foregroundWpsWindow.ProcessId}", LogHelper.LogType.Trace);
}
}
@@ -820,7 +820,7 @@ namespace Ink_Canvas {
if (wpsProcesses.Count > 0)
{
wpsProcess = wpsProcesses.First();
- LogHelper.WriteLogToFile($"通过进程名检测到WPS进程: {wpsProcess.Id}", LogHelper.LogType.Trace);
+ //LogHelper.WriteLogToFile($"通过进程名检测到WPS进程: {wpsProcess.Id}", LogHelper.LogType.Trace);
}
}
@@ -1976,45 +1976,7 @@ namespace Ink_Canvas {
}
return wpsProcesses;
}
-
- ///
- /// 调试方法:输出所有窗口信息
- ///
- private void DebugAllWindows()
- {
- try
- {
- LogHelper.WriteLogToFile("开始调试所有窗口信息", LogHelper.LogType.Trace);
- var windowCount = 0;
-
- EnumWindows((hWnd, lParam) =>
- {
- try
- {
- if (!IsWindow(hWnd)) return true;
-
- var windowInfo = GetWindowInfo(hWnd);
- if (!string.IsNullOrEmpty(windowInfo.Title) || !string.IsNullOrEmpty(windowInfo.ClassName))
- {
- windowCount++;
- LogHelper.WriteLogToFile($"窗口{windowCount}: 标题='{windowInfo.Title}', 类名='{windowInfo.ClassName}', 进程ID={windowInfo.ProcessId}, 可见={windowInfo.IsVisible}, 最小化={windowInfo.IsMinimized}", LogHelper.LogType.Trace);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"调试窗口时出错: {ex}", LogHelper.LogType.Error);
- }
- return true;
- }, IntPtr.Zero);
-
- LogHelper.WriteLogToFile($"调试完成,共发现{windowCount}个有效窗口", LogHelper.LogType.Trace);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"调试窗口失败: {ex}", LogHelper.LogType.Error);
- }
- }
-
+
private bool CheckForWpsWindowsByEnumeration()
{
try
From 8fc33f5649eab292342491e9adb5fda0b492e5d4 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Mon, 28 Jul 2025 23:51:14 +0800
Subject: [PATCH 02/22] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=BD=AF=E4=BB=B6?=
=?UTF-8?q?=E5=90=8D=E7=A7=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/InkCanvasForClass.csproj | 2 +-
Ink Canvas/Properties/AssemblyInfo.cs | 4 ++--
Ink Canvas/app.manifest | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj
index 4c53cecb..2c0ee1dd 100644
--- a/Ink Canvas/InkCanvasForClass.csproj
+++ b/Ink Canvas/InkCanvasForClass.csproj
@@ -3,7 +3,7 @@
win;win-x86;win-x64;win-arm64
WinExe
Ink_Canvas
- InkCanvasForClass
+ InkCanvasForClass CE
net472
{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
true
diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs
index ad173d2c..6674f45c 100644
--- a/Ink Canvas/Properties/AssemblyInfo.cs
+++ b/Ink Canvas/Properties/AssemblyInfo.cs
@@ -5,11 +5,11 @@ using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("InkCanvasForClass")]
+[assembly: AssemblyTitle("InkCanvasForClass CE")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("HARKOTEK Studio")]
-[assembly: AssemblyProduct("InkCanvasForClass")]
+[assembly: AssemblyProduct("InkCanvasForClass CE")]
[assembly: AssemblyCopyright("© Copyright HARKOTEK Studio 2024-now")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/Ink Canvas/app.manifest b/Ink Canvas/app.manifest
index df1798c0..89e4303d 100644
--- a/Ink Canvas/app.manifest
+++ b/Ink Canvas/app.manifest
@@ -1,6 +1,6 @@
-
+
From 00d7549bdefebd9e6f455eb386f8afd4d68caa5d Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Mon, 28 Jul 2025 23:57:50 +0800
Subject: [PATCH 03/22] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=92=E6=96=A5?=
=?UTF-8?q?=E9=94=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/App.xaml.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs
index 8439db7a..0e97be05 100644
--- a/Ink Canvas/App.xaml.cs
+++ b/Ink Canvas/App.xaml.cs
@@ -456,7 +456,7 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}");
bool ret;
- mutex = new Mutex(true, "InkCanvasForClass", out ret);
+ mutex = new Mutex(true, "InkCanvasForClass CE", out ret);
if (!ret && !e.Args.Contains("-m")) //-m multiple
{
From a6316797e650e49c8676fcb15ab979f9fae158f2 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 00:01:47 +0800
Subject: [PATCH 04/22] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=BD=AF=E4=BB=B6?=
=?UTF-8?q?=E4=BF=A1=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/AssemblyInfo.cs | 6 +++---
Ink Canvas/Properties/AssemblyInfo.cs | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs
index 89b7106e..78d28eef 100644
--- a/Ink Canvas/AssemblyInfo.cs
+++ b/Ink Canvas/AssemblyInfo.cs
@@ -5,11 +5,11 @@ using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("InkCanvasForClass")]
+[assembly: AssemblyTitle("InkCanvasForClass CE")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Dubi906w")]
-[assembly: AssemblyProduct("InkCanvasForClass")]
+[assembly: AssemblyCompany("CJK_mkp")]
+[assembly: AssemblyProduct("InkCanvasForClass CE")]
[assembly: AssemblyCopyright("Copyright © HARKOTEK Studio 2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs
index 6674f45c..398e3414 100644
--- a/Ink Canvas/Properties/AssemblyInfo.cs
+++ b/Ink Canvas/Properties/AssemblyInfo.cs
@@ -8,7 +8,7 @@ using System.Windows;
[assembly: AssemblyTitle("InkCanvasForClass CE")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("HARKOTEK Studio")]
+[assembly: AssemblyCompany("CJK_mkp")]
[assembly: AssemblyProduct("InkCanvasForClass CE")]
[assembly: AssemblyCopyright("© Copyright HARKOTEK Studio 2024-now")]
[assembly: AssemblyTrademark("")]
From 4913019c5c6d19477347747af8038f82babaefb9 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 01:15:32 +0800
Subject: [PATCH 05/22] =?UTF-8?q?=E4=BC=98=E5=8C=96PPT=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/PPTInkManager.cs | 397 +++
Ink Canvas/Helpers/PPTManager.cs | 1334 ++++++++++
Ink Canvas/Helpers/PPTUIManager.cs | 435 ++++
Ink Canvas/MainWindow.xaml.cs | 16 +-
.../MainWindow_cs/MW_FloatingBarIcons.cs | 12 +-
Ink Canvas/MainWindow_cs/MW_PPT.cs | 2260 +++++------------
.../MainWindow_cs/MW_Save&OpenStrokes.cs | 39 +-
Ink Canvas/MainWindow_cs/MW_Settings.cs | 101 +-
Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs | 4 +-
Ink Canvas/MainWindow_cs/MW_Timer.cs | 5 +-
10 files changed, 2901 insertions(+), 1702 deletions(-)
create mode 100644 Ink Canvas/Helpers/PPTInkManager.cs
create mode 100644 Ink Canvas/Helpers/PPTManager.cs
create mode 100644 Ink Canvas/Helpers/PPTUIManager.cs
diff --git a/Ink Canvas/Helpers/PPTInkManager.cs b/Ink Canvas/Helpers/PPTInkManager.cs
new file mode 100644
index 00000000..549ab5ab
--- /dev/null
+++ b/Ink Canvas/Helpers/PPTInkManager.cs
@@ -0,0 +1,397 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using System.Windows.Ink;
+using System.Windows.Threading;
+using Microsoft.Office.Interop.PowerPoint;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// PPT墨迹管理器 - 负责PPT中墨迹的保存、加载和同步
+ ///
+ public class PPTInkManager : IDisposable
+ {
+ #region Properties
+ public bool IsAutoSaveEnabled { get; set; } = true;
+ public string AutoSaveLocation { get; set; } = "";
+ public StrokeCollection CurrentStrokes { get; private set; } = new StrokeCollection();
+ #endregion
+
+ #region Private Fields
+ private MemoryStream[] _memoryStreams;
+ private int _maxSlides = 100;
+ private string _currentPresentationId = "";
+ private readonly object _lockObject = new object();
+ private bool _disposed = false;
+
+ // 墨迹锁定机制,防止翻页时的墨迹冲突
+ private DateTime _inkLockUntil = DateTime.MinValue;
+ private int _lockedSlideIndex = -1;
+ private const int InkLockMilliseconds = 500;
+ #endregion
+
+ #region Constructor
+ public PPTInkManager()
+ {
+ InitializeMemoryStreams();
+ }
+
+ private void InitializeMemoryStreams()
+ {
+ _memoryStreams = new MemoryStream[_maxSlides + 2];
+ }
+ #endregion
+
+ #region Public Methods
+ ///
+ /// 初始化新的演示文稿
+ ///
+ public void InitializePresentation(Presentation presentation)
+ {
+ if (presentation == null) return;
+
+ lock (_lockObject)
+ {
+ try
+ {
+ // 生成演示文稿唯一标识符
+ _currentPresentationId = GeneratePresentationId(presentation);
+
+ // 重新初始化内存流数组
+ var slideCount = presentation.Slides.Count;
+ _memoryStreams = new MemoryStream[slideCount + 2];
+
+ // 如果启用自动保存,尝试加载已保存的墨迹
+ if (IsAutoSaveEnabled && !string.IsNullOrEmpty(AutoSaveLocation))
+ {
+ LoadSavedStrokes();
+ }
+
+ LogHelper.WriteLogToFile($"已初始化演示文稿墨迹管理: {presentation.Name}, 幻灯片数量: {slideCount}", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"初始化演示文稿墨迹管理失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ }
+
+ ///
+ /// 保存当前页面的墨迹
+ ///
+ public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes)
+ {
+ if (slideIndex <= 0 || strokes == null) return;
+
+ lock (_lockObject)
+ {
+ try
+ {
+ // 检查墨迹锁定
+ if (!CanWriteInk(slideIndex))
+ {
+ LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning);
+ return;
+ }
+
+ if (slideIndex < _memoryStreams.Length)
+ {
+ var ms = new MemoryStream();
+ strokes.Save(ms);
+ ms.Position = 0;
+
+ // 释放旧的内存流
+ _memoryStreams[slideIndex]?.Dispose();
+ _memoryStreams[slideIndex] = ms;
+
+ LogHelper.WriteLogToFile($"已保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"保存第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ }
+
+ ///
+ /// 加载指定页面的墨迹
+ ///
+ public StrokeCollection LoadSlideStrokes(int slideIndex)
+ {
+ if (slideIndex <= 0) return new StrokeCollection();
+
+ lock (_lockObject)
+ {
+ try
+ {
+ if (slideIndex < _memoryStreams.Length && _memoryStreams[slideIndex] != null && _memoryStreams[slideIndex].Length > 0)
+ {
+ _memoryStreams[slideIndex].Position = 0;
+ var strokes = new StrokeCollection(_memoryStreams[slideIndex]);
+ LogHelper.WriteLogToFile($"已加载第{slideIndex}页墨迹,笔画数量: {strokes.Count}", LogHelper.LogType.Trace);
+ return strokes;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"加载第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ return new StrokeCollection();
+ }
+
+ ///
+ /// 切换到指定页面并加载墨迹
+ ///
+ public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null)
+ {
+ lock (_lockObject)
+ {
+ try
+ {
+ // 如果有当前墨迹,先保存
+ if (currentStrokes != null && currentStrokes.Count > 0)
+ {
+ SaveCurrentSlideStrokes(_lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex, currentStrokes);
+ }
+
+ // 设置墨迹锁定
+ LockInkForSlide(slideIndex);
+
+ // 加载新页面的墨迹
+ return LoadSlideStrokes(slideIndex);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"切换到第{slideIndex}页失败: {ex}", LogHelper.LogType.Error);
+ return new StrokeCollection();
+ }
+ }
+ }
+
+ ///
+ /// 保存所有墨迹到文件
+ ///
+ public void SaveAllStrokesToFile(Presentation presentation)
+ {
+ if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
+
+ lock (_lockObject)
+ {
+ try
+ {
+ var folderPath = GetPresentationFolderPath();
+ if (!Directory.Exists(folderPath))
+ {
+ Directory.CreateDirectory(folderPath);
+ }
+
+ // 保存位置信息
+ try
+ {
+ File.WriteAllText(Path.Combine(folderPath, "Position"), _lockedSlideIndex.ToString());
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"保存位置信息失败: {ex}", LogHelper.LogType.Error);
+ }
+
+ // 保存所有页面的墨迹
+ int savedCount = 0;
+ for (int i = 1; i <= presentation.Slides.Count && i < _memoryStreams.Length; i++)
+ {
+ if (_memoryStreams[i] != null)
+ {
+ try
+ {
+ if (_memoryStreams[i].Length > 8)
+ {
+ var srcBuf = new byte[_memoryStreams[i].Length];
+ _memoryStreams[i].Position = 0;
+ var byteLength = _memoryStreams[i].Read(srcBuf, 0, srcBuf.Length);
+
+ var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk");
+ File.WriteAllBytes(filePath, srcBuf);
+ savedCount++;
+
+ LogHelper.WriteLogToFile($"已保存第{i}页墨迹,大小: {byteLength} bytes", LogHelper.LogType.Trace);
+ }
+ else
+ {
+ // 删除空的墨迹文件
+ var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk");
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"保存第{i}页墨迹失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ }
+
+ LogHelper.WriteLogToFile($"已保存{savedCount}页墨迹到文件", LogHelper.LogType.Event);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"保存墨迹到文件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ }
+
+ ///
+ /// 从文件加载已保存的墨迹
+ ///
+ public void LoadSavedStrokes()
+ {
+ if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation)) return;
+
+ lock (_lockObject)
+ {
+ try
+ {
+ var folderPath = GetPresentationFolderPath();
+ if (!Directory.Exists(folderPath)) return;
+
+ var files = new DirectoryInfo(folderPath).GetFiles("*.icstk");
+ int loadedCount = 0;
+
+ foreach (var file in files)
+ {
+ try
+ {
+ if (int.TryParse(Path.GetFileNameWithoutExtension(file.Name), out int slideIndex))
+ {
+ if (slideIndex > 0 && slideIndex < _memoryStreams.Length)
+ {
+ var fileBytes = File.ReadAllBytes(file.FullName);
+ _memoryStreams[slideIndex] = new MemoryStream(fileBytes);
+ _memoryStreams[slideIndex].Position = 0;
+ loadedCount++;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"加载墨迹文件{file.Name}失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ LogHelper.WriteLogToFile($"已从文件加载{loadedCount}页墨迹", LogHelper.LogType.Event);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ }
+
+ ///
+ /// 清除所有墨迹
+ ///
+ public void ClearAllStrokes()
+ {
+ lock (_lockObject)
+ {
+ try
+ {
+ for (int i = 0; i < _memoryStreams.Length; i++)
+ {
+ _memoryStreams[i]?.Dispose();
+ _memoryStreams[i] = null;
+ }
+
+ CurrentStrokes.Clear();
+ LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"清除墨迹失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ }
+
+ ///
+ /// 翻页后锁定墨迹写入
+ ///
+ public void LockInkForSlide(int slideIndex)
+ {
+ _inkLockUntil = DateTime.Now.AddMilliseconds(InkLockMilliseconds);
+ _lockedSlideIndex = slideIndex;
+ }
+
+ ///
+ /// 检查是否可以写入墨迹
+ ///
+ public bool CanWriteInk(int currentSlideIndex)
+ {
+ if (DateTime.Now < _inkLockUntil) return false;
+ if (currentSlideIndex != _lockedSlideIndex && _lockedSlideIndex > 0) return false;
+ return true;
+ }
+ #endregion
+
+ #region Private Methods
+ private string GeneratePresentationId(Presentation presentation)
+ {
+ try
+ {
+ var presentationPath = presentation.FullName;
+ var fileHash = GetFileHash(presentationPath);
+ return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}";
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"生成演示文稿ID失败: {ex}", LogHelper.LogType.Error);
+ 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 ex)
+ {
+ LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error);
+ return "error";
+ }
+ }
+
+ private string GetPresentationFolderPath()
+ {
+ return Path.Combine(AutoSaveLocation, "Auto Saved - Presentations", _currentPresentationId);
+ }
+ #endregion
+
+ #region Dispose
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ lock (_lockObject)
+ {
+ ClearAllStrokes();
+ }
+ _disposed = true;
+ }
+ }
+ #endregion
+ }
+}
diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs
new file mode 100644
index 00000000..0e511191
--- /dev/null
+++ b/Ink Canvas/Helpers/PPTManager.cs
@@ -0,0 +1,1334 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Timers;
+using System.Windows;
+using System.Windows.Threading;
+using Microsoft.Office.Core;
+using Microsoft.Office.Interop.PowerPoint;
+using Application = System.Windows.Application;
+using Timer = System.Timers.Timer;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// PPT联动管理器 - 统一管理PPT和WPS的连接、事件处理和进程管理
+ ///
+ public class PPTManager : IDisposable
+ {
+ #region Events
+ public event Action SlideShowBegin;
+ public event Action SlideShowNextSlide;
+ public event Action SlideShowEnd;
+ public event Action PresentationOpen;
+ public event Action PresentationClose;
+ public event Action PPTConnectionChanged;
+ #endregion
+
+ #region Properties
+ public Microsoft.Office.Interop.PowerPoint.Application PPTApplication { get; private set; }
+ public Presentation CurrentPresentation { get; private set; }
+ public Slides CurrentSlides { get; private set; }
+ public Slide CurrentSlide { get; private set; }
+ public int SlidesCount { get; private set; }
+ public bool IsConnected => PPTApplication != null;
+ public bool IsInSlideShow
+ {
+ get
+ {
+ try
+ {
+ if (PPTApplication == null || !Marshal.IsComObject(PPTApplication)) return false;
+ return PPTApplication.SlideShowWindows?.Count > 0;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ return false;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+ public bool IsSupportWPS { get; set; } = false;
+ #endregion
+
+ #region Private Fields
+ private Timer _connectionCheckTimer;
+ private Timer _wpsProcessCheckTimer;
+ private Process _wpsProcess;
+ private bool _hasWpsProcessId;
+ private DateTime _wpsProcessRecordTime = DateTime.MinValue;
+ private int _wpsProcessCheckCount;
+ private WpsWindowInfo _lastForegroundWpsWindow;
+ private DateTime _lastWindowCheckTime = DateTime.MinValue;
+ private readonly object _lockObject = new object();
+ private bool _disposed = false;
+ #endregion
+
+ #region Constructor & Initialization
+ public PPTManager()
+ {
+ InitializeConnectionTimer();
+ }
+
+ private void InitializeConnectionTimer()
+ {
+ _connectionCheckTimer = new Timer(500);
+ _connectionCheckTimer.Elapsed += OnConnectionCheckTimerElapsed;
+ _connectionCheckTimer.AutoReset = true;
+ }
+
+ public void StartMonitoring()
+ {
+ if (!_disposed)
+ {
+ _connectionCheckTimer?.Start();
+ LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Trace);
+ }
+ }
+
+ public void StopMonitoring()
+ {
+ _connectionCheckTimer?.Stop();
+ DisconnectFromPPT();
+ LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Trace);
+ }
+ #endregion
+
+ #region Connection Management
+ private void OnConnectionCheckTimerElapsed(object sender, ElapsedEventArgs e)
+ {
+ try
+ {
+ CheckAndConnectToPPT();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"PPT连接检查失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void CheckAndConnectToPPT()
+ {
+ lock (_lockObject)
+ {
+ try
+ {
+ // 尝试连接到PowerPoint
+ var pptApp = TryConnectToPowerPoint();
+ if (pptApp == null && IsSupportWPS)
+ {
+ // 如果PowerPoint连接失败且支持WPS,尝试连接WPS
+ pptApp = TryConnectToWPS();
+ }
+
+ if (pptApp != null && PPTApplication == null)
+ {
+ ConnectToPPT(pptApp);
+ }
+ else if (pptApp == null && PPTApplication != null)
+ {
+ DisconnectFromPPT();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"PPT连接检查异常: {ex}", LogHelper.LogType.Error);
+ if (PPTApplication != null)
+ {
+ DisconnectFromPPT();
+ }
+ }
+ }
+ }
+
+ private Microsoft.Office.Interop.PowerPoint.Application TryConnectToPowerPoint()
+ {
+ try
+ {
+ var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application");
+
+ // 验证COM对象是否有效
+ if (pptApp != null && Marshal.IsComObject(pptApp))
+ {
+ // 尝试访问一个简单的属性来验证连接
+ var _ = pptApp.Name;
+ return pptApp;
+ }
+ return null;
+ }
+ catch (COMException ex)
+ {
+ // 忽略常见的COM连接错误
+ var hr = (uint)ex.HResult;
+ if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E)
+ {
+ LogHelper.WriteLogToFile($"连接PowerPoint失败: {ex}", LogHelper.LogType.Warning);
+ }
+ return null;
+ }
+ catch (InvalidCastException)
+ {
+ // COM对象类型转换失败
+ return null;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"连接PowerPoint时发生意外错误: {ex}", LogHelper.LogType.Warning);
+ return null;
+ }
+ }
+
+ private Microsoft.Office.Interop.PowerPoint.Application TryConnectToWPS()
+ {
+ try
+ {
+ var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application");
+
+ // 验证COM对象是否有效
+ if (wpsApp != null && Marshal.IsComObject(wpsApp))
+ {
+ // 尝试访问一个简单的属性来验证连接
+ var _ = wpsApp.Name;
+ return wpsApp;
+ }
+ return null;
+ }
+ catch (COMException ex)
+ {
+ var hr = (uint)ex.HResult;
+ if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E)
+ {
+ LogHelper.WriteLogToFile($"连接WPS失败: {ex}", LogHelper.LogType.Warning);
+ }
+ return null;
+ }
+ catch (InvalidCastException)
+ {
+ // COM对象类型转换失败
+ return null;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"连接WPS时发生意外错误: {ex}", LogHelper.LogType.Warning);
+ return null;
+ }
+ }
+
+ private void ConnectToPPT(Microsoft.Office.Interop.PowerPoint.Application pptApp)
+ {
+ try
+ {
+ PPTApplication = pptApp;
+
+ // 在主线程中注册事件,确保COM对象在正确的线程中
+ Application.Current?.Dispatcher?.Invoke(() =>
+ {
+ try
+ {
+ PPTApplication.PresentationOpen += OnPresentationOpen;
+ PPTApplication.PresentationClose += OnPresentationClose;
+ PPTApplication.SlideShowBegin += OnSlideShowBegin;
+ PPTApplication.SlideShowNextSlide += OnSlideShowNextSlide;
+ PPTApplication.SlideShowEnd += OnSlideShowEnd;
+
+ LogHelper.WriteLogToFile("PPT事件注册成功", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"PPT事件注册失败: {ex}", LogHelper.LogType.Error);
+ throw; // 重新抛出异常,让外层处理
+ }
+ }, DispatcherPriority.Normal, System.Threading.CancellationToken.None, TimeSpan.FromSeconds(2));
+
+ // 获取当前演示文稿信息
+ UpdateCurrentPresentationInfo();
+
+ // 停止连接检查定时器
+ _connectionCheckTimer?.Stop();
+
+ // 触发连接成功事件
+ PPTConnectionChanged?.Invoke(true);
+
+ LogHelper.WriteLogToFile("成功连接到PPT应用程序", LogHelper.LogType.Event);
+
+ // 如果已经在放映状态,立即触发放映开始事件
+ if (IsInSlideShow)
+ {
+ OnSlideShowBegin(PPTApplication.SlideShowWindows[1]);
+ }
+ else if (CurrentPresentation != null)
+ {
+ OnPresentationOpen(CurrentPresentation);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"连接PPT应用程序失败: {ex}", LogHelper.LogType.Error);
+ PPTApplication = null;
+ }
+ }
+
+ private void DisconnectFromPPT()
+ {
+ try
+ {
+ if (PPTApplication != null)
+ {
+ // 取消事件注册 - 使用更安全的方式
+ try
+ {
+ // 检查COM对象是否仍然有效
+ if (Marshal.IsComObject(PPTApplication))
+ {
+ // 尝试在主线程中取消事件注册
+ Application.Current?.Dispatcher?.Invoke(() =>
+ {
+ try
+ {
+ PPTApplication.PresentationOpen -= OnPresentationOpen;
+ PPTApplication.PresentationClose -= OnPresentationClose;
+ PPTApplication.SlideShowBegin -= OnSlideShowBegin;
+ PPTApplication.SlideShowNextSlide -= OnSlideShowNextSlide;
+ PPTApplication.SlideShowEnd -= OnSlideShowEnd;
+ }
+ catch (COMException comEx)
+ {
+ // COM对象已经被释放或在错误的线程中,忽略这些错误
+ var hr = (uint)comEx.HResult;
+ if (hr != 0x8001010E && hr != 0x80004005 && hr != 0x800706B5)
+ {
+ LogHelper.WriteLogToFile($"取消PPT事件注册COM异常: {comEx}", LogHelper.LogType.Warning);
+ }
+ }
+ catch (InvalidCastException)
+ {
+ // COM对象类型转换失败,通常是因为对象已经被释放
+ LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace);
+ }
+ }, DispatcherPriority.Normal, System.Threading.CancellationToken.None, TimeSpan.FromSeconds(1));
+ }
+ }
+ catch (Exception ex)
+ {
+ // 记录但不抛出异常,确保清理过程能够继续
+ LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.GetType().Name} - {ex.Message}", LogHelper.LogType.Warning);
+ }
+
+ // 清理引用
+ PPTApplication = null;
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
+
+ // 重新启动连接检查定时器
+ _connectionCheckTimer?.Start();
+
+ // 触发连接断开事件
+ PPTConnectionChanged?.Invoke(false);
+
+ LogHelper.WriteLogToFile("已断开PPT连接", LogHelper.LogType.Event);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"断开PPT连接失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void UpdateCurrentPresentationInfo()
+ {
+ try
+ {
+ if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
+ {
+ // 检查是否有活动的演示文稿
+ try
+ {
+ var activePresentation = PPTApplication.ActivePresentation;
+ if (activePresentation != null)
+ {
+ CurrentPresentation = activePresentation;
+ CurrentSlides = CurrentPresentation.Slides;
+ SlidesCount = CurrentSlides.Count;
+
+ // 获取当前幻灯片
+ try
+ {
+ if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0)
+ {
+ CurrentSlide = PPTApplication.SlideShowWindows[1].View.Slide;
+ }
+ else if (PPTApplication.ActiveWindow?.Selection?.SlideRange?.SlideNumber > 0)
+ {
+ CurrentSlide = CurrentSlides[PPTApplication.ActiveWindow.Selection.SlideRange.SlideNumber];
+ }
+ else if (SlidesCount > 0)
+ {
+ // 如果获取失败,使用第一张幻灯片
+ CurrentSlide = CurrentSlides[1];
+ }
+ }
+ catch (COMException comEx)
+ {
+ // COM异常,尝试使用第一张幻灯片
+ var hr = (uint)comEx.HResult;
+ if (hr != 0x8001010E && hr != 0x80004005)
+ {
+ LogHelper.WriteLogToFile($"获取当前幻灯片失败: {comEx.Message}", LogHelper.LogType.Warning);
+ }
+
+ if (SlidesCount > 0)
+ {
+ CurrentSlide = CurrentSlides[1];
+ }
+ }
+ }
+ else
+ {
+ // 没有活动演示文稿,清理状态
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
+ }
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // 常见的COM错误,可能是没有活动演示文稿
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
+ }
+ else
+ {
+ LogHelper.WriteLogToFile($"访问活动演示文稿失败: {comEx}", LogHelper.LogType.Warning);
+ }
+ }
+ }
+ else
+ {
+ // PPT应用程序无效,清理状态
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"更新演示文稿信息失败: {ex}", LogHelper.LogType.Error);
+ // 发生异常时清理状态
+ CurrentPresentation = null;
+ CurrentSlides = null;
+ CurrentSlide = null;
+ SlidesCount = 0;
+ }
+ }
+ #endregion
+
+ #region Event Handlers
+ private void OnPresentationOpen(Presentation pres)
+ {
+ try
+ {
+ UpdateCurrentPresentationInfo();
+ PresentationOpen?.Invoke(pres);
+ LogHelper.WriteLogToFile($"演示文稿已打开: {pres?.Name}", LogHelper.LogType.Event);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void OnPresentationClose(Presentation pres)
+ {
+ try
+ {
+ PresentationClose?.Invoke(pres);
+ LogHelper.WriteLogToFile($"演示文稿已关闭: {pres?.Name}", LogHelper.LogType.Event);
+
+ // 重新启动连接检查
+ _connectionCheckTimer?.Start();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理演示文稿关闭事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void OnSlideShowBegin(SlideShowWindow wn)
+ {
+ try
+ {
+ UpdateCurrentPresentationInfo();
+ SlideShowBegin?.Invoke(wn);
+ LogHelper.WriteLogToFile("幻灯片放映开始", LogHelper.LogType.Event);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片放映开始事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void OnSlideShowNextSlide(SlideShowWindow wn)
+ {
+ try
+ {
+ UpdateCurrentPresentationInfo();
+ SlideShowNextSlide?.Invoke(wn);
+ LogHelper.WriteLogToFile($"幻灯片切换到第{wn?.View?.CurrentShowPosition}页", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片切换事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void OnSlideShowEnd(Presentation pres)
+ {
+ try
+ {
+ // 记录WPS进程用于后续管理
+ if (IsSupportWPS && PPTApplication != null)
+ {
+ RecordWpsProcessForManagement();
+ }
+
+ SlideShowEnd?.Invoke(pres);
+ LogHelper.WriteLogToFile("幻灯片放映结束", LogHelper.LogType.Event);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片放映结束事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ #endregion
+
+ #region Public Methods
+ public bool TryNavigateToSlide(int slideNumber)
+ {
+ try
+ {
+ if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false;
+ if (!Marshal.IsComObject(PPTApplication)) return false;
+
+ var slideShowWindow = PPTApplication.SlideShowWindows[1];
+ if (slideShowWindow?.View != null)
+ {
+ slideShowWindow.View.GotoSlide(slideNumber);
+ return true;
+ }
+ return false;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {comEx.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ public bool TryNavigateNext()
+ {
+ try
+ {
+ if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false;
+ if (!Marshal.IsComObject(PPTApplication)) return false;
+
+ var slideShowWindow = PPTApplication.SlideShowWindows[1];
+ if (slideShowWindow?.View != null)
+ {
+ slideShowWindow.Activate();
+ slideShowWindow.View.Next();
+ return true;
+ }
+ return false;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"切换到下一页失败: {comEx.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"切换到下一页失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ public bool TryNavigatePrevious()
+ {
+ try
+ {
+ if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false;
+ if (!Marshal.IsComObject(PPTApplication)) return false;
+
+ var slideShowWindow = PPTApplication.SlideShowWindows[1];
+ if (slideShowWindow?.View != null)
+ {
+ slideShowWindow.Activate();
+ slideShowWindow.View.Previous();
+ return true;
+ }
+ return false;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"切换到上一页失败: {comEx.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"切换到上一页失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ public bool TryEndSlideShow()
+ {
+ try
+ {
+ if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false;
+ if (!Marshal.IsComObject(PPTApplication)) return false;
+
+ var slideShowWindow = PPTApplication.SlideShowWindows[1];
+ if (slideShowWindow?.View != null)
+ {
+ slideShowWindow.View.Exit();
+ return true;
+ }
+ return false;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"结束幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"结束幻灯片放映失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ public bool TryStartSlideShow()
+ {
+ try
+ {
+ if (!IsConnected || CurrentPresentation == null || PPTApplication == null) return false;
+ if (!Marshal.IsComObject(PPTApplication) || !Marshal.IsComObject(CurrentPresentation)) return false;
+
+ CurrentPresentation.SlideShowSettings.Run();
+ return true;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"开始幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"开始幻灯片放映失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ public int GetCurrentSlideNumber()
+ {
+ try
+ {
+ if (!IsConnected || !IsInSlideShow || PPTApplication == null) return 0;
+ if (!Marshal.IsComObject(PPTApplication)) return 0;
+
+ return PPTApplication.SlideShowWindows[1]?.View?.CurrentShowPosition ?? 0;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {comEx.Message}", LogHelper.LogType.Warning);
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {ex}", LogHelper.LogType.Error);
+ return 0;
+ }
+ }
+
+ public string GetPresentationName()
+ {
+ try
+ {
+ if (CurrentPresentation == null || !Marshal.IsComObject(CurrentPresentation)) return "";
+
+ return CurrentPresentation.Name ?? "";
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"获取演示文稿名称失败: {comEx.Message}", LogHelper.LogType.Warning);
+ return "";
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"获取演示文稿名称失败: {ex}", LogHelper.LogType.Error);
+ return "";
+ }
+ }
+
+ public string GetPresentationPath()
+ {
+ try
+ {
+ if (CurrentPresentation == null || !Marshal.IsComObject(CurrentPresentation)) return "";
+
+ return CurrentPresentation.FullName ?? "";
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"获取演示文稿路径失败: {comEx.Message}", LogHelper.LogType.Warning);
+ return "";
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"获取演示文稿路径失败: {ex}", LogHelper.LogType.Error);
+ return "";
+ }
+ }
+
+ public bool TryShowSlideNavigation()
+ {
+ try
+ {
+ LogHelper.WriteLogToFile($"尝试显示幻灯片导航 - 连接状态: {IsConnected}, 放映状态: {IsInSlideShow}", LogHelper.LogType.Trace);
+
+ if (!IsConnected || !IsInSlideShow || PPTApplication == null)
+ {
+ LogHelper.WriteLogToFile("PPT未连接或未在放映状态", LogHelper.LogType.Warning);
+ return false;
+ }
+
+ if (!Marshal.IsComObject(PPTApplication))
+ {
+ LogHelper.WriteLogToFile("PPT应用程序COM对象无效", LogHelper.LogType.Warning);
+ return false;
+ }
+
+ var slideShowWindow = PPTApplication.SlideShowWindows[1];
+ if (slideShowWindow == null)
+ {
+ LogHelper.WriteLogToFile("幻灯片放映窗口为空", LogHelper.LogType.Warning);
+ return false;
+ }
+
+ // 检查是否为WPS,WPS可能不支持SlideNavigation
+ try
+ {
+ if (slideShowWindow.SlideNavigation != null)
+ {
+ slideShowWindow.SlideNavigation.Visible = true;
+ LogHelper.WriteLogToFile("成功显示幻灯片导航(PowerPoint模式)", LogHelper.LogType.Event);
+ return true;
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("SlideNavigation对象为空,可能是WPS不支持此功能", LogHelper.LogType.Warning);
+ return false;
+ }
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ // 0x80020006: 未知名称 - WPS可能不支持SlideNavigation
+ if (hr == 0x80020006)
+ {
+ LogHelper.WriteLogToFile("WPS不支持SlideNavigation功能", LogHelper.LogType.Warning);
+ return false;
+ }
+ throw; // 重新抛出其他COM异常
+ }
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ if (hr == 0x8001010E || hr == 0x80004005)
+ {
+ // COM对象已失效,触发断开连接
+ DisconnectFromPPT();
+ }
+ LogHelper.WriteLogToFile($"显示幻灯片导航COM异常: {comEx.Message} (HRESULT: 0x{hr:X8})", LogHelper.LogType.Error);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"显示幻灯片导航失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+ #endregion
+
+ #region WPS Process Management
+ private void RecordWpsProcessForManagement()
+ {
+ if (!IsSupportWPS || PPTApplication == null) return;
+
+ try
+ {
+ Process wpsProcess = null;
+
+ // 方法1:通过应用程序路径检测
+ if (PPTApplication.Path.Contains("Kingsoft\\WPS Office\\") ||
+ PPTApplication.Path.Contains("WPS Office\\"))
+ {
+ uint processId;
+ GetWindowThreadProcessId((IntPtr)PPTApplication.HWND, out processId);
+ wpsProcess = Process.GetProcessById((int)processId);
+ }
+
+ // 方法2:通过前台窗口检测
+ if (wpsProcess == null)
+ {
+ var foregroundWpsWindow = GetForegroundWpsWindow();
+ if (foregroundWpsWindow != null)
+ {
+ wpsProcess = Process.GetProcessById((int)foregroundWpsWindow.ProcessId);
+ }
+ }
+
+ // 方法3:通过进程名检测
+ if (wpsProcess == null)
+ {
+ var wpsProcesses = GetWpsProcesses();
+ if (wpsProcesses.Count > 0)
+ {
+ wpsProcess = wpsProcesses.First();
+ }
+ }
+
+ if (wpsProcess != null)
+ {
+ _wpsProcess = wpsProcess;
+ _hasWpsProcessId = true;
+ _wpsProcessRecordTime = DateTime.Now;
+ _wpsProcessCheckCount = 0;
+ LogHelper.WriteLogToFile($"成功记录 WPS 进程 ID: {wpsProcess.Id}", LogHelper.LogType.Trace);
+
+ StartWpsProcessCheckTimer();
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("未能检测到WPS进程", LogHelper.LogType.Warning);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"记录WPS进程失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void StartWpsProcessCheckTimer()
+ {
+ if (!IsSupportWPS) return;
+
+ if (_wpsProcessCheckTimer != null)
+ {
+ _wpsProcessCheckTimer.Stop();
+ _wpsProcessCheckTimer.Dispose();
+ }
+
+ _wpsProcessCheckTimer = new Timer(500);
+ _wpsProcessCheckTimer.Elapsed += OnWpsProcessCheckTimerElapsed;
+ _wpsProcessCheckTimer.Start();
+ LogHelper.WriteLogToFile("启动 WPS 进程检测定时器", LogHelper.LogType.Trace);
+ }
+
+ private void OnWpsProcessCheckTimerElapsed(object sender, ElapsedEventArgs e)
+ {
+ if (!IsSupportWPS)
+ {
+ StopWpsProcessCheckTimer();
+ return;
+ }
+
+ try
+ {
+ if (_wpsProcess == null || !_hasWpsProcessId)
+ {
+ StopWpsProcessCheckTimer();
+ return;
+ }
+
+ _wpsProcess.Refresh();
+ _wpsProcessCheckCount++;
+
+ if (_wpsProcess.HasExited)
+ {
+ LogHelper.WriteLogToFile("WPS 进程已正常关闭", LogHelper.LogType.Trace);
+ StopWpsProcessCheckTimer();
+ return;
+ }
+
+ // 检查前台WPS窗口是否存在
+ bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActive();
+
+ if (isForegroundWpsWindowActive)
+ {
+ if (_wpsProcessCheckCount % 10 == 0)
+ {
+ LogHelper.WriteLogToFile($"前台WPS窗口仍然存在,继续监控(已检查{_wpsProcessCheckCount}次)", LogHelper.LogType.Trace);
+ }
+ return;
+ }
+
+ // 前台窗口已消失,检查是否需要结束进程
+ LogHelper.WriteLogToFile("检测到前台WPS窗口已消失", LogHelper.LogType.Event);
+
+ // 检查所有WPS文档是否已保存
+ bool allSaved = CheckAllWpsDocumentsSaved();
+
+ if (!allSaved)
+ {
+ LogHelper.WriteLogToFile("检测到有未保存的WPS文档,跳过进程结束", LogHelper.LogType.Trace);
+ }
+
+ // 结束WPS进程
+ try
+ {
+ if (!_wpsProcess.HasExited)
+ {
+ _wpsProcess.Kill();
+ LogHelper.WriteLogToFile("成功结束WPS进程", LogHelper.LogType.Event);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"结束WPS进程失败: {ex}", LogHelper.LogType.Error);
+ }
+ finally
+ {
+ StopWpsProcessCheckTimer();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"WPS 进程检测失败: {ex}", LogHelper.LogType.Error);
+ StopWpsProcessCheckTimer();
+ }
+ }
+
+ private bool CheckAllWpsDocumentsSaved()
+ {
+ try
+ {
+ if (PPTApplication?.Presentations != null)
+ {
+ foreach (Presentation pres in PPTApplication.Presentations)
+ {
+ if (pres.Saved == MsoTriState.msoFalse)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查WPS文档保存状态失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ private void StopWpsProcessCheckTimer()
+ {
+ if (_wpsProcessCheckTimer != null)
+ {
+ _wpsProcessCheckTimer.Stop();
+ _wpsProcessCheckTimer.Dispose();
+ _wpsProcessCheckTimer = null;
+ }
+
+ _wpsProcess = null;
+ _hasWpsProcessId = false;
+ _wpsProcessRecordTime = DateTime.MinValue;
+ _wpsProcessCheckCount = 0;
+ _lastForegroundWpsWindow = null;
+ _lastWindowCheckTime = DateTime.MinValue;
+ LogHelper.WriteLogToFile("停止 WPS 进程检测定时器", LogHelper.LogType.Trace);
+ }
+ #endregion
+
+ #region WPS Window Detection
+ [DllImport("user32.dll")]
+ private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
+
+ [DllImport("user32.dll")]
+ private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool IsWindowVisible(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern bool IsIconic(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern bool IsZoomed(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr GetForegroundWindow();
+
+ [DllImport("user32.dll")]
+ private static extern bool IsWindow(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct RECT
+ {
+ public int Left;
+ public int Top;
+ public int Right;
+ public int Bottom;
+
+ public int Width => Right - Left;
+ public int Height => Bottom - Top;
+ }
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
+
+ private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
+
+ private class WpsWindowInfo
+ {
+ public IntPtr Handle { get; set; }
+ public string Title { get; set; }
+ public string ClassName { get; set; }
+ public bool IsVisible { get; set; }
+ public bool IsMinimized { get; set; }
+ public bool IsMaximized { get; set; }
+ public RECT Rect { get; set; }
+ public uint ProcessId { get; set; }
+ public string ProcessName { get; set; }
+ }
+
+ private WpsWindowInfo GetForegroundWpsWindow()
+ {
+ try
+ {
+ var foregroundHwnd = GetForegroundWindow();
+ if (foregroundHwnd != IntPtr.Zero && IsWindow(foregroundHwnd))
+ {
+ var windowInfo = GetWindowInfo(foregroundHwnd);
+ if (IsWpsWindow(windowInfo))
+ {
+ return windowInfo;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"获取前台WPS窗口失败: {ex}", LogHelper.LogType.Error);
+ }
+ return null;
+ }
+
+ private WpsWindowInfo GetWindowInfo(IntPtr hWnd)
+ {
+ var windowInfo = new WpsWindowInfo
+ {
+ Handle = hWnd,
+ IsVisible = IsWindowVisible(hWnd),
+ IsMinimized = IsIconic(hWnd),
+ IsMaximized = IsZoomed(hWnd)
+ };
+
+ // 获取窗口标题
+ var windowTitle = new StringBuilder(256);
+ GetWindowText(hWnd, windowTitle, 256);
+ windowInfo.Title = windowTitle.ToString().Trim();
+
+ // 获取窗口类名
+ var className = new StringBuilder(256);
+ GetClassName(hWnd, className, 256);
+ windowInfo.ClassName = className.ToString().Trim();
+
+ // 获取窗口位置
+ GetWindowRect(hWnd, out RECT rect);
+ windowInfo.Rect = rect;
+
+ // 获取进程ID
+ uint processId;
+ GetWindowThreadProcessId(hWnd, out processId);
+ windowInfo.ProcessId = processId;
+
+ // 获取进程名
+ windowInfo.ProcessName = "";
+ try
+ {
+ var proc = Process.GetProcessById((int)processId);
+ windowInfo.ProcessName = proc.ProcessName.ToLower();
+ }
+ catch { }
+
+ return windowInfo;
+ }
+
+ private bool IsWpsWindow(WpsWindowInfo windowInfo)
+ {
+ if (string.IsNullOrEmpty(windowInfo.Title) && string.IsNullOrEmpty(windowInfo.ClassName))
+ return false;
+
+ var title = windowInfo.Title.ToLower();
+ var className = windowInfo.ClassName.ToLower();
+ var processName = windowInfo.ProcessName ?? "";
+
+ // WPS相关关键词
+ var wpsKeywords = new[] { "wps", "wpp", "kingsoft", "金山", "wps演示", "wps presentation", "wps office", "kingsoft office" };
+ // 微软Office相关进程名
+ var msOfficeProcess = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "microsoftoffice", "office" };
+
+ // 只要进程名是微软Office,直接排除
+ if (msOfficeProcess.Any(keyword => processName.Contains(keyword)))
+ return false;
+
+ // 只要进程名是WPS/WPP/Kingsoft,直接通过
+ if (wpsKeywords.Any(keyword => processName.Contains(keyword)))
+ return true;
+
+ // 标题或类名包含WPS相关关键词
+ bool hasWpsTitle = wpsKeywords.Any(keyword => title.Contains(keyword));
+ bool hasWpsClass = wpsKeywords.Any(keyword => className.Contains(keyword));
+ bool isWpsClass = className.Contains("wps") || className.Contains("kingsoft") || className.Contains("wpp");
+ bool hasValidSize = (windowInfo.Rect.Right - windowInfo.Rect.Left) > 0 && (windowInfo.Rect.Bottom - windowInfo.Rect.Top) > 0;
+
+ return (hasWpsTitle || hasWpsClass || isWpsClass) && hasValidSize;
+ }
+
+ private List GetWpsProcesses()
+ {
+ var wpsProcesses = new List();
+ try
+ {
+ var allProcesses = Process.GetProcesses();
+ foreach (var process in allProcesses)
+ {
+ try
+ {
+ var pname = process.ProcessName.ToLower();
+ if ((pname.Contains("wps") || pname.Contains("wpp") || pname.Contains("presentation"))
+ && !pname.Contains("powerpnt")
+ && !pname.Contains("office")
+ && !pname.Contains("onenote")
+ && !pname.Contains("excel")
+ && !pname.Contains("word")
+ && !pname.Contains("outlook")
+ && !pname.Contains("microsoft"))
+ {
+ wpsProcesses.Add(process);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查进程{process.ProcessName}失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"获取WPS进程失败: {ex}", LogHelper.LogType.Error);
+ }
+ return wpsProcesses;
+ }
+
+ private bool IsForegroundWpsWindowStillActive()
+ {
+ try
+ {
+ var currentTime = DateTime.Now;
+ var currentForegroundWindow = GetForegroundWpsWindow();
+
+ // 检查窗口状态是否发生变化
+ if (_lastForegroundWpsWindow != null && currentForegroundWindow != null)
+ {
+ if (_lastForegroundWpsWindow.Handle != currentForegroundWindow.Handle ||
+ _lastForegroundWpsWindow.Title != currentForegroundWindow.Title)
+ {
+ LogHelper.WriteLogToFile($"前台WPS窗口发生变化: {_lastForegroundWpsWindow.Title} -> {currentForegroundWindow.Title}", LogHelper.LogType.Trace);
+ }
+ }
+ else if (_lastForegroundWpsWindow == null && currentForegroundWindow != null)
+ {
+ LogHelper.WriteLogToFile($"检测到新的前台WPS窗口: {currentForegroundWindow.Title}", LogHelper.LogType.Trace);
+ }
+ else if (_lastForegroundWpsWindow != null && currentForegroundWindow == null)
+ {
+ LogHelper.WriteLogToFile($"前台WPS窗口已消失: {_lastForegroundWpsWindow.Title}", LogHelper.LogType.Trace);
+ }
+
+ // 更新记录
+ _lastForegroundWpsWindow = currentForegroundWindow;
+ _lastWindowCheckTime = currentTime;
+
+ if (currentForegroundWindow != null)
+ {
+ if (IsWindow(currentForegroundWindow.Handle) && IsWindowVisible(currentForegroundWindow.Handle))
+ {
+ return true;
+ }
+ }
+
+ // 检查所有WPS进程的活跃窗口
+ var wpsProcesses = GetWpsProcesses();
+ foreach (var process in wpsProcesses)
+ {
+ var windows = GetWpsWindowsByProcess(process.Id);
+ if (windows.Any(w => w.IsVisible && !w.IsMinimized && w.Handle == GetForegroundWindow()))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查前台WPS窗口状态失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ private List GetWpsWindowsByProcess(int processId)
+ {
+ var wpsWindows = new List();
+
+ try
+ {
+ EnumWindows((hWnd, lParam) =>
+ {
+ try
+ {
+ if (!IsWindow(hWnd)) return true;
+
+ uint windowProcessId;
+ GetWindowThreadProcessId(hWnd, out windowProcessId);
+
+ if ((int)windowProcessId == processId)
+ {
+ var windowInfo = GetWindowInfo(hWnd);
+ if (IsWpsWindow(windowInfo))
+ {
+ wpsWindows.Add(windowInfo);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"枚举窗口时出错: {ex}", LogHelper.LogType.Error);
+ }
+ return true;
+ }, IntPtr.Zero);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"获取WPS窗口失败: {ex}", LogHelper.LogType.Error);
+ }
+
+ return wpsWindows;
+ }
+ #endregion
+
+ #region Dispose
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ StopMonitoring();
+ StopWpsProcessCheckTimer();
+
+ _connectionCheckTimer?.Dispose();
+ _wpsProcessCheckTimer?.Dispose();
+
+ _disposed = true;
+ }
+ }
+ #endregion
+ }
+}
diff --git a/Ink Canvas/Helpers/PPTUIManager.cs b/Ink Canvas/Helpers/PPTUIManager.cs
new file mode 100644
index 00000000..3bd1713d
--- /dev/null
+++ b/Ink Canvas/Helpers/PPTUIManager.cs
@@ -0,0 +1,435 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Threading;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// PPT UI管理器 - 统一管理PPT相关的UI更新和样式设置
+ ///
+ public class PPTUIManager
+ {
+ #region Properties
+ public bool ShowPPTButton { get; set; } = true;
+ public int PPTButtonsDisplayOption { get; set; } = 2222;
+ public int PPTSButtonsOption { get; set; } = 221;
+ public int PPTBButtonsOption { get; set; } = 121;
+ public int PPTLSButtonPosition { get; set; } = 0;
+ public int PPTRSButtonPosition { get; set; } = 0;
+ public bool EnablePPTButtonPageClickable { get; set; } = true;
+ #endregion
+
+ #region Private Fields
+ private readonly MainWindow _mainWindow;
+ private readonly Dispatcher _dispatcher;
+ #endregion
+
+ #region Constructor
+ public PPTUIManager(MainWindow mainWindow)
+ {
+ _mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
+ _dispatcher = _mainWindow.Dispatcher;
+ }
+ #endregion
+
+ #region Public Methods
+ ///
+ /// 更新PPT连接状态UI
+ ///
+ public void UpdateConnectionStatus(bool isConnected)
+ {
+ _dispatcher.InvokeAsync(() =>
+ {
+ try
+ {
+ if (isConnected)
+ {
+ _mainWindow.StackPanelPPTControls.Visibility = Visibility.Visible;
+ _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible;
+ }
+ else
+ {
+ _mainWindow.StackPanelPPTControls.Visibility = Visibility.Collapsed;
+ _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed;
+ _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
+ HideAllNavigationPanels();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"更新PPT连接状态UI失败: {ex}", LogHelper.LogType.Error);
+ }
+ });
+ }
+
+ ///
+ /// 更新幻灯片放映状态UI
+ ///
+ public void UpdateSlideShowStatus(bool isInSlideShow, int currentSlide = 0, int totalSlides = 0)
+ {
+ _dispatcher.InvokeAsync(() =>
+ {
+ try
+ {
+ if (isInSlideShow)
+ {
+ _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed;
+ _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Visible;
+
+ if (currentSlide > 0 && totalSlides > 0)
+ {
+ _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
+ _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
+ }
+
+ UpdateNavigationPanelsVisibility();
+ UpdateNavigationButtonStyles();
+ }
+ else
+ {
+ _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible;
+ _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
+ HideAllNavigationPanels();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"更新幻灯片放映状态UI失败: {ex}", LogHelper.LogType.Error);
+ }
+ });
+ }
+
+ ///
+ /// 更新当前页码显示
+ ///
+ public void UpdateCurrentSlideNumber(int currentSlide, int totalSlides)
+ {
+ _dispatcher.InvokeAsync(() =>
+ {
+ try
+ {
+ _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
+ _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"更新页码显示失败: {ex}", LogHelper.LogType.Error);
+ }
+ });
+ }
+
+ ///
+ /// 更新导航面板显示状态
+ ///
+ public void UpdateNavigationPanelsVisibility()
+ {
+ _dispatcher.InvokeAsync(() =>
+ {
+ try
+ {
+ // 检查是否应该显示PPT按钮
+ bool shouldShowButtons = ShowPPTButton && _mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible;
+
+ if (!shouldShowButtons)
+ {
+ HideAllNavigationPanels();
+ return;
+ }
+
+ // 设置侧边按钮位置
+ _mainWindow.LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTLSButtonPosition * 2);
+ _mainWindow.RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTRSButtonPosition * 2);
+
+ // 根据显示选项设置面板可见性
+ var displayOption = PPTButtonsDisplayOption.ToString();
+ if (displayOption.Length >= 4)
+ {
+ var options = displayOption.ToCharArray();
+
+ // 左下角面板
+ if (options[0] == '2')
+ AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftBottomPanelForPPTNavigation);
+ else
+ _mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
+
+ // 右下角面板
+ if (options[1] == '2')
+ AnimationsHelper.ShowWithFadeIn(_mainWindow.RightBottomPanelForPPTNavigation);
+ else
+ _mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
+
+ // 左侧面板
+ if (options[2] == '2')
+ AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftSidePanelForPPTNavigation);
+ else
+ _mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+
+ // 右侧面板
+ if (options[3] == '2')
+ AnimationsHelper.ShowWithFadeIn(_mainWindow.RightSidePanelForPPTNavigation);
+ else
+ _mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"更新导航面板显示状态失败: {ex}", LogHelper.LogType.Error);
+ }
+ });
+ }
+
+ ///
+ /// 更新导航按钮样式
+ ///
+ public void UpdateNavigationButtonStyles()
+ {
+ _dispatcher.InvokeAsync(() =>
+ {
+ try
+ {
+ UpdateSideButtonStyles();
+ UpdateBottomButtonStyles();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"更新导航按钮样式失败: {ex}", LogHelper.LogType.Error);
+ }
+ });
+ }
+
+ ///
+ /// 隐藏所有导航面板
+ ///
+ public void HideAllNavigationPanels()
+ {
+ _dispatcher.InvokeAsync(() =>
+ {
+ try
+ {
+ _mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ _mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ _mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ _mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"隐藏导航面板失败: {ex}", LogHelper.LogType.Error);
+ }
+ });
+ }
+
+ ///
+ /// 显示/隐藏侧边栏退出按钮
+ ///
+ public void UpdateSidebarExitButtons(bool show)
+ {
+ _dispatcher.InvokeAsync(() =>
+ {
+ try
+ {
+ var visibility = show ? Visibility.Visible : Visibility.Collapsed;
+
+ if (_mainWindow.BtnExitPptFromSidebarLeft != null)
+ _mainWindow.BtnExitPptFromSidebarLeft.Visibility = visibility;
+
+ if (_mainWindow.BtnExitPptFromSidebarRight != null)
+ _mainWindow.BtnExitPptFromSidebarRight.Visibility = visibility;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"更新侧边栏退出按钮失败: {ex}", LogHelper.LogType.Error);
+ }
+ });
+ }
+
+ ///
+ /// 设置浮动栏透明度
+ ///
+ public void SetFloatingBarOpacity(double opacity)
+ {
+ _dispatcher.InvokeAsync(() =>
+ {
+ try
+ {
+ _mainWindow.ViewboxFloatingBar.Opacity = opacity;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"设置浮动栏透明度失败: {ex}", LogHelper.LogType.Error);
+ }
+ });
+ }
+
+ ///
+ /// 设置主面板边距
+ ///
+ public void SetMainPanelMargin(Thickness margin)
+ {
+ _dispatcher.InvokeAsync(() =>
+ {
+ try
+ {
+ _mainWindow.ViewBoxStackPanelMain.Margin = margin;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"设置主面板边距失败: {ex}", LogHelper.LogType.Error);
+ }
+ });
+ }
+ #endregion
+
+ #region Private Methods
+ private void UpdateSideButtonStyles()
+ {
+ try
+ {
+ var sideOption = PPTSButtonsOption.ToString();
+ if (sideOption.Length < 3) return;
+
+ var options = sideOption.ToCharArray();
+
+ // 页码按钮显示
+ var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
+ _mainWindow.PPTLSPageButton.Visibility = pageButtonVisibility;
+ _mainWindow.PPTRSPageButton.Visibility = pageButtonVisibility;
+
+ // 透明度设置
+ var opacity = options[1] == '2' ? 0.5 : 1.0;
+ _mainWindow.PPTBtnLSBorder.Opacity = opacity;
+ _mainWindow.PPTBtnRSBorder.Opacity = opacity;
+
+ // 颜色主题
+ bool isDarkTheme = options[2] == '2';
+ ApplyButtonTheme(_mainWindow.PPTBtnLSBorder, _mainWindow.PPTBtnRSBorder, isDarkTheme, true);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"更新侧边按钮样式失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void UpdateBottomButtonStyles()
+ {
+ try
+ {
+ var bottomOption = PPTBButtonsOption.ToString();
+ if (bottomOption.Length < 3) return;
+
+ var options = bottomOption.ToCharArray();
+
+ // 页码按钮显示
+ var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
+ _mainWindow.PPTLBPageButton.Visibility = pageButtonVisibility;
+ _mainWindow.PPTRBPageButton.Visibility = pageButtonVisibility;
+
+ // 透明度设置
+ var opacity = options[1] == '2' ? 0.5 : 1.0;
+ _mainWindow.PPTBtnLBBorder.Opacity = opacity;
+ _mainWindow.PPTBtnRBBorder.Opacity = opacity;
+
+ // 颜色主题
+ bool isDarkTheme = options[2] == '2';
+ ApplyButtonTheme(_mainWindow.PPTBtnLBBorder, _mainWindow.PPTBtnRBBorder, isDarkTheme, false);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"更新底部按钮样式失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void ApplyButtonTheme(Border leftBorder, Border rightBorder, bool isDarkTheme, bool isSideButton)
+ {
+ try
+ {
+ Color backgroundColor, borderColor, foregroundColor, feedbackColor;
+
+ if (isDarkTheme)
+ {
+ backgroundColor = Color.FromRgb(39, 39, 42);
+ borderColor = Color.FromRgb(82, 82, 91);
+ foregroundColor = Colors.White;
+ feedbackColor = Colors.White;
+ }
+ else
+ {
+ backgroundColor = Color.FromRgb(244, 244, 245);
+ borderColor = Color.FromRgb(161, 161, 170);
+ foregroundColor = Color.FromRgb(39, 39, 42);
+ feedbackColor = Color.FromRgb(24, 24, 27);
+ }
+
+ // 应用背景和边框颜色
+ var backgroundBrush = new SolidColorBrush(backgroundColor);
+ var borderBrush = new SolidColorBrush(borderColor);
+
+ leftBorder.Background = backgroundBrush;
+ leftBorder.BorderBrush = borderBrush;
+ rightBorder.Background = backgroundBrush;
+ rightBorder.BorderBrush = borderBrush;
+
+ // 应用图标和文字颜色
+ var foregroundBrush = new SolidColorBrush(foregroundColor);
+ var feedbackBrush = new SolidColorBrush(feedbackColor);
+
+ if (isSideButton)
+ {
+ ApplySideButtonColors(foregroundBrush, feedbackBrush);
+ }
+ else
+ {
+ ApplyBottomButtonColors(foregroundBrush, feedbackBrush);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"应用按钮主题失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void ApplySideButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush)
+ {
+ // 图标颜色
+ _mainWindow.PPTLSPreviousButtonGeometry.Brush = foregroundBrush;
+ _mainWindow.PPTRSPreviousButtonGeometry.Brush = foregroundBrush;
+ _mainWindow.PPTLSNextButtonGeometry.Brush = foregroundBrush;
+ _mainWindow.PPTRSNextButtonGeometry.Brush = foregroundBrush;
+
+ // 反馈背景颜色
+ _mainWindow.PPTLSPreviousButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTRSPreviousButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTLSPageButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTRSPageButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTLSNextButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTRSNextButtonFeedbackBorder.Background = feedbackBrush;
+
+ // 文字颜色
+ TextBlock.SetForeground(_mainWindow.PPTLSPageButton, foregroundBrush);
+ TextBlock.SetForeground(_mainWindow.PPTRSPageButton, foregroundBrush);
+ }
+
+ private void ApplyBottomButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush)
+ {
+ // 图标颜色
+ _mainWindow.PPTLBPreviousButtonGeometry.Brush = foregroundBrush;
+ _mainWindow.PPTRBPreviousButtonGeometry.Brush = foregroundBrush;
+ _mainWindow.PPTLBNextButtonGeometry.Brush = foregroundBrush;
+ _mainWindow.PPTRBNextButtonGeometry.Brush = foregroundBrush;
+
+ // 反馈背景颜色
+ _mainWindow.PPTLBPreviousButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTRBPreviousButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTLBPageButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTRBPageButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTLBNextButtonFeedbackBorder.Background = feedbackBrush;
+ _mainWindow.PPTRBNextButtonFeedbackBorder.Background = feedbackBrush;
+
+ // 文字颜色
+ TextBlock.SetForeground(_mainWindow.PPTLBPageButton, foregroundBrush);
+ TextBlock.SetForeground(_mainWindow.PPTRBPageButton, foregroundBrush);
+ }
+ #endregion
+ }
+}
diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs
index c96ff4b5..fcd72650 100644
--- a/Ink Canvas/MainWindow.xaml.cs
+++ b/Ink Canvas/MainWindow.xaml.cs
@@ -336,12 +336,21 @@ namespace Ink_Canvas {
// 加载自定义背景颜色
LoadCustomBackgroundColor();
-
+
// 注册设置面板滚动事件
if (SettingsPanelScrollViewer != null)
{
SettingsPanelScrollViewer.ScrollChanged += SettingsPanelScrollViewer_ScrollChanged;
}
+
+ // 初始化PPT管理器
+ InitializePPTManagers();
+
+ // 如果启用PPT支持,开始监控
+ if (Settings.PowerPointSettings.PowerPointSupport)
+ {
+ StartPPTMonitoring();
+ }
// HasNewUpdateWindow hasNewUpdateWindow = new HasNewUpdateWindow();
if (Environment.Is64BitProcess) GroupBoxInkRecognition.Visibility = Visibility.Collapsed;
@@ -500,8 +509,11 @@ namespace Ink_Canvas {
private void Window_Closed(object sender, EventArgs e) {
SystemEvents.DisplaySettingsChanged -= SystemEventsOnDisplaySettingsChanged;
+ // 释放PPT管理器资源
+ DisposePPTManagers();
+
LogHelper.WriteLogToFile("Ink Canvas closed", LogHelper.LogType.Event);
-
+
// 检查是否有待安装的更新
CheckPendingUpdates();
}
diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
index 32206b45..1ebf3391 100644
--- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
+++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
@@ -672,7 +672,11 @@ namespace Ink_Canvas {
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) {
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
- SaveScreenShot(true, $"{pptName}/{previousSlideID}_{DateTime.Now:HH-mm-ss}");
+ {
+ var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
+ var presentationName = _pptManager?.GetPresentationName() ?? "";
+ SaveScreenShot(true, $"{presentationName}/{currentSlide}_{DateTime.Now:HH-mm-ss}");
+ }
else
SaveScreenShot(true);
}
@@ -1287,7 +1291,11 @@ namespace Ink_Canvas {
if (inkCanvas.Strokes.Count > 0 &&
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) {
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
- SaveScreenShot(true, $"{pptName}/{previousSlideID}_{DateTime.Now:HH-mm-ss}");
+ {
+ var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
+ var presentationName = _pptManager?.GetPresentationName() ?? "";
+ SaveScreenShot(true, $"{presentationName}/{currentSlide}_{DateTime.Now:HH-mm-ss}");
+ }
else SaveScreenShot(true);
}
diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs
index 834f04d8..695c5166 100644
--- a/Ink Canvas/MainWindow_cs/MW_PPT.cs
+++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs
@@ -1,17 +1,11 @@
using System;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
-using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Timers;
using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Forms;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
@@ -23,12 +17,12 @@ using Microsoft.Office.Interop.PowerPoint;
using Application = System.Windows.Application;
using File = System.IO.File;
using MessageBox = System.Windows.MessageBox;
+using MouseButtonEventArgs = System.Windows.Input.MouseButtonEventArgs;
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
-using Point = System.Drawing.Point;
-using Timer = System.Timers.Timer;
namespace Ink_Canvas {
public partial class MainWindow : Window {
+ #region Win32 API Declarations
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
@@ -38,9 +32,6 @@ namespace Ink_Canvas {
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
- [DllImport("user32.dll")]
- private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId, out uint lpdwThreadId);
-
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWindowVisible(IntPtr hWnd);
@@ -51,8 +42,6 @@ namespace Ink_Canvas {
[DllImport("user32.dll")]
private static extern bool IsZoomed(IntPtr hWnd);
-
-
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
@@ -74,649 +63,231 @@ namespace Ink_Canvas {
private const uint GW_HWNDNEXT = 2;
private const uint GW_HWNDPREV = 3;
-
-
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
+ #endregion
+ #region PPT Application Variables
public static Microsoft.Office.Interop.PowerPoint.Application pptApplication;
public static Presentation presentation;
public static Slides slides;
public static Slide slide;
public static int slidescount;
+ #endregion
- // 在类中添加字段
+ #region PPT State Management
private bool wasFloatingBarFoldedWhenEnterSlideShow;
-
- // 新增:用于控制WPS强制关闭提示只弹一次
private static bool hasShownWpsForceCloseWarning = false;
+ private bool isEnteredSlideShowEndEvent; //防止重复调用本函数导致墨迹保存失效
+ private bool isPresentationHaveBlackSpace;
+ private string pptName;
+ private bool _isPptClickingBtnTurned;
+ #endregion
- private void BtnCheckPPT_Click(object sender, RoutedEventArgs e) {
- try {
- pptApplication =
- (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application");
- //pptApplication.SlideShowWindows[1].View.Next();
- if (pptApplication != null) {
- //获得演示文稿对象
- presentation = pptApplication.ActivePresentation;
- pptApplication.SlideShowBegin += PptApplication_SlideShowBegin;
- pptApplication.SlideShowNextSlide += PptApplication_SlideShowNextSlide;
- pptApplication.SlideShowEnd += PptApplication_SlideShowEnd;
- // 获得幻灯片对象集合
- slides = presentation.Slides;
- // 获得幻灯片的数量
- slidescount = slides.Count;
- memoryStreams = new MemoryStream[slidescount + 2];
- // 获得当前选中的幻灯片
- try {
- // 在普通视图下这种方式可以获得当前选中的幻灯片对象
- // 然而在阅读模式下,这种方式会出现异常
- slide = slides[pptApplication.ActiveWindow.Selection.SlideRange.SlideNumber];
- }
- catch {
- // 在阅读模式下出现异常时,通过下面的方式来获得当前选中的幻灯片对象
- try {
- if (pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1)
- {
- slide = pptApplication.SlideShowWindows[1].View.Slide;
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"获取当前幻灯片失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
+ #region PPT Managers
+ private PPTManager _pptManager;
+ private PPTInkManager _pptInkManager;
+ private PPTUIManager _pptUIManager;
+ #endregion
- if (pptApplication == null) throw new Exception();
- //BtnCheckPPT.Visibility = Visibility.Collapsed;
- StackPanelPPTControls.Visibility = Visibility.Visible;
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"检查PPT应用程序失败: {ex}", LogHelper.LogType.Error);
- //BtnCheckPPT.Visibility = Visibility.Visible;
- StackPanelPPTControls.Visibility = Visibility.Collapsed;
- LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
- MessageBox.Show("未找到幻灯片");
- }
- }
+ #region PPT Manager Initialization
+ private void InitializePPTManagers()
+ {
+ try
+ {
+ // 初始化PPT管理器
+ _pptManager = new PPTManager();
+ _pptManager.IsSupportWPS = Settings.PowerPointSettings.IsSupportWPS;
- private void ToggleSwitchSupportWPS_Toggled(object sender, RoutedEventArgs e) {
- if (!isLoaded) return;
+ // 注册事件
+ _pptManager.PPTConnectionChanged += OnPPTConnectionChanged;
+ _pptManager.SlideShowBegin += OnPPTSlideShowBegin;
+ _pptManager.SlideShowNextSlide += OnPPTSlideShowNextSlide;
+ _pptManager.SlideShowEnd += OnPPTSlideShowEnd;
+ _pptManager.PresentationOpen += OnPPTPresentationOpen;
+ _pptManager.PresentationClose += OnPPTPresentationClose;
- Settings.PowerPointSettings.IsSupportWPS = ToggleSwitchSupportWPS.IsOn;
- SaveSettingsToFile();
- }
+ // 初始化墨迹管理器
+ _pptInkManager = new PPTInkManager();
+ _pptInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint;
+ _pptInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation;
- private static bool isWPSSupportOn => Settings.PowerPointSettings.IsSupportWPS;
+ // 初始化UI管理器
+ _pptUIManager = new PPTUIManager(this);
+ _pptUIManager.ShowPPTButton = Settings.PowerPointSettings.ShowPPTButton;
+ _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption;
+ _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption;
+ _pptUIManager.PPTBButtonsOption = Settings.PowerPointSettings.PPTBButtonsOption;
+ _pptUIManager.PPTLSButtonPosition = Settings.PowerPointSettings.PPTLSButtonPosition;
+ _pptUIManager.PPTRSButtonPosition = Settings.PowerPointSettings.PPTRSButtonPosition;
+ _pptUIManager.EnablePPTButtonPageClickable = Settings.PowerPointSettings.EnablePPTButtonPageClickable;
- public static bool IsShowingRestoreHiddenSlidesWindow;
- private static bool IsShowingAutoplaySlidesWindow;
-
- // WPP 相关变量
- private static Process wppProcess;
- private static bool hasWppProcessID;
- private static Timer wppProcessCheckTimer;
- private static DateTime wppProcessRecordTime = DateTime.MinValue; // 记录进程时间
- private static int wppProcessCheckCount; // 检查次数计数器
- private static WpsWindowInfo lastForegroundWpsWindow; // 记录上次检测到的前台WPS窗口
- private static DateTime lastWindowCheckTime = DateTime.MinValue; // 记录上次窗口检查时间
-
-
- private void TimerCheckPPT_Elapsed(object sender, ElapsedEventArgs e) {
- if (IsShowingRestoreHiddenSlidesWindow || IsShowingAutoplaySlidesWindow) return;
- try {
-
- pptApplication =
- (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application");
-
- if (pptApplication != null) {
- timerCheckPPT.Stop();
- //获得演示文稿对象
- presentation = pptApplication.ActivePresentation;
-
- // 获得幻灯片对象集合
- slides = presentation.Slides;
-
- // 获得幻灯片的数量
- slidescount = slides.Count;
- memoryStreams = new MemoryStream[slidescount + 2];
- // 获得当前选中的幻灯片
- try {
- // 在普通视图下这种方式可以获得当前选中的幻灯片对象
- // 然而在阅读模式下,这种方式会出现异常
- slide = slides[pptApplication.ActiveWindow.Selection.SlideRange.SlideNumber];
- }
- catch {
- // 在阅读模式下出现异常时,通过下面的方式来获得当前选中的幻灯片对象
- try {
- if (pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1)
- {
- slide = pptApplication.SlideShowWindows[1].View.Slide;
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"获取当前幻灯片失败: {ex}", LogHelper.LogType.Error);
- }
- }
-
- pptApplication.PresentationOpen += PptApplication_PresentationOpen;
- pptApplication.PresentationClose += PptApplication_PresentationClose;
- pptApplication.SlideShowBegin += PptApplication_SlideShowBegin;
- pptApplication.SlideShowNextSlide += PptApplication_SlideShowNextSlide;
- pptApplication.SlideShowEnd += PptApplication_SlideShowEnd;
- }
-
- if (pptApplication == null) return;
- //BtnCheckPPT.Visibility = Visibility.Collapsed;
-
- // 此处是已经开启了
- PptApplication_PresentationOpen(null);
-
- //如果检测到已经开始放映,则立即进入画板模式
- if (pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1)
- {
- try {
- PptApplication_SlideShowBegin(pptApplication.SlideShowWindows[1]);
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"启动幻灯片放映失败: {ex}", LogHelper.LogType.Error);
- }
- }
+ LogHelper.WriteLogToFile("PPT管理器初始化完成", LogHelper.LogType.Event);
}
catch (Exception ex)
{
- // 忽略常见的COM对象失效错误
- if (ex is COMException comEx)
- {
- uint hr = (uint)comEx.HResult;
- // 0x800401E3: 操作无法使用
- // 0x80004005: 未指定错误(常见于PPT已关闭)
- // 0x800706B5: RPC服务器不可用
- // 0x80048240: 没有活动的演示文稿
- // 0x800706BE: 远程过程调用失败
- if (hr == 0x800401E3 || hr == 0x80004005 || hr == 0x800706B5 || hr == 0x80048240 || hr == 0x800706BE)
- {
- Application.Current.Dispatcher.Invoke(() => { BtnPPTSlideShow.Visibility = Visibility.Collapsed; });
- timerCheckPPT.Start();
- return;
- }
- }
- LogHelper.WriteLogToFile($"检查PPT状态失败: {ex}", LogHelper.LogType.Error);
- Application.Current.Dispatcher.Invoke(() => { BtnPPTSlideShow.Visibility = Visibility.Collapsed; });
- timerCheckPPT.Start();
+ LogHelper.WriteLogToFile($"PPT管理器初始化失败: {ex}", LogHelper.LogType.Error);
}
}
- private void PptApplication_PresentationOpen(Presentation Pres) {
- // 新增逻辑:如果开启"重新进入放映时回到首页",则直接跳转第一页
- if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter)
+ private void StartPPTMonitoring()
+ {
+ if (Settings.PowerPointSettings.PowerPointSupport)
{
- Application.Current.Dispatcher.BeginInvoke(new Action(() => {
- try
- {
- if (presentation == null)
- {
- LogHelper.WriteLogToFile("演示文稿为空,无法跳转到首页", LogHelper.LogType.Warning);
- return;
- }
- if (pptApplication != null && pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1)
- presentation.SlideShowWindow.View.GotoSlide(1);
- else if (presentation.Windows != null && presentation.Windows.Count >= 1)
- presentation.Windows[1].View.GotoSlide(1);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"跳转到首页失败: {ex}", LogHelper.LogType.Error);
- }
- }), DispatcherPriority.Normal);
- }
- else if (Settings.PowerPointSettings.IsNotifyPreviousPage)
- Application.Current.Dispatcher.BeginInvoke(new Action(() => {
- try {
- // 添加安全检查
- if (presentation == null)
- {
- LogHelper.WriteLogToFile("演示文稿为空,无法跳转到上次播放页", LogHelper.LogType.Warning);
- return;
- }
-
- // 使用更精确的文件标识符:文件名_页数_文件路径哈希值
- string presentationPath = presentation.FullName;
- string fileHash = GetFileHash(presentationPath);
- string folderName = presentation.Name + "_" + presentation.Slides.Count + "_" + fileHash;
- var folderPath = Settings.Automation.AutoSavedStrokesLocation +
- @"\Auto Saved - Presentations\" + folderName;
- try {
- if (!File.Exists(folderPath + "/Position")) return;
- if (!int.TryParse(File.ReadAllText(folderPath + "/Position"), out var page)) return;
- if (page <= 0) return;
- new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () => {
- try {
- if (pptApplication != null && pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1)
- // 如果已经播放了的话, 跳转
- presentation.SlideShowWindow.View.GotoSlide(page);
- else if (presentation.Windows != null && presentation.Windows.Count >= 1)
- presentation.Windows[1].View.GotoSlide(page);
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"跳转到指定页面失败: {ex}", LogHelper.LogType.Error);
- }
- }).ShowDialog();
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"读取上次播放位置失败: {ex}", LogHelper.LogType.Error);
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"处理上次播放页跳转失败: {ex}", LogHelper.LogType.Error);
- }
- }), DispatcherPriority.Normal);
-
-
- //检查是否有隐藏幻灯片
- if (Settings.PowerPointSettings.IsNotifyHiddenPage) {
- try {
- var isHaveHiddenSlide = false;
- if (slides != null)
- {
- foreach (Slide slide in slides)
- if (slide.SlideShowTransition.Hidden == MsoTriState.msoTrue) {
- isHaveHiddenSlide = true;
- break;
- }
- }
-
- Application.Current.Dispatcher.BeginInvoke(new Action(() => {
- if (isHaveHiddenSlide && !IsShowingRestoreHiddenSlidesWindow) {
- IsShowingRestoreHiddenSlidesWindow = true;
- new YesOrNoNotificationWindow("检测到此演示文档中包含隐藏的幻灯片,是否取消隐藏?",
- () => {
- try {
- if (slides != null)
- {
- foreach (Slide slide in slides)
- if (slide.SlideShowTransition.Hidden ==
- MsoTriState.msoTrue)
- slide.SlideShowTransition.Hidden =
- MsoTriState.msoFalse;
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"取消隐藏幻灯片失败: {ex}", LogHelper.LogType.Error);
- }
- finally {
- IsShowingRestoreHiddenSlidesWindow = false;
- }
- }, () => { IsShowingRestoreHiddenSlidesWindow = false; },
- () => { IsShowingRestoreHiddenSlidesWindow = false; }).ShowDialog();
- }
-
- BtnPPTSlideShow.Visibility = Visibility.Visible;
- }), DispatcherPriority.Normal);
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"检查隐藏幻灯片失败: {ex}", LogHelper.LogType.Error);
- }
- }
-
- //检测是否有自动播放
- if (Settings.PowerPointSettings.IsNotifyAutoPlayPresentation
- // && presentation.SlideShowSettings.AdvanceMode == PpSlideShowAdvanceMode.ppSlideShowUseSlideTimings
- && BtnPPTSlideShowEnd.Visibility != Visibility.Visible) {
- try {
- bool hasSlideTimings = false;
- if (presentation != null && presentation.Slides != null)
- {
- foreach (Slide slide in presentation.Slides) {
- if (slide.SlideShowTransition.AdvanceOnTime == MsoTriState.msoTrue &&
- slide.SlideShowTransition.AdvanceTime > 0) {
- hasSlideTimings = true;
- break;
- }
- }
- }
-
- if (hasSlideTimings) {
- Application.Current.Dispatcher.BeginInvoke((Action)(() => {
- if (hasSlideTimings && !IsShowingAutoplaySlidesWindow) {
- IsShowingAutoplaySlidesWindow = true;
- new YesOrNoNotificationWindow("检测到此演示文档中自动播放或排练计时已经启用,可能导致幻灯片自动翻页,是否取消?",
- () => {
- try {
- if (presentation != null)
- {
- presentation.SlideShowSettings.AdvanceMode =
- PpSlideShowAdvanceMode.ppSlideShowManualAdvance;
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"设置手动播放模式失败: {ex}", LogHelper.LogType.Error);
- }
- finally {
- IsShowingAutoplaySlidesWindow = false;
- }
- }, () => { IsShowingAutoplaySlidesWindow = false; },
- () => { IsShowingAutoplaySlidesWindow = false; }).ShowDialog();
- }
- }));
- try {
- if (presentation != null)
- {
- presentation.SlideShowSettings.AdvanceMode = PpSlideShowAdvanceMode.ppSlideShowManualAdvance;
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"设置演示文稿播放模式失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"检查自动播放设置失败: {ex}", LogHelper.LogType.Error);
- }
+ _pptManager?.StartMonitoring();
+ LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Event);
}
}
- private void PptApplication_PresentationClose(Presentation Pres) {
- try {
- pptApplication.PresentationOpen -= PptApplication_PresentationOpen;
- pptApplication.PresentationClose -= PptApplication_PresentationClose;
- pptApplication.SlideShowBegin -= PptApplication_SlideShowBegin;
- pptApplication.SlideShowNextSlide -= PptApplication_SlideShowNextSlide;
- pptApplication.SlideShowEnd -= PptApplication_SlideShowEnd;
-
-
- timerCheckPPT.Start();
-
- Application.Current.Dispatcher.Invoke(() => {
- BtnPPTSlideShow.Visibility = Visibility.Collapsed;
- BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
+ private void StopPPTMonitoring()
+ {
+ _pptManager?.StopMonitoring();
+ LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Event);
+ }
+
+ private void DisposePPTManagers()
+ {
+ try
+ {
+ _pptManager?.Dispose();
+ _pptInkManager?.Dispose();
+ _pptManager = null;
+ _pptInkManager = null;
+ _pptUIManager = null;
+ LogHelper.WriteLogToFile("PPT管理器已释放", LogHelper.LogType.Event);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"释放PPT管理器失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ #endregion
+
+ #region New PPT Event Handlers
+ private void OnPPTConnectionChanged(bool isConnected)
+ {
+ try
+ {
+ Application.Current.Dispatcher.InvokeAsync(() =>
+ {
+ _pptUIManager?.UpdateConnectionStatus(isConnected);
+
+ if (isConnected)
+ {
+ LogHelper.WriteLogToFile("PPT连接已建立", LogHelper.LogType.Event);
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("PPT连接已断开", LogHelper.LogType.Event);
+ // 清理墨迹管理器
+ _pptInkManager?.ClearAllStrokes();
+ }
});
}
- catch (Exception ex) {
- LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error);
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理PPT连接状态变化失败: {ex}", LogHelper.LogType.Error);
}
}
- private bool isPresentationHaveBlackSpace;
- private string pptName;
+ private void OnPPTPresentationOpen(Presentation pres)
+ {
+ try
+ {
+ Application.Current.Dispatcher.InvokeAsync(() =>
+ {
+ // 初始化墨迹管理器
+ _pptInkManager?.InitializePresentation(pres);
- private void UpdatePPTBtnStyleSettingsStatus() {
- try {
- var sopt = Settings.PowerPointSettings.PPTSButtonsOption.ToString();
- char[] soptc = sopt.ToCharArray();
- if (soptc[0] == '2')
- {
- PPTLSPageButton.Visibility = Visibility.Visible;
- PPTRSPageButton.Visibility = Visibility.Visible;
- }
- else
- {
- PPTLSPageButton.Visibility = Visibility.Collapsed;
- PPTRSPageButton.Visibility = Visibility.Collapsed;
- }
- if (soptc[2] == '2')
- {
- // 这里先堆一点屎山,没空用Resources了
- PPTBtnLSBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTBtnRSBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTBtnLSBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(82, 82, 91));
- PPTBtnRSBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(82, 82, 91));
- PPTLSPreviousButtonGeometry.Brush = new SolidColorBrush(Colors.White);
- PPTRSPreviousButtonGeometry.Brush = new SolidColorBrush(Colors.White);
- PPTLSNextButtonGeometry.Brush = new SolidColorBrush(Colors.White);
- PPTRSNextButtonGeometry.Brush = new SolidColorBrush(Colors.White);
- PPTLSPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTRSPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTLSPageButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTRSPageButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTLSNextButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTRSNextButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- TextBlock.SetForeground(PPTLSPageButton, new SolidColorBrush(Colors.White));
- TextBlock.SetForeground(PPTRSPageButton, new SolidColorBrush(Colors.White));
- }
- else
- {
- PPTBtnLSBorder.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
- PPTBtnRSBorder.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
- PPTBtnLSBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
- PPTBtnRSBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
- PPTLSPreviousButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTRSPreviousButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTLSNextButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTRSNextButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTLSPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTRSPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTLSPageButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTRSPageButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTLSNextButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTRSNextButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- TextBlock.SetForeground(PPTLSPageButton, new SolidColorBrush(Color.FromRgb(24, 24, 27)));
- TextBlock.SetForeground(PPTRSPageButton, new SolidColorBrush(Color.FromRgb(24, 24, 27)));
- }
- if (soptc[1] == '2')
- {
- PPTBtnLSBorder.Opacity = 0.5;
- PPTBtnRSBorder.Opacity = 0.5;
- }
- else
- {
- PPTBtnLSBorder.Opacity = 1;
- PPTBtnRSBorder.Opacity = 1;
- }
+ // 处理跳转到首页或上次播放页的逻辑
+ HandlePresentationOpenNavigation(pres);
- var bopt = Settings.PowerPointSettings.PPTBButtonsOption.ToString();
- char[] boptc = bopt.ToCharArray();
- if (boptc[0] == '2')
- {
- PPTLBPageButton.Visibility = Visibility.Visible;
- PPTRBPageButton.Visibility = Visibility.Visible;
- }
- else
- {
- PPTLBPageButton.Visibility = Visibility.Collapsed;
- PPTRBPageButton.Visibility = Visibility.Collapsed;
- }
- if (boptc[2] == '2')
- {
- // 这里先堆一点屎山,没空用Resources了
- PPTBtnLBBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTBtnRBBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTBtnLBBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(82, 82, 91));
- PPTBtnRBBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(82, 82, 91));
- PPTLBPreviousButtonGeometry.Brush = new SolidColorBrush(Colors.White);
- PPTRBPreviousButtonGeometry.Brush = new SolidColorBrush(Colors.White);
- PPTLBNextButtonGeometry.Brush = new SolidColorBrush(Colors.White);
- PPTRBNextButtonGeometry.Brush = new SolidColorBrush(Colors.White);
- PPTLBPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTRBPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTLBPageButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTRBPageButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTLBNextButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- PPTRBNextButtonFeedbackBorder.Background = new SolidColorBrush(Colors.White);
- TextBlock.SetForeground(PPTLBPageButton, new SolidColorBrush(Colors.White));
- TextBlock.SetForeground(PPTRBPageButton, new SolidColorBrush(Colors.White));
- }
- else
- {
- PPTBtnLBBorder.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
- PPTBtnRBBorder.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
- PPTBtnLBBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
- PPTBtnRBBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
- PPTLBPreviousButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTRBPreviousButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTLBNextButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTRBNextButtonGeometry.Brush = new SolidColorBrush(Color.FromRgb(39, 39, 42));
- PPTLBPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTRBPreviousButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTLBPageButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTRBPageButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTLBNextButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- PPTRBNextButtonFeedbackBorder.Background = new SolidColorBrush(Color.FromRgb(24, 24, 27));
- TextBlock.SetForeground(PPTLBPageButton, new SolidColorBrush(Color.FromRgb(24, 24, 27)));
- TextBlock.SetForeground(PPTRBPageButton, new SolidColorBrush(Color.FromRgb(24, 24, 27)));
- }
- if (boptc[1] == '2')
- {
- PPTBtnLBBorder.Opacity = 0.5;
- PPTBtnRBBorder.Opacity = 0.5;
- }
- else
- {
- PPTBtnLBBorder.Opacity = 1;
- PPTBtnRBBorder.Opacity = 1;
- }
+ // 检查隐藏幻灯片
+ if (Settings.PowerPointSettings.IsNotifyHiddenPage)
+ {
+ CheckAndNotifyHiddenSlides(pres);
+ }
+
+ // 检查自动播放设置
+ if (Settings.PowerPointSettings.IsNotifyAutoPlayPresentation)
+ {
+ CheckAndNotifyAutoPlaySettings(pres);
+ }
+
+ _pptUIManager?.UpdateConnectionStatus(true);
+ });
}
- catch (Exception ex) {
- LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error);
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error);
}
}
- private void UpdatePPTBtnDisplaySettingsStatus() {
- try {
- // 检查是否应该显示PPT按钮
- bool shouldShowButtons = Settings.PowerPointSettings.ShowPPTButton &&
- (BtnPPTSlideShowEnd.Visibility == Visibility.Visible ||
- (pptApplication != null && pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count > 0));
-
- if (!shouldShowButtons)
+ private void OnPPTPresentationClose(Presentation pres)
+ {
+ try
+ {
+ Application.Current.Dispatcher.InvokeAsync(() =>
{
- LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
- return;
- }
+ // 保存所有墨迹
+ _pptInkManager?.SaveAllStrokesToFile(pres);
- var lsp = Settings.PowerPointSettings.PPTLSButtonPosition;
- LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, lsp*2);
- var rsp = Settings.PowerPointSettings.PPTRSButtonPosition;
- RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, rsp*2);
+ // 清理墨迹管理器
+ _pptInkManager?.ClearAllStrokes();
- var dopt = Settings.PowerPointSettings.PPTButtonsDisplayOption.ToString();
- char[] doptc = dopt.ToCharArray();
- if (doptc[0] == '2') AnimationsHelper.ShowWithFadeIn(LeftBottomPanelForPPTNavigation);
- else LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- if (doptc[1] == '2') AnimationsHelper.ShowWithFadeIn(RightBottomPanelForPPTNavigation);
- else RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- if (doptc[2] == '2') AnimationsHelper.ShowWithFadeIn(LeftSidePanelForPPTNavigation);
- else LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
- if (doptc[3] == '2') AnimationsHelper.ShowWithFadeIn(RightSidePanelForPPTNavigation);
- else RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ _pptUIManager?.UpdateConnectionStatus(false);
+ });
}
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"更新PPT按钮显示状态失败: {ex}", LogHelper.LogType.Error);
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理演示文稿关闭事件失败: {ex}", LogHelper.LogType.Error);
}
}
- private async void PptApplication_SlideShowBegin(SlideShowWindow Wn) {
- try {
+ private async void OnPPTSlideShowBegin(SlideShowWindow wn)
+ {
+ try
+ {
// 记录进入放映时浮动栏收纳状态
wasFloatingBarFoldedWhenEnterSlideShow = isFloatingBarFolded;
-
+
if (Settings.Automation.IsAutoFoldInPPTSlideShow && !isFloatingBarFolded)
FoldFloatingBar_MouseUp(new object(), null);
- else if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
+ else if (isFloatingBarFolded)
+ await UnFoldFloatingBar(new object());
isStopInkReplay = true;
- LogHelper.WriteLogToFile("PowerPoint Application Slide Show Begin", LogHelper.LogType.Event);
-
- await Application.Current.Dispatcher.InvokeAsync(() => {
- // 新增:如果设置开启,进入放映时强制跳转到第一页
+ await Application.Current.Dispatcher.InvokeAsync(() =>
+ {
+ // 处理跳转到首页
if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter)
{
- try
- {
- if (Wn != null && Wn.Presentation != null && Wn.View != null)
- {
- Wn.View.GotoSlide(1);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"放映开始时跳转首页失败: {ex}", LogHelper.LogType.Error);
- }
+ _pptManager?.TryNavigateToSlide(1);
}
- //调整颜色
- var screenRatio = SystemParameters.PrimaryScreenWidth / SystemParameters.PrimaryScreenHeight;
- if (Math.Abs(screenRatio - 16.0 / 9) <= -0.01) {
- if (Wn.Presentation.PageSetup.SlideWidth / Wn.Presentation.PageSetup.SlideHeight < 1.65) {
- isPresentationHaveBlackSpace = true;
+ // 更新UI状态
+ var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
+ var totalSlides = _pptManager?.SlidesCount ?? 0;
+ _pptUIManager?.UpdateSlideShowStatus(true, currentSlide, totalSlides);
- if (BtnSwitchTheme.Content.ToString() == "深色") {
- //Light
- BtnExit.Foreground = Brushes.White;
- ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
- }
- //Dark
- }
- } else if (screenRatio == -256 / 135) { }
+ // 设置浮动栏透明度和边距
+ _pptUIManager?.SetFloatingBarOpacity(Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue);
+ _pptUIManager?.SetMainPanelMargin(new Thickness(10, 10, 10, 10));
- lastDesktopInkColor = 1;
-
- slidescount = Wn.Presentation.Slides.Count;
- previousSlideID = 0;
- memoryStreams = new MemoryStream[slidescount + 2];
-
- pptName = Wn.Presentation.Name;
- LogHelper.NewLog("Name: " + Wn.Presentation.Name);
- LogHelper.NewLog("Slides Count: " + slidescount);
-
- //检查是否有已有墨迹,并加载
- if (Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint)
- {
- // 使用更精确的文件标识符:文件名_页数_文件路径哈希值
- string presentationPath = Wn.Presentation.FullName;
- string fileHash = GetFileHash(presentationPath);
- string folderName = Wn.Presentation.Name + "_" + Wn.Presentation.Slides.Count + "_" + fileHash;
-
- if (Directory.Exists(Settings.Automation.AutoSavedStrokesLocation +
- @"\Auto Saved - Presentations\" + folderName)) {
- LogHelper.WriteLogToFile("Found saved strokes", LogHelper.LogType.Trace);
- var files = new DirectoryInfo(Settings.Automation.AutoSavedStrokesLocation +
- @"\Auto Saved - Presentations\" + folderName).GetFiles();
- var count = 0;
- foreach (var file in files)
- if (file.Name != "Position") {
- var i = -1;
- try {
- i = int.Parse(Path.GetFileNameWithoutExtension(file.Name));
- memoryStreams[i] = new MemoryStream(File.ReadAllBytes(file.FullName));
- memoryStreams[i].Position = 0;
- count++;
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile(
- $"Failed to load strokes on Slide {i}\n{ex}",
- LogHelper.LogType.Error);
- }
- }
-
- LogHelper.WriteLogToFile($"Loaded {count.ToString()} saved strokes");
- }
- }
-
- StackPanelPPTControls.Visibility = Visibility.Visible;
- UpdatePPTBtnDisplaySettingsStatus();
- UpdatePPTBtnStyleSettingsStatus();
-
- BtnPPTSlideShow.Visibility = Visibility.Collapsed;
- BtnPPTSlideShowEnd.Visibility = Visibility.Visible;
- ViewBoxStackPanelMain.Margin = new Thickness(10, 10, 10, 10);
- ViewboxFloatingBar.Opacity = Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue;
+ // 显示侧边栏退出按钮
+ _pptUIManager?.UpdateSidebarExitButtons(true);
+ // 处理画板显示
if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow &&
!Settings.Automation.IsAutoFoldInPPTSlideShow &&
- GridTransparencyFakeBackground.Background == Brushes.Transparent && !isFloatingBarFolded) {
+ GridTransparencyFakeBackground.Background == Brushes.Transparent && !isFloatingBarFolded)
+ {
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
}
if (currentMode != 0)
{
- ImageBlackboard_MouseUp(null,null);
+ ImageBlackboard_MouseUp(null, null);
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
}
@@ -727,362 +298,481 @@ namespace Ink_Canvas {
BtnColorRed_Click(null, null);
isEnteredSlideShowEndEvent = false;
- PPTBtnPageNow.Text = $"{Wn.View.CurrentShowPosition}";
- PPTBtnPageTotal.Text = $"/ {Wn.Presentation.Slides.Count}";
- LogHelper.NewLog("PowerPoint Slide Show Loading process complete");
- // 新增:主动加载当前页墨迹,解决首次放映时当前页墨迹不显示的问题
- try
- {
- var currentPage = Wn.View.CurrentShowPosition;
- if (memoryStreams != null && currentPage < memoryStreams.Length && memoryStreams[currentPage] != null && memoryStreams[currentPage].Length > 0)
- {
- memoryStreams[currentPage].Position = 0;
- inkCanvas.Strokes.Clear();
- inkCanvas.Strokes.Add(new StrokeCollection(memoryStreams[currentPage]));
- }
- else
- {
- inkCanvas.Strokes.Clear();
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"加载当前页墨迹失败: {ex}", LogHelper.LogType.Error);
- }
+ // 加载当前页墨迹
+ LoadCurrentSlideInk(currentSlide);
+ });
- if (!isFloatingBarFolded) {
- new Thread(() => {
- Thread.Sleep(100);
- Application.Current.Dispatcher.Invoke(() => {
- ViewboxFloatingBarMarginAnimation(60);
- });
- }).Start();
- }
- });
- await Application.Current.Dispatcher.InvokeAsync(() => {
- if (BtnExitPptFromSidebarLeft != null)
- BtnExitPptFromSidebarLeft.Visibility = Visibility.Visible;
- if (BtnExitPptFromSidebarRight != null)
- BtnExitPptFromSidebarRight.Visibility = Visibility.Visible;
- });
+ if (!isFloatingBarFolded)
+ {
+ new Thread(() =>
+ {
+ Thread.Sleep(100);
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ ViewboxFloatingBarMarginAnimation(60);
+ });
+ }).Start();
+ }
}
- catch (Exception ex) {
- LogHelper.WriteLogToFile("PowerPoint Application Slide Show Begin Error: " + ex, LogHelper.LogType.Error);
- LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error);
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片放映开始事件失败: {ex}", LogHelper.LogType.Error);
}
}
- private bool isEnteredSlideShowEndEvent; //防止重复调用本函数导致墨迹保存失效
+ private void OnPPTSlideShowNextSlide(SlideShowWindow wn)
+ {
+ try
+ {
+ Application.Current.Dispatcher.InvokeAsync(() =>
+ {
+ var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
+ var totalSlides = _pptManager?.SlidesCount ?? 0;
- private async void PptApplication_SlideShowEnd(Presentation Pres) {
- try {
- // 新增逻辑:如果设置开启且进入PPT放映时浮动栏是收纳的,退出时也自动收纳;否则自动展开
- if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && wasFloatingBarFoldedWhenEnterSlideShow) {
+ // 保存上一页墨迹并加载当前页墨迹
+ SwitchSlideInk(currentSlide);
+
+ // 更新UI
+ _pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides);
+
+ LogHelper.WriteLogToFile($"幻灯片切换到第{currentSlide}页", LogHelper.LogType.Trace);
+ });
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片切换事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private async void OnPPTSlideShowEnd(Presentation pres)
+ {
+ try
+ {
+ // 处理浮动栏状态
+ if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && wasFloatingBarFoldedWhenEnterSlideShow)
+ {
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null);
- } else {
+ }
+ else
+ {
if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
}
- // 记录 WPP 进程 ID,用于后续检测未关闭的进程
- if (pptApplication != null)
+ if (isEnteredSlideShowEndEvent) return;
+ isEnteredSlideShowEndEvent = true;
+
+ // 保存所有墨迹
+ _pptInkManager?.SaveAllStrokesToFile(pres);
+
+ await Application.Current.Dispatcher.InvokeAsync(() =>
{
try
{
- // 尝试多种方式获取WPS进程
- Process wpsProcess = null;
-
- // 方法1:通过应用程序路径检测
- if (pptApplication.Path.Contains("Kingsoft\\WPS Office\\") ||
- pptApplication.Path.Contains("WPS Office\\"))
- {
- uint processId;
- GetWindowThreadProcessId((IntPtr)pptApplication.HWND, out processId);
- wpsProcess = Process.GetProcessById((int)processId);
- //LogHelper.WriteLogToFile($"通过路径检测到WPS进程: {processId}", LogHelper.LogType.Trace);
- }
-
- // 方法2:通过前台窗口检测
- if (wpsProcess == null)
- {
- var foregroundWpsWindow = GetForegroundWpsWindow();
- if (foregroundWpsWindow != null)
- {
- wpsProcess = Process.GetProcessById((int)foregroundWpsWindow.ProcessId);
- //LogHelper.WriteLogToFile($"通过前台窗口检测到WPS进程: {foregroundWpsWindow.ProcessId}", LogHelper.LogType.Trace);
- }
- }
-
- // 方法3:通过进程名检测
- if (wpsProcess == null)
- {
- var wpsProcesses = GetWpsProcesses();
- if (wpsProcesses.Count > 0)
- {
- wpsProcess = wpsProcesses.First();
- //LogHelper.WriteLogToFile($"通过进程名检测到WPS进程: {wpsProcess.Id}", LogHelper.LogType.Trace);
- }
- }
-
- if (wpsProcess != null)
- {
- wppProcess = wpsProcess;
- hasWppProcessID = true;
- wppProcessRecordTime = DateTime.Now;
- wppProcessCheckCount = 0;
- LogHelper.WriteLogToFile($"成功记录 WPP 进程 ID: {wpsProcess.Id}", LogHelper.LogType.Trace);
- }
- else
- {
- LogHelper.WriteLogToFile("未能检测到WPS进程", LogHelper.LogType.Warning);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"记录WPS进程失败: {ex}", LogHelper.LogType.Error);
- }
- }
-
- LogHelper.WriteLogToFile("PowerPoint Slide Show End", LogHelper.LogType.Event);
- if (isEnteredSlideShowEndEvent) {
- LogHelper.WriteLogToFile("Detected previous entrance, returning");
- return;
- }
-
- isEnteredSlideShowEndEvent = true;
- if (Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint) {
- // 使用更精确的文件标识符:文件名_页数_文件路径哈希值
- string presentationPath = Pres.FullName;
- string fileHash = GetFileHash(presentationPath);
- string folderName = Pres.Name + "_" + Pres.Slides.Count + "_" + fileHash;
- var folderPath = Settings.Automation.AutoSavedStrokesLocation + @"\Auto Saved - Presentations\" + folderName;
- if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
- try {
- File.WriteAllText(folderPath + "/Position", previousSlideID.ToString());
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error);
- }
-
- for (var i = 1; i <= Pres.Slides.Count; i++)
- if (memoryStreams[i] != null)
- try {
- if (memoryStreams[i].Length > 8) {
- var srcBuf = new byte[memoryStreams[i].Length];
- memoryStreams[i].Position = 0;
- var byteLength = memoryStreams[i].Read(srcBuf, 0, srcBuf.Length);
- // 使用Path.Combine构建文件路径
- File.WriteAllBytes(folderPath + @"\" + i.ToString("0000") + ".icstk", srcBuf);
- LogHelper.WriteLogToFile(string.Format(
- "Saved strokes for Slide {0}, size={1}, byteLength={2}", i.ToString(),
- memoryStreams[i].Length, byteLength));
- } else {
- if (File.Exists(folderPath + @"\" + i.ToString("0000") + ".icstk"))
- File.Delete(folderPath + @"\" + i.ToString("0000") + ".icstk");
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile(
- $"Failed to save strokes for Slide {i}\n{ex}",
- LogHelper.LogType.Error);
- if (File.Exists(folderPath + @"\" + i.ToString("0000") + ".icstk"))
- File.Delete(folderPath + @"\" + i.ToString("0000") + ".icstk");
- }
- }
-
- await Application.Current.Dispatcher.InvokeAsync(() => {
- try {
isPresentationHaveBlackSpace = false;
- if (BtnSwitchTheme.Content.ToString() == "深色") {
- //Light
+ // 恢复主题
+ if (BtnSwitchTheme.Content.ToString() == "深色")
+ {
BtnExit.Foreground = Brushes.Black;
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
}
- //Dark
- BtnPPTSlideShow.Visibility = Visibility.Visible;
- BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
- StackPanelPPTControls.Visibility = Visibility.Collapsed;
- LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ // 更新UI状态
+ _pptUIManager?.UpdateSlideShowStatus(false);
+ _pptUIManager?.UpdateSidebarExitButtons(false);
+ _pptUIManager?.SetMainPanelMargin(new Thickness(10, 10, 10, 55));
+ _pptUIManager?.SetFloatingBarOpacity(Settings.Appearance.ViewboxFloatingBarOpacityValue);
- ViewBoxStackPanelMain.Margin = new Thickness(10, 10, 10, 55);
-
- if (currentMode != 0) {
+ if (currentMode != 0)
+ {
CloseWhiteboardImmediately();
currentMode = 0;
}
ClearStrokes(true);
-
- // 清空备份历史记录,防止退出白板时恢复已结束PPT的墨迹
TimeMachineHistories[0] = null;
if (GridTransparencyFakeBackground.Background != Brushes.Transparent)
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
-
- ViewboxFloatingBar.Opacity = Settings.Appearance.ViewboxFloatingBarOpacityValue;
}
- catch (Exception ex) {
- LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error);
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片放映结束UI更新失败: {ex}", LogHelper.LogType.Error);
}
});
await Task.Delay(150);
-
- await Application.Current.Dispatcher.InvokeAsync(() => {
+ await Application.Current.Dispatcher.InvokeAsync(() =>
+ {
ViewboxFloatingBarMarginAnimation(100, true);
});
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理幻灯片放映结束事件失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+ #endregion
- // 启动 WPP 进程检测定时器
- if (hasWppProcessID && wppProcess != null)
+ #region Helper Methods
+ private void HandlePresentationOpenNavigation(Presentation pres)
+ {
+ try
+ {
+ if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter)
{
- StartWppProcessCheckTimer();
+ _pptManager?.TryNavigateToSlide(1);
+ }
+ else if (Settings.PowerPointSettings.IsNotifyPreviousPage)
+ {
+ ShowPreviousPageNotification(pres);
}
}
- catch (Exception ex) {
- LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error);
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理演示文稿导航失败: {ex}", LogHelper.LogType.Error);
}
}
- private int previousSlideID;
- private MemoryStream[] memoryStreams = new MemoryStream[50];
+ private void ShowPreviousPageNotification(Presentation pres)
+ {
+ try
+ {
+ if (pres == null) return;
- private DateTime inkLockUntil = DateTime.MinValue;
- private int lockedSlideIndex;
- private const int InkLockMilliseconds = 500;
+ var presentationPath = pres.FullName;
+ var fileHash = GetFileHash(presentationPath);
+ var folderName = pres.Name + "_" + pres.Slides.Count + "_" + fileHash;
+ var folderPath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "Auto Saved - Presentations", folderName);
+ var positionFile = Path.Combine(folderPath, "Position");
- private void PptApplication_SlideShowNextSlide(SlideShowWindow Wn) {
- try {
- // 添加安全检查
- if (Wn == null || Wn.View == null)
+ if (!File.Exists(positionFile)) return;
+
+ if (int.TryParse(File.ReadAllText(positionFile), out var page) && page > 0)
{
- LogHelper.WriteLogToFile("幻灯片放映窗口或视图为空", LogHelper.LogType.Warning);
- return;
+ new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () =>
+ {
+ _pptManager?.TryNavigateToSlide(page);
+ }).ShowDialog();
}
- LogHelper.WriteLogToFile($"PowerPoint Next Slide (Slide {Wn.View.CurrentShowPosition})",
- LogHelper.LogType.Event);
- // 先保存旧页墨迹
- Application.Current.Dispatcher.Invoke(() => {
- var ms = new MemoryStream();
- inkCanvas.Strokes.Save(ms);
- ms.Position = 0;
- if (previousSlideID > 0 && previousSlideID < memoryStreams.Length)
- memoryStreams[previousSlideID] = ms;
- ClearStrokes(true);
- timeMachine.ClearStrokeHistory();
- int newPage = Wn.View.CurrentShowPosition;
- if (newPage > 0 && newPage < memoryStreams.Length &&
- memoryStreams[newPage] != null && memoryStreams[newPage].Length > 0) {
- memoryStreams[newPage].Position = 0;
- inkCanvas.Strokes.Add(new StrokeCollection(memoryStreams[newPage]));
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"显示上次播放页通知失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void CheckAndNotifyHiddenSlides(Presentation pres)
+ {
+ try
+ {
+ bool hasHiddenSlides = false;
+ if (pres?.Slides != null)
+ {
+ foreach (Slide slide in pres.Slides)
+ {
+ if (slide.SlideShowTransition.Hidden == MsoTriState.msoTrue)
+ {
+ hasHiddenSlides = true;
+ break;
+ }
}
- PPTBtnPageNow.Text = $"{Wn.View.CurrentShowPosition}";
- PPTBtnPageTotal.Text = $"/ {Wn.Presentation.Slides.Count}";
- previousSlideID = newPage; // 最后赋值,确保索引同步
- LockInkForCurrentSlide(newPage); // 翻页后加锁
+ }
+
+ if (hasHiddenSlides && !IsShowingRestoreHiddenSlidesWindow)
+ {
+ IsShowingRestoreHiddenSlidesWindow = true;
+ new YesOrNoNotificationWindow("检测到此演示文档中包含隐藏的幻灯片,是否取消隐藏?",
+ () =>
+ {
+ try
+ {
+ if (pres?.Slides != null)
+ {
+ foreach (Slide slide in pres.Slides)
+ {
+ if (slide.SlideShowTransition.Hidden == MsoTriState.msoTrue)
+ slide.SlideShowTransition.Hidden = MsoTriState.msoFalse;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"取消隐藏幻灯片失败: {ex}", LogHelper.LogType.Error);
+ }
+ finally
+ {
+ IsShowingRestoreHiddenSlidesWindow = false;
+ }
+ },
+ () => { IsShowingRestoreHiddenSlidesWindow = false; },
+ () => { IsShowingRestoreHiddenSlidesWindow = false; }).ShowDialog();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查隐藏幻灯片失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void CheckAndNotifyAutoPlaySettings(Presentation pres)
+ {
+ try
+ {
+ if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) return;
+
+ bool hasSlideTimings = false;
+ if (pres?.Slides != null)
+ {
+ foreach (Slide slide in pres.Slides)
+ {
+ if (slide.SlideShowTransition.AdvanceOnTime == MsoTriState.msoTrue &&
+ slide.SlideShowTransition.AdvanceTime > 0)
+ {
+ hasSlideTimings = true;
+ break;
+ }
+ }
+ }
+
+ if (hasSlideTimings && !IsShowingAutoplaySlidesWindow)
+ {
+ IsShowingAutoplaySlidesWindow = true;
+ new YesOrNoNotificationWindow("检测到此演示文档中自动播放或排练计时已经启用,可能导致幻灯片自动翻页,是否取消?",
+ () =>
+ {
+ try
+ {
+ if (pres != null)
+ {
+ pres.SlideShowSettings.AdvanceMode = PpSlideShowAdvanceMode.ppSlideShowManualAdvance;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"设置手动播放模式失败: {ex}", LogHelper.LogType.Error);
+ }
+ finally
+ {
+ IsShowingAutoplaySlidesWindow = false;
+ }
+ },
+ () => { IsShowingAutoplaySlidesWindow = false; },
+ () => { IsShowingAutoplaySlidesWindow = false; }).ShowDialog();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查自动播放设置失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void LoadCurrentSlideInk(int slideIndex)
+ {
+ try
+ {
+ var strokes = _pptInkManager?.LoadSlideStrokes(slideIndex);
+ if (strokes != null)
+ {
+ inkCanvas.Strokes.Clear();
+ inkCanvas.Strokes.Add(strokes);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"加载当前页墨迹失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void SwitchSlideInk(int newSlideIndex)
+ {
+ try
+ {
+ var newStrokes = _pptInkManager?.SwitchToSlide(newSlideIndex, inkCanvas.Strokes);
+ if (newStrokes != null)
+ {
+ inkCanvas.Strokes.Clear();
+ inkCanvas.Strokes.Add(newStrokes);
+ }
+
+ // 设置墨迹锁定
+ _pptInkManager?.LockInkForSlide(newSlideIndex);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"切换页面墨迹失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ 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 ex)
+ {
+ LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error);
+ return "error";
+ }
+ }
+ #endregion
+
+ private void BtnCheckPPT_Click(object sender, RoutedEventArgs e) {
+ try {
+ // 使用新的PPT管理器进行连接检查
+ if (_pptManager == null)
+ {
+ InitializePPTManagers();
+ }
+
+ // 手动触发一次连接检查
+ _pptManager?.StartMonitoring();
+
+ // 等待一小段时间让连接建立
+ Task.Delay(500).ContinueWith(_ =>
+ {
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ if (_pptManager?.IsConnected == true)
+ {
+ LogHelper.WriteLogToFile("手动PPT连接检查成功", LogHelper.LogType.Event);
+ }
+ else
+ {
+ MessageBox.Show("未找到幻灯片");
+ LogHelper.WriteLogToFile("手动PPT连接检查失败", LogHelper.LogType.Warning);
+ }
+ });
});
}
catch (Exception ex) {
- LogHelper.WriteLogToFile($"幻灯片切换事件处理失败: {ex}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"手动检查PPT应用程序失败: {ex}", LogHelper.LogType.Error);
+ _pptUIManager?.UpdateConnectionStatus(false);
+ MessageBox.Show("未找到幻灯片");
}
}
- private bool _isPptClickingBtnTurned;
+ private void ToggleSwitchSupportWPS_Toggled(object sender, RoutedEventArgs e) {
+ if (!isLoaded) return;
+
+ Settings.PowerPointSettings.IsSupportWPS = ToggleSwitchSupportWPS.IsOn;
+
+ // 更新PPT管理器的WPS支持设置
+ if (_pptManager != null)
+ {
+ _pptManager.IsSupportWPS = Settings.PowerPointSettings.IsSupportWPS;
+ }
+
+ SaveSettingsToFile();
+ }
+
+ private static bool isWPSSupportOn => Settings.PowerPointSettings.IsSupportWPS;
+
+ public static bool IsShowingRestoreHiddenSlidesWindow;
+ private static bool IsShowingAutoplaySlidesWindow;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
private void BtnPPTSlidesUp_Click(object sender, RoutedEventArgs e) {
Application.Current.Dispatcher.Invoke(() => {
- // 切换前同步保存墨迹
- SaveCurrentSlideInkStrokes();
- _isPptClickingBtnTurned = true;
- // 添加安全检查
- if (pptApplication == null)
- {
- LogHelper.WriteLogToFile("PPT应用程序为空,无法执行上一页操作", LogHelper.LogType.Warning);
- return;
- }
try
{
- // 检查SlideShowWindows是否存在且有效
- if (pptApplication.SlideShowWindows == null || pptApplication.SlideShowWindows.Count == 0)
+ _isPptClickingBtnTurned = true;
+
+ // 保存当前页墨迹
+ var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
+ if (currentSlide > 0)
{
- LogHelper.WriteLogToFile("PPT放映窗口不存在,无法执行上一页操作", LogHelper.LogType.Warning);
- return;
+ _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
}
- // 安全访问当前幻灯片信息
- if (pptApplication.SlideShowWindows.Count >= 1)
+
+ // 保存截图(如果启用)
+ if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber &&
+ Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint)
{
- var slideShowWindow = pptApplication.SlideShowWindows[1];
- if (slideShowWindow != null && slideShowWindow.View != null)
- {
- if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber &&
- Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint)
- SaveScreenShot(true,
- slideShowWindow.Presentation.Name + "/" +
- slideShowWindow.View.CurrentShowPosition);
- slideShowWindow.Activate();
- slideShowWindow.View.Previous();
- LockInkForCurrentSlide(slideShowWindow.View.CurrentShowPosition); // 翻页后加锁
- }
+ var presentationName = _pptManager?.GetPresentationName() ?? "";
+ SaveScreenShot(true, $"{presentationName}/{currentSlide}");
+ }
+
+ // 执行翻页
+ if (_pptManager?.TryNavigatePrevious() == true)
+ {
+ // 翻页成功,等待事件处理墨迹切换
+ LogHelper.WriteLogToFile("成功切换到上一页", LogHelper.LogType.Trace);
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("切换到上一页失败", LogHelper.LogType.Warning);
+ _pptUIManager?.UpdateConnectionStatus(false);
}
}
catch (Exception ex) {
LogHelper.WriteLogToFile($"PPT上一页操作异常: {ex}", LogHelper.LogType.Error);
- StackPanelPPTControls.Visibility = Visibility.Collapsed;
- LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ _pptUIManager?.UpdateConnectionStatus(false);
}
});
}
private void BtnPPTSlidesDown_Click(object sender, RoutedEventArgs e) {
Application.Current.Dispatcher.Invoke(() => {
- // 切换前同步保存墨迹
- SaveCurrentSlideInkStrokes();
- _isPptClickingBtnTurned = true;
- // 添加安全检查
- if (pptApplication == null)
- {
- LogHelper.WriteLogToFile("PPT应用程序为空,无法执行下一页操作", LogHelper.LogType.Warning);
- return;
- }
try
{
- // 检查SlideShowWindows是否存在且有效
- if (pptApplication.SlideShowWindows == null || pptApplication.SlideShowWindows.Count == 0)
+ _isPptClickingBtnTurned = true;
+
+ // 保存当前页墨迹
+ var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
+ if (currentSlide > 0)
{
- LogHelper.WriteLogToFile("PPT放映窗口不存在,无法执行下一页操作", LogHelper.LogType.Warning);
- return;
+ _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
}
- // 安全访问当前幻灯片信息
- if (pptApplication.SlideShowWindows.Count >= 1)
+
+ // 保存截图(如果启用)
+ if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber &&
+ Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint)
{
- var slideShowWindow = pptApplication.SlideShowWindows[1];
- if (slideShowWindow != null && slideShowWindow.View != null)
- {
- if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber &&
- Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint)
- SaveScreenShot(true,
- slideShowWindow.Presentation.Name + "/" +
- slideShowWindow.View.CurrentShowPosition);
- slideShowWindow.Activate();
- slideShowWindow.View.Next();
- LockInkForCurrentSlide(slideShowWindow.View.CurrentShowPosition); // 翻页后加锁
- }
+ var presentationName = _pptManager?.GetPresentationName() ?? "";
+ SaveScreenShot(true, $"{presentationName}/{currentSlide}");
+ }
+
+ // 执行翻页
+ if (_pptManager?.TryNavigateNext() == true)
+ {
+ // 翻页成功,等待事件处理墨迹切换
+ LogHelper.WriteLogToFile("成功切换到下一页", LogHelper.LogType.Trace);
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("切换到下一页失败", LogHelper.LogType.Warning);
+ _pptUIManager?.UpdateConnectionStatus(false);
}
}
catch (Exception ex) {
LogHelper.WriteLogToFile($"PPT下一页操作异常: {ex}", LogHelper.LogType.Error);
- StackPanelPPTControls.Visibility = Visibility.Collapsed;
- LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ _pptUIManager?.UpdateConnectionStatus(false);
}
});
}
@@ -1152,39 +842,27 @@ namespace Ink_Canvas {
if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return;
- // 添加安全检查
- if (pptApplication == null)
+ // 使用新的PPT管理器检查连接状态
+ if (_pptManager?.IsConnected != true || _pptManager?.IsInSlideShow != true)
{
- LogHelper.WriteLogToFile("PPT应用程序为空,无法执行翻页操作", LogHelper.LogType.Warning);
+ LogHelper.WriteLogToFile("PPT未连接或未在放映状态,无法执行页码点击操作", LogHelper.LogType.Warning);
return;
}
try
{
- // 检查SlideShowWindows是否存在且有效
- if (pptApplication.SlideShowWindows == null || pptApplication.SlideShowWindows.Count == 0)
- {
- LogHelper.WriteLogToFile("PPT放映窗口不存在,无法执行翻页操作", LogHelper.LogType.Warning);
- return;
- }
-
GridTransparencyFakeBackground.Opacity = 1;
GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF"));
CursorIcon_Click(null, null);
-
- try {
- // 安全访问SlideShowWindows[1]
- if (pptApplication.SlideShowWindows.Count >= 1)
- {
- var slideShowWindow = pptApplication.SlideShowWindows[1];
- if (slideShowWindow != null)
- {
- slideShowWindow.SlideNavigation.Visible = true;
- }
- }
+
+ // 使用新的PPT管理器显示导航
+ if (_pptManager.TryShowSlideNavigation())
+ {
+ LogHelper.WriteLogToFile("成功显示PPT幻灯片导航", LogHelper.LogType.Trace);
}
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"设置PPT导航可见性失败: {ex}", LogHelper.LogType.Error);
+ else
+ {
+ LogHelper.WriteLogToFile("显示PPT幻灯片导航失败", LogHelper.LogType.Warning);
}
// 控制居中
@@ -1202,71 +880,46 @@ namespace Ink_Canvas {
private void BtnPPTSlideShow_Click(object sender, RoutedEventArgs e) {
new Thread(() => {
try {
- presentation.SlideShowSettings.Run();
+ if (_pptManager?.TryStartSlideShow() != true)
+ {
+ LogHelper.WriteLogToFile("启动幻灯片放映失败", LogHelper.LogType.Warning);
+ }
+ }
+ catch (Exception ex) {
+ LogHelper.WriteLogToFile($"启动幻灯片放映异常: {ex}", LogHelper.LogType.Error);
}
- catch { }
}).Start();
}
private async void BtnPPTSlideShowEnd_Click(object sender, RoutedEventArgs e) {
- // 添加安全检查
- if (pptApplication == null)
- {
- LogHelper.WriteLogToFile("PPT应用程序为空,无法结束放映", LogHelper.LogType.Warning);
- return;
- }
-
- // 切换前同步保存墨迹
- Application.Current.Dispatcher.Invoke(() => {
- SaveCurrentSlideInkStrokes();
- });
-
try
{
- // 检查SlideShowWindows是否存在且有效
- if (pptApplication.SlideShowWindows == null || pptApplication.SlideShowWindows.Count == 0)
+ // 保存当前页墨迹
+ var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
+ if (currentSlide > 0)
{
- LogHelper.WriteLogToFile("PPT放映窗口不存在,无法结束放映", LogHelper.LogType.Warning);
- return;
+ Application.Current.Dispatcher.Invoke(() => {
+ _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
+ timeMachine.ClearStrokeHistory();
+ });
}
- Application.Current.Dispatcher.Invoke(() => {
- try {
- // 安全访问SlideShowWindows[1]
- if (pptApplication.SlideShowWindows.Count >= 1)
- {
- var slideShowWindow = pptApplication.SlideShowWindows[1];
- if (slideShowWindow != null && slideShowWindow.View != null)
- {
- var ms = new MemoryStream();
- inkCanvas.Strokes.Save(ms);
- ms.Position = 0;
- memoryStreams[slideShowWindow.View.CurrentShowPosition] = ms;
- timeMachine.ClearStrokeHistory();
- }
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"保存当前页面墨迹失败: {ex}", LogHelper.LogType.Error);
- }
- });
-
- new Thread(() => {
- try {
- // 安全访问SlideShowWindows[1]
- if (pptApplication.SlideShowWindows.Count >= 1)
- {
- var slideShowWindow = pptApplication.SlideShowWindows[1];
- if (slideShowWindow != null && slideShowWindow.View != null)
- {
- slideShowWindow.View.Exit();
- }
- }
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"退出PPT放映失败: {ex}", LogHelper.LogType.Error);
- }
- }).Start();
+ // 结束放映
+ if (_pptManager?.TryEndSlideShow() == true)
+ {
+ LogHelper.WriteLogToFile("成功结束幻灯片放映", LogHelper.LogType.Event);
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("结束幻灯片放映失败", LogHelper.LogType.Warning);
+
+ // 手动更新UI状态,防止事件未触发
+ await Application.Current.Dispatcher.InvokeAsync(() => {
+ _pptUIManager?.UpdateSlideShowStatus(false);
+ _pptUIManager?.UpdateSidebarExitButtons(false);
+ LogHelper.WriteLogToFile("手动更新放映结束UI状态", LogHelper.LogType.Trace);
+ });
+ }
HideSubPanels("cursor");
await Task.Delay(150);
@@ -1275,30 +928,13 @@ namespace Ink_Canvas {
catch (Exception ex)
{
LogHelper.WriteLogToFile($"结束PPT放映操作异常: {ex}", LogHelper.LogType.Error);
+
+ // 确保UI状态正确
+ await Application.Current.Dispatcher.InvokeAsync(() => {
+ _pptUIManager?.UpdateSlideShowStatus(false);
+ _pptUIManager?.UpdateSidebarExitButtons(false);
+ });
}
- await Application.Current.Dispatcher.InvokeAsync(() => {
- // 隐藏侧边栏退出按钮
- if (BtnExitPptFromSidebarLeft != null)
- BtnExitPptFromSidebarLeft.Visibility = Visibility.Collapsed;
- if (BtnExitPptFromSidebarRight != null)
- BtnExitPptFromSidebarRight.Visibility = Visibility.Collapsed;
-
- // 确保所有放映模式按钮都被隐藏,防止PptApplication_SlideShowEnd事件未触发的情况
- try {
- BtnPPTSlideShow.Visibility = Visibility.Visible;
- BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
- StackPanelPPTControls.Visibility = Visibility.Collapsed;
- LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
- LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
- RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
-
- LogHelper.WriteLogToFile("手动隐藏所有放映模式按钮", LogHelper.LogType.Trace);
- }
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"手动隐藏放映模式按钮失败: {ex}", LogHelper.LogType.Error);
- }
- });
}
private void GridPPTControlPrevious_MouseDown(object sender, MouseButtonEventArgs e)
@@ -1403,719 +1039,27 @@ namespace Ink_Canvas {
BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null);
}
- private void StartWppProcessCheckTimer()
- {
- // 新增:WPS联动未启用时不查杀wpp进程
- if (!Settings.PowerPointSettings.IsSupportWPS)
- {
- LogHelper.WriteLogToFile("WPS联动未启用,跳过WPP进程查杀", LogHelper.LogType.Trace);
- return;
- }
- if (wppProcessCheckTimer != null)
- {
- wppProcessCheckTimer.Stop();
- wppProcessCheckTimer.Dispose();
- }
- wppProcessCheckTimer = new Timer(500); // 改为500ms检查一次,提高响应速度
- wppProcessCheckTimer.Elapsed += WppProcessCheckTimer_Elapsed;
- wppProcessCheckTimer.Start();
- LogHelper.WriteLogToFile("启动 WPP 进程检测定时器(前台窗口监控模式)", LogHelper.LogType.Trace);
- }
- private void WppProcessCheckTimer_Elapsed(object sender, ElapsedEventArgs e)
- {
- // 新增:WPS联动未启用时不查杀wpp进程
- if (!Settings.PowerPointSettings.IsSupportWPS)
- {
- LogHelper.WriteLogToFile("WPS联动未启用,跳过WPP进程查杀", LogHelper.LogType.Trace);
- StopWppProcessCheckTimer();
- return;
- }
- try
- {
- if (wppProcess == null || hasWppProcessID == false)
- {
- StopWppProcessCheckTimer();
- return;
- }
- if (!Settings.PowerPointSettings.EnableWppProcessKill)
- {
- LogHelper.WriteLogToFile("WPP进程查杀功能已被关闭,跳过查杀", LogHelper.LogType.Trace);
- StopWppProcessCheckTimer();
- return;
- }
- // 刷新进程状态
- wppProcess.Refresh();
- wppProcessCheckCount++;
- if (wppProcess.HasExited)
- {
- LogHelper.WriteLogToFile("WPP 进程已正常关闭", LogHelper.LogType.Trace);
- StopWppProcessCheckTimer();
- return;
- }
- // 检查前台WPS窗口是否存在
- bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActive();
-
- if (isForegroundWpsWindowActive)
- {
- // 前台窗口仍然存在,继续监控
- if (wppProcessCheckCount % 10 == 0) // 每5秒记录一次日志,避免日志过多
- {
- LogHelper.WriteLogToFile($"前台WPS窗口仍然存在,继续监控(已检查{wppProcessCheckCount}次)", LogHelper.LogType.Trace);
- }
- return;
- }
-
- // 前台窗口已消失,立即结束WPP进程
- LogHelper.WriteLogToFile("检测到前台WPS窗口已消失,立即结束WPP进程", LogHelper.LogType.Event);
-
- // 检查所有WPS文档是否已保存
- bool allSaved = true;
- try
- {
- if (pptApplication != null)
- {
- foreach (Presentation pres in pptApplication.Presentations)
- {
- if (pres.Saved == MsoTriState.msoFalse)
- {
- allSaved = false;
- break;
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"检查WPS文档保存状态失败: {ex}", LogHelper.LogType.Error);
- allSaved = false; // 出错时默认不安全
- }
- if (!allSaved)
- {
- // 直接跳过弹窗,自动继续
- LogHelper.WriteLogToFile("检测到有未保存的WPS文档,但已取消弹窗提示,自动继续。", LogHelper.LogType.Trace);
- }
- // 立即结束WPP进程
- try
- {
- LogHelper.WriteLogToFile("前台窗口消失,开始结束WPP进程", LogHelper.LogType.Event);
-
- // 尝试优雅地结束进程
- if (!wppProcess.HasExited)
- {
- wppProcess.Kill();
- LogHelper.WriteLogToFile("前台窗口消失,成功结束WPP进程", LogHelper.LogType.Event);
- }
- else
- {
- LogHelper.WriteLogToFile("WPP进程已经自然结束", LogHelper.LogType.Trace);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"结束WPP进程失败: {ex}", LogHelper.LogType.Error);
-
- // 如果常规方法失败,尝试强制结束
- try
- {
- var processes = Process.GetProcessesByName(wppProcess.ProcessName);
- foreach (var process in processes)
- {
- if (process.Id == wppProcess.Id)
- {
- process.Kill();
- LogHelper.WriteLogToFile("强制结束WPP进程成功", LogHelper.LogType.Event);
- break;
- }
- }
- }
- catch (Exception forceKillEx)
- {
- LogHelper.WriteLogToFile($"强制结束WPP进程也失败: {forceKillEx}", LogHelper.LogType.Error);
- }
- }
- finally
- {
- StopWppProcessCheckTimer();
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"WPP 进程检测失败: {ex}", LogHelper.LogType.Error);
- StopWppProcessCheckTimer();
- }
- }
- private bool CheckForOtherWpsWindows()
- {
- try
- {
- if (wppProcess != null)
- {
- var wpsWindows = GetWpsWindowsByProcess(wppProcess.Id);
- LogHelper.WriteLogToFile($"检测到{wpsWindows.Count}个WPS窗口", LogHelper.LogType.Trace);
-
- foreach (var window in wpsWindows)
- {
- LogHelper.WriteLogToFile($"WPS窗口: 标题='{window.Title}', 类名='{window.ClassName}', 可见={window.IsVisible}, 最小化={window.IsMinimized}", LogHelper.LogType.Trace);
- }
-
- // 只要当前wpp进程没有可见窗口,就允许Kill
- return wpsWindows.Any(w => w.IsVisible && !w.IsMinimized);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"检查WPP窗口失败: {ex}", LogHelper.LogType.Error);
- }
- return false; // 出错时,默认允许Kill
- }
- ///
- /// WPS窗口信息结构
- ///
- private class WpsWindowInfo
- {
- public IntPtr Handle { get; set; }
- public string Title { get; set; }
- public string ClassName { get; set; }
- public bool IsVisible { get; set; }
- public bool IsMinimized { get; set; }
- public bool IsMaximized { get; set; }
- public ForegroundWindowInfo.RECT Rect { get; set; }
- public uint ProcessId { get; set; }
- public string ProcessName { get; set; } // 新增
- }
- ///
- /// 获取指定进程的所有WPS窗口
- ///
- private List GetWpsWindowsByProcess(int processId)
- {
- var wpsWindows = new List();
-
- try
- {
- EnumWindows((hWnd, lParam) =>
- {
- try
- {
- if (!IsWindow(hWnd)) return true;
-
- uint windowProcessId;
- GetWindowThreadProcessId(hWnd, out windowProcessId);
-
- if ((int)windowProcessId == processId)
- {
- var windowInfo = GetWindowInfo(hWnd);
- if (IsWpsWindow(windowInfo))
- {
- wpsWindows.Add(windowInfo);
- LogHelper.WriteLogToFile($"发现WPS窗口: {windowInfo.Title} ({windowInfo.ClassName})", LogHelper.LogType.Trace);
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"枚举窗口时出错: {ex}", LogHelper.LogType.Error);
- }
- return true;
- }, IntPtr.Zero);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"获取WPS窗口失败: {ex}", LogHelper.LogType.Error);
- }
-
- return wpsWindows;
- }
- ///
- /// 获取窗口详细信息
- ///
- private WpsWindowInfo GetWindowInfo(IntPtr hWnd)
- {
- var windowInfo = new WpsWindowInfo
- {
- Handle = hWnd,
- IsVisible = IsWindowVisible(hWnd),
- IsMinimized = IsIconic(hWnd),
- IsMaximized = IsZoomed(hWnd)
- };
- // 获取窗口标题
- var windowTitle = new StringBuilder(256);
- GetWindowText(hWnd, windowTitle, 256);
- windowInfo.Title = windowTitle.ToString().Trim();
- // 获取窗口类名
- var className = new StringBuilder(256);
- GetClassName(hWnd, className, 256);
- windowInfo.ClassName = className.ToString().Trim();
- // 获取窗口位置
- GetWindowRect(hWnd, out ForegroundWindowInfo.RECT rect);
- windowInfo.Rect = rect;
- // 获取进程ID
- uint processId;
- GetWindowThreadProcessId(hWnd, out processId);
- windowInfo.ProcessId = processId;
- // 新增:获取进程名
- windowInfo.ProcessName = "";
- try
- {
- var proc = Process.GetProcessById((int)processId);
- windowInfo.ProcessName = proc.ProcessName.ToLower();
- }
- catch { }
- return windowInfo;
- }
- ///
- /// 判断是否为WPS窗口
- ///
- private bool IsWpsWindow(WpsWindowInfo windowInfo)
- {
- if (string.IsNullOrEmpty(windowInfo.Title) && string.IsNullOrEmpty(windowInfo.ClassName))
- return false;
- var title = windowInfo.Title.ToLower();
- var className = windowInfo.ClassName.ToLower();
- var processName = windowInfo.ProcessName ?? "";
-
- // WPS相关关键词
- var wpsKeywords = new[] { "wps", "wpp", "kingsoft", "金山", "wps演示", "wps presentation", "wps office", "kingsoft office" };
- // 微软Office相关进程名
- var msOfficeProcess = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "microsoftoffice", "office" };
-
- // 只要进程名是微软Office,直接排除
- if (msOfficeProcess.Any(keyword => processName.Contains(keyword)))
- return false;
-
- // 只要进程名是WPS/WPP/Kingsoft,直接通过
- if (wpsKeywords.Any(keyword => processName.Contains(keyword)))
- return true;
-
- // 标题或类名包含WPS相关关键词
- bool hasWpsTitle = wpsKeywords.Any(keyword => title.Contains(keyword));
- bool hasWpsClass = wpsKeywords.Any(keyword => className.Contains(keyword));
- bool isWpsClass = className.Contains("wps") || className.Contains("kingsoft") || className.Contains("wpp");
- bool hasValidSize = (windowInfo.Rect.Right - windowInfo.Rect.Left) > 0 && (windowInfo.Rect.Bottom - windowInfo.Rect.Top) > 0;
-
- return (hasWpsTitle || hasWpsClass || isWpsClass) && hasValidSize;
- }
-
- ///
- /// 获取前台WPS窗口
- ///
- private WpsWindowInfo GetForegroundWpsWindow()
- {
- try
- {
- var foregroundHwnd = GetForegroundWindow();
- if (foregroundHwnd != IntPtr.Zero && IsWindow(foregroundHwnd))
- {
- var windowInfo = GetWindowInfo(foregroundHwnd);
- if (IsWpsWindow(windowInfo))
- {
- LogHelper.WriteLogToFile($"前台WPS窗口: {windowInfo.Title}", LogHelper.LogType.Trace);
- return windowInfo;
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"获取前台WPS窗口失败: {ex}", LogHelper.LogType.Error);
- }
- return null;
- }
-
- ///
- /// 检查前台WPS窗口是否仍然存在(更精确的检测)
- ///
- private bool IsForegroundWpsWindowStillActive()
- {
- try
- {
- var currentTime = DateTime.Now;
- var currentForegroundWindow = GetForegroundWpsWindow();
-
- // 检查窗口状态是否发生变化
- bool windowStateChanged = false;
- if (lastForegroundWpsWindow != null && currentForegroundWindow != null)
- {
- // 检查窗口是否发生了变化
- if (lastForegroundWpsWindow.Handle != currentForegroundWindow.Handle ||
- lastForegroundWpsWindow.Title != currentForegroundWindow.Title)
- {
- windowStateChanged = true;
- LogHelper.WriteLogToFile($"前台WPS窗口发生变化: {lastForegroundWpsWindow.Title} -> {currentForegroundWindow.Title}", LogHelper.LogType.Trace);
- }
- }
- else if (lastForegroundWpsWindow == null && currentForegroundWindow != null)
- {
- // 从无窗口变为有窗口
- windowStateChanged = true;
- LogHelper.WriteLogToFile($"检测到新的前台WPS窗口: {currentForegroundWindow.Title}", LogHelper.LogType.Trace);
- }
- else if (lastForegroundWpsWindow != null && currentForegroundWindow == null)
- {
- // 从有窗口变为无窗口
- windowStateChanged = true;
- LogHelper.WriteLogToFile($"前台WPS窗口已消失: {lastForegroundWpsWindow.Title}", LogHelper.LogType.Trace);
- }
-
- // 更新记录
- lastForegroundWpsWindow = currentForegroundWindow;
- lastWindowCheckTime = currentTime;
-
- if (currentForegroundWindow != null)
- {
- // 验证窗口仍然有效
- if (IsWindow(currentForegroundWindow.Handle) && IsWindowVisible(currentForegroundWindow.Handle))
- {
- return true;
- }
- }
-
- // 方法2:检查所有WPS进程的活跃窗口
- var wpsProcesses = GetWpsProcesses();
- foreach (var process in wpsProcesses)
- {
- var windows = GetWpsWindowsByProcess(process.Id);
- if (windows.Any(w => w.IsVisible && !w.IsMinimized && w.Handle == GetForegroundWindow()))
- {
- return true;
- }
- }
-
- // 方法3:检查顶级WPS窗口
- var topLevelWindows = GetTopLevelWpsWindows();
- if (topLevelWindows.Any(w => w.IsVisible && !w.IsMinimized))
- {
- return true;
- }
-
- return false;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"检查前台WPS窗口状态失败: {ex}", LogHelper.LogType.Error);
- return false;
- }
- }
-
- ///
- /// 获取顶级WPS窗口(包括前台窗口和最近的活跃窗口)
- ///
- private List GetTopLevelWpsWindows()
- {
- var topLevelWindows = new List();
-
- try
- {
- // 获取前台窗口
- var foregroundWindow = GetForegroundWpsWindow();
- if (foregroundWindow != null)
- {
- topLevelWindows.Add(foregroundWindow);
- }
-
- // 获取所有可见的WPS窗口,按层级排序
- var allWpsWindows = new List();
- var wpsProcesses = GetWpsProcesses();
-
- foreach (var process in wpsProcesses)
- {
- var windows = GetWpsWindowsByProcess(process.Id);
- allWpsWindows.AddRange(windows.Where(w => w.IsVisible && !w.IsMinimized));
- }
-
- // 按窗口位置排序,优先选择屏幕中央的窗口
- var screenCenter = new Point(
- Screen.PrimaryScreen.Bounds.Width / 2,
- Screen.PrimaryScreen.Bounds.Height / 2
- );
-
- var sortedWindows = allWpsWindows
- .Where(w => !topLevelWindows.Any(t => t.Handle == w.Handle)) // 排除已添加的前台窗口
- .OrderBy(w => Math.Abs((w.Rect.Left + w.Rect.Right) / 2 - screenCenter.X) +
- Math.Abs((w.Rect.Top + w.Rect.Bottom) / 2 - screenCenter.Y))
- .Take(3); // 取最近的3个窗口
-
- topLevelWindows.AddRange(sortedWindows);
-
- LogHelper.WriteLogToFile($"找到{topLevelWindows.Count}个顶级WPS窗口", LogHelper.LogType.Trace);
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"获取顶级WPS窗口失败: {ex}", LogHelper.LogType.Error);
- }
-
- return topLevelWindows;
- }
-
- ///
- /// 检查是否有活跃的WPS窗口(包括前台窗口)
- ///
- private bool HasActiveWpsWindows()
- {
- return HasActiveWpsWindowsWithRetry(3); // 重试3次
- }
-
- ///
- /// 带重试机制的WPS窗口检测
- ///
- private bool HasActiveWpsWindowsWithRetry(int maxRetries)
- {
- for (int attempt = 1; attempt <= maxRetries; attempt++)
- {
- try
- {
- LogHelper.WriteLogToFile($"第{attempt}次尝试检测WPS窗口", LogHelper.LogType.Trace);
-
- // 首先检查前台窗口
- var foregroundWpsWindow = GetForegroundWpsWindow();
- if (foregroundWpsWindow != null)
- {
- LogHelper.WriteLogToFile($"第{attempt}次尝试检测到前台WPS窗口", LogHelper.LogType.Trace);
- return true;
- }
-
- // 检查顶级WPS窗口
- var topLevelWindows = GetTopLevelWpsWindows();
- if (topLevelWindows.Any())
- {
- LogHelper.WriteLogToFile($"第{attempt}次尝试检测到{topLevelWindows.Count}个顶级WPS窗口", LogHelper.LogType.Trace);
- return true;
- }
-
- // 然后检查所有WPS进程的窗口
- var wpsProcesses = GetWpsProcesses();
- foreach (var process in wpsProcesses)
- {
- var windows = GetWpsWindowsByProcess(process.Id);
- if (windows.Any(w => w.IsVisible && !w.IsMinimized))
- {
- LogHelper.WriteLogToFile($"第{attempt}次尝试检测到进程{process.Id}的活跃WPS窗口", LogHelper.LogType.Trace);
- return true;
- }
- }
-
- // 如果还有重试机会,等待一小段时间再重试
- if (attempt < maxRetries)
- {
- Thread.Sleep(100); // 等待100ms
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"第{attempt}次尝试检查活跃WPS窗口失败: {ex}", LogHelper.LogType.Error);
-
- // 如果还有重试机会,等待一小段时间再重试
- if (attempt < maxRetries)
- {
- Thread.Sleep(100); // 等待100ms
- }
- }
- }
-
- LogHelper.WriteLogToFile($"经过{maxRetries}次尝试,未检测到活跃的WPS窗口", LogHelper.LogType.Trace);
- return false;
- }
-
- ///
- /// 获取所有WPS相关进程
- ///
- private List GetWpsProcesses()
- {
- var wpsProcesses = new List();
- try
- {
- var allProcesses = Process.GetProcesses();
- foreach (var process in allProcesses)
- {
- try
- {
- var pname = process.ProcessName.ToLower();
- // 只允许WPS/WPP相关进程,排除PowerPoint及微软Office
- if ((pname.Contains("wps") || pname.Contains("wpp") || pname.Contains("presentation"))
- && !pname.Contains("powerpnt")
- && !pname.Contains("office")
- && !pname.Contains("onenote")
- && !pname.Contains("excel")
- && !pname.Contains("word")
- && !pname.Contains("outlook")
- && !pname.Contains("microsoft"))
- {
- wpsProcesses.Add(process);
- LogHelper.WriteLogToFile($"发现WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"检查进程{process.ProcessName}失败: {ex}", LogHelper.LogType.Error);
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"获取WPS进程失败: {ex}", LogHelper.LogType.Error);
- }
- return wpsProcesses;
- }
-
- private bool CheckForWpsWindowsByEnumeration()
- {
- try
- {
- var wpsWindowCount = 0;
- var currentProcessId = wppProcess?.Id ?? 0;
-
- EnumWindows((hWnd, lParam) =>
- {
- try
- {
- uint windowProcessId;
- GetWindowThreadProcessId(hWnd, out windowProcessId);
-
- // 检查是否是WPP进程的窗口
- if (windowProcessId == currentProcessId)
- {
- var windowTitle = new StringBuilder(256);
- GetWindowText(hWnd, windowTitle, 256);
- var title = windowTitle.ToString().Trim();
-
- // 检查窗口标题是否包含WPS相关标识
- if (!string.IsNullOrEmpty(title) &&
- (title.Contains("WPS") || title.Contains("演示文稿") || title.Contains(".ppt") || title.Contains(".pptx")))
- {
- wpsWindowCount++;
- LogHelper.WriteLogToFile($"发现WPS窗口: {title}", LogHelper.LogType.Trace);
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"枚举窗口时出错: {ex}", LogHelper.LogType.Error);
- }
-
- return true; // 继续枚举
- }, IntPtr.Zero);
-
- if (wpsWindowCount > 1)
- {
- LogHelper.WriteLogToFile($"检测到{wpsWindowCount}个WPS窗口,可能存在多个文档", LogHelper.LogType.Trace);
- return true;
- }
-
- return false;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"通过枚举检查WPS窗口失败: {ex}", LogHelper.LogType.Error);
- return false;
- }
- }
-
- private void StopWppProcessCheckTimer()
- {
- if (wppProcessCheckTimer != null)
- {
- wppProcessCheckTimer.Stop();
- wppProcessCheckTimer.Dispose();
- wppProcessCheckTimer = null;
- }
-
- wppProcess = null;
- hasWppProcessID = false;
- wppProcessRecordTime = DateTime.MinValue;
- wppProcessCheckCount = 0;
- lastForegroundWpsWindow = null;
- lastWindowCheckTime = DateTime.MinValue;
- LogHelper.WriteLogToFile("停止 WPP 进程检测定时器", LogHelper.LogType.Trace);
- }
-
- ///
- /// 计算文件路径的哈希值,用于生成唯一的文件夹标识符
- ///
- /// 文件路径
- /// 文件路径的哈希值字符串
- 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 ex)
- {
- LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error);
- return "error";
- }
- }
-
- // 新增:同步保存当前页面墨迹的方法
- private void SaveCurrentSlideInkStrokes()
- {
- try
- {
- if (pptApplication != null && pptApplication.SlideShowWindows != null && pptApplication.SlideShowWindows.Count >= 1)
- {
- var slideShowWindow = pptApplication.SlideShowWindows[1];
- if (slideShowWindow != null && slideShowWindow.View != null)
- {
- int currentSlide = slideShowWindow.View.CurrentShowPosition;
- if (!CanWriteInk(currentSlide))
- {
- LogHelper.WriteLogToFile($"墨迹写入被锁定或页码不符,当前页:{currentSlide},锁定页:{lockedSlideIndex}", LogHelper.LogType.Warning);
- return;
- }
- var ms = new MemoryStream();
- inkCanvas.Strokes.Save(ms);
- ms.Position = 0;
- memoryStreams[currentSlide] = ms;
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"保存当前页面墨迹失败: {ex}", LogHelper.LogType.Error);
- }
- }
-
- // 翻页后调用
- private void LockInkForCurrentSlide(int slideIndex)
- {
- inkLockUntil = DateTime.Now.AddMilliseconds(InkLockMilliseconds);
- lockedSlideIndex = slideIndex;
- }
-
- // 写入墨迹前调用
- private bool CanWriteInk(int currentSlideIndex)
- {
- if (DateTime.Now < inkLockUntil) return false;
- if (currentSlideIndex != lockedSlideIndex) return false;
- return true;
- }
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs
index 8fc996b1..16809605 100644
--- a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs
@@ -57,18 +57,21 @@ namespace Ink_Canvas {
List allPageStrokes = new List();
// 检查PPT放映模式下的多页面墨迹
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible && pptApplication != null)
+ if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible && _pptManager?.IsConnected == true)
{
hasMultiplePages = true;
// 收集PPT放映模式下的所有页面墨迹
- for (int i = 1; i <= pptApplication.SlideShowWindows[1].Presentation.Slides.Count; i++)
+ var totalSlides = _pptManager.SlidesCount;
+ var currentSlide = _pptManager.GetCurrentSlideNumber();
+
+ for (int i = 1; i <= totalSlides; i++)
{
- if (memoryStreams[i] != null && memoryStreams[i].Length > 8)
+ var slideStrokes = _pptInkManager?.LoadSlideStrokes(i);
+ if (slideStrokes != null && slideStrokes.Count > 0)
{
- memoryStreams[i].Position = 0;
- allPageStrokes.Add(new StrokeCollection(memoryStreams[i]));
+ allPageStrokes.Add(slideStrokes);
}
- else if (i == previousSlideID && inkCanvas.Strokes.Count > 0)
+ else if (i == currentSlide && inkCanvas.Strokes.Count > 0)
{
// 当前页面的墨迹
allPageStrokes.Add(inkCanvas.Strokes.Clone());
@@ -485,10 +488,8 @@ namespace Ink_Canvas {
timeMachine.ClearStrokeHistory();
// 重置PPT墨迹存储
- if (memoryStreams == null) {
- memoryStreams = new MemoryStream[50];
- }
-
+ _pptInkManager?.ClearAllStrokes();
+
// 读取所有页面的墨迹文件
var files = Directory.GetFiles(tempDir, "page_*.icstk");
foreach (var file in files) {
@@ -497,23 +498,19 @@ namespace Ink_Canvas {
using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read)) {
var strokes = new StrokeCollection(fs);
if (strokes.Count > 0) {
- var ms = new MemoryStream();
- strokes.Save(ms);
- ms.Position = 0;
- memoryStreams[pageNumber] = ms;
+ _pptInkManager?.SaveCurrentSlideStrokes(pageNumber, strokes);
}
}
}
}
-
+
// 恢复当前页面的墨迹
- if (pptApplication.SlideShowWindows.Count > 0) {
- int currentSlide = pptApplication.SlideShowWindows[1].View.CurrentShowPosition;
- if (memoryStreams[currentSlide] != null && memoryStreams[currentSlide].Length > 0) {
- memoryStreams[currentSlide].Position = 0;
- inkCanvas.Strokes.Add(new StrokeCollection(memoryStreams[currentSlide]));
+ if (_pptManager?.IsInSlideShow == true) {
+ int currentSlide = _pptManager.GetCurrentSlideNumber();
+ var currentStrokes = _pptInkManager?.LoadSlideStrokes(currentSlide);
+ if (currentStrokes != null && currentStrokes.Count > 0) {
+ inkCanvas.Strokes.Add(currentStrokes);
}
- previousSlideID = currentSlide;
}
LogHelper.WriteLogToFile($"成功恢复PPT墨迹,共{files.Length}页");
diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs
index b0fd4ffb..527cc08b 100644
--- a/Ink Canvas/MainWindow_cs/MW_Settings.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs
@@ -95,10 +95,19 @@ namespace Ink_Canvas {
Settings.PowerPointSettings.PowerPointSupport = ToggleSwitchSupportPowerPoint.IsOn;
SaveSettingsToFile();
+ // 使用新的PPT管理器
if (Settings.PowerPointSettings.PowerPointSupport)
- timerCheckPPT.Start();
+ {
+ if (_pptManager == null)
+ {
+ InitializePPTManagers();
+ }
+ StartPPTMonitoring();
+ }
else
- timerCheckPPT.Stop();
+ {
+ StopPPTMonitoring();
+ }
}
private void ToggleSwitchShowCanvasAtNewSlideShow_Toggled(object sender, RoutedEventArgs e) {
@@ -419,7 +428,12 @@ namespace Ink_Canvas {
if (!isLoaded) return;
Settings.PowerPointSettings.ShowPPTButton = ToggleSwitchShowPPTButton.IsOn;
SaveSettingsToFile();
- UpdatePPTBtnDisplaySettingsStatus();
+ // 更新PPT UI管理器设置
+ if (_pptUIManager != null)
+ {
+ _pptUIManager.ShowPPTButton = Settings.PowerPointSettings.ShowPPTButton;
+ _pptUIManager.UpdateNavigationPanelsVisibility();
+ }
UpdatePPTBtnPreview();
}
@@ -436,7 +450,12 @@ namespace Ink_Canvas {
c[0] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTButtonsDisplayOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus();
+ // 更新PPT UI管理器设置
+ if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+ {
+ _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption;
+ _pptUIManager.UpdateNavigationPanelsVisibility();
+ }
UpdatePPTBtnPreview();
}
@@ -448,7 +467,12 @@ namespace Ink_Canvas {
c[1] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTButtonsDisplayOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus();
+ // 更新PPT UI管理器设置
+ if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+ {
+ _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption;
+ _pptUIManager.UpdateNavigationPanelsVisibility();
+ }
UpdatePPTBtnPreview();
}
@@ -460,7 +484,12 @@ namespace Ink_Canvas {
c[2] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTButtonsDisplayOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus();
+ // 更新PPT UI管理器设置
+ if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+ {
+ _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption;
+ _pptUIManager.UpdateNavigationPanelsVisibility();
+ }
UpdatePPTBtnPreview();
}
@@ -472,7 +501,12 @@ namespace Ink_Canvas {
c[3] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTButtonsDisplayOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus();
+ // 更新PPT UI管理器设置
+ if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+ {
+ _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption;
+ _pptUIManager.UpdateNavigationPanelsVisibility();
+ }
UpdatePPTBtnPreview();
}
@@ -484,7 +518,12 @@ namespace Ink_Canvas {
c[0] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTSButtonsOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus();
+ // 更新PPT UI管理器设置
+ if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+ {
+ _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption;
+ _pptUIManager.UpdateNavigationButtonStyles();
+ }
UpdatePPTBtnPreview();
}
@@ -496,7 +535,12 @@ namespace Ink_Canvas {
c[1] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTSButtonsOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus();
+ // 更新PPT UI管理器设置
+ if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+ {
+ _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption;
+ _pptUIManager.UpdateNavigationButtonStyles();
+ }
UpdatePPTBtnPreview();
}
@@ -508,7 +552,12 @@ namespace Ink_Canvas {
c[2] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTSButtonsOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus();
+ // 更新PPT UI管理器设置
+ if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+ {
+ _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption;
+ _pptUIManager.UpdateNavigationButtonStyles();
+ }
UpdatePPTBtnPreview();
}
@@ -520,7 +569,12 @@ namespace Ink_Canvas {
c[0] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTBButtonsOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus();
+ // 更新PPT UI管理器设置
+ if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+ {
+ _pptUIManager.PPTBButtonsOption = Settings.PowerPointSettings.PPTBButtonsOption;
+ _pptUIManager.UpdateNavigationButtonStyles();
+ }
UpdatePPTBtnPreview();
}
@@ -532,7 +586,7 @@ namespace Ink_Canvas {
c[1] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTBButtonsOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus();
+ UpdatePPTUIManagerSettings();
UpdatePPTBtnPreview();
}
@@ -544,7 +598,7 @@ namespace Ink_Canvas {
c[2] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
Settings.PowerPointSettings.PPTBButtonsOption = int.Parse(new string(c));
SaveSettingsToFile();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnStyleSettingsStatus();
+ UpdatePPTUIManagerSettings();
UpdatePPTBtnPreview();
}
@@ -552,7 +606,7 @@ namespace Ink_Canvas {
if (!isLoaded) return;
Settings.PowerPointSettings.PPTLSButtonPosition = (int)PPTButtonLeftPositionValueSlider.Value;
UpdatePPTBtnSlidersStatus();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus();
+ UpdatePPTUIManagerSettings();
SliderDelayAction.DebounceAction(2000, null, SaveSettingsToFile);
UpdatePPTBtnPreview();
}
@@ -686,11 +740,28 @@ namespace Ink_Canvas {
if (!isLoaded) return;
Settings.PowerPointSettings.PPTRSButtonPosition = (int)PPTButtonRightPositionValueSlider.Value;
UpdatePPTBtnSlidersStatus();
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) UpdatePPTBtnDisplaySettingsStatus();
+ UpdatePPTUIManagerSettings();
SliderDelayAction.DebounceAction(2000,null, SaveSettingsToFile);
UpdatePPTBtnPreview();
}
+ ///
+ /// 更新PPT UI管理器设置的通用方法
+ ///
+ private void UpdatePPTUIManagerSettings()
+ {
+ if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+ {
+ _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption;
+ _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption;
+ _pptUIManager.PPTBButtonsOption = Settings.PowerPointSettings.PPTBButtonsOption;
+ _pptUIManager.PPTLSButtonPosition = Settings.PowerPointSettings.PPTLSButtonPosition;
+ _pptUIManager.PPTRSButtonPosition = Settings.PowerPointSettings.PPTRSButtonPosition;
+ _pptUIManager.UpdateNavigationPanelsVisibility();
+ _pptUIManager.UpdateNavigationButtonStyles();
+ }
+ }
+
private void UpdatePPTBtnPreview() {
//new BitmapImage(new Uri("pack://application:,,,/Resources/new-icons/unfold-chevron.png"));
var bopt = Settings.PowerPointSettings.PPTBButtonsOption.ToString();
diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
index 8f761e53..03e8dcd7 100644
--- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
@@ -278,10 +278,10 @@ namespace Ink_Canvas {
if (Settings.PowerPointSettings.PowerPointSupport) {
ToggleSwitchSupportPowerPoint.IsOn = true;
- timerCheckPPT.Start();
+ // PPT监控将在Window_Loaded中启动
} else {
ToggleSwitchSupportPowerPoint.IsOn = false;
- timerCheckPPT.Stop();
+ // PPT监控将保持停止状态
}
ToggleSwitchShowCanvasAtNewSlideShow.IsOn = Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow;
diff --git a/Ink Canvas/MainWindow_cs/MW_Timer.cs b/Ink Canvas/MainWindow_cs/MW_Timer.cs
index 26d3dd55..f1948f2d 100644
--- a/Ink Canvas/MainWindow_cs/MW_Timer.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Timer.cs
@@ -88,8 +88,9 @@ namespace Ink_Canvas {
}
private void InitTimers() {
- timerCheckPPT.Elapsed += TimerCheckPPT_Elapsed;
- timerCheckPPT.Interval = 500;
+ // PPT检查现在由PPTManager处理,不再需要定时器
+ // timerCheckPPT.Elapsed += TimerCheckPPT_Elapsed;
+ // timerCheckPPT.Interval = 500;
timerKillProcess.Elapsed += TimerKillProcess_Elapsed;
timerKillProcess.Interval = 2000;
timerCheckAutoFold.Elapsed += timerCheckAutoFold_Elapsed;
From 86f432ef01a2849f323819bda8ee504b4f2ed70c Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 01:24:42 +0800
Subject: [PATCH 06/22] =?UTF-8?q?improve:PPT=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/PPTManager.cs | 256 +++++++++++++++++++++++++++----
1 file changed, 226 insertions(+), 30 deletions(-)
diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs
index 0e511191..23c8767c 100644
--- a/Ink Canvas/Helpers/PPTManager.cs
+++ b/Ink Canvas/Helpers/PPTManager.cs
@@ -908,7 +908,8 @@ namespace Ink_Canvas.Helpers
_wpsProcessCheckTimer.Dispose();
}
- _wpsProcessCheckTimer = new Timer(500);
+ // 优化:增加检查间隔到2秒,减少性能开销
+ _wpsProcessCheckTimer = new Timer(2000);
_wpsProcessCheckTimer.Elapsed += OnWpsProcessCheckTimerElapsed;
_wpsProcessCheckTimer.Start();
LogHelper.WriteLogToFile("启动 WPS 进程检测定时器", LogHelper.LogType.Trace);
@@ -940,50 +941,218 @@ namespace Ink_Canvas.Helpers
return;
}
- // 检查前台WPS窗口是否存在
- bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActive();
+ // 优化:增加延迟检查,避免误杀
+ if (_wpsProcessCheckCount < 3) // 前6秒不进行查杀检查
+ {
+ LogHelper.WriteLogToFile($"WPS进程查杀延迟中,等待{6 - _wpsProcessCheckCount * 2}秒", LogHelper.LogType.Trace);
+ return;
+ }
+
+ // 检查前台WPS窗口是否存在(优化版)
+ bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized();
if (isForegroundWpsWindowActive)
{
- if (_wpsProcessCheckCount % 10 == 0)
+ if (_wpsProcessCheckCount % 5 == 0) // 每10秒记录一次日志
{
- LogHelper.WriteLogToFile($"前台WPS窗口仍然存在,继续监控(已检查{_wpsProcessCheckCount}次)", LogHelper.LogType.Trace);
+ LogHelper.WriteLogToFile($"WPS窗口仍然活跃,继续监控(已检查{_wpsProcessCheckCount}次)", LogHelper.LogType.Trace);
}
return;
}
- // 前台窗口已消失,检查是否需要结束进程
- LogHelper.WriteLogToFile("检测到前台WPS窗口已消失", LogHelper.LogType.Event);
-
- // 检查所有WPS文档是否已保存
- bool allSaved = CheckAllWpsDocumentsSaved();
-
- if (!allSaved)
+ // 优化:多重验证确保准确性
+ if (!PerformMultipleWpsWindowChecks())
{
- LogHelper.WriteLogToFile("检测到有未保存的WPS文档,跳过进程结束", LogHelper.LogType.Trace);
+ LogHelper.WriteLogToFile("多重验证显示WPS窗口仍然存在,跳过查杀", LogHelper.LogType.Trace);
+ return;
}
- // 结束WPS进程
+ // 前台窗口已消失,检查是否需要结束进程
+ LogHelper.WriteLogToFile("多重验证确认WPS窗口已消失,准备结束WPS进程", LogHelper.LogType.Event);
+
+ // 检查文档保存状态
+ if (!CheckAllWpsDocumentsSaved())
+ {
+ LogHelper.WriteLogToFile("检测到有未保存的WPS文档,跳过进程结束", LogHelper.LogType.Warning);
+ StopWpsProcessCheckTimer();
+ return;
+ }
+
+ // 安全结束WPS进程
+ SafeTerminateWpsProcess();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"WPS 进程检测失败: {ex}", LogHelper.LogType.Error);
+ StopWpsProcessCheckTimer();
+ }
+ }
+
+ ///
+ /// 优化版的前台WPS窗口检测,减少性能开销
+ ///
+ private bool IsForegroundWpsWindowStillActiveOptimized()
+ {
+ try
+ {
+ // 快速检查:直接检查前台窗口
+ var foregroundWindow = GetForegroundWindow();
+ if (foregroundWindow == IntPtr.Zero) return false;
+
+ // 获取前台窗口的进程ID
+ uint processId;
+ GetWindowThreadProcessId(foregroundWindow, out processId);
+
+ // 如果前台窗口就是我们监控的WPS进程,则认为仍然活跃
+ if (processId == _wpsProcess?.Id)
+ {
+ return true;
+ }
+
+ // 检查是否为WPS相关窗口
+ var windowInfo = GetWindowInfo(foregroundWindow);
+ return IsWpsWindow(windowInfo);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"优化版WPS窗口检测失败: {ex}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ ///
+ /// 多重验证WPS窗口状态,确保查杀准确性
+ ///
+ private bool PerformMultipleWpsWindowChecks()
+ {
+ try
+ {
+ // 第一重验证:等待1秒后再次检查
+ Thread.Sleep(1000);
+ if (IsForegroundWpsWindowStillActiveOptimized())
+ {
+ LogHelper.WriteLogToFile("第一重验证:WPS窗口仍然存在", LogHelper.LogType.Trace);
+ return false;
+ }
+
+ // 第二重验证:检查所有WPS进程的窗口
+ var wpsProcesses = GetWpsProcesses();
+ foreach (var process in wpsProcesses)
+ {
+ if (process.Id == _wpsProcess?.Id) continue; // 跳过当前监控的进程
+
+ var windows = GetWpsWindowsByProcess(process.Id);
+ if (windows.Any(w => w.IsVisible && !w.IsMinimized))
+ {
+ LogHelper.WriteLogToFile($"第二重验证:发现其他WPS进程{process.Id}有活跃窗口", LogHelper.LogType.Trace);
+ return false;
+ }
+ }
+
+ // 第三重验证:检查任务栏中的WPS窗口
+ if (HasWpsWindowInTaskbar())
+ {
+ LogHelper.WriteLogToFile("第三重验证:任务栏中仍有WPS窗口", LogHelper.LogType.Trace);
+ return false;
+ }
+
+ LogHelper.WriteLogToFile("多重验证完成:确认WPS窗口已全部消失", LogHelper.LogType.Event);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"多重验证失败: {ex}", LogHelper.LogType.Error);
+ return false; // 出错时保守处理,不进行查杀
+ }
+ }
+
+ ///
+ /// 检查任务栏中是否有WPS窗口
+ ///
+ private bool HasWpsWindowInTaskbar()
+ {
+ try
+ {
+ var allWindows = new List();
+
+ EnumWindows((hWnd, lParam) =>
+ {
+ try
+ {
+ if (IsWindow(hWnd) && IsWindowVisible(hWnd))
+ {
+ var windowInfo = GetWindowInfo(hWnd);
+ if (IsWpsWindow(windowInfo) && !string.IsNullOrEmpty(windowInfo.Title))
+ {
+ allWindows.Add(windowInfo);
+ }
+ }
+ }
+ catch { }
+ return true;
+ }, IntPtr.Zero);
+
+ return allWindows.Count > 0;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查任务栏WPS窗口失败: {ex}", LogHelper.LogType.Error);
+ return true; // 出错时保守处理,认为仍有窗口
+ }
+ }
+
+ ///
+ /// 安全地结束WPS进程
+ ///
+ private void SafeTerminateWpsProcess()
+ {
+ try
+ {
+ if (_wpsProcess == null || _wpsProcess.HasExited)
+ {
+ LogHelper.WriteLogToFile("WPS进程已经结束,无需查杀", LogHelper.LogType.Trace);
+ StopWpsProcessCheckTimer();
+ return;
+ }
+
+ LogHelper.WriteLogToFile($"开始安全结束WPS进程 (PID: {_wpsProcess.Id})", LogHelper.LogType.Event);
+
+ // 尝试优雅关闭
+ try
+ {
+ _wpsProcess.CloseMainWindow();
+ if (_wpsProcess.WaitForExit(3000)) // 等待3秒
+ {
+ LogHelper.WriteLogToFile("WPS进程已优雅关闭", LogHelper.LogType.Event);
+ StopWpsProcessCheckTimer();
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"优雅关闭WPS进程失败: {ex}", LogHelper.LogType.Warning);
+ }
+
+ // 强制结束
try
{
if (!_wpsProcess.HasExited)
{
_wpsProcess.Kill();
- LogHelper.WriteLogToFile("成功结束WPS进程", LogHelper.LogType.Event);
+ LogHelper.WriteLogToFile("WPS进程已强制结束", LogHelper.LogType.Event);
}
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"结束WPS进程失败: {ex}", LogHelper.LogType.Error);
- }
- finally
- {
- StopWpsProcessCheckTimer();
+ LogHelper.WriteLogToFile($"强制结束WPS进程失败: {ex}", LogHelper.LogType.Error);
}
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"WPS 进程检测失败: {ex}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"安全结束WPS进程时发生异常: {ex}", LogHelper.LogType.Error);
+ }
+ finally
+ {
StopWpsProcessCheckTimer();
}
}
@@ -1193,16 +1362,41 @@ namespace Ink_Canvas.Helpers
try
{
var pname = process.ProcessName.ToLower();
- if ((pname.Contains("wps") || pname.Contains("wpp") || pname.Contains("presentation"))
- && !pname.Contains("powerpnt")
- && !pname.Contains("office")
- && !pname.Contains("onenote")
- && !pname.Contains("excel")
- && !pname.Contains("word")
- && !pname.Contains("outlook")
- && !pname.Contains("microsoft"))
+
+ // 精确的WPS进程名匹配,避免误杀
+ var exactWpsNames = new[] { "wps", "wpp", "et", "wpspdf", "wpsoffice" };
+ var microsoftOfficeNames = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "winword", "msaccess" };
+
+ // 排除微软Office进程
+ if (microsoftOfficeNames.Any(name => pname.Contains(name)))
{
- wpsProcesses.Add(process);
+ continue;
+ }
+
+ // 精确匹配WPS进程名
+ bool isWpsProcess = exactWpsNames.Any(name => pname.Equals(name) || pname.StartsWith(name + "."));
+
+ // 额外验证:检查进程路径
+ if (isWpsProcess)
+ {
+ try
+ {
+ var processPath = process.MainModule?.FileName?.ToLower() ?? "";
+ if (processPath.Contains("kingsoft") || processPath.Contains("wps office"))
+ {
+ wpsProcesses.Add(process);
+ LogHelper.WriteLogToFile($"检测到WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace);
+ }
+ }
+ catch
+ {
+ // 无法访问进程路径时,基于进程名判断
+ if (exactWpsNames.Contains(pname))
+ {
+ wpsProcesses.Add(process);
+ LogHelper.WriteLogToFile($"基于进程名检测到WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace);
+ }
+ }
}
}
catch (Exception ex)
@@ -1215,6 +1409,8 @@ namespace Ink_Canvas.Helpers
{
LogHelper.WriteLogToFile($"获取WPS进程失败: {ex}", LogHelper.LogType.Error);
}
+
+ LogHelper.WriteLogToFile($"共检测到{wpsProcesses.Count}个WPS进程", LogHelper.LogType.Trace);
return wpsProcesses;
}
From 5934abd448fed5596b4b359376c07e4e44d07e08 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 01:31:21 +0800
Subject: [PATCH 07/22] =?UTF-8?q?fix:=E8=BF=9B=E9=80=80=E7=99=BD=E6=9D=BF?=
=?UTF-8?q?=E5=A2=A8=E8=BF=B9=E6=B6=88=E5=A4=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../MainWindow_cs/MW_FloatingBarIcons.cs | 50 ++++++-------------
Ink Canvas/MainWindow_cs/MW_PPT.cs | 2 +
2 files changed, 18 insertions(+), 34 deletions(-)
diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
index 1ebf3391..f457c4aa 100644
--- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
+++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
@@ -1333,14 +1333,10 @@ namespace Ink_Canvas {
if (currentMode != 0) {
SaveStrokes();
- // 检查是否在PPT放映模式,如果不在则不恢复可能包含PPT墨迹的备份
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) {
- RestoreStrokes(true);
- } else {
- // 不在PPT模式时,清空备份以避免显示已结束PPT的墨迹
- TimeMachineHistories[0] = null;
- timeMachine.ClearStrokeHistory();
- }
+ // 总是恢复备份墨迹,不管是否在PPT模式
+ // PPT墨迹和白板墨迹应该分别管理,不应该互相影响
+ RestoreStrokes(true);
+ LogHelper.WriteLogToFile($"退出白板模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace);
}
if (BtnSwitchTheme.Content.ToString() == "浅色")
@@ -1795,16 +1791,10 @@ namespace Ink_Canvas {
SaveStrokes(true);
ClearStrokes(true);
- // 检查是否在PPT放映模式,如果不在则不恢复可能包含PPT墨迹的备份
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) {
- LogHelper.WriteLogToFile("退出白板:当前在PPT放映模式,恢复备份墨迹", LogHelper.LogType.Trace);
- RestoreStrokes();
- } else {
- // 不在PPT模式时,清空备份以避免显示已结束PPT的墨迹
- LogHelper.WriteLogToFile("退出白板:当前不在PPT放映模式,清空备份以避免显示已结束PPT的墨迹", LogHelper.LogType.Trace);
- TimeMachineHistories[0] = null;
- timeMachine.ClearStrokeHistory();
- }
+ // 总是恢复备份墨迹,不管是否在PPT模式
+ // PPT墨迹和白板墨迹应该分别管理,不应该互相影响
+ RestoreStrokes();
+ LogHelper.WriteLogToFile($"退出白板模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace);
// 退出白板时清空图片
inkCanvas.Children.Clear();
@@ -1846,14 +1836,10 @@ namespace Ink_Canvas {
SaveStrokes();
ClearStrokes(true);
- // 检查是否在PPT放映模式,如果不在则不恢复可能包含PPT墨迹的备份
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) {
- RestoreStrokes(true);
- } else {
- // 不在PPT模式时,清空备份以避免显示已结束PPT的墨迹
- TimeMachineHistories[0] = null;
- timeMachine.ClearStrokeHistory();
- }
+ // 总是恢复备份墨迹,不管是否在PPT模式
+ // PPT墨迹和白板墨迹应该分别管理,不应该互相影响
+ RestoreStrokes(true);
+ LogHelper.WriteLogToFile($"切换到桌面模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace);
// 退出白板时清空图片
inkCanvas.Children.Clear();
@@ -1891,14 +1877,10 @@ namespace Ink_Canvas {
SaveStrokes(true);
ClearStrokes(true);
- // 检查是否在PPT放映模式,如果不在则不恢复可能包含PPT墨迹的备份
- if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) {
- RestoreStrokes();
- } else {
- // 不在PPT模式时,清空备份以避免显示已结束PPT的墨迹
- TimeMachineHistories[0] = null;
- timeMachine.ClearStrokeHistory();
- }
+ // 总是恢复备份墨迹,不管是否在PPT模式
+ // PPT墨迹和白板墨迹应该分别管理,不应该互相影响
+ RestoreStrokes();
+ LogHelper.WriteLogToFile($"进入白板模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace);
BtnSwitch.Content = "屏幕";
if (BtnSwitchTheme.Content.ToString() == "浅色") {
diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs
index 695c5166..85e658aa 100644
--- a/Ink Canvas/MainWindow_cs/MW_PPT.cs
+++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs
@@ -391,6 +391,8 @@ namespace Ink_Canvas {
}
ClearStrokes(true);
+ // 清空备份历史记录,防止退出白板时恢复已结束PPT的墨迹
+ // 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上)
TimeMachineHistories[0] = null;
if (GridTransparencyFakeBackground.Background != Brushes.Transparent)
From 90c1630af4b8a3841c8a75fe26184ac015b5fc83 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 01:34:51 +0800
Subject: [PATCH 08/22] =?UTF-8?q?improve:PPT=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/PPTManager.cs | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs
index 23c8767c..ada2b7d5 100644
--- a/Ink Canvas/Helpers/PPTManager.cs
+++ b/Ink Canvas/Helpers/PPTManager.cs
@@ -174,9 +174,14 @@ namespace Ink_Canvas.Helpers
}
catch (COMException ex)
{
- // 忽略常见的COM连接错误
var hr = (uint)ex.HResult;
- if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E)
+ // 忽略常见的PowerPoint连接错误:
+ // 0x800401E3: 操作无法使用
+ // 0x80004005: 未指定错误
+ // 0x800706B5: RPC服务器不可用
+ // 0x8001010E: 应用程序调用一个已为另一线程整理的接口
+ // 0x800401F3: 无效的类字符串(PowerPoint未安装或COM组件未注册)
+ if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E && hr != 0x800401F3)
{
LogHelper.WriteLogToFile($"连接PowerPoint失败: {ex}", LogHelper.LogType.Warning);
}
@@ -212,7 +217,13 @@ namespace Ink_Canvas.Helpers
catch (COMException ex)
{
var hr = (uint)ex.HResult;
- if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E)
+ // 忽略常见的WPS连接错误:
+ // 0x800401E3: 操作无法使用
+ // 0x80004005: 未指定错误
+ // 0x800706B5: RPC服务器不可用
+ // 0x8001010E: 应用程序调用一个已为另一线程整理的接口
+ // 0x800401F3: 无效的类字符串(WPS未安装或COM组件未注册)
+ if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E && hr != 0x800401F3)
{
LogHelper.WriteLogToFile($"连接WPS失败: {ex}", LogHelper.LogType.Warning);
}
From 506ba52502a4686ac0baaebb60fc2b29bc865f1f Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 01:40:35 +0800
Subject: [PATCH 09/22] =?UTF-8?q?improve:=E7=9B=B4=E7=BA=BF=E6=8B=89?=
=?UTF-8?q?=E7=9B=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../MW_SimulatePressure&InkToShape.cs | 415 +++++++++++++++++-
1 file changed, 409 insertions(+), 6 deletions(-)
diff --git a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs
index 4f04ed8d..bcd43ce2 100644
--- a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs
@@ -653,9 +653,9 @@ namespace Ink_Canvas {
// New method: Checks if a stroke is potentially a straight line
private bool IsPotentialStraightLine(Stroke stroke) {
// 确保有足够的点来进行线条分析
- if (stroke.StylusPoints.Count < 5)
+ if (stroke.StylusPoints.Count < 5)
return false;
-
+
Point start = stroke.StylusPoints.First().ToPoint();
Point end = stroke.StylusPoints.Last().ToPoint();
double lineLength = GetDistance(start, end);
@@ -664,6 +664,14 @@ namespace Ink_Canvas {
// 线条必须足够长才考虑拉直,使用自适应阈值
if (lineLength < adaptiveThreshold)
return false;
+
+ // 新增:检查墨迹复杂度,避免将复杂图形拉直
+ if (IsComplexShape(stroke))
+ return false;
+
+ // 新增:检查是否为明显的曲线
+ if (IsObviousCurve(stroke))
+ return false;
// 获取用户设置的灵敏度值,确保使用正确的设置
double sensitivity = Settings.InkToShape.LineStraightenSensitivity;
@@ -726,7 +734,284 @@ namespace Ink_Canvas {
return true;
}
-
+
+ ///
+ /// 检查墨迹是否为复杂形状(如一团墨迹、涂鸦等)
+ ///
+ private bool IsComplexShape(Stroke stroke)
+ {
+ if (stroke.StylusPoints.Count < 10) return false;
+
+ Point start = stroke.StylusPoints.First().ToPoint();
+ Point end = stroke.StylusPoints.Last().ToPoint();
+ double lineLength = GetDistance(start, end);
+
+ // 计算墨迹的实际路径长度
+ double actualLength = 0;
+ for (int i = 1; i < stroke.StylusPoints.Count; i++)
+ {
+ Point p1 = stroke.StylusPoints[i - 1].ToPoint();
+ Point p2 = stroke.StylusPoints[i].ToPoint();
+ actualLength += GetDistance(p1, p2);
+ }
+
+ // 如果实际路径长度远大于直线距离,说明是复杂形状
+ double complexityRatio = actualLength / Math.Max(lineLength, 1);
+ if (complexityRatio > 2.5) // 实际路径是直线距离的2.5倍以上
+ {
+ Debug.WriteLine($"检测到复杂形状:复杂度比率 = {complexityRatio:F2}");
+ return true;
+ }
+
+ // 检查方向变化次数
+ int directionChanges = CountDirectionChanges(stroke);
+ int maxAllowedChanges = Math.Max(3, stroke.StylusPoints.Count / 20); // 动态阈值
+ if (directionChanges > maxAllowedChanges)
+ {
+ Debug.WriteLine($"检测到复杂形状:方向变化次数 = {directionChanges},阈值 = {maxAllowedChanges}");
+ return true;
+ }
+
+ // 检查是否有明显的回环或重叠
+ if (HasSignificantLoops(stroke))
+ {
+ Debug.WriteLine("检测到复杂形状:存在明显回环");
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// 检查是否为明显的曲线(如圆弧、抛物线等)
+ ///
+ private bool IsObviousCurve(Stroke stroke)
+ {
+ if (stroke.StylusPoints.Count < 10) return false;
+
+ Point start = stroke.StylusPoints.First().ToPoint();
+ Point end = stroke.StylusPoints.Last().ToPoint();
+ double lineLength = GetDistance(start, end);
+
+ // 检查曲率一致性
+ if (HasConsistentCurvature(stroke))
+ {
+ Debug.WriteLine("检测到明显曲线:曲率一致");
+ return true;
+ }
+
+ // 检查中点偏移(对圆弧特别有效)
+ int midIndex = stroke.StylusPoints.Count / 2;
+ Point midPoint = stroke.StylusPoints[midIndex].ToPoint();
+ double midDeviation = DistanceFromLineToPoint(start, end, midPoint);
+
+ // 如果中点偏移超过线长的15%,且偏移方向一致,可能是圆弧
+ if (midDeviation > lineLength * 0.15)
+ {
+ // 检查偏移方向的一致性
+ if (IsConsistentArcDirection(stroke))
+ {
+ Debug.WriteLine($"检测到明显曲线:中点偏移 = {midDeviation:F2},线长 = {lineLength:F2}");
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 计算方向变化次数
+ ///
+ private int CountDirectionChanges(Stroke stroke)
+ {
+ if (stroke.StylusPoints.Count < 3) return 0;
+
+ int changes = 0;
+ double lastAngle = 0;
+ bool hasLastAngle = false;
+
+ for (int i = 1; i < stroke.StylusPoints.Count - 1; i++)
+ {
+ Point p1 = stroke.StylusPoints[i - 1].ToPoint();
+ Point p2 = stroke.StylusPoints[i].ToPoint();
+ Point p3 = stroke.StylusPoints[i + 1].ToPoint();
+
+ // 计算角度变化
+ double angle1 = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
+ double angle2 = Math.Atan2(p3.Y - p2.Y, p3.X - p2.X);
+ double angleDiff = Math.Abs(angle2 - angle1);
+
+ // 处理角度跨越问题
+ if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
+
+ // 如果角度变化超过30度,认为是方向变化
+ if (angleDiff > Math.PI / 6) // 30度
+ {
+ if (hasLastAngle && Math.Abs(angleDiff - lastAngle) > Math.PI / 12) // 15度
+ {
+ changes++;
+ }
+ lastAngle = angleDiff;
+ hasLastAngle = true;
+ }
+ }
+
+ return changes;
+ }
+
+ ///
+ /// 检查是否有明显的回环
+ ///
+ private bool HasSignificantLoops(Stroke stroke)
+ {
+ if (stroke.StylusPoints.Count < 20) return false;
+
+ // 检查起点和终点是否接近(可能是闭合图形)
+ Point start = stroke.StylusPoints.First().ToPoint();
+ Point end = stroke.StylusPoints.Last().ToPoint();
+ double startEndDistance = GetDistance(start, end);
+
+ // 计算平均点间距
+ double totalDistance = 0;
+ for (int i = 1; i < stroke.StylusPoints.Count; i++)
+ {
+ Point p1 = stroke.StylusPoints[i - 1].ToPoint();
+ Point p2 = stroke.StylusPoints[i].ToPoint();
+ totalDistance += GetDistance(p1, p2);
+ }
+ double avgPointDistance = totalDistance / (stroke.StylusPoints.Count - 1);
+
+ // 如果起点和终点很接近,可能是闭合图形
+ if (startEndDistance < avgPointDistance * 5)
+ {
+ return true;
+ }
+
+ // 检查是否有点重复经过相似区域
+ int overlapCount = 0;
+ double overlapThreshold = avgPointDistance * 3;
+
+ for (int i = 0; i < stroke.StylusPoints.Count - 10; i += 5)
+ {
+ Point p1 = stroke.StylusPoints[i].ToPoint();
+ for (int j = i + 10; j < stroke.StylusPoints.Count; j += 5)
+ {
+ Point p2 = stroke.StylusPoints[j].ToPoint();
+ if (GetDistance(p1, p2) < overlapThreshold)
+ {
+ overlapCount++;
+ if (overlapCount > 3) return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 检查曲率是否一致(用于识别圆弧等规则曲线)
+ ///
+ private bool HasConsistentCurvature(Stroke stroke)
+ {
+ if (stroke.StylusPoints.Count < 15) return false;
+
+ List curvatures = new List();
+
+ // 计算每个点的曲率
+ for (int i = 2; i < stroke.StylusPoints.Count - 2; i++)
+ {
+ Point p1 = stroke.StylusPoints[i - 2].ToPoint();
+ Point p2 = stroke.StylusPoints[i].ToPoint();
+ Point p3 = stroke.StylusPoints[i + 2].ToPoint();
+
+ double curvature = CalculateCurvature(p1, p2, p3);
+ if (!double.IsNaN(curvature) && !double.IsInfinity(curvature))
+ {
+ curvatures.Add(Math.Abs(curvature));
+ }
+ }
+
+ if (curvatures.Count < 5) return false;
+
+ // 计算曲率的标准差
+ double avgCurvature = curvatures.Average();
+ double variance = curvatures.Select(c => Math.Pow(c - avgCurvature, 2)).Average();
+ double stdDev = Math.Sqrt(variance);
+
+ // 如果曲率变化很小且平均曲率不为零,可能是规则曲线
+ return avgCurvature > 0.001 && stdDev / avgCurvature < 0.5;
+ }
+
+ ///
+ /// 检查圆弧方向是否一致
+ ///
+ private bool IsConsistentArcDirection(Stroke stroke)
+ {
+ if (stroke.StylusPoints.Count < 10) return false;
+
+ Point start = stroke.StylusPoints.First().ToPoint();
+ Point end = stroke.StylusPoints.Last().ToPoint();
+
+ int positiveDeviations = 0;
+ int negativeDeviations = 0;
+
+ // 检查多个点相对于直线的偏移方向
+ for (int i = 1; i < stroke.StylusPoints.Count - 1; i += Math.Max(1, stroke.StylusPoints.Count / 10))
+ {
+ Point p = stroke.StylusPoints[i].ToPoint();
+ double signedDistance = SignedDistanceFromLineToPoint(start, end, p);
+
+ if (Math.Abs(signedDistance) > 5) // 忽略很小的偏移
+ {
+ if (signedDistance > 0) positiveDeviations++;
+ else negativeDeviations++;
+ }
+ }
+
+ // 如果大部分点都在直线的同一侧,说明是一致的弧形
+ int totalSignificantDeviations = positiveDeviations + negativeDeviations;
+ if (totalSignificantDeviations < 3) return false;
+
+ double consistency = Math.Max(positiveDeviations, negativeDeviations) / (double)totalSignificantDeviations;
+ return consistency > 0.8; // 80%的点在同一侧
+ }
+
+ ///
+ /// 计算三点的曲率
+ ///
+ private double CalculateCurvature(Point p1, Point p2, Point p3)
+ {
+ // 使用三点计算曲率的公式
+ double a = GetDistance(p1, p2);
+ double b = GetDistance(p2, p3);
+ double c = GetDistance(p1, p3);
+
+ if (a == 0 || b == 0 || c == 0) return 0;
+
+ // 使用海伦公式计算面积
+ double s = (a + b + c) / 2;
+ double area = Math.Sqrt(s * (s - a) * (s - b) * (s - c));
+
+ // 曲率 = 4 * 面积 / (a * b * c)
+ return 4 * area / (a * b * c);
+ }
+
+ ///
+ /// 计算点到直线的有符号距离
+ ///
+ private double SignedDistanceFromLineToPoint(Point lineStart, Point lineEnd, Point point)
+ {
+ // 使用叉积计算有符号距离
+ double dx = lineEnd.X - lineStart.X;
+ double dy = lineEnd.Y - lineStart.Y;
+ double lineLength = Math.Sqrt(dx * dx + dy * dy);
+
+ if (lineLength == 0) return 0;
+
+ return ((lineEnd.Y - lineStart.Y) * point.X - (lineEnd.X - lineStart.X) * point.Y +
+ lineEnd.X * lineStart.Y - lineEnd.Y * lineStart.X) / lineLength;
+ }
+
// New method: Determines if a stroke should be straightened into a line
private bool ShouldStraightenLine(Stroke stroke) {
Point start = stroke.StylusPoints.First().ToPoint();
@@ -737,8 +1022,24 @@ namespace Ink_Canvas {
double adaptiveThreshold = Settings.Canvas.AutoStraightenLineThreshold * GetResolutionScale();
// 如果线条太短,不进行拉直处理,使用自适应阈值
if (lineLength < adaptiveThreshold) {
- // 显示调试信息 - 线条长度不足
- // MessageBox.Show($"线条太短: {lineLength} < {Settings.Canvas.AutoStraightenLineThreshold}", "调试信息");
+ Debug.WriteLine($"线条太短: {lineLength} < {adaptiveThreshold}");
+ return false;
+ }
+
+ // 新增:再次检查复杂度(双重保险)
+ if (IsComplexShape(stroke))
+ {
+ Debug.WriteLine("拒绝拉直:检测到复杂形状");
+ return false;
+ }
+
+ // 新增:检查线条的直线度评分
+ double straightnessScore = CalculateStraightnessScore(stroke);
+ double minStraightnessThreshold = 0.7; // 最低直线度要求
+
+ if (straightnessScore < minStraightnessThreshold)
+ {
+ Debug.WriteLine($"拒绝拉直:直线度评分过低 {straightnessScore:F3} < {minStraightnessThreshold}");
return false;
}
@@ -976,9 +1277,111 @@ namespace Ink_Canvas {
}
}
- Debug.WriteLine("接受拉直");
+ Debug.WriteLine($"接受拉直:直线度评分 = {straightnessScore:F3}");
return true;
}
+
+ ///
+ /// 计算墨迹的直线度评分(0-1,1表示完美直线)
+ ///
+ private double CalculateStraightnessScore(Stroke stroke)
+ {
+ if (stroke.StylusPoints.Count < 3) return 0;
+
+ Point start = stroke.StylusPoints.First().ToPoint();
+ Point end = stroke.StylusPoints.Last().ToPoint();
+ double lineLength = GetDistance(start, end);
+
+ if (lineLength == 0) return 0;
+
+ // 1. 计算偏差评分(基于点到直线的距离)
+ double totalDeviation = 0;
+ double maxDeviation = 0;
+ int pointCount = 0;
+
+ foreach (StylusPoint sp in stroke.StylusPoints)
+ {
+ Point p = sp.ToPoint();
+ double deviation = DistanceFromLineToPoint(start, end, p);
+ totalDeviation += deviation;
+ maxDeviation = Math.Max(maxDeviation, deviation);
+ pointCount++;
+ }
+
+ double avgDeviation = totalDeviation / pointCount;
+
+ // 偏差评分:基于平均偏差和最大偏差
+ double deviationScore = Math.Max(0, 1 - (avgDeviation / (lineLength * 0.05)) - (maxDeviation / (lineLength * 0.1)));
+
+ // 2. 计算方向一致性评分
+ double directionScore = CalculateDirectionConsistency(stroke);
+
+ // 3. 计算路径效率评分(实际路径长度 vs 直线距离)
+ double actualLength = 0;
+ for (int i = 1; i < stroke.StylusPoints.Count; i++)
+ {
+ Point p1 = stroke.StylusPoints[i - 1].ToPoint();
+ Point p2 = stroke.StylusPoints[i].ToPoint();
+ actualLength += GetDistance(p1, p2);
+ }
+ double efficiencyScore = Math.Max(0, Math.Min(1, lineLength / actualLength));
+
+ // 4. 计算端点连接度评分(起点到终点的直接性)
+ double endpointScore = 1.0; // 默认满分,因为我们已经有了起点和终点
+
+ // 综合评分(加权平均)
+ double finalScore = (deviationScore * 0.4 + directionScore * 0.3 + efficiencyScore * 0.2 + endpointScore * 0.1);
+
+ Debug.WriteLine($"直线度评分详情: 偏差={deviationScore:F3}, 方向={directionScore:F3}, 效率={efficiencyScore:F3}, 综合={finalScore:F3}");
+
+ return Math.Max(0, Math.Min(1, finalScore));
+ }
+
+ ///
+ /// 计算方向一致性评分
+ ///
+ private double CalculateDirectionConsistency(Stroke stroke)
+ {
+ if (stroke.StylusPoints.Count < 5) return 1.0;
+
+ Point start = stroke.StylusPoints.First().ToPoint();
+ Point end = stroke.StylusPoints.Last().ToPoint();
+
+ // 目标方向
+ double targetAngle = Math.Atan2(end.Y - start.Y, end.X - start.X);
+
+ double totalAngleDifference = 0;
+ int segmentCount = 0;
+
+ // 计算每个线段与目标方向的角度差
+ for (int i = 1; i < stroke.StylusPoints.Count; i++)
+ {
+ Point p1 = stroke.StylusPoints[i - 1].ToPoint();
+ Point p2 = stroke.StylusPoints[i].ToPoint();
+
+ double segmentLength = GetDistance(p1, p2);
+ if (segmentLength < 2) continue; // 忽略太短的线段
+
+ double segmentAngle = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
+ double angleDiff = Math.Abs(segmentAngle - targetAngle);
+
+ // 处理角度跨越问题
+ if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
+
+ totalAngleDifference += angleDiff;
+ segmentCount++;
+ }
+
+ if (segmentCount == 0) return 1.0;
+
+ double avgAngleDifference = totalAngleDifference / segmentCount;
+
+ // 将角度差转换为评分(0-1)
+ // 0度差 = 1分,90度差 = 0分
+ double directionScore = Math.Max(0, 1 - (avgAngleDifference / (Math.PI / 2)));
+
+ return directionScore;
+ }
// New method: Creates a straight line stroke between two points
private StylusPointCollection CreateStraightLine(Point start, Point end) {
From 7182f3554aa8f6298ef087983ba20cb5b3d06ae0 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 02:33:19 +0800
Subject: [PATCH 10/22] Revert "fix:issue #110"
This reverts commit f8e4732dcdfc1ca9e2f45e3cb2ede6f1e8a5a796.
---
Ink Canvas/App.xaml.cs | 6 +++---
Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs | 12 ++++--------
Ink Canvas/MainWindow_cs/MW_TrayIcon.cs | 9 ++++-----
3 files changed, 11 insertions(+), 16 deletions(-)
diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs
index 0e97be05..299c22e5 100644
--- a/Ink Canvas/App.xaml.cs
+++ b/Ink Canvas/App.xaml.cs
@@ -423,7 +423,7 @@ namespace Ink_Canvas
try
{
string exePath = Process.GetCurrentProcess().MainModule.FileName;
- Process.Start(exePath, "-m");
+ Process.Start(exePath);
}
catch { }
Environment.Exit(1);
@@ -654,7 +654,7 @@ namespace Ink_Canvas
try
{
string exePath = Process.GetCurrentProcess().MainModule.FileName;
- Process.Start(exePath, "-m");
+ Process.Start(exePath);
}
catch { }
Environment.Exit(1);
@@ -714,7 +714,7 @@ namespace Ink_Canvas
Environment.Exit(1);
}
string exePath = Process.GetCurrentProcess().MainModule.FileName;
- Process.Start(exePath, "-m");
+ Process.Start(exePath);
}
// CrashActionType.NoAction 时不重启,直接退出
}
diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
index f457c4aa..6bd57502 100644
--- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
+++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
@@ -1654,14 +1654,10 @@ namespace Ink_Canvas {
}
public void BtnRestart_Click(object sender, RoutedEventArgs e) {
- try {
- Process.Start(System.Windows.Forms.Application.ExecutablePath, "-m");
- App.IsAppExitByUser = true;
- CloseIsFromButton = true;
- Application.Current.Shutdown();
- } catch (Exception ex) {
- LogHelper.NewLog($"重启程序时出错: {ex.Message}");
- }
+ Process.Start(System.Windows.Forms.Application.ExecutablePath, "-m");
+ App.IsAppExitByUser = true;
+ CloseIsFromButton = true;
+ Application.Current.Shutdown();
}
private void SettingsOverlayClick(object sender, MouseButtonEventArgs e) {
diff --git a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs
index c182ced8..d3c22217 100644
--- a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs
+++ b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs
@@ -62,21 +62,20 @@ namespace Ink_Canvas
var mainWin = (MainWindow)Current.MainWindow;
if (mainWin.IsLoaded) {
IsAppExitByUser = true;
-
+
try {
- // 启动新实例,添加 -m 参数允许多实例启动
+ // 启动新实例
string exePath = Process.GetCurrentProcess().MainModule.FileName;
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = exePath;
- startInfo.Arguments = "-m";
startInfo.UseShellExecute = true;
-
+
// 启动进程但不等待
Process.Start(startInfo);
} catch (Exception ex) {
LogHelper.NewLog($"重启程序时出错: {ex.Message}");
}
-
+
// 退出当前实例
Current.Shutdown();
}
From 497a820ba249c11980c5251f0cadaaab0c34e3f0 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 08:44:17 +0800
Subject: [PATCH 11/22] =?UTF-8?q?improve:PPT=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/PPTManager.cs | 99 +++++++++++++++++++++++++++++---
1 file changed, 91 insertions(+), 8 deletions(-)
diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs
index ada2b7d5..04431888 100644
--- a/Ink Canvas/Helpers/PPTManager.cs
+++ b/Ink Canvas/Helpers/PPTManager.cs
@@ -1113,7 +1113,7 @@ namespace Ink_Canvas.Helpers
}
///
- /// 安全地结束WPS进程
+ /// 安全地结束WPS进程 - 通过释放PPTCOM对象
///
private void SafeTerminateWpsProcess()
{
@@ -1126,29 +1126,99 @@ namespace Ink_Canvas.Helpers
return;
}
- LogHelper.WriteLogToFile($"开始安全结束WPS进程 (PID: {_wpsProcess.Id})", LogHelper.LogType.Event);
+ LogHelper.WriteLogToFile($"开始通过释放PPTCOM对象安全结束WPS进程 (PID: {_wpsProcess.Id})", LogHelper.LogType.Event);
- // 尝试优雅关闭
+ // 第一步:释放 pptActWindow 对象(SlideShowWindow)
+ SlideShowWindow pptActWindow = null;
try
{
- _wpsProcess.CloseMainWindow();
- if (_wpsProcess.WaitForExit(3000)) // 等待3秒
+ if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
{
- LogHelper.WriteLogToFile("WPS进程已优雅关闭", LogHelper.LogType.Event);
+ if (PPTApplication.SlideShowWindows?.Count > 0)
+ {
+ pptActWindow = PPTApplication.SlideShowWindows[1];
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"获取SlideShowWindow对象时发生异常: {ex}", LogHelper.LogType.Warning);
+ }
+
+ if (pptActWindow != null)
+ {
+ Marshal.ReleaseComObject(pptActWindow);
+ pptActWindow = null;
+ LogHelper.WriteLogToFile("已释放pptActWindow对象", LogHelper.LogType.Trace);
+ }
+
+ // 第二步:释放 pptActDoc 对象(CurrentPresentation)
+ Presentation pptActDoc = CurrentPresentation;
+ if (pptActDoc != null)
+ {
+ Marshal.ReleaseComObject(pptActDoc);
+ pptActDoc = null;
+ CurrentPresentation = null;
+ LogHelper.WriteLogToFile("已释放pptActDoc对象", LogHelper.LogType.Trace);
+ }
+
+ // 第三步:释放 pptApp 对象(PPTApplication)
+ Microsoft.Office.Interop.PowerPoint.Application pptApp = PPTApplication;
+ if (pptApp != null)
+ {
+ Marshal.ReleaseComObject(pptApp);
+ pptApp = null;
+ PPTApplication = null;
+ LogHelper.WriteLogToFile("已释放pptApp对象", LogHelper.LogType.Trace);
+ }
+
+ // 第四步:强制垃圾回收及等待终结器执行
+ LogHelper.WriteLogToFile("执行强制垃圾回收", LogHelper.LogType.Trace);
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ // 等待一段时间让COM对象完全释放
+ Thread.Sleep(1000);
+
+ // 检查进程是否已经结束
+ try
+ {
+ _wpsProcess.Refresh();
+ if (_wpsProcess.HasExited)
+ {
+ LogHelper.WriteLogToFile("WPS进程已通过COM对象释放成功结束", LogHelper.LogType.Event);
StopWpsProcessCheckTimer();
return;
}
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"优雅关闭WPS进程失败: {ex}", LogHelper.LogType.Warning);
+ LogHelper.WriteLogToFile($"检查WPS进程状态失败: {ex}", LogHelper.LogType.Warning);
}
- // 强制结束
+ // 备用方案:如果COM对象释放后进程仍未结束,尝试关闭
+ try
+ {
+ LogHelper.WriteLogToFile("COM对象释放后进程仍在运行,尝试关闭", LogHelper.LogType.Warning);
+ _wpsProcess.CloseMainWindow();
+ if (_wpsProcess.WaitForExit(3000)) // 等待3秒
+ {
+ LogHelper.WriteLogToFile("WPS进程已关闭", LogHelper.LogType.Event);
+ StopWpsProcessCheckTimer();
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"关闭WPS进程失败: {ex}", LogHelper.LogType.Warning);
+ }
+
+ // 最后备用方案:强制结束进程
try
{
if (!_wpsProcess.HasExited)
{
+ LogHelper.WriteLogToFile("所有方法都失败,强制结束WPS进程", LogHelper.LogType.Warning);
_wpsProcess.Kill();
LogHelper.WriteLogToFile("WPS进程已强制结束", LogHelper.LogType.Event);
}
@@ -1164,6 +1234,19 @@ namespace Ink_Canvas.Helpers
}
finally
{
+ // 确保清理状态
+ if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide))
+ {
+ try { Marshal.ReleaseComObject(CurrentSlide); } catch { }
+ }
+ if (CurrentSlides != null && Marshal.IsComObject(CurrentSlides))
+ {
+ try { Marshal.ReleaseComObject(CurrentSlides); } catch { }
+ }
+ CurrentSlide = null;
+ CurrentSlides = null;
+ CurrentPresentation = null;
+ SlidesCount = 0;
StopWpsProcessCheckTimer();
}
}
From 2140e1ebe17e0e19a719a01c370476716f068434 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 09:02:03 +0800
Subject: [PATCH 12/22] =?UTF-8?q?improve:=E5=A2=A8=E8=BF=B9=E5=B9=B3?=
=?UTF-8?q?=E6=BB=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/AdvancedBezierSmoothing.cs | 6 ++---
.../HardwareAcceleratedInkProcessor.cs | 22 +++++++++----------
Ink Canvas/Helpers/InkSmoothingManager.cs | 7 ++++--
Ink Canvas/Resources/Settings.cs | 2 +-
4 files changed, 20 insertions(+), 17 deletions(-)
diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs
index 8dae35a3..c7701754 100644
--- a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs
+++ b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs
@@ -28,7 +28,7 @@ namespace Ink_Canvas.Helpers
public double SmoothingStrength { get; set; } = 0.3; // 大幅降低强度
public double ResampleInterval { get; set; } = 3.0; // 大幅增加间隔减少点数
- public int InterpolationSteps { get; set; } = 4; // 极大减少插值步数
+ public int InterpolationSteps { get; set; } = 8; // 从4增加到8,提高插值步数
public bool UseHardwareAcceleration { get; set; } = true;
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
@@ -354,7 +354,7 @@ namespace Ink_Canvas.Helpers
{
public double SmoothingStrength { get; set; } = 0.3;
public double ResampleInterval { get; set; } = 3.0;
- public int InterpolationSteps { get; set; } = 4;
+ public int InterpolationSteps { get; set; } = 8;
public Stroke SmoothStroke(Stroke stroke)
{
@@ -457,7 +457,7 @@ namespace Ink_Canvas.Helpers
return result;
}
- private List SlidingBezierFit(List points, int window = 4, int steps = 24)
+ private List SlidingBezierFit(List points, int window = 4, int steps = 48) // 从24增加到48
{
var result = new List();
if (points.Count < window) return points;
diff --git a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs
index 42f9f7f8..9ac6f811 100644
--- a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs
+++ b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs
@@ -74,13 +74,13 @@ namespace Ink_Canvas.Helpers
pathFigure.StartPoint = new Point(points[0].X, points[0].Y);
- // 使用贝塞尔曲线段创建平滑路径
- for (int i = 0; i < points.Count - 1; i += 3)
+ // 使用贝塞尔曲线段创建平滑路径,增加插点密度
+ for (int i = 0; i < points.Count - 1; i += 2) // 从i+=3改为i+=2,增加插点密度
{
var p1 = i + 1 < points.Count ? new Point(points[i + 1].X, points[i + 1].Y) : pathFigure.StartPoint;
var p2 = i + 2 < points.Count ? new Point(points[i + 2].X, points[i + 2].Y) : p1;
var p3 = i + 3 < points.Count ? new Point(points[i + 3].X, points[i + 3].Y) : p2;
-
+
var bezierSegment = new BezierSegment(p1, p2, p3, true);
pathFigure.Segments.Add(bezierSegment);
}
@@ -145,7 +145,7 @@ namespace Ink_Canvas.Helpers
///
/// 使用GPU加速的并行贝塞尔计算
///
- public static StylusPoint[] ParallelBezierInterpolation(StylusPoint[] controlPoints, int segments = 16)
+ public static StylusPoint[] ParallelBezierInterpolation(StylusPoint[] controlPoints, int segments = 32)
{
if (controlPoints.Length < 4) return controlPoints;
@@ -212,13 +212,13 @@ namespace Ink_Canvas.Helpers
///
public class InkSmoothingConfig
{
- public InkSmoothingQuality Quality { get; set; } = InkSmoothingQuality.Balanced;
+ public InkSmoothingQuality Quality { get; set; } = InkSmoothingQuality.HighQuality;
public bool UseHardwareAcceleration { get; set; } = true;
public bool UseAsyncProcessing { get; set; } = true;
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
- public double SmoothingStrength { get; set; } = 0.6;
- public double ResampleInterval { get; set; } = 1.2;
- public int InterpolationSteps { get; set; } = 16;
+ public double SmoothingStrength { get; set; } = 0.8; // 高质量模式的平滑强度
+ public double ResampleInterval { get; set; } = 0.8; // 高质量模式的重采样间隔
+ public int InterpolationSteps { get; set; } = 64; // 高质量模式的插值步数
public static InkSmoothingConfig FromSettings()
{
@@ -239,17 +239,17 @@ namespace Ink_Canvas.Helpers
case InkSmoothingQuality.HighPerformance:
SmoothingStrength = 0.4;
ResampleInterval = 2.0;
- InterpolationSteps = 8;
+ InterpolationSteps = 16;
break;
case InkSmoothingQuality.Balanced:
SmoothingStrength = 0.6;
ResampleInterval = 1.2;
- InterpolationSteps = 16;
+ InterpolationSteps = 32;
break;
case InkSmoothingQuality.HighQuality:
SmoothingStrength = 0.8;
ResampleInterval = 0.8;
- InterpolationSteps = 32;
+ InterpolationSteps = 64;
break;
}
}
diff --git a/Ink Canvas/Helpers/InkSmoothingManager.cs b/Ink Canvas/Helpers/InkSmoothingManager.cs
index f28331c2..c6490e41 100644
--- a/Ink Canvas/Helpers/InkSmoothingManager.cs
+++ b/Ink Canvas/Helpers/InkSmoothingManager.cs
@@ -194,15 +194,17 @@ namespace Ink_Canvas.Helpers
var processorCount = Environment.ProcessorCount;
var isHardwareAccelerated = IsHardwareAccelerationSupported();
- if (processorCount >= 8 && isHardwareAccelerated)
+ if (processorCount >= 4 && isHardwareAccelerated)
{
+ // 降低高质量模式的门槛,4核以上且支持硬件加速就使用高质量
config.Quality = InkSmoothingQuality.HighQuality;
config.UseHardwareAcceleration = true;
config.UseAsyncProcessing = true;
config.MaxConcurrentTasks = Math.Min(processorCount, 8);
}
- else if (processorCount >= 4)
+ else if (processorCount >= 2)
{
+ // 2核以上使用平衡模式
config.Quality = InkSmoothingQuality.Balanced;
config.UseHardwareAcceleration = isHardwareAccelerated;
config.UseAsyncProcessing = true;
@@ -210,6 +212,7 @@ namespace Ink_Canvas.Helpers
}
else
{
+ // 单核或性能较低的设备使用高性能模式
config.Quality = InkSmoothingQuality.HighPerformance;
config.UseHardwareAcceleration = false;
config.UseAsyncProcessing = false;
diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs
index 17d7b2d5..8d9291f2 100644
--- a/Ink Canvas/Resources/Settings.cs
+++ b/Ink Canvas/Resources/Settings.cs
@@ -56,7 +56,7 @@ namespace Ink_Canvas
[JsonProperty("useHardwareAcceleration")]
public bool UseHardwareAcceleration { get; set; } = true; // 默认启用硬件加速
[JsonProperty("inkSmoothingQuality")]
- public int InkSmoothingQuality { get; set; } = 1; // 0-低质量高性能, 1-平衡, 2-高质量低性能
+ public int InkSmoothingQuality { get; set; } = 2; // 0-低质量高性能, 1-平衡, 2-高质量低性能,默认为高质量
[JsonProperty("maxConcurrentSmoothingTasks")]
public int MaxConcurrentSmoothingTasks { get; set; } // 0表示自动检测CPU核心数
[JsonProperty("clearCanvasAndClearTimeMachine")]
From 351a00fb1eccbb5cd3a7b309d6ff11a04dd2fd70 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 09:08:39 +0800
Subject: [PATCH 13/22] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?=
=?UTF-8?q?=E5=8F=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/AssemblyInfo.cs | 4 ++--
Ink Canvas/Properties/AssemblyInfo.cs | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs
index 78d28eef..5c71c241 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.5.0")]
-[assembly: AssemblyFileVersion("1.7.5.0")]
+[assembly: AssemblyVersion("1.7.5.1")]
+[assembly: AssemblyFileVersion("1.7.5.1")]
diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs
index 398e3414..85f56ff9 100644
--- a/Ink Canvas/Properties/AssemblyInfo.cs
+++ b/Ink Canvas/Properties/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.5.0")]
-[assembly: AssemblyFileVersion("1.7.5.0")]
+[assembly: AssemblyVersion("1.7.5.1")]
+[assembly: AssemblyFileVersion("1.7.5.1")]
From 299a77eea84d64e57c2d5bec7f4e90c44a603f69 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 09:19:27 +0800
Subject: [PATCH 14/22] =?UTF-8?q?improve:PPT=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/PPTManager.cs | 33 ++------------------------------
1 file changed, 2 insertions(+), 31 deletions(-)
diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs
index 04431888..0f255bd8 100644
--- a/Ink Canvas/Helpers/PPTManager.cs
+++ b/Ink Canvas/Helpers/PPTManager.cs
@@ -978,17 +978,9 @@ namespace Ink_Canvas.Helpers
return;
}
- // 前台窗口已消失,检查是否需要结束进程
+ // 前台窗口已消失,准备结束WPS进程
LogHelper.WriteLogToFile("多重验证确认WPS窗口已消失,准备结束WPS进程", LogHelper.LogType.Event);
- // 检查文档保存状态
- if (!CheckAllWpsDocumentsSaved())
- {
- LogHelper.WriteLogToFile("检测到有未保存的WPS文档,跳过进程结束", LogHelper.LogType.Warning);
- StopWpsProcessCheckTimer();
- return;
- }
-
// 安全结束WPS进程
SafeTerminateWpsProcess();
}
@@ -1251,28 +1243,7 @@ namespace Ink_Canvas.Helpers
}
}
- private bool CheckAllWpsDocumentsSaved()
- {
- try
- {
- if (PPTApplication?.Presentations != null)
- {
- foreach (Presentation pres in PPTApplication.Presentations)
- {
- if (pres.Saved == MsoTriState.msoFalse)
- {
- return false;
- }
- }
- }
- return true;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"检查WPS文档保存状态失败: {ex}", LogHelper.LogType.Error);
- return false;
- }
- }
+
private void StopWpsProcessCheckTimer()
{
From 6c4bfeff29bc69e092aa22e93e624d421532cff4 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 09:35:47 +0800
Subject: [PATCH 15/22] =?UTF-8?q?improve:PPT=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/PPTManager.cs | 57 ++++++++++++++++++++++++++++++--
1 file changed, 55 insertions(+), 2 deletions(-)
diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs
index 0f255bd8..0949f478 100644
--- a/Ink Canvas/Helpers/PPTManager.cs
+++ b/Ink Canvas/Helpers/PPTManager.cs
@@ -38,7 +38,35 @@ namespace Ink_Canvas.Helpers
public Slides CurrentSlides { get; private set; }
public Slide CurrentSlide { get; private set; }
public int SlidesCount { get; private set; }
- public bool IsConnected => PPTApplication != null;
+ public bool IsConnected
+ {
+ get
+ {
+ try
+ {
+ if (PPTApplication == null) return false;
+ if (!Marshal.IsComObject(PPTApplication)) return false;
+
+ // 尝试访问一个简单的属性来验证连接是否有效
+ var _ = PPTApplication.Name;
+ return true;
+ }
+ catch (COMException comEx)
+ {
+ var hr = (uint)comEx.HResult;
+ // 如果COM对象已失效,返回false
+ if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706B5)
+ {
+ return false;
+ }
+ return false;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
public bool IsInSlideShow
{
get
@@ -137,12 +165,19 @@ namespace Ink_Canvas.Helpers
pptApp = TryConnectToWPS();
}
- if (pptApp != null && PPTApplication == null)
+ if (pptApp != null && !IsConnected)
{
+ // 有可用的PPT/WPS应用程序且当前未连接,建立连接
ConnectToPPT(pptApp);
}
+ else if (pptApp == null && IsConnected)
+ {
+ // 没有可用的PPT/WPS应用程序但当前显示已连接,断开连接
+ DisconnectFromPPT();
+ }
else if (pptApp == null && PPTApplication != null)
{
+ // PPT/WPS应用程序不可用但PPTApplication对象仍存在,清理无效连接
DisconnectFromPPT();
}
}
@@ -1235,11 +1270,29 @@ namespace Ink_Canvas.Helpers
{
try { Marshal.ReleaseComObject(CurrentSlides); } catch { }
}
+ if (CurrentPresentation != null && Marshal.IsComObject(CurrentPresentation))
+ {
+ try { Marshal.ReleaseComObject(CurrentPresentation); } catch { }
+ }
+ if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
+ {
+ try { Marshal.ReleaseComObject(PPTApplication); } catch { }
+ }
+
CurrentSlide = null;
CurrentSlides = null;
CurrentPresentation = null;
+ PPTApplication = null;
SlidesCount = 0;
StopWpsProcessCheckTimer();
+
+ // 重新启动连接检查定时器,以便能够检测新的WPS实例
+ _connectionCheckTimer?.Start();
+
+ // 触发连接断开事件
+ PPTConnectionChanged?.Invoke(false);
+
+ LogHelper.WriteLogToFile("WPS进程结束后已清理所有COM对象并重启连接检查", LogHelper.LogType.Event);
}
}
From bd2fa1e4277d5d5bdb543653fa6d6acab22aafb8 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 09:43:18 +0800
Subject: [PATCH 16/22] =?UTF-8?q?improve:PPT=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/PPTManager.cs | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs
index 0949f478..59a2f553 100644
--- a/Ink Canvas/Helpers/PPTManager.cs
+++ b/Ink Canvas/Helpers/PPTManager.cs
@@ -986,13 +986,7 @@ namespace Ink_Canvas.Helpers
StopWpsProcessCheckTimer();
return;
}
-
- // 优化:增加延迟检查,避免误杀
- if (_wpsProcessCheckCount < 3) // 前6秒不进行查杀检查
- {
- LogHelper.WriteLogToFile($"WPS进程查杀延迟中,等待{6 - _wpsProcessCheckCount * 2}秒", LogHelper.LogType.Trace);
- return;
- }
+
// 检查前台WPS窗口是否存在(优化版)
bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized();
From bfb6346812fd07d4d4cf4c46b51d25a34caad08d Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 16:08:24 +0800
Subject: [PATCH 17/22] =?UTF-8?q?delete:IACore=E9=87=8A=E6=94=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/App.xaml.cs | 11 +-
Ink Canvas/Helpers/IACoreDllExtractor.cs | 168 -----------------------
Ink Canvas/InkCanvasForClass.csproj | 4 -
3 files changed, 1 insertion(+), 182 deletions(-)
delete mode 100644 Ink Canvas/Helpers/IACoreDllExtractor.cs
diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs
index 299c22e5..1237f47d 100644
--- a/Ink Canvas/App.xaml.cs
+++ b/Ink Canvas/App.xaml.cs
@@ -438,16 +438,7 @@ namespace Ink_Canvas
/*if (!StoreHelper.IsStoreApp) */RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version));
-
- // 在应用启动时自动释放IACore相关DLL
- try
- {
- Helpers.IACoreDllExtractor.ExtractIACoreDlls();
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
- }
+
// 记录应用启动(设备标识符)
DeviceIdentifier.RecordAppLaunch();
diff --git a/Ink Canvas/Helpers/IACoreDllExtractor.cs b/Ink Canvas/Helpers/IACoreDllExtractor.cs
deleted file mode 100644
index de717826..00000000
--- a/Ink Canvas/Helpers/IACoreDllExtractor.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-using System;
-using System.IO;
-using System.Reflection;
-using System.Windows;
-
-namespace Ink_Canvas.Helpers
-{
- ///
- /// IACore DLL自动释放器
- /// 在应用启动时自动释放IACore相关的DLL文件到应用程序目录
- ///
- public static class IACoreDllExtractor
- {
- private static readonly string[] RequiredDlls = {
- "IACore.dll",
- "IALoader.dll",
- "IAWinFX.dll"
- };
-
- ///
- /// 在应用启动时释放IACore相关DLL
- ///
- public static void ExtractIACoreDlls()
- {
- try
- {
- string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
- LogHelper.WriteLogToFile("开始检查并释放IACore相关DLL文件");
-
- foreach (string dllName in RequiredDlls)
- {
- string targetPath = Path.Combine(appDirectory, dllName);
-
- // 检查文件是否已存在且有效
- if (File.Exists(targetPath) && IsValidDll(targetPath))
- {
- LogHelper.WriteLogToFile($"{dllName} 已存在且有效,跳过释放");
- continue;
- }
-
- // 从嵌入资源中释放DLL
- if (ExtractDllFromResource(dllName, targetPath))
- {
- LogHelper.WriteLogToFile($"成功释放 {dllName} 到 {targetPath}");
- }
- else
- {
- LogHelper.WriteLogToFile($"警告:无法释放 {dllName},可能影响形状识别功能", LogHelper.LogType.Warning);
- }
- }
-
- LogHelper.WriteLogToFile("IACore DLL释放检查完成");
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
-
- ///
- /// 从嵌入资源中提取DLL文件
- ///
- private static bool ExtractDllFromResource(string dllName, string targetPath)
- {
- try
- {
- Assembly assembly = Assembly.GetExecutingAssembly();
- string resourceName = $"Ink_Canvas.Resources.IACore.{dllName}";
-
- using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
- {
- if (resourceStream == null)
- {
- LogHelper.WriteLogToFile($"未找到嵌入资源: {resourceName}", LogHelper.LogType.Warning);
- return false;
- }
-
- // 确保目标目录存在
- string targetDirectory = Path.GetDirectoryName(targetPath);
- if (!Directory.Exists(targetDirectory))
- {
- Directory.CreateDirectory(targetDirectory);
- }
-
- // 写入文件
- using (FileStream fileStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write))
- {
- resourceStream.CopyTo(fileStream);
- }
-
- return true;
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"从资源提取 {dllName} 失败: {ex.Message}", LogHelper.LogType.Error);
- return false;
- }
- }
-
- ///
- /// 检查DLL文件是否有效
- ///
- private static bool IsValidDll(string filePath)
- {
- try
- {
- if (!File.Exists(filePath))
- return false;
-
- FileInfo fileInfo = new FileInfo(filePath);
-
- // 检查文件大小(空文件或过小的文件可能无效)
- if (fileInfo.Length < 1024) // 小于1KB可能无效
- return false;
-
- // 简单检查PE头(DLL文件应该以MZ开头)
- using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
- {
- byte[] buffer = new byte[2];
- if (fs.Read(buffer, 0, 2) == 2)
- {
- return buffer[0] == 0x4D && buffer[1] == 0x5A; // "MZ"
- }
- }
-
- return false;
- }
- catch
- {
- return false;
- }
- }
-
- ///
- /// 清理释放的DLL文件(可选,在应用退出时调用)
- ///
- public static void CleanupExtractedDlls()
- {
- try
- {
- string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
-
- foreach (string dllName in RequiredDlls)
- {
- string filePath = Path.Combine(appDirectory, dllName);
-
- if (File.Exists(filePath))
- {
- try
- {
- File.Delete(filePath);
- LogHelper.WriteLogToFile($"已清理 {dllName}");
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"清理 {dllName} 失败: {ex.Message}", LogHelper.LogType.Warning);
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"清理IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
- }
-}
diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj
index 2c0ee1dd..ee0ac7e1 100644
--- a/Ink Canvas/InkCanvasForClass.csproj
+++ b/Ink Canvas/InkCanvasForClass.csproj
@@ -143,10 +143,6 @@
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
From 53a498c5815f1446a3728925cd24e342a52f60fc Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 16:28:22 +0800
Subject: [PATCH 18/22] fix:IACore
---
Ink Canvas/AssemblyInfo.cs | 4 ++--
Ink Canvas/InkCanvasForClass.csproj | 8 ++++----
Ink Canvas/Properties/AssemblyInfo.cs | 4 ++--
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs
index 5c71c241..49035c1e 100644
--- a/Ink Canvas/AssemblyInfo.cs
+++ b/Ink Canvas/AssemblyInfo.cs
@@ -5,11 +5,11 @@ using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("InkCanvasForClass CE")]
+[assembly: AssemblyTitle("InkCanvasForClass")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("CJK_mkp")]
-[assembly: AssemblyProduct("InkCanvasForClass CE")]
+[assembly: AssemblyProduct("InkCanvasForClass")]
[assembly: AssemblyCopyright("Copyright © HARKOTEK Studio 2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj
index ee0ac7e1..31ad10da 100644
--- a/Ink Canvas/InkCanvasForClass.csproj
+++ b/Ink Canvas/InkCanvasForClass.csproj
@@ -3,7 +3,7 @@
win;win-x86;win-x64;win-arm64
WinExe
Ink_Canvas
- InkCanvasForClass CE
+ InkCanvasForClass
net472
{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
true
@@ -113,15 +113,15 @@
.\IACore.dll
- False
+ True
.\IALoader.dll
- False
+ True
.\IAWinFX.dll
- False
+ True
diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs
index 85f56ff9..adae27eb 100644
--- a/Ink Canvas/Properties/AssemblyInfo.cs
+++ b/Ink Canvas/Properties/AssemblyInfo.cs
@@ -5,11 +5,11 @@ using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("InkCanvasForClass CE")]
+[assembly: AssemblyTitle("InkCanvasForClass")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("CJK_mkp")]
-[assembly: AssemblyProduct("InkCanvasForClass CE")]
+[assembly: AssemblyProduct("InkCanvasForClass")]
[assembly: AssemblyCopyright("© Copyright HARKOTEK Studio 2024-now")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
From 1c1dd81474ac25dddb7a84e7bbfb06b53af56bd4 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 17:38:58 +0800
Subject: [PATCH 19/22] =?UTF-8?q?fix:=E5=9B=BE=E7=89=87=E5=A2=A8=E8=BF=B9?=
=?UTF-8?q?=E4=BF=9D=E5=AD=98=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/MainWindow_cs/MW_BoardControls.cs | 107 +++++++++++++++++-
.../MainWindow_cs/MW_ElementsControls.cs | 39 +++++--
.../MainWindow_cs/MW_FloatingBarIcons.cs | 24 ++--
.../MainWindow_cs/MW_SelectionGestures.cs | 11 ++
Ink Canvas/MainWindow_cs/MW_TimeMachine.cs | 43 ++++++-
5 files changed, 192 insertions(+), 32 deletions(-)
diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs
index 3d3a0e98..0fde1b60 100644
--- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs
+++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs
@@ -24,14 +24,91 @@ namespace Ink_Canvas {
// 保存每页白板图片信息
private void SaveStrokes(bool isBackupMain = false) {
+ // 确保画布上的所有UI元素都被保存到时间机器历史记录中
+ var currentHistory = timeMachine.ExportTimeMachineHistory();
+ var elementsInHistory = new HashSet();
+
+ // 收集已经在历史记录中的元素
+ if (currentHistory != null) {
+ foreach (var h in currentHistory) {
+ if (h.CommitType == TimeMachineHistoryType.ElementInsert &&
+ h.InsertedElement != null &&
+ !h.StrokeHasBeenCleared) {
+ elementsInHistory.Add(h.InsertedElement);
+ }
+ }
+ }
+
+ // 检查画布上的所有UI元素,确保它们都在历史记录中
+ var missingElements = 0;
+ foreach (UIElement child in inkCanvas.Children) {
+ if (child is Image || child is MediaElement) {
+ if (!elementsInHistory.Contains(child)) {
+ timeMachine.CommitElementInsertHistory(child);
+ missingElements++;
+ LogHelper.WriteLogToFile($"SaveStrokes: 补充保存遗漏的UI元素 {child.GetType().Name}", LogHelper.LogType.Trace);
+ }
+ }
+ }
+
+ if (missingElements > 0) {
+ LogHelper.WriteLogToFile($"SaveStrokes: 总共补充保存了 {missingElements} 个遗漏的UI元素", LogHelper.LogType.Trace);
+ }
+
+ // 确保画布上的所有墨迹都被保存
+ if (inkCanvas.Strokes.Count > 0) {
+ // 检查是否有墨迹没有在时间机器历史记录中
+ var strokesInHistory = new HashSet();
+ if (currentHistory != null) {
+ foreach (var h in currentHistory) {
+ if (h.CommitType == TimeMachineHistoryType.UserInput &&
+ h.CurrentStroke != null &&
+ !h.StrokeHasBeenCleared) {
+ foreach (Stroke stroke in h.CurrentStroke) {
+ strokesInHistory.Add(stroke);
+ }
+ }
+ }
+ }
+
+ // 收集没有在历史记录中的墨迹
+ var missingStrokes = new StrokeCollection();
+ foreach (Stroke stroke in inkCanvas.Strokes) {
+ if (!strokesInHistory.Contains(stroke)) {
+ missingStrokes.Add(stroke);
+ }
+ }
+
+ if (missingStrokes.Count > 0) {
+ timeMachine.CommitStrokeUserInputHistory(missingStrokes);
+ LogHelper.WriteLogToFile($"SaveStrokes: 补充保存了 {missingStrokes.Count} 个遗漏的墨迹", LogHelper.LogType.Trace);
+ }
+ }
+
if (isBackupMain) {
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
TimeMachineHistories[0] = timeMachineHistory;
timeMachine.ClearStrokeHistory();
+
+ // 调试信息
+ var elementCount = timeMachineHistory?.Count(h => h.CommitType == TimeMachineHistoryType.ElementInsert && !h.StrokeHasBeenCleared) ?? 0;
+ var strokeHistoryCount = timeMachineHistory?.Count(h => h.CommitType == TimeMachineHistoryType.UserInput && !h.StrokeHasBeenCleared) ?? 0;
+ var currentCanvasElements = inkCanvas.Children.Count;
+ var currentCanvasStrokes = inkCanvas.Strokes.Count;
+ var selectedElement = selectedUIElement?.GetType().Name ?? "无";
+ LogHelper.WriteLogToFile($"SaveStrokes(备份主页面): 保存了 {elementCount} 个UI元素, {strokeHistoryCount} 个墨迹历史, 当前画布有 {currentCanvasElements} 个元素, {currentCanvasStrokes} 个墨迹, 选中元素: {selectedElement}", LogHelper.LogType.Trace);
} else {
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory;
timeMachine.ClearStrokeHistory();
+
+ // 调试信息
+ var elementCount = timeMachineHistory?.Count(h => h.CommitType == TimeMachineHistoryType.ElementInsert && !h.StrokeHasBeenCleared) ?? 0;
+ var strokeHistoryCount = timeMachineHistory?.Count(h => h.CommitType == TimeMachineHistoryType.UserInput && !h.StrokeHasBeenCleared) ?? 0;
+ var currentCanvasElements = inkCanvas.Children.Count;
+ var currentCanvasStrokes = inkCanvas.Strokes.Count;
+ var selectedElement = selectedUIElement?.GetType().Name ?? "无";
+ LogHelper.WriteLogToFile($"SaveStrokes(页面{CurrentWhiteboardIndex}): 保存了 {elementCount} 个UI元素, {strokeHistoryCount} 个墨迹历史, 当前画布有 {currentCanvasElements} 个元素, {currentCanvasStrokes} 个墨迹, 选中元素: {selectedElement}", LogHelper.LogType.Trace);
}
}
@@ -54,17 +131,29 @@ namespace Ink_Canvas {
try {
var targetIndex = isBackupMain ? 0 : CurrentWhiteboardIndex;
- // 先清空当前画布的所有内容(墨迹和图片)
- // 这里必须清除图片,因为页面切换时需要完全重置画布状态
+ // 先清空当前画布的墨迹
inkCanvas.Strokes.Clear();
+
+ // 保存当前的UI元素,稍后会被ApplyHistoryToCanvas正确处理
+ var currentElements = new List();
+ for (int i = inkCanvas.Children.Count - 1; i >= 0; i--)
+ {
+ currentElements.Add(inkCanvas.Children[i]);
+ }
inkCanvas.Children.Clear();
+ LogHelper.WriteLogToFile($"RestoreStrokes: 清空了 {currentElements.Count} 个当前UI元素", LogHelper.LogType.Trace);
// 如果历史记录为空,直接返回(新页面或空页面)
if (TimeMachineHistories[targetIndex] == null) {
timeMachine.ClearStrokeHistory();
+ LogHelper.WriteLogToFile($"RestoreStrokes({(isBackupMain ? "备份主页面" : $"页面{CurrentWhiteboardIndex}")}): 历史记录为空", LogHelper.LogType.Trace);
return;
}
+ var targetHistory = TimeMachineHistories[targetIndex];
+ var elementCount = targetHistory?.Count(h => h.CommitType == TimeMachineHistoryType.ElementInsert && !h.StrokeHasBeenCleared) ?? 0;
+ LogHelper.WriteLogToFile($"RestoreStrokes({(isBackupMain ? "备份主页面" : $"页面{CurrentWhiteboardIndex}")}): 准备恢复 {elementCount} 个UI元素", LogHelper.LogType.Trace);
+
if (isBackupMain) {
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[0]);
foreach (var item in TimeMachineHistories[0]) ApplyHistoryToCanvas(item);
@@ -73,9 +162,19 @@ namespace Ink_Canvas {
// 通过时间机器历史恢复所有内容(墨迹和图片)
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item);
}
+
+ // 恢复后检查实际的UI元素数量
+ var actualElementCount = inkCanvas.Children.Count;
+ LogHelper.WriteLogToFile($"RestoreStrokes({(isBackupMain ? "备份主页面" : $"页面{CurrentWhiteboardIndex}")}): 实际恢复了 {actualElementCount} 个UI元素", LogHelper.LogType.Trace);
+
+ // 确保选中状态被清除,因为我们切换了页面
+ if (selectedUIElement != null) {
+ DeselectUIElement();
+ LogHelper.WriteLogToFile($"RestoreStrokes: 清除了选中状态", LogHelper.LogType.Trace);
+ }
}
- catch {
- // ignored
+ catch (Exception ex) {
+ LogHelper.WriteLogToFile($"RestoreStrokes失败: {ex.Message}", LogHelper.LogType.Error);
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs
index df09130b..acdeeeef 100644
--- a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs
+++ b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs
@@ -5,6 +5,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
+using Ink_Canvas.Helpers;
using Microsoft.Win32;
namespace Ink_Canvas
@@ -28,17 +29,10 @@ namespace Ink_Canvas
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
image.Name = timestamp;
- // 新缩放逻辑:最大宽高为画布一半,并居中
- double maxWidth = inkCanvas.ActualWidth / 2;
- double maxHeight = inkCanvas.ActualHeight / 2;
- double scaleX = maxWidth / image.Width;
- double scaleY = maxHeight / image.Height;
- double scale = Math.Min(1, Math.Min(scaleX, scaleY));
- image.Width = image.Width * scale;
- image.Height = image.Height * scale;
- InkCanvas.SetLeft(image, (inkCanvas.ActualWidth - image.Width) / 2);
- InkCanvas.SetTop(image, (inkCanvas.ActualHeight - image.Height) / 2);
+ CenterAndScaleElement(image);
+ InkCanvas.SetLeft(image, 0);
+ InkCanvas.SetTop(image, 0);
inkCanvas.Children.Add(image);
// 添加鼠标事件处理,使图片可以被选择
@@ -46,6 +40,7 @@ namespace Ink_Canvas
image.IsManipulationEnabled = true;
timeMachine.CommitElementInsertHistory(image);
+ LogHelper.WriteLogToFile($"图片插入: {image.Name}, 当前画布UI元素数量: {inkCanvas.Children.Count}", LogHelper.LogType.Trace);
}
}
}
@@ -119,6 +114,8 @@ namespace Ink_Canvas
if (mediaElement != null)
{
+ CenterAndScaleElement(mediaElement);
+
InkCanvas.SetLeft(mediaElement, 0);
InkCanvas.SetTop(mediaElement, 0);
inkCanvas.Children.Add(mediaElement);
@@ -256,5 +253,27 @@ namespace Ink_Canvas
}
#endregion
+
+ private void CenterAndScaleElement(FrameworkElement element)
+ {
+ double maxWidth = SystemParameters.PrimaryScreenWidth / 2;
+ double maxHeight = SystemParameters.PrimaryScreenHeight / 2;
+
+ double scaleX = maxWidth / element.Width;
+ double scaleY = maxHeight / element.Height;
+ double scale = Math.Min(scaleX, scaleY);
+
+ TransformGroup transformGroup = new TransformGroup();
+ transformGroup.Children.Add(new ScaleTransform(scale, scale));
+
+ double canvasWidth = inkCanvas.ActualWidth;
+ double canvasHeight = inkCanvas.ActualHeight;
+ double centerX = (canvasWidth - element.Width * scale) / 2;
+ double centerY = (canvasHeight - element.Height * scale) / 2;
+
+ transformGroup.Children.Add(new TranslateTransform(centerX, centerY));
+
+ element.RenderTransform = transformGroup;
+ }
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
index 6bd57502..8f27b792 100644
--- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
+++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
@@ -1789,11 +1789,11 @@ namespace Ink_Canvas {
// 总是恢复备份墨迹,不管是否在PPT模式
// PPT墨迹和白板墨迹应该分别管理,不应该互相影响
- RestoreStrokes();
+ RestoreStrokes(true);
LogHelper.WriteLogToFile($"退出白板模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace);
- // 退出白板时清空图片
- inkCanvas.Children.Clear();
+ // 注释掉:退出白板时不应该清空图片,因为RestoreStrokes()已经恢复了正确的状态
+ // inkCanvas.Children.Clear();
if (BtnSwitchTheme.Content.ToString() == "浅色") {
BtnSwitch.Content = "黑板";
@@ -1837,8 +1837,8 @@ namespace Ink_Canvas {
RestoreStrokes(true);
LogHelper.WriteLogToFile($"切换到桌面模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace);
- // 退出白板时清空图片
- inkCanvas.Children.Clear();
+ // 注释掉:退出白板时不应该清空图片,因为RestoreStrokes()已经恢复了正确的状态
+ // inkCanvas.Children.Clear();
if (BtnSwitchTheme.Content.ToString() == "浅色") {
BtnSwitch.Content = "黑板";
@@ -2044,17 +2044,10 @@ namespace Ink_Canvas {
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
image.Name = timestamp;
- // 新缩放逻辑:最大宽高为画布一半,并居中
- double maxWidth = inkCanvas.ActualWidth / 2;
- double maxHeight = inkCanvas.ActualHeight / 2;
- double scaleX = maxWidth / image.Width;
- double scaleY = maxHeight / image.Height;
- double scale = Math.Min(1, Math.Min(scaleX, scaleY));
- image.Width = image.Width * scale;
- image.Height = image.Height * scale;
- InkCanvas.SetLeft(image, (inkCanvas.ActualWidth - image.Width) / 2);
- InkCanvas.SetTop(image, (inkCanvas.ActualHeight - image.Height) / 2);
+ CenterAndScaleElement(image);
+ InkCanvas.SetLeft(image, 0);
+ InkCanvas.SetTop(image, 0);
inkCanvas.Children.Add(image);
timeMachine.CommitElementInsertHistory(image);
@@ -2062,6 +2055,5 @@ namespace Ink_Canvas {
}
}
-
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs
index dc3c2c53..33cd8fdf 100644
--- a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs
@@ -6,6 +6,7 @@ using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
+using Ink_Canvas.Helpers;
using iNKORE.UI.WPF.Modern.Controls;
using Point = System.Windows.Point;
@@ -587,6 +588,7 @@ namespace Ink_Canvas {
}
selectedUIElement = element;
+ LogHelper.WriteLogToFile($"SelectUIElement: 设置选中元素为 {element?.GetType().Name ?? "null"}", LogHelper.LogType.Trace);
if (element != null)
{
@@ -600,11 +602,13 @@ namespace Ink_Canvas {
if (element is Image)
{
ShowImageToolbar();
+ LogHelper.WriteLogToFile($"SelectUIElement: 显示图片工具栏", LogHelper.LogType.Trace);
}
else
{
// 对于其他UI元素,显示拖拽手柄
ShowResizeHandles();
+ LogHelper.WriteLogToFile($"SelectUIElement: 显示拖拽手柄", LogHelper.LogType.Trace);
}
}
}
@@ -867,6 +871,8 @@ namespace Ink_Canvas {
private void UIElement_MouseDown(object sender, MouseButtonEventArgs e)
{
+ LogHelper.WriteLogToFile($"UIElement_MouseDown: 编辑模式={inkCanvas.EditingMode}, 元素类型={sender.GetType().Name}", LogHelper.LogType.Trace);
+
if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
{
var element = sender as UIElement;
@@ -875,9 +881,14 @@ namespace Ink_Canvas {
// 切换到选择模式并选择这个元素
inkCanvas.Select(new[] { element });
SelectUIElement(element);
+ LogHelper.WriteLogToFile($"UIElement_MouseDown: 选择了UI元素 {element.GetType().Name}", LogHelper.LogType.Trace);
e.Handled = true;
}
}
+ else
+ {
+ LogHelper.WriteLogToFile($"UIElement_MouseDown: 编辑模式不是Select,无法选择UI元素", LogHelper.LogType.Trace);
+ }
}
diff --git a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs
index d5409d6a..6303b6ec 100644
--- a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs
+++ b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs
@@ -135,12 +135,51 @@ namespace Ink_Canvas {
if (item.StrokeHasBeenCleared) {
// Undo: 移除元素
- if (item.InsertedElement != null && targetCanvas.Children.Contains(item.InsertedElement))
+ if (item.InsertedElement != null && targetCanvas.Children.Contains(item.InsertedElement)) {
targetCanvas.Children.Remove(item.InsertedElement);
+ LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 移除UI元素 {item.InsertedElement.GetType().Name}", LogHelper.LogType.Trace);
+ }
} else {
// Redo: 添加元素
- if (item.InsertedElement != null && !targetCanvas.Children.Contains(item.InsertedElement))
+ if (item.InsertedElement != null) {
+ // 确保元素不在画布上,如果在就先移除
+ if (targetCanvas.Children.Contains(item.InsertedElement)) {
+ targetCanvas.Children.Remove(item.InsertedElement);
+ LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 先移除已存在的UI元素 {item.InsertedElement.GetType().Name}", LogHelper.LogType.Trace);
+ }
+
+ // 确保元素没有其他父容器
+ if (item.InsertedElement is FrameworkElement fe && fe.Parent != null) {
+ if (fe.Parent is Panel parentPanel) {
+ parentPanel.Children.Remove(fe);
+ LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 从其他父容器移除UI元素 {item.InsertedElement.GetType().Name}", LogHelper.LogType.Trace);
+ }
+ }
+
targetCanvas.Children.Add(item.InsertedElement);
+ LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 添加UI元素 {item.InsertedElement.GetType().Name}", LogHelper.LogType.Trace);
+
+ // 重新绑定事件处理器(仅对主画布)
+ if (targetCanvas == inkCanvas) {
+ if (item.InsertedElement is Image img) {
+ img.MouseDown -= UIElement_MouseDown;
+ img.MouseDown += UIElement_MouseDown;
+ img.IsManipulationEnabled = true;
+
+ // 重新应用CenterAndScaleElement变换
+ CenterAndScaleElement(img);
+ LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 重新配置图片元素 {img.Name}", LogHelper.LogType.Trace);
+ } else if (item.InsertedElement is MediaElement media) {
+ media.MouseDown -= UIElement_MouseDown;
+ media.MouseDown += UIElement_MouseDown;
+ media.IsManipulationEnabled = true;
+
+ // 重新应用CenterAndScaleElement变换
+ CenterAndScaleElement(media);
+ LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 重新配置媒体元素 {media.Name}", LogHelper.LogType.Trace);
+ }
+ }
+ }
}
}
From 7f88d9ae2723e2995c630faa7e92581587248709 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 17:44:15 +0800
Subject: [PATCH 20/22] =?UTF-8?q?fix:=E5=9B=BE=E7=89=87=E5=A2=A8=E8=BF=B9?=
=?UTF-8?q?=E4=BF=9D=E5=AD=98=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/MainWindow_cs/MW_BoardControls.cs | 46 +++----------------
.../MainWindow_cs/MW_ElementsControls.cs | 1 -
.../MainWindow_cs/MW_SelectionGestures.cs | 10 ----
Ink Canvas/MainWindow_cs/MW_TimeMachine.cs | 23 +---------
4 files changed, 9 insertions(+), 71 deletions(-)
diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs
index 0fde1b60..de356541 100644
--- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs
+++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs
@@ -46,14 +46,10 @@ namespace Ink_Canvas {
if (!elementsInHistory.Contains(child)) {
timeMachine.CommitElementInsertHistory(child);
missingElements++;
- LogHelper.WriteLogToFile($"SaveStrokes: 补充保存遗漏的UI元素 {child.GetType().Name}", LogHelper.LogType.Trace);
}
}
}
-
- if (missingElements > 0) {
- LogHelper.WriteLogToFile($"SaveStrokes: 总共补充保存了 {missingElements} 个遗漏的UI元素", LogHelper.LogType.Trace);
- }
+
// 确保画布上的所有墨迹都被保存
if (inkCanvas.Strokes.Count > 0) {
@@ -81,7 +77,6 @@ namespace Ink_Canvas {
if (missingStrokes.Count > 0) {
timeMachine.CommitStrokeUserInputHistory(missingStrokes);
- LogHelper.WriteLogToFile($"SaveStrokes: 补充保存了 {missingStrokes.Count} 个遗漏的墨迹", LogHelper.LogType.Trace);
}
}
@@ -90,25 +85,13 @@ namespace Ink_Canvas {
TimeMachineHistories[0] = timeMachineHistory;
timeMachine.ClearStrokeHistory();
- // 调试信息
- var elementCount = timeMachineHistory?.Count(h => h.CommitType == TimeMachineHistoryType.ElementInsert && !h.StrokeHasBeenCleared) ?? 0;
- var strokeHistoryCount = timeMachineHistory?.Count(h => h.CommitType == TimeMachineHistoryType.UserInput && !h.StrokeHasBeenCleared) ?? 0;
- var currentCanvasElements = inkCanvas.Children.Count;
- var currentCanvasStrokes = inkCanvas.Strokes.Count;
- var selectedElement = selectedUIElement?.GetType().Name ?? "无";
- LogHelper.WriteLogToFile($"SaveStrokes(备份主页面): 保存了 {elementCount} 个UI元素, {strokeHistoryCount} 个墨迹历史, 当前画布有 {currentCanvasElements} 个元素, {currentCanvasStrokes} 个墨迹, 选中元素: {selectedElement}", LogHelper.LogType.Trace);
+
} else {
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory;
timeMachine.ClearStrokeHistory();
- // 调试信息
- var elementCount = timeMachineHistory?.Count(h => h.CommitType == TimeMachineHistoryType.ElementInsert && !h.StrokeHasBeenCleared) ?? 0;
- var strokeHistoryCount = timeMachineHistory?.Count(h => h.CommitType == TimeMachineHistoryType.UserInput && !h.StrokeHasBeenCleared) ?? 0;
- var currentCanvasElements = inkCanvas.Children.Count;
- var currentCanvasStrokes = inkCanvas.Strokes.Count;
- var selectedElement = selectedUIElement?.GetType().Name ?? "无";
- LogHelper.WriteLogToFile($"SaveStrokes(页面{CurrentWhiteboardIndex}): 保存了 {elementCount} 个UI元素, {strokeHistoryCount} 个墨迹历史, 当前画布有 {currentCanvasElements} 个元素, {currentCanvasStrokes} 个墨迹, 选中元素: {selectedElement}", LogHelper.LogType.Trace);
+
}
}
@@ -134,26 +117,16 @@ namespace Ink_Canvas {
// 先清空当前画布的墨迹
inkCanvas.Strokes.Clear();
- // 保存当前的UI元素,稍后会被ApplyHistoryToCanvas正确处理
- var currentElements = new List();
- for (int i = inkCanvas.Children.Count - 1; i >= 0; i--)
- {
- currentElements.Add(inkCanvas.Children[i]);
- }
+ // 清空当前画布的所有内容(墨迹和图片)
+ // 这里必须清除图片,因为页面切换时需要完全重置画布状态
inkCanvas.Children.Clear();
- LogHelper.WriteLogToFile($"RestoreStrokes: 清空了 {currentElements.Count} 个当前UI元素", LogHelper.LogType.Trace);
// 如果历史记录为空,直接返回(新页面或空页面)
if (TimeMachineHistories[targetIndex] == null) {
timeMachine.ClearStrokeHistory();
- LogHelper.WriteLogToFile($"RestoreStrokes({(isBackupMain ? "备份主页面" : $"页面{CurrentWhiteboardIndex}")}): 历史记录为空", LogHelper.LogType.Trace);
return;
}
- var targetHistory = TimeMachineHistories[targetIndex];
- var elementCount = targetHistory?.Count(h => h.CommitType == TimeMachineHistoryType.ElementInsert && !h.StrokeHasBeenCleared) ?? 0;
- LogHelper.WriteLogToFile($"RestoreStrokes({(isBackupMain ? "备份主页面" : $"页面{CurrentWhiteboardIndex}")}): 准备恢复 {elementCount} 个UI元素", LogHelper.LogType.Trace);
-
if (isBackupMain) {
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[0]);
foreach (var item in TimeMachineHistories[0]) ApplyHistoryToCanvas(item);
@@ -163,18 +136,13 @@ namespace Ink_Canvas {
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item);
}
- // 恢复后检查实际的UI元素数量
- var actualElementCount = inkCanvas.Children.Count;
- LogHelper.WriteLogToFile($"RestoreStrokes({(isBackupMain ? "备份主页面" : $"页面{CurrentWhiteboardIndex}")}): 实际恢复了 {actualElementCount} 个UI元素", LogHelper.LogType.Trace);
-
// 确保选中状态被清除,因为我们切换了页面
if (selectedUIElement != null) {
DeselectUIElement();
- LogHelper.WriteLogToFile($"RestoreStrokes: 清除了选中状态", LogHelper.LogType.Trace);
}
}
- catch (Exception ex) {
- LogHelper.WriteLogToFile($"RestoreStrokes失败: {ex.Message}", LogHelper.LogType.Error);
+ catch {
+ // ignored
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs
index acdeeeef..1c06148a 100644
--- a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs
+++ b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs
@@ -40,7 +40,6 @@ namespace Ink_Canvas
image.IsManipulationEnabled = true;
timeMachine.CommitElementInsertHistory(image);
- LogHelper.WriteLogToFile($"图片插入: {image.Name}, 当前画布UI元素数量: {inkCanvas.Children.Count}", LogHelper.LogType.Trace);
}
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs
index 33cd8fdf..6e7042fb 100644
--- a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs
@@ -588,7 +588,6 @@ namespace Ink_Canvas {
}
selectedUIElement = element;
- LogHelper.WriteLogToFile($"SelectUIElement: 设置选中元素为 {element?.GetType().Name ?? "null"}", LogHelper.LogType.Trace);
if (element != null)
{
@@ -602,13 +601,11 @@ namespace Ink_Canvas {
if (element is Image)
{
ShowImageToolbar();
- LogHelper.WriteLogToFile($"SelectUIElement: 显示图片工具栏", LogHelper.LogType.Trace);
}
else
{
// 对于其他UI元素,显示拖拽手柄
ShowResizeHandles();
- LogHelper.WriteLogToFile($"SelectUIElement: 显示拖拽手柄", LogHelper.LogType.Trace);
}
}
}
@@ -871,8 +868,6 @@ namespace Ink_Canvas {
private void UIElement_MouseDown(object sender, MouseButtonEventArgs e)
{
- LogHelper.WriteLogToFile($"UIElement_MouseDown: 编辑模式={inkCanvas.EditingMode}, 元素类型={sender.GetType().Name}", LogHelper.LogType.Trace);
-
if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
{
var element = sender as UIElement;
@@ -881,14 +876,9 @@ namespace Ink_Canvas {
// 切换到选择模式并选择这个元素
inkCanvas.Select(new[] { element });
SelectUIElement(element);
- LogHelper.WriteLogToFile($"UIElement_MouseDown: 选择了UI元素 {element.GetType().Name}", LogHelper.LogType.Trace);
e.Handled = true;
}
}
- else
- {
- LogHelper.WriteLogToFile($"UIElement_MouseDown: 编辑模式不是Select,无法选择UI元素", LogHelper.LogType.Trace);
- }
}
diff --git a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs
index 6303b6ec..6642350e 100644
--- a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs
+++ b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs
@@ -135,29 +135,12 @@ namespace Ink_Canvas {
if (item.StrokeHasBeenCleared) {
// Undo: 移除元素
- if (item.InsertedElement != null && targetCanvas.Children.Contains(item.InsertedElement)) {
+ if (item.InsertedElement != null && targetCanvas.Children.Contains(item.InsertedElement))
targetCanvas.Children.Remove(item.InsertedElement);
- LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 移除UI元素 {item.InsertedElement.GetType().Name}", LogHelper.LogType.Trace);
- }
} else {
// Redo: 添加元素
- if (item.InsertedElement != null) {
- // 确保元素不在画布上,如果在就先移除
- if (targetCanvas.Children.Contains(item.InsertedElement)) {
- targetCanvas.Children.Remove(item.InsertedElement);
- LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 先移除已存在的UI元素 {item.InsertedElement.GetType().Name}", LogHelper.LogType.Trace);
- }
-
- // 确保元素没有其他父容器
- if (item.InsertedElement is FrameworkElement fe && fe.Parent != null) {
- if (fe.Parent is Panel parentPanel) {
- parentPanel.Children.Remove(fe);
- LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 从其他父容器移除UI元素 {item.InsertedElement.GetType().Name}", LogHelper.LogType.Trace);
- }
- }
-
+ if (item.InsertedElement != null && !targetCanvas.Children.Contains(item.InsertedElement)) {
targetCanvas.Children.Add(item.InsertedElement);
- LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 添加UI元素 {item.InsertedElement.GetType().Name}", LogHelper.LogType.Trace);
// 重新绑定事件处理器(仅对主画布)
if (targetCanvas == inkCanvas) {
@@ -168,7 +151,6 @@ namespace Ink_Canvas {
// 重新应用CenterAndScaleElement变换
CenterAndScaleElement(img);
- LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 重新配置图片元素 {img.Name}", LogHelper.LogType.Trace);
} else if (item.InsertedElement is MediaElement media) {
media.MouseDown -= UIElement_MouseDown;
media.MouseDown += UIElement_MouseDown;
@@ -176,7 +158,6 @@ namespace Ink_Canvas {
// 重新应用CenterAndScaleElement变换
CenterAndScaleElement(media);
- LogHelper.WriteLogToFile($"ApplyHistoryToCanvas: 重新配置媒体元素 {media.Name}", LogHelper.LogType.Trace);
}
}
}
From d31f40408baf8183082f453c1976f27c9c0d8e07 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 17:59:41 +0800
Subject: [PATCH 21/22] =?UTF-8?q?fix:=E5=9B=BE=E7=89=87=E5=A2=A8=E8=BF=B9?=
=?UTF-8?q?=E4=BF=9D=E5=AD=98=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../MainWindow_cs/MW_FloatingBarIcons.cs | 19 ++----------------
...vasForClass.csproj.AssemblyReference.cache | Bin 35374 -> 35404 bytes
2 files changed, 2 insertions(+), 17 deletions(-)
diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
index 8f27b792..bafc0805 100644
--- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
+++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
@@ -1780,20 +1780,13 @@ namespace Ink_Canvas {
AnimationsHelper.HideWithSlideAndFade(BlackboardLeftSide);
AnimationsHelper.HideWithSlideAndFade(BlackboardCenterSide);
AnimationsHelper.HideWithSlideAndFade(BlackboardRightSide);
-
- // 取消任何UI元素的选择
+
DeselectUIElement();
SaveStrokes(true);
ClearStrokes(true);
-
- // 总是恢复备份墨迹,不管是否在PPT模式
- // PPT墨迹和白板墨迹应该分别管理,不应该互相影响
RestoreStrokes(true);
- LogHelper.WriteLogToFile($"退出白板模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace);
-
- // 注释掉:退出白板时不应该清空图片,因为RestoreStrokes()已经恢复了正确的状态
- // inkCanvas.Children.Clear();
+
if (BtnSwitchTheme.Content.ToString() == "浅色") {
BtnSwitch.Content = "黑板";
@@ -1831,14 +1824,7 @@ namespace Ink_Canvas {
SaveStrokes();
ClearStrokes(true);
-
- // 总是恢复备份墨迹,不管是否在PPT模式
- // PPT墨迹和白板墨迹应该分别管理,不应该互相影响
RestoreStrokes(true);
- LogHelper.WriteLogToFile($"切换到桌面模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace);
-
- // 注释掉:退出白板时不应该清空图片,因为RestoreStrokes()已经恢复了正确的状态
- // inkCanvas.Children.Clear();
if (BtnSwitchTheme.Content.ToString() == "浅色") {
BtnSwitch.Content = "黑板";
@@ -1876,7 +1862,6 @@ namespace Ink_Canvas {
// 总是恢复备份墨迹,不管是否在PPT模式
// PPT墨迹和白板墨迹应该分别管理,不应该互相影响
RestoreStrokes();
- LogHelper.WriteLogToFile($"进入白板模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "桌面")}", LogHelper.LogType.Trace);
BtnSwitch.Content = "屏幕";
if (BtnSwitchTheme.Content.ToString() == "浅色") {
diff --git a/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache b/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache
index 4af914e22079d93b58f0e03491291464a0dba41e..2631e131b8cff64b6949ef6a68518314d6da74c5 100644
GIT binary patch
delta 112
zcmZ2Ch3U)`rU~a5ttOt=W=Y8`E}8h#lhJ9i4T~{1h^O!A=$v1aI$1wnbMgX~zRku=
o6Brq7U}`77WAzg=yUsrU~a5O(&k$p7`H`(RQ*oi}B_LMqegIbD+@Vf6P9N_8>71Pe-5p#FW&c
h$%a9ilh-r$ZFXXv&d6v6QpCmX#b^iC*vr1Z2>>6Y9fJS>
From 39d54cc493bfc305d38b05f47c287f9c0dbab2ee Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Tue, 29 Jul 2025 18:06:59 +0800
Subject: [PATCH 22/22] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?=
=?UTF-8?q?=E5=8F=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
AutomaticUpdateVersionControl.txt | 2 +-
Ink Canvas/AssemblyInfo.cs | 4 ++--
Ink Canvas/Properties/AssemblyInfo.cs | 4 ++--
...vasForClass.csproj.AssemblyReference.cache | Bin 35404 -> 35177 bytes
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt
index 823a14ad..f2f7432d 100644
--- a/AutomaticUpdateVersionControl.txt
+++ b/AutomaticUpdateVersionControl.txt
@@ -1 +1 @@
-1.7.5.0
+1.7.6.0
diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs
index 49035c1e..2fcd67b5 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.5.1")]
-[assembly: AssemblyFileVersion("1.7.5.1")]
+[assembly: AssemblyVersion("1.76.0")]
+[assembly: AssemblyFileVersion("1.7.6.0")]
diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs
index adae27eb..9c2ed554 100644
--- a/Ink Canvas/Properties/AssemblyInfo.cs
+++ b/Ink Canvas/Properties/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.5.1")]
-[assembly: AssemblyFileVersion("1.7.5.1")]
+[assembly: AssemblyVersion("1.7.6.0")]
+[assembly: AssemblyFileVersion("1.7.6.0")]
diff --git a/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache b/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache
index 2631e131b8cff64b6949ef6a68518314d6da74c5..df53462f42d7ad1d669739764f109a584bd67f81 100644
GIT binary patch
delta 246
zcmX>zh3VxaCJr`6T?PgQ#>B}5GNBV=3mHu(p4XoE--FS1vNwzI<_5+H#>oQWJd?ZF
zEG7%^vP{-scAC7vh-GpSv&-avyzIQ3WrlhNddB7krVNbcK&_MiG5avugYXdvJ>lEuo=%;y(c#avrK-&;Q%)4fdU8Ee01~7K<06=dokL9-O|e*
y4sr|YB~n4xtlc3mL5@p4Vnc$t*6J_|ucoX|fKBF&BuZ@9F5A
zUo<%}Ol$H4mcGfxOtU8kRB}%Km%u*x7L&!KOpeK7%ubU(_;O5MTf;LsgV|#8gcR<{
zGue0;ZD0mYzQ^p#=mIhkZ2smrmRLrR#{I0`lLLUX2&2Sg17;1dA%7D%K<2^?v4$IR
zfz6xI8DWSYdjuocC8CVtP~D=Fe{)!XP2rCbn(WC5VfO}$Po4v1-{5qDyIze8WDD=+
ybS|d)$tsHBlTUC7C^_q;EtqGzCIY+!6?$p8SOf@%E#