feat(docstring):添加部分docstring (#376)
* feat(docstring):添加docstring Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * fix(docstring):修复部分docstring格式错误 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * fix(docstring):修复部分docstring Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * chore(Docstring):MW_* 前14 * chore(Docstring):MW_* part 2 * chore(Docstring):MW_* part 3 * chore:优化缩进 * fix: 修复数学计算中的潜在除零错误和数值稳定性问题 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * chore:删除Rebase时多余的OOBE函数 * chore: 更新代码注释和文档格式 修复XML文档注释中的格式问题,统一使用<c>和<see>标签 更新ConfigHelper类的预留说明,明确未来扩展用途 优化TimerDisplayDate_Elapsed方法的注释,说明UI异步更新机制 合并重复的注释摘要行,提高文档可读性 添加形状识别功能的64位进程限制说明 修正视频呈现器设备选择逻辑的文档说明 * chore(IPPTLinkManager): 更新TryEndSlideShow方法的XML注释格式 * chore: 修正代码注释中的术语和格式问题 更新多个文件中的XML注释,统一使用<see langword="..."/>标记代替<c>...</c>标记 规范术语使用(如"延迟初始化"代替"懒惰初始化") 修正注释中的格式错误和补充说明 调整代码区域的注释对齐格式 --------- Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com>
This commit is contained in:
+34
-2
@@ -665,6 +665,18 @@ namespace Ink_Canvas
|
||||
|
||||
private TaskbarIcon _taskbar;
|
||||
|
||||
/// <summary>
|
||||
/// 处理应用启动流程:根据命令行与设置显示启动画面、初始化组件与遥测、处理更新相关逻辑、单实例检查并在必要时通过 IPC 与已运行实例通信,最终创建并显示主窗口并启动文件关联与 IPC 监听器。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的发送者(通常为 Application 对象)。</param>
|
||||
/// <param name="e">启动事件参数;其 Args 可包含控制启动流程的标志,例如:
|
||||
/// - "--final-app":表示这是更新后的最终应用启动(会清理更新标记等)
|
||||
/// - "--update-mode":表示以更新模式启动(跳过主窗口显示)
|
||||
/// - "--board":直接进入白板模式
|
||||
/// - "--show":退出收纳模式并恢复浮动栏
|
||||
/// - "--skip-mutex-check":跳过单实例互斥检查
|
||||
/// - "-m":允许多实例启动
|
||||
/// 另外也可能包含以 "icc:" 开头的 URI 参数或 .icstk 文件路径用于启动时的 IPC 交互。</param>
|
||||
async void App_Startup(object sender, StartupEventArgs e)
|
||||
{
|
||||
appStartTime = DateTime.Now;
|
||||
@@ -1159,6 +1171,17 @@ namespace Ink_Canvas
|
||||
private static DateTime splashScreenStartTime = DateTime.MinValue;
|
||||
private static DateTime appStartupStartTime = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// 启动并管理应用的心跳与守护检查定时器,监测启动阶段与主线程是否无响应,并在符合配置的情况下尝试静默重启应用。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 启动一个每秒更新心跳时间戳的调度定时器和一个每3秒运行的守护定时器。
|
||||
/// - 守护定时器在首次运行的启动阶段若检测到超过两分钟未完成启动,会根据 CrashAction 配置尝试静默重启。
|
||||
/// - 在启动完成后若检测到主线程超过10秒无响应,会根据 CrashAction 配置尝试静默重启。
|
||||
/// - 对连续重启次数有保护:若重启计数达到或超过5次,会弹出提示并停止自动重启(重置重启计数并退出进程)。
|
||||
/// - 在 OOBE(首次引导)展示期间不执行守护检查。
|
||||
/// - 该方法会产生外部可观察的副作用:可能启动新进程并调用 Environment.Exit 终止当前进程,或显示消息框。
|
||||
/// </remarks>
|
||||
private void StartHeartbeatMonitor()
|
||||
{
|
||||
heartbeatTimer = new DispatcherTimer
|
||||
@@ -1248,7 +1271,16 @@ namespace Ink_Canvas
|
||||
watchdogProcess = Process.Start(psi);
|
||||
}
|
||||
|
||||
// 看门狗主逻辑
|
||||
/// <summary>
|
||||
/// 作为守护进程监视指定的主进程,并在主进程异常退出时根据配置执行重启或退出操作。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该方法期望命令行参数格式为:"--watchdog <pid> <exitSignalFile>"(args[1..3])。
|
||||
/// - 每 2 秒检查一次指定的主进程是否仍在运行;同时检测退出信号文件,若存在则删除该文件并以代码 0 退出守护进程。
|
||||
/// - 当主进程退出时,会同步崩溃处理设置(SyncCrashActionFromSettings)。若启用了 UIA 顶层访问(IsUIAccessTopMostEnabled),守护进程直接退出。
|
||||
/// - 若崩溃动作为 SilentRestart,则增加启动计数并:当连续重启计数达到 5 次及以上时弹出错误对话框、重置计数并以代码 1 退出;否则启动新的主进程实例。
|
||||
/// 方法对内部异常静默处理,并在完成后确保进程退出。
|
||||
/// </remarks>
|
||||
public static void RunWatchdogIfNeeded()
|
||||
{
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
@@ -1405,4 +1437,4 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ namespace Ink_Canvas.Helpers
|
||||
/// 检查是否需要执行自动备份
|
||||
/// </summary>
|
||||
/// <param name="settings">设置对象</param>
|
||||
/// <returns>如果需要备份返回true,否则返回false</returns>
|
||||
/// <returns>如果需要备份返回<see langword="true"/>,否则返回<see langword="false"/></returns>
|
||||
public static bool ShouldPerformAutoBackup(Settings settings)
|
||||
{
|
||||
try
|
||||
@@ -50,8 +50,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 执行自动备份
|
||||
/// </summary>
|
||||
/// <param name="settings">设置对象</param>
|
||||
/// <returns>备份是否成功</returns>
|
||||
/// <remarks>
|
||||
/// 为主配置文件创建一次自动备份并在成功后更新并保存设置中的最后备份时间。
|
||||
/// </remarks>
|
||||
/// <param name="settings">应用的设置对象;在成功备份后会更新 settings.Advanced.LastAutoBackupTime 并调用保存操作。</param>
|
||||
/// <returns><see langword="true"/> 表示备份成功,<see langword="false"/> 表示备份失败或被跳过。</returns>
|
||||
public static bool PerformAutoBackup(Settings settings)
|
||||
{
|
||||
try
|
||||
@@ -91,7 +94,10 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 尝试从备份恢复配置文件
|
||||
/// </summary>
|
||||
/// <returns>恢复是否成功</returns>
|
||||
/// <remarks>
|
||||
/// 从最新可用的自动备份恢复主设置文件(Settings.json)。如果当前设置文件存在,会先将其复制到备份目录并加上时间戳作为“损坏”的备份副本,然后用最新备份覆盖原文件。
|
||||
/// </remarks>
|
||||
/// <returns><see langword="true"/> 如果恢复成功,<see langword="false"/> 否则。</returns>
|
||||
public static bool TryRestoreFromBackup()
|
||||
{
|
||||
try
|
||||
@@ -156,6 +162,10 @@ namespace Ink_Canvas.Helpers
|
||||
/// 清理过期的备份文件
|
||||
/// 保留最近30天的备份文件
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 删除备份目录中按“备份前缀”匹配且创建时间早于 30 天的自动备份文件(即自动备份文件的命名前缀),不会删除诸如 Settings_Corrupted_*.json 之类的其他备份或错误状态文件。
|
||||
/// 如果备份目录不存在则不执行任何操作;删除操作在受写入保护的上下文中执行,任何错误会被记录但不会抛出异常。
|
||||
/// </remarks>
|
||||
public static void CleanupOldBackups()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1320,7 +1320,13 @@ namespace Ink_Canvas.Helpers
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 保存下载状态
|
||||
/// <summary>
|
||||
/// 将下载完成状态写入预定义的状态文件以供后续检查。
|
||||
/// </summary>
|
||||
/// <param name="isSuccess">指示下载是否成功;将以字符串形式写入状态文件("True" 或 "False")。</param>
|
||||
/// <remarks>
|
||||
/// 如果状态文件路径为空则不执行任何操作;方法内部捕获异常并记录日志,不会向调用方抛出异常。
|
||||
/// </remarks>
|
||||
private static void SaveDownloadStatus(bool isSuccess)
|
||||
{
|
||||
try
|
||||
@@ -1341,7 +1347,13 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
// 安装新版本应用
|
||||
/// <summary>
|
||||
/// 安装指定版本的更新包并启动新版本进程以完成替换,然后退出当前应用程序。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该方法会临时将 App.IsUpdateInstalling 置为 true、尝试关闭进程保护(并在结束时还原)、在必要时备份当前设置、解压更新 ZIP、启动解压后的新可执行文件(以更新模式传递旧进程 ID、解压路径和目标路径等参数),并在新进程启动后关闭当前进程。方法会记录日志并在遇到错误时安全退出相应步骤,但不会抛出异常给调用方以外的上下文。</remarks>
|
||||
/// <param name="version">要安装的版本号,用于定位更新包文件名(例如 InkCanvasForClass.CE.{version}.zip)。</param>
|
||||
/// <param name="isInSilence">指示是否以静默模式启动新版本(影响传递给新进程的参数和可能的用户提示)。</param>
|
||||
public static void InstallNewVersionApp(string version, bool isInSilence)
|
||||
{
|
||||
bool wasProcessProtectionEnabled = false;
|
||||
@@ -2304,4 +2316,4 @@ namespace Ink_Canvas.Helpers
|
||||
return currentTime >= StartTime || currentTime <= EndTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,22 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region 生命周期管理
|
||||
/// <summary>
|
||||
/// 开始监控本地 PowerPoint 的连接与运行状态,并在状态变化时触发相应事件。
|
||||
/// </summary>
|
||||
public void StartMonitoring() => _inner.StartMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 停止对 PowerPoint 的监控,断开当前连接并停止触发相关事件。
|
||||
/// </summary>
|
||||
public void StopMonitoring() => _inner.StopMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 强制断开当前 COM PPT 连接并停止对其监控,同时写入事件日志。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 会向日志记录一条事件信息并调用内部管理器停止监控;该方法不会重新启动监控或重新初始化内部管理器实例。
|
||||
/// </remarks>
|
||||
public void ReloadConnection()
|
||||
{
|
||||
LogHelper.WriteLogToFile("COM PPT 执行热重载:强制断开并重新连接", LogHelper.LogType.Event);
|
||||
@@ -90,4 +102,3 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -360,6 +360,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 保存设备ID到文件
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将设备标识信息以格式化的 JSON 写入指定文件,并确保目标目录存在;在失败时记录错误但不抛出异常。
|
||||
/// </remarks>
|
||||
/// <param name="filePath">目标文件的完整路径,用于保存设备标识信息。</param>
|
||||
/// <param name="info">要保存的设备标识信息对象(包含 DeviceId 和 可选的硬件指纹)。</param>
|
||||
private static void SaveDeviceIdToFile(string filePath, DeviceIdInfo info)
|
||||
{
|
||||
try
|
||||
@@ -1619,4 +1624,3 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,32 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
object PPTApplication { get; }
|
||||
|
||||
// 生命周期管理
|
||||
/// <summary>
|
||||
/// 开始监视与 PowerPoint 的连接以及幻灯片放映相关状态,并在状态变化时触发对应事件。
|
||||
/// </summary>
|
||||
void StartMonitoring();
|
||||
/// <summary>
|
||||
/// 停止监控 PowerPoint 的连接与事件,停止接收并处理与演示文稿和幻灯片放映相关的通知。
|
||||
/// </summary>
|
||||
void StopMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载或重建与 PowerPoint 的连接。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 调用后实现应刷新内部连接与状态,必要时重建与 PowerPoint 的会话;此操作可能导致 IsConnected 变化并触发 PPTConnectionChanged 或其他相关事件(例如 SlideShowStateChanged)。
|
||||
/// </remarks>
|
||||
void ReloadConnection();
|
||||
|
||||
// 放映控制
|
||||
/// <summary>
|
||||
/// 尝试启动当前演示文稿的放映模式。
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> 如果放映已成功启动,<c>false</c> 否则。</returns>
|
||||
bool TryStartSlideShow();
|
||||
/// <summary>
|
||||
/// 尝试结束当前正在进行的幻灯片放映。
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> 如果放映已成功结束,<c>false</c> 否则。</returns>
|
||||
bool TryEndSlideShow();
|
||||
|
||||
// 导航控制
|
||||
@@ -43,4 +61,3 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@ namespace Ink_Canvas.Helpers
|
||||
WriteLogToFile(msg, LogType.Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一条日志消息记录到应用的日志文件中(可能是单一日志文件或按启动时间存档的文件),同时在日志条目中包含时间戳、线程 ID 和调用者信息,并遵循应用的日志设置。
|
||||
/// </summary>
|
||||
/// <param name="str">要记录的日志文本消息。</param>
|
||||
/// <param name="logType">日志的类型/等级,用于在日志条目中标识(例如 Info、Error、Warning 等)。</param>
|
||||
public static void WriteLogToFile(string str, LogType logType = LogType.Info)
|
||||
{
|
||||
// 检查日志是否启用
|
||||
@@ -92,6 +97,16 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定日志文件夹的总大小,并在超过 MaxLogsFolderSizeBytes 时删除该文件夹下的所有文件并记录清理日志。
|
||||
/// </summary>
|
||||
/// <param name="logsPath">要检查和清理的日志文件夹路径。</param>
|
||||
/// <remarks>
|
||||
/// - 如果目录不存在则直接返回。
|
||||
/// - 当总大小超过 MaxLogsFolderSizeBytes 时,会尝试删除目录下的每个文件(单个删除失败将被忽略)。
|
||||
/// - 清理完成后会向该目录下的 Log_{AppStartTime}.txt 写入一条带有时间戳和 [Cleanup] 标签的记录。
|
||||
/// - 方法内部捕获并忽略所有异常以避免影响调用者流程。
|
||||
/// </remarks>
|
||||
private static void CheckAndCleanLogsFolder(string logsPath)
|
||||
{
|
||||
try
|
||||
@@ -155,4 +170,4 @@ namespace Ink_Canvas.Helpers
|
||||
Warning
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,11 +46,18 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// 初始化 PPTInkManager 实例并为内部内存流分配初始容量以跟踪默认最大幻灯片数加上备用槽位。
|
||||
/// </summary>
|
||||
public PPTInkManager()
|
||||
{
|
||||
InitializeMemoryStreams(DefaultMaxSlides + 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定容量初始化用于存储每页墨迹的内存流数组。
|
||||
/// </summary>
|
||||
/// <param name="capacity">期望的数组容量;如果小于 2,则会使用 2 作为最小容量。</param>
|
||||
private void InitializeMemoryStreams(int capacity)
|
||||
{
|
||||
if (_memoryStreams != null)
|
||||
@@ -69,6 +76,12 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 初始化新的演示文稿
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 为新的或当前的演示文稿初始化墨迹管理器的内部状态。
|
||||
/// 方法会清除所有内存中的笔迹数据,重置墨迹写入锁与快速切换追踪,并根据演示文稿的幻灯片数量分配内部内存缓冲区。
|
||||
/// 如果已启用自动保存且设置了 <see cref="AutoSaveLocation"/>,则会尝试加载磁盘上的已保存墨迹文件。
|
||||
/// </remarks>
|
||||
/// <param name="presentation">要初始化的 PowerPoint Presentation 实例;为 null 时方法不执行任何操作并直接返回。</param>
|
||||
public void InitializePresentation(Presentation presentation)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -115,6 +128,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 保存当前页面的墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将指定幻灯片的墨迹保存到内部内存缓存,并在必要时触发内存清理。
|
||||
/// </remarks>
|
||||
/// <param name="slideIndex">要保存的幻灯片索引(从 1 开始)。方法在索引小于或等于 0 时不执行任何操作。</param>
|
||||
/// <param name="strokes">要保存的墨迹集合;为 null 时方法不执行任何操作。</param>
|
||||
public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -140,6 +158,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 强制保存指定页墨迹到内存(不受锁定限制)。用于放映结束前保存当前画布到当前页。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 强制将指定幻灯片的墨迹保存到内部内存缓存,覆盖该幻灯片已有的墨迹数据。
|
||||
/// </remarks>
|
||||
/// <param name="slideIndex">要保存的幻灯片索引(从 1 开始)。</param>
|
||||
/// <param name="strokes">要保存的墨迹集合,不能为空。</param>
|
||||
public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -163,6 +186,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 加载指定页面的墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 加载并返回指定幻灯片的墨迹集合。
|
||||
/// </remarks>
|
||||
/// <param name="slideIndex">要加载的幻灯片索引(从1开始)。</param>
|
||||
/// <returns>包含指定幻灯片的墨迹的 StrokeCollection;如果该幻灯片没有已保存的墨迹或加载失败,则返回空的 StrokeCollection。</returns>
|
||||
public StrokeCollection LoadSlideStrokes(int slideIndex)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -190,6 +218,12 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 切换到指定页面并加载墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 切换到指定幻灯片并返回该幻灯片的已加载笔迹集合。
|
||||
/// </remarks>
|
||||
/// <param name="slideIndex">要切换到的幻灯片索引(从 1 开始)。</param>
|
||||
/// <param name="currentStrokes">可选的当前笔迹集合,用于在切换时提供当前画面状态。</param>
|
||||
/// <returns>`StrokeCollection`:指定幻灯片已加载的笔迹集合;若加载失败则返回一个空的 `StrokeCollection`。</returns>
|
||||
public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -221,8 +255,12 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 保存所有墨迹到文件
|
||||
/// </summary>
|
||||
/// <param name="presentation">演示文稿对象</param>
|
||||
/// <param name="currentSlideIndex">当前播放的页码,如果提供则使用此值保存位置,否则使用_lockedSlideIndex</param>
|
||||
/// <remarks>
|
||||
/// 将内存中当前演示文稿的每页墨迹保存到磁盘,并根据情况写入当前播放位置文件。
|
||||
/// 仅在 IsAutoSaveEnabled 为真且 AutoSaveLocation 已设置时执行。会在演示文稿专属文件夹中写入按页编号的墨迹文件(带 `.icstk` 扩展名)和可选的 Position 文件。遇到特定 COM 错误(HRESULT 0x80048010)时会中止保存当前幻灯片计数读取而不抛出异常;单页保存失败会记录错误并继续处理其他页。
|
||||
/// </remarks>
|
||||
/// <param name="presentation">要保存墨迹的 PowerPoint 演示文稿对象。</param>
|
||||
/// <param name="currentSlideIndex">当前播放的页码;如果大于 0 则以此值写入 Position 文件,否则使用当前被锁定的页码或最后切换的页码作为保存位置。</param>
|
||||
public void SaveAllStrokesToFile(Presentation presentation, int currentSlideIndex = -1)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -288,6 +326,12 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 从文件加载已保存的墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 从自动保存目录加载已保存的幻灯片墨迹数据到内存流中,供后续显示和编辑使用。
|
||||
/// 仅在启用自动保存且已设置 AutoSaveLocation 时执行。函数获取当前演示文稿的自动保存文件夹,遍历以 <c>.icstk</c> 为扩展名的文件,
|
||||
/// 将文件名(去除扩展名)解析为幻灯片索引并在合法且文件大小大于 8 字节时加载到对应的内存流槽位。对单个文件的读取失败会记录错误并继续处理其他文件;
|
||||
/// 若成功加载则会记录已加载页数。方法在内部使用锁以保证线程安全。
|
||||
/// </remarks>
|
||||
public void LoadSavedStrokes()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -337,6 +381,10 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 清除所有墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 清除并释放当前演示文稿所有幻灯片的墨迹数据和相关内存资源。
|
||||
/// 该方法在内部加锁以保证线程安全;会处置并清空所有内部存储的墨迹流、重建内部流数组并清空 CurrentStrokes。
|
||||
/// </remarks>
|
||||
public void ClearAllStrokes()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -346,6 +394,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为指定幻灯片设置短时墨迹写入锁,防止在该时间窗口内对其他幻灯片进行写入操作。
|
||||
/// </summary>
|
||||
/// <param name="slideIndex">要上锁的幻灯片索引(大于 0)。锁从调用时刻开始,持续 InkLockMilliseconds 毫秒。</param>
|
||||
public void LockInkForSlide(int slideIndex)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -353,6 +405,11 @@ namespace Ink_Canvas.Helpers
|
||||
_lockedSlideIndex = slideIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定在当前滑页上下文中是否允许写入墨迹(基于短期的墨迹写入锁与容差窗口)。
|
||||
/// </summary>
|
||||
/// <param name="currentSlideIndex">当前尝试写入墨迹的幻灯片索引(从 1 开始)。</param>
|
||||
/// <returns>`true` 如果允许写入墨迹(锁已过期、目标为被锁定的幻灯片,或处于短暂的容差窗口内),`false` 否则。</returns>
|
||||
public bool CanWriteInk(int currentSlideIndex)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -362,6 +419,12 @@ namespace Ink_Canvas.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置与墨迹书写和幻灯片切换相关的锁与跟踪状态为初始(未锁定)值。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将内部的墨迹写入到期时间、当前被锁定的幻灯片索引、上次切换时间和上次切换的幻灯片索引均恢复为默认未设置状态。
|
||||
/// </remarks>
|
||||
public void ResetLockState()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -378,6 +441,14 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
/// <summary>
|
||||
/// 释放并清除类内用于存储各页墨迹的所有内存流,清空当前画笔集合,并重置内部内存流数组容量为 _maxSlides + 2。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 会逐个释放已存在的 MemoryStream(忽略释放过程中的异常),并将对应槽位设为 null。
|
||||
/// - 会清空 CurrentStrokes 集合。
|
||||
/// - 会记录一条跟踪日志,指示已完成清除操作。
|
||||
/// </remarks>
|
||||
private void ClearAllStrokesInternal()
|
||||
{
|
||||
if (_memoryStreams != null)
|
||||
@@ -393,6 +464,11 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用指定的笔迹集合替换内部存储中对应幻灯片索引的内存流:释放(并忽略释放错误)旧流,将 <paramref name="strokes"/> 序列化到新的 <see cref="MemoryStream"/> 并保存回内部数组。
|
||||
/// </summary>
|
||||
/// <param name="slideIndex">要替换的幻灯片索引(内部内存流数组的索引)。</param>
|
||||
/// <param name="strokes">要序列化并保存到内存流的笔迹集合。</param>
|
||||
private void ReplaceSlideStream(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
try { _memoryStreams[slideIndex]?.Dispose(); } catch (Exception ex) { LogHelper.WriteLogToFile($"释放旧内存流失败: {ex}", LogHelper.LogType.Warning); }
|
||||
@@ -402,6 +478,11 @@ namespace Ink_Canvas.Helpers
|
||||
_memoryStreams[slideIndex] = ms;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定文件夹删除对应幻灯片的笔迹文件(按四位索引命名);如果文件不存在或删除失败则静默忽略错误。
|
||||
/// </summary>
|
||||
/// <param name="folderPath">存放笔迹文件的文件夹路径。</param>
|
||||
/// <param name="slideIndex">用于生成文件名的幻灯片索引(格式化为四位,例如 1 -> "0001")。</param>
|
||||
private void TryDeleteStrokeFile(string folderPath, int slideIndex)
|
||||
{
|
||||
try
|
||||
@@ -412,6 +493,12 @@ namespace Ink_Canvas.Helpers
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查当前墨迹内存使用状况并在超过阈值时触发清理操作。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 会更新内部的内存使用统计并刷新上次清理时间;当总占用超过 MaxMemoryUsageBytes 时,会记录警告并调用 CleanupInactiveSlideStrokes 清理不活跃幻灯页的墨迹流。若检查或清理过程中发生异常,会记录错误日志。
|
||||
/// </remarks>
|
||||
private void CheckAndPerformMemoryCleanup()
|
||||
{
|
||||
try
|
||||
@@ -440,6 +527,12 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理不活跃幻灯片的内存化墨迹数据以回收内存空间。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将释放除当前锁定幻灯片与最近切换幻灯片之外的每页内存流(若存在),并将对应数组项设为 null;完成后若有释放,会记录已清理页数与释放的总大小(KB)。
|
||||
/// </remarks>
|
||||
private void CleanupInactiveSlideStrokes()
|
||||
{
|
||||
if (_memoryStreams == null) return;
|
||||
@@ -459,6 +552,10 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"已清理 {cleaned} 页墨迹,释放 {freed / 1024}KB", LogHelper.LogType.Trace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成基于演示文稿名称、幻灯片数量和路径哈希的标识符字符串。
|
||||
/// </summary>
|
||||
/// <returns>由 `名称_幻灯片数_路径哈希` 组成的标识符;若生成失败则返回形如 `unknown_{ticks}` 的回退标识符。</returns>
|
||||
private string GeneratePresentationId(Presentation presentation)
|
||||
{
|
||||
try
|
||||
@@ -483,6 +580,12 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
#region Dispose
|
||||
|
||||
/// <summary>
|
||||
/// 释放 PPTInkManager 持有的资源并清除所有内存中的笔迹数据。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 调用后该实例将进入已释放状态,不应再被使用。方法为幂等且线程安全:如果已释放则立即返回,否则在同步区内清理资源并标记为已释放。
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
@@ -498,4 +601,4 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,13 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 在系统的运行对象表(ROT)中查找并返回最合适的正在运行的 PowerPoint 应用实例。
|
||||
/// </summary>
|
||||
/// <param name="targetApp">可选的目标 PowerPoint COM 对象,用于优先比较;传入 null 表示不指定目标。</param>
|
||||
/// <param name="bestPriority">输出参数:返回找到的最佳实例的优先级(0 表示未找到或无活动演示)。</param>
|
||||
/// <param name="targetPriority">输出参数:返回与 <paramref name="targetApp"/> 对应实例的优先级(如果未提供或未命中则为 0)。</param>
|
||||
/// <returns>最合适的 PowerPoint 应用对象(通常为 COM Application 实例),若未找到则返回 null。</returns>
|
||||
public static object GetAnyActivePowerPoint(object targetApp, out int bestPriority, out int targetPriority)
|
||||
{
|
||||
IRunningObjectTable rot = null;
|
||||
@@ -480,4 +487,3 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Ink_Canvas.Helpers
|
||||
get { lock (_lock) return _enabled; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从应用设置读取 EnableProcessProtection 并相应地启用或禁用进程保护。
|
||||
/// </summary>
|
||||
public static void ApplyFromSettings()
|
||||
{
|
||||
try
|
||||
@@ -43,6 +46,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换进程保护的启用状态;在状态发生变化时触发相应的启用或禁用操作并保证线程安全。
|
||||
/// </summary>
|
||||
/// <param name="enabled">为 `true` 时启用进程保护,为 `false` 时禁用进程保护。</param>
|
||||
public static void SetEnabled(bool enabled)
|
||||
{
|
||||
lock (_lock)
|
||||
@@ -69,6 +76,15 @@ namespace Ink_Canvas.Helpers
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在受进程保护的上下文中执行对指定目标的写入操作;在执行时会在必要情况下临时释放针对目标路径及其父目录的锁,执行完成后恢复这些锁。
|
||||
/// </summary>
|
||||
/// <param name="targetPath">目标文件或目录的路径,用于确定需要临时释放和随后恢复的锁。</param>
|
||||
/// <param name="action">执行写入的操作委托,不能为空。</param>
|
||||
/// <remarks>
|
||||
/// 如果 ProcessProtectionManager.Enabled 为 false,会直接执行 <paramref name="action"/>;
|
||||
/// 若在有限时间内无法获取写入门闩,会记录警告并降级为直接执行 <paramref name="action"/>。方法在内部处理异常,不会抛出异常给调用者。
|
||||
/// </remarks>
|
||||
public static void WithWriteAccess(string targetPath, Action action)
|
||||
{
|
||||
if (action == null) return;
|
||||
@@ -214,6 +230,11 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试在指定的毫秒数内获取写入门控(write gate)。
|
||||
/// </summary>
|
||||
/// <param name="timeoutMs">等待超时时间(毫秒)。小于或等于 0 时视为 1 毫秒。</param>
|
||||
/// <returns>`true` 如果在指定时间内成功获取到写入门控,`false` 否则。</returns>
|
||||
private static bool TryEnterWriteGate(int timeoutMs)
|
||||
{
|
||||
if (timeoutMs <= 0) timeoutMs = 1;
|
||||
@@ -230,11 +251,19 @@ namespace Ink_Canvas.Helpers
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用进程保护并对应用根路径进行完整重扫描以锁定需要保护的目录和文件。
|
||||
/// </summary>
|
||||
private static void Enable()
|
||||
{
|
||||
Enable(rescanRoot: true, rescanDirs: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在应用根目录或提供的路径集合上建立目录句柄和文件读取锁以启用进程保护。
|
||||
/// </summary>
|
||||
/// <param name="rescanRoot">为 true 时对 App.RootPath 进行递归扫描并锁定其下的目录与文件;为 false 时仅处理 <paramref name="rescanDirs"/> 指定的路径(若为 null 则不处理)。</param>
|
||||
/// <param name="rescanDirs">当 <paramref name="rescanRoot"/> 为 false 时,按项对存在的目录建立目录锁,对存在的文件建立文件锁;可为 null。</param>
|
||||
private static void Enable(bool rescanRoot, IEnumerable<string> rescanDirs)
|
||||
{
|
||||
try
|
||||
@@ -282,6 +311,13 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放并清除当前进程持有的所有文件和目录锁定句柄与流资源。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在内部同步锁定下逐一 Dispose 已记录的 FileStream 和 SafeFileHandle,并清空对应的缓存字典;
|
||||
/// 释放过程中发生的异常会被忽略(吞掉)。
|
||||
/// </remarks>
|
||||
private static void Disable()
|
||||
{
|
||||
lock (_lock)
|
||||
@@ -300,6 +336,11 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归地尝试为指定目录及其所有子目录建立并保持目录句柄锁定,跳过配置的排除目录。
|
||||
/// </summary>
|
||||
/// <param name="root">起始目录的路径;从此路径开始遍历并对符合条件的子目录尝试建立锁定。</param>
|
||||
/// <remarks>遇到的异常会被捕获并忽略,不会向调用方抛出。</remarks>
|
||||
private static void LockDirectoryRecursive(string root)
|
||||
{
|
||||
try
|
||||
@@ -321,6 +362,12 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归扫描指定根目录下的所有文件,并为具有特定扩展名的文件建立读取锁定以防止被进程修改或替换。
|
||||
/// </summary>
|
||||
/// <param name="root">要开始扫描的根目录路径。</param>
|
||||
/// <remarks>
|
||||
/// 仅处理扩展名为 `.exe`, `.dll`, `.config`, `.manifest`, `.dat`, `.enc` 的文件;会跳过被 IsExcludedPath 判定为排除的路径。遇到任何 I/O 或访问错误时会静默忽略,不会抛出异常。</remarks>
|
||||
private static void LockFilesRecursive(string root)
|
||||
{
|
||||
try
|
||||
@@ -347,6 +394,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以只读方式打开并保留指定文件的句柄,将其加入内部锁定缓存以减少该文件被外部修改或删除的可能性。
|
||||
/// </summary>
|
||||
/// <param name="filePath">要锁定的文件的路径(会被规范化为完整路径)。</param>
|
||||
private static void LockFile(string filePath)
|
||||
{
|
||||
filePath = NormalizePath(filePath);
|
||||
@@ -364,6 +415,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试为指定目录获取一个用于保持目录打开的句柄并将其保存为内部锁定记录;若目录已被记录则不作任何操作,发生错误时静默忽略。
|
||||
/// </summary>
|
||||
/// <param name="dirPath">要锁定的目录路径;调用时会对路径进行规范化(转换为完整路径并移除多余分隔符)。</param>
|
||||
private static void LockDirectory(string dirPath)
|
||||
{
|
||||
dirPath = NormalizePath(dirPath);
|
||||
@@ -384,6 +439,11 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将路径标准化为不含末尾路径分隔符的绝对路径。
|
||||
/// </summary>
|
||||
/// <param name="p">要规范化的路径;如果为 null、空或仅空白,则返回原值。</param>
|
||||
/// <returns>规范化后的路径:在解析成功时返回去除末尾分隔符的绝对路径;在解析失败时返回原始输入。</returns>
|
||||
private static string NormalizePath(string p)
|
||||
{
|
||||
try
|
||||
@@ -397,6 +457,11 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建从指定路径向上直到应用根目录(App.RootPath)的目录链,并按从下到上的顺序返回已规范化的目录路径。
|
||||
/// </summary>
|
||||
/// <param name="path">起始路径,既可为文件路径也可为目录路径;若为文件则使用其所在目录作为起点。</param>
|
||||
/// <returns>包含起始目录及其各级父目录直到并包含应用根目录的列表;当根路径无效或未能匹配到根目录时返回空列表。</returns>
|
||||
private static List<string> GetDirChainToRoot(string path)
|
||||
{
|
||||
var list = new List<string>();
|
||||
@@ -420,6 +485,11 @@ namespace Ink_Canvas.Helpers
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查给定路径是否位于应用根目录下的受排除子目录之一。
|
||||
/// </summary>
|
||||
/// <param name="path">要检查的文件或目录路径。</param>
|
||||
/// <returns>`true` 如果路径位于任何配置为排除的子目录下,`false` 否则。</returns>
|
||||
private static bool IsExcludedPath(string path)
|
||||
{
|
||||
try
|
||||
@@ -442,6 +512,11 @@ namespace Ink_Canvas.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为指定目录打开一个文件句柄,便于对该目录进行锁定或访问其元数据。
|
||||
/// </summary>
|
||||
/// <param name="dirPath">目标目录的完整路径。</param>
|
||||
/// <returns>表示已打开目录的 <see cref="SafeFileHandle"/>;若无法打开则返回无效的句柄,调用方应检查句柄有效性。</returns>
|
||||
private static SafeFileHandle CreateDirectoryHandle(string dirPath)
|
||||
{
|
||||
const uint GENERIC_READ = 0x80000000;
|
||||
@@ -459,6 +534,17 @@ namespace Ink_Canvas.Helpers
|
||||
IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用原生 CreateFile API 打开或创建一个文件/目录并返回底层句柄的包装函数声明。
|
||||
/// </summary>
|
||||
/// <param name="lpFileName">要打开或创建的文件或目录的完整路径(UTF-16 编码)。</param>
|
||||
/// <param name="dwDesiredAccess">请求的访问权限位掩码(例如读取或写入访问)。</param>
|
||||
/// <param name="dwShareMode">共享模式位掩码,指定其他进程可以如何共享此文件句柄。</param>
|
||||
/// <param name="lpSecurityAttributes">指向安全属性结构的指针,或为 <see cref="IntPtr.Zero"/> 表示默认安全性。</param>
|
||||
/// <param name="dwCreationDisposition">指定如何处理已存在或不存在的文件(例如打开、创建或截断)。</param>
|
||||
/// <param name="dwFlagsAndAttributes">文件属性和标志位,用于控制文件或目录的特殊行为(例如备份语义)。</param>
|
||||
/// <param name="hTemplateFile">用于创建新文件时的模板句柄,通常为 <see cref="IntPtr.Zero"/>。</param>
|
||||
/// <returns>表示文件或目录句柄的 <see cref="Microsoft.Win32.SafeHandles.SafeFileHandle"/>;调用失败时返回无效的句柄(可通过检查句柄或调用 <see cref="System.Runtime.InteropServices.Marshal.GetLastWin32Error"/> 获取错误码)。</returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern SafeFileHandle CreateFile(
|
||||
string lpFileName,
|
||||
@@ -470,4 +556,3 @@ namespace Ink_Canvas.Helpers
|
||||
IntPtr hTemplateFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,6 +195,12 @@ namespace Ink_Canvas.Helpers
|
||||
OnSlideShowStateCheckTimerElapsed(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动内部轮询计时器以开始监测 ROT/PPT 连接状态和幻灯片放映状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果实例已被释放(Dispose 已调用)或计时器未初始化,则此方法不会执行任何操作。
|
||||
/// </remarks>
|
||||
public void StartMonitoring()
|
||||
{
|
||||
if (_disposed) return;
|
||||
@@ -202,12 +208,18 @@ namespace Ink_Canvas.Helpers
|
||||
_unifiedRotTimer?.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止内部的 ROT 监视计时器并断开与当前 PowerPoint 实例的连接。
|
||||
/// </summary>
|
||||
public void StopMonitoring()
|
||||
{
|
||||
_unifiedRotTimer?.Stop();
|
||||
DisconnectFromPPT(restartMonitoring: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制断开当前与 PowerPoint 的 ROT 连接并触发后续的重连流程。
|
||||
/// </summary>
|
||||
public void ReloadConnection()
|
||||
{
|
||||
if (_disposed) return;
|
||||
@@ -217,6 +229,12 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region Connection Management
|
||||
/// <summary>
|
||||
/// 在计时器触发时检查与 PowerPoint 的 ROT 连接,并在尚未处于已释放或模块卸载期间时尝试通过 ROT 建立连接。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果在检查或连接过程中发生异常,异常信息会记录到日志并被吞掉,不会向上抛出。
|
||||
/// </remarks>
|
||||
private void OnConnectionCheckTimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -231,6 +249,12 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在定时器触发时检查并在必要时更新 PPT 的放映状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当实例未被释放、模块未在卸载过程中且与 PowerPoint 仍保持连接时会调用内部的放映状态检查逻辑。若检查过程中发生异常,会将错误写入日志并吞并异常以保证定时器循环继续运行。
|
||||
/// </remarks>
|
||||
private void OnSlideShowStateCheckTimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -245,6 +269,16 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 ROT 检查并在必要时建立、切换或断开与 PowerPoint 的 COM 连接。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当管理器已释放或模块正在卸载时不执行任何操作。此方法在内部加锁以防止并发切换;会通过 ROT 获取当前可用的 PowerPoint 实例并:
|
||||
/// - 若尚未连接则建立连接;
|
||||
/// - 若当前无可用实例则断开现有连接;
|
||||
/// - 若检测到已连接的实例发生变化则先断开再重新连接。
|
||||
/// 遇到异常时会记录错误日志,并在存在活动连接时尝试断开以保证状态一致性。
|
||||
/// </remarks>
|
||||
private void CheckAndConnectToPPTViaRot()
|
||||
{
|
||||
if (_disposed || _isModuleUnloading) return;
|
||||
@@ -318,6 +352,13 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的 PowerPoint COM 应用对象作为当前连接并在 UI 线程上注册必要的演示文稿与幻灯片放映事件,触发连接通知并在已处于放映时尝试触发一次放映开始回调。
|
||||
/// </summary>
|
||||
/// <param name="appObj">要绑定的 PowerPoint COM 应用对象(通常来自 ROT)。</param>
|
||||
/// <remarks>
|
||||
/// 如果事件注册或后续处理失败,会记录错误并在异常路径上清除内部引用(将内部应用对象置空)。方法会触发 <c>PPTConnectionChanged(true)</c> 并记录连接成功的事件日志;若检测到当前正处于幻灯片放映,则尝试调用一次放映开始的内部处理以同步状态。异常信息会写入日志,但方法不会向外抛出未捕获的异常(内部会捕获并记录错误后清理状态)。
|
||||
/// </remarks>
|
||||
private void ConnectToPPT(object appObj)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -16,23 +16,54 @@ namespace Ink_Canvas.Helpers
|
||||
private const int SaltSizeBytes = 16;
|
||||
private const int HashSizeBytes = 32;
|
||||
|
||||
/// <summary>
|
||||
/// 检查设置中是否启用了密码安全功能。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用程序设置对象(可能为 null)。</param>
|
||||
/// <returns>`true` 当 settings 非 null 且其 Security 部分存在且已启用密码功能;`false` 否则。</returns>
|
||||
public static bool IsPasswordFeatureEnabled(Settings settings)
|
||||
=> settings?.Security != null && settings.Security.PasswordEnabled;
|
||||
=> settings?.Security != null && settings.Security.PasswordEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// 确定给定设置中是否已配置密码(存在非空的密码盐和密码哈希)。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用的设置;为 null 或未包含 Security 部分时视为未配置密码。</param>
|
||||
/// <returns>`true` 如果设置包含非空的 PasswordSalt 和 PasswordHash,否则 `false`。</returns>
|
||||
public static bool HasPasswordConfigured(Settings settings)
|
||||
=> settings?.Security != null
|
||||
&& !string.IsNullOrWhiteSpace(settings.Security.PasswordSalt)
|
||||
&& !string.IsNullOrWhiteSpace(settings.Security.PasswordHash);
|
||||
&& !string.IsNullOrWhiteSpace(settings.Security.PasswordSalt)
|
||||
&& !string.IsNullOrWhiteSpace(settings.Security.PasswordHash);
|
||||
|
||||
/// <summary>
|
||||
/// 确定在退出应用时是否需要输入密码。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用配置;如果为 null,则视为未启用或未配置密码。</param>
|
||||
/// <returns>`true` 当密码功能已启用、已配置密码且设置要求在退出时需要密码,`false` 否则。</returns>
|
||||
public static bool IsPasswordRequiredForExit(Settings settings)
|
||||
=> IsPasswordFeatureEnabled(settings) && HasPasswordConfigured(settings) && settings.Security.RequirePasswordOnExit;
|
||||
|
||||
/// <summary>
|
||||
/// 确定在进入设置界面时是否需要输入密码。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用配置;为 null 或未启用密码功能时视为未配置密码。</param>
|
||||
/// <returns>`true` 如果已启用密码功能、已配置密码且已设置为在进入设置时要求密码,`false` 否则。</returns>
|
||||
public static bool IsPasswordRequiredForEnterSettings(Settings settings)
|
||||
=> IsPasswordFeatureEnabled(settings) && HasPasswordConfigured(settings) && settings.Security.RequirePasswordOnEnterSettings;
|
||||
|
||||
/// <summary>
|
||||
/// 指示在重置配置时是否需要输入密码。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用设置对象;如果为 null 或未启用密码功能,则视为不需要密码。</param>
|
||||
/// <returns>`true` 如果已启用密码功能、已有配置的密码且设置要求在重置配置时进行密码验证;`false` 否则。</returns>
|
||||
public static bool IsPasswordRequiredForResetConfig(Settings settings)
|
||||
=> IsPasswordFeatureEnabled(settings) && HasPasswordConfigured(settings) && settings.Security.RequirePasswordOnResetConfig;
|
||||
|
||||
/// <summary>
|
||||
/// 将提供的明文密码与 Settings 中存储的密码散列进行比对以验证密码是否正确。
|
||||
/// </summary>
|
||||
/// <param name="settings">包含存储的密码盐和哈希的设置对象(使用 Base64 编码的 PasswordSalt 和 PasswordHash)。</param>
|
||||
/// <param name="password">要验证的明文密码。</param>
|
||||
/// <returns>`true` 如果密码与存储的哈希匹配,`false` 否则(包括未配置密码、password 为 null 或在解析/派生过程中发生错误)。</returns>
|
||||
public static bool VerifyPassword(Settings settings, string password)
|
||||
{
|
||||
if (!HasPasswordConfigured(settings)) return false;
|
||||
@@ -52,6 +83,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果已配置密码,显示一个对话框提示用户输入密码并验证;如果未配置密码则直接允许通过。
|
||||
/// </summary>
|
||||
/// <returns>`true` 如果未配置密码或用户确认并输入了正确的密码,`false` 如果用户取消或验证失败。</returns>
|
||||
public static async Task<bool> PromptAndVerifyAsync(Settings settings, Window owner, string title, string message)
|
||||
{
|
||||
if (!HasPasswordConfigured(settings)) return true;
|
||||
@@ -90,6 +125,11 @@ namespace Ink_Canvas.Helpers
|
||||
return VerifyPassword(settings, passwordBox.Password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示一个对话框让用户输入并确认新密码,成功时返回该密码。
|
||||
/// </summary>
|
||||
/// <param name="owner">对话框的所属窗口(用于指定父窗口)。</param>
|
||||
/// <returns>用户输入的新密码;如果用户取消或输入无效(长度不足或两次不匹配),则返回 <c>null</c>。</returns>
|
||||
public static async Task<string> PromptSetNewPasswordAsync(Window owner)
|
||||
{
|
||||
var dialog = new ContentDialog
|
||||
@@ -141,6 +181,12 @@ namespace Ink_Canvas.Helpers
|
||||
return pwd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹出对话框以更改已配置的安全密码;如果尚未配置密码则转而提示设置新密码。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用配置对象,包含当前存储的密码信息。</param>
|
||||
/// <param name="owner">对话框的父窗口(用于定位/所有权)。</param>
|
||||
/// <returns>用户成功更改后返回新的密码字符串;当用户取消、验证失败或校验不通过时返回 <c>null</c>。</returns>
|
||||
public static async Task<string> PromptChangePasswordAsync(Settings settings, Window owner)
|
||||
{
|
||||
if (!HasPasswordConfigured(settings))
|
||||
@@ -207,6 +253,11 @@ namespace Ink_Canvas.Helpers
|
||||
return newPwd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为指定 Settings 生成并存储新的密码盐与哈希到 settings.Security 中。
|
||||
/// </summary>
|
||||
/// <param name="settings">要更新的设置对象;如果为 null 或其 Security 为 null 则不执行任何操作。</param>
|
||||
/// <param name="password">用于派生哈希的原始密码字符串。</param>
|
||||
public static void SetPassword(Settings settings, string password)
|
||||
{
|
||||
if (settings?.Security == null) return;
|
||||
@@ -222,6 +273,10 @@ namespace Ink_Canvas.Helpers
|
||||
settings.Security.PasswordHash = Convert.ToBase64String(hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除设置中存储的密码信息。
|
||||
/// </summary>
|
||||
/// <param name="settings">要更新的设置对象;将把其 Security.PasswordSalt 和 Security.PasswordHash 设为空字符串。若 <paramref name="settings"/> 为 null 或其 Security 为 null 则不执行任何操作。</param>
|
||||
public static void ClearPassword(Settings settings)
|
||||
{
|
||||
if (settings?.Security == null) return;
|
||||
@@ -229,6 +284,13 @@ namespace Ink_Canvas.Helpers
|
||||
settings.Security.PasswordHash = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 PBKDF2(Rfc2898)从给定的密码和盐派生指定长度的密钥字节。
|
||||
/// </summary>
|
||||
/// <param name="password">用于派生的密码字符串。</param>
|
||||
/// <param name="salt">用于派生的盐字节数组(不可为 null)。</param>
|
||||
/// <param name="keyBytes">要返回的密钥字节长度(以字节为单位)。</param>
|
||||
/// <returns>派生出的密钥字节数组,长度等于 <paramref name="keyBytes"/>。</returns>
|
||||
private static byte[] DeriveKey(string password, byte[] salt, int keyBytes)
|
||||
{
|
||||
// 注意:Rfc2898DeriveBytes 在 net472 默认 HMACSHA1
|
||||
@@ -238,6 +300,12 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以固定时间方式比较两个字节数组的内容是否完全相同,防止基于时序的比对攻击。
|
||||
/// </summary>
|
||||
/// <param name="a">要比较的第一个字节数组。</param>
|
||||
/// <param name="b">要比较的第二个字节数组。</param>
|
||||
/// <returns>`true` 如果两个数组长度相同且所有字节相等,`false` 否则。</returns>
|
||||
private static bool FixedTimeEquals(byte[] a, byte[] b)
|
||||
{
|
||||
if (a == null || b == null) return false;
|
||||
@@ -251,4 +319,3 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+190
-22
@@ -76,6 +76,13 @@ namespace Ink_Canvas
|
||||
|
||||
#region Window Initialization
|
||||
|
||||
/// <summary>
|
||||
/// 初始化主窗口实例,构建并配置界面元素、初始页面和应用程序运行时状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 执行 UI 可见性与布局初始设置、浮动栏位置计算与动画、日志文件清理与调试标记、定时器与撤销/重做绑定、输入事件与墨迹管理器初始化、
|
||||
/// 首页画布创建、左右侧面板的触摸滑动与点击分页交互绑定、无焦点与置顶模式应用、滑块触摸支持以及延迟的首-run OOBE 检查等启动工作。
|
||||
/// </remarks>
|
||||
public MainWindow()
|
||||
{
|
||||
/*
|
||||
@@ -322,6 +329,13 @@ namespace Ink_Canvas
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在应用启动时检查是否需要展示首次运行引导(OOBE);如果尚未显示,则延迟触发 OOBE 窗口并在完成后调用 OnOobeCompleted。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在显示 OOBE 时会临时隐藏浮动工具栏(ViewboxFloatingBar);若显示过程中发生错误,会记录日志并恢复浮动工具栏的可见性。
|
||||
/// 该方法捕获内部异常并将错误写入日志,不会向上抛出异常。
|
||||
/// </remarks>
|
||||
private void CheckAndShowOobe()
|
||||
{
|
||||
try
|
||||
@@ -375,6 +389,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理完成首次引导(OOBE)后的状态更新与界面恢复。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将启动配置标记为已显示 OOBE 并持久化;在常规模式(currentMode == 0)下恢复并显示浮动工具栏(并触发边距动画);记录完成事件或在出错时记录错误信息。
|
||||
/// </remarks>
|
||||
private void OnOobeCompleted()
|
||||
{
|
||||
try
|
||||
@@ -401,6 +421,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将计时器切换为最小化视图并把最小化容器定位到当前计时器的位置。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 计算原计时器容器在窗口中的位置(若容器居中则使用 TransformToAncestor 获取实际坐标,否则使用 Margin 的 Left/Top),
|
||||
/// 将该位置应用到最小化容器并把两者的对齐方式设为左上,然后隐藏原计时器并显示最小化容器。
|
||||
/// </remarks>
|
||||
private void TimerControl_ShowMinimizedRequested(object sender, EventArgs e)
|
||||
{
|
||||
var timerContainer = FindName("TimerContainer") as FrameworkElement;
|
||||
@@ -530,6 +557,13 @@ namespace Ink_Canvas
|
||||
private bool _isBoardBrushMode;
|
||||
private double _savedInkWidthBeforeBoardBrush = 5;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化并配置画笔绘制属性并将手势事件处理器附加到 inkCanvas。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 根据应用设置(例如高级贝塞尔平滑或 FitToCurve)设置 drawingAttributes 的颜色、宽高及高亮模式;
|
||||
/// 最后订阅 inkCanvas 的 Gesture 事件以处理手势交互。
|
||||
/// </remarks>
|
||||
private void loadPenCanvas()
|
||||
{
|
||||
try
|
||||
@@ -558,6 +592,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将给定的十六进制颜色字符串规范化为一个带指定不透明度的 Color 值。
|
||||
/// </summary>
|
||||
/// <param name="hex">颜色字符串(支持 "#RRGGBB", "#AARRGGBB", "RRGGBB" 等形式);为空或无效时会使用默认值。</param>
|
||||
/// <param name="alpha">用于输出颜色的 alpha 通道(0-255)。</param>
|
||||
/// <returns>`Color`:返回与输入对应的颜色并应用给定的 alpha;对于若干常用调色板色值会做规范化映射;解析失败时返回带指定 alpha 的纯红色。</returns>
|
||||
private static Color GetCanonicalPaletteColorFromHex(string hex, byte alpha)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hex)) return Color.FromArgb(alpha, 255, 0, 0);
|
||||
@@ -600,9 +640,20 @@ namespace Ink_Canvas
|
||||
return Color.FromArgb(alpha, 255, 0, 0);
|
||||
}
|
||||
|
||||
/// <param name="color">目标颜色</param>
|
||||
/// <param name="width">目标粗细</param>
|
||||
/// <param name="height">目标高度</param>
|
||||
/// <summary>
|
||||
/// 立即应用画笔颜色、粗细与高度到当前画布并同步相关状态与 UI 元素。
|
||||
/// </summary>
|
||||
/// <param name="color">要设置的画笔颜色(包含 alpha 通道)。</param>
|
||||
/// <param name="width">要设置的画笔宽度(绘制时使用的逻辑宽度)。</param>
|
||||
/// <param name="height">要设置的画笔高度(绘制时使用的逻辑高度)。</param>
|
||||
/// <remarks>
|
||||
/// 此方法会:
|
||||
/// - 更新当前绘图属性和 inkCanvas 的默认绘图属性的颜色与尺寸(在 penType != 1 时更新宽高)。
|
||||
/// - 根据当前模式(桌面或白板)记录最近使用的颜色索引用于后续恢复或 UI 显示。
|
||||
/// - 同步 Settings.Canvas 中的 InkWidth 与 InkAlpha 值(如果 Settings 可用)。
|
||||
/// - 更新相关的宽度与透明度滑块值(若对应控件已初始化)。
|
||||
/// - 调用主题检查以确保颜色主题一致性并更新内部的 Ink_DefaultColor 状态。
|
||||
/// </remarks>
|
||||
private void SetBrushAttributesDirectly(Color color, double width, double height)
|
||||
{
|
||||
try
|
||||
@@ -688,6 +739,9 @@ namespace Ink_Canvas
|
||||
private const double BoardBrushInkWidth = 16;
|
||||
private const double BoardBrushInkHeight = 50;
|
||||
|
||||
/// <summary>
|
||||
/// 切换“板刷”模式:在板刷与普通画笔间切换,保存/恢复画笔宽度,更新 InkCanvas 的 DrawingAttributes(宽度、高度、笔尖形状、是否忽略压力等),并同步相关 UI 状态(按钮背景、滑块值)与 Settings.Canvas.InkWidth。
|
||||
/// </summary>
|
||||
private void BoardBrushModeButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_isBoardBrushMode = !_isBoardBrushMode;
|
||||
@@ -743,6 +797,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换“画板画笔”(Board brush)模式,并将画笔属性与相关 UI 状态同步为画板或普通画笔配置。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 在点击事件的发送者不是 BoardBrushModeButton 或最后一次按下的对象不匹配时不会执行任何操作。
|
||||
/// - 切换为画板模式时会保存当前宽度、设置矩形笔尖、禁用压力感应并将画笔宽高调整为画板预设值,同时将按钮背景置为激活色。
|
||||
/// - 取消画板模式时会恢复之前保存的宽度(并更新滑块与 Settings.Canvas.InkWidth)、恢复椭圆笔尖和压力感应设置,并清除按钮的自定义背景。
|
||||
/// - 如果当前 penType 等于 1,则在切换内部模式标志后不会修改画笔属性或 UI。
|
||||
/// - 内部异常会被捕获并记录,但不会向调用者抛出异常。
|
||||
/// </remarks>
|
||||
private void BoardBrushModeButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender != BoardBrushModeButton) return;
|
||||
@@ -801,6 +865,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化用于自动恢复画笔属性的计时器并应用当前的时间间隔设置。
|
||||
/// </summary>
|
||||
private void InitBrushAutoRestoreTimer()
|
||||
{
|
||||
if (_brushAutoRestoreTimer == null)
|
||||
@@ -812,6 +879,15 @@ namespace Ink_Canvas
|
||||
UpdateBrushAutoRestoreTimerInterval();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// — 根据配置计算并设置画笔自动恢复计时器的下次间隔。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 优先尝试从 Settings.Canvas.BrushAutoRestoreTimes 解析一组时间点(支持 ';', ';', ',', ',' 分隔),
|
||||
/// 并选择距离当前时间的下一个时间点来计算间隔(若当天无剩余时间点则选择下一天的最早时间点)。
|
||||
/// 若未提供有效时间点或解析失败,则使用 Settings.Canvas.BrushAutoRestoreDelaySeconds(最小为 1 秒)作为间隔。
|
||||
/// 计算得到的间隔最终赋值给 _brushAutoRestoreTimer.Interval。
|
||||
/// </remarks>
|
||||
private void UpdateBrushAutoRestoreTimerInterval()
|
||||
{
|
||||
if (_brushAutoRestoreTimer == null) return;
|
||||
@@ -888,6 +964,14 @@ namespace Ink_Canvas
|
||||
_brushAutoRestoreTimer.Interval = nextInterval.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安排(初始化并启动或重启)画笔自动恢复计时器,以便在计时器到期时恢复画笔的预设属性。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果全局设置或画布设置为空,或未启用画笔自动恢复,则不会进行任何操作。
|
||||
/// 在需要时会初始化计时器或更新其间隔,然后停止并重新启动计时器以重置计时周期。
|
||||
/// 方法内部捕获并记录异常,不会将异常向上传播。
|
||||
/// </remarks>
|
||||
internal void ScheduleBrushAutoRestore()
|
||||
{
|
||||
try
|
||||
@@ -915,6 +999,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在自动还原画笔定时器触发时,将画笔属性恢复为用户设置的颜色、不透明度和宽度,并重置定时器间隔以继续周期性还原。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果设置未启用或缺失则不会进行任何操作。透明度会限定在 0 到 255 之间;当配置宽度无效时使用当前画笔宽度或默认值作为回退值。
|
||||
/// </remarks>
|
||||
private void BrushAutoRestoreTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1047,6 +1137,12 @@ namespace Ink_Canvas
|
||||
private bool isLoaded;
|
||||
private bool forcePointEraser;
|
||||
|
||||
/// <summary>
|
||||
/// 在窗口加载完成后初始化应用的核心子系统、UI 状态和运行时监控组件。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 执行设置加载与修复、主题与背景应用、PPT 与插件相关管理器初始化、全局功能(剪贴板监控、全局快捷键、墨迹渐隐等)初始化,恢复启动参数相关状态(白板/显示模式、崩溃后动作等),注册必要的系统与控件事件,并为计时器、滑块触摸与画笔性能(如 IA 加载、画笔恢复等)做好预热与绑定。该方法为窗口呈现后的完整准备流程,不包含具体 UI 交互逻辑的实现细节描述。
|
||||
/// </remarks>
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
loadPenCanvas();
|
||||
@@ -1352,6 +1448,12 @@ namespace Ink_Canvas
|
||||
AddTouchSupportToSliders();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 响应显示器/分辨率配置变化:在检测启用时显示分辨率变更通知,并在后台检查悬浮工具栏是否位于屏幕之外,若是则在延迟后尝试将其通过动画恢复到可见区域(在演示模式下使用不同的动画偏移)。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的源对象(通常由系统事件触发)。</param>
|
||||
/// <param name="e">事件参数(未使用)。</param>
|
||||
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!Settings.Advanced.IsEnableResolutionChangeDetection) return;
|
||||
@@ -1415,6 +1517,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 Settings.Advanced.WindowMode 切换窗口显示模式。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果该设置为 true,将窗口置为普通状态并调整到主屏幕的左上角(0,0)及主屏幕分辨率的宽高,使窗口覆盖整个主屏幕;
|
||||
/// 否则将窗口设为最大化状态。
|
||||
/// </remarks>
|
||||
private void SetWindowMode()
|
||||
{
|
||||
if (Settings.Advanced.WindowMode)
|
||||
@@ -1434,6 +1543,17 @@ namespace Ink_Canvas
|
||||
private bool _allowCloseAfterExitVerification;
|
||||
private bool _isExitVerificationInProgress;
|
||||
|
||||
/// <summary>
|
||||
/// 处理主窗口的关闭流程:记录关闭事件,按需进行退出密码验证或多次确认并据此取消或允许关闭。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 会首先写入关闭日志。
|
||||
/// - 如果启用了退出密码验证,事件会被取消并异步弹出密码验证对话;验证通过后会再次触发关闭。
|
||||
/// - 如果设置了“关闭时二次确认”,会依次弹出最多三个确认对话框,任一对话被取消则终止关闭。
|
||||
/// - 在任何取消关闭的情况下都会写入相应的日志记录。
|
||||
/// </remarks>
|
||||
/// <param name="sender">触发关闭事件的源对象(通常为窗口本身)。</param>
|
||||
/// <param name="e">关闭事件参数;方法会在需要中止关闭时将 <c>e.Cancel</c> 设为 <c>true</c>。</param>
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
LogHelper.WriteLogToFile("Ink Canvas closing", LogHelper.LogType.Event);
|
||||
@@ -1550,6 +1670,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 在窗口关闭时释放和清理所有相关资源并执行退出流程。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发关闭事件的对象(通常为主窗口)。</param>
|
||||
/// <param name="e">关闭事件的参数(未使用)。</param>
|
||||
private void Window_Closed(object sender, EventArgs e)
|
||||
{
|
||||
SystemEvents.DisplaySettingsChanged -= SystemEventsOnDisplaySettingsChanged;
|
||||
@@ -2605,30 +2730,36 @@ namespace Ink_Canvas
|
||||
|
||||
#region 新设置窗口
|
||||
|
||||
private async void BtnOpenNewSettings_Click(object sender, RoutedEventArgs e)
|
||||
/// <summary>
|
||||
/// 在隐藏子面板后打开新的设置窗口;若需要则先提示并验证安全密码,并在正在打开或隐藏设置面板时不执行任何操作。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在验证密码失败或发生异常时会中止操作。成功通过验证后以模式窗口方式显示设置窗口并将当前窗口设为其所有者。
|
||||
/// </remarks>
|
||||
private async void BtnOpenNewSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isOpeningOrHidingSettingsPane) return;
|
||||
HideSubPanels();
|
||||
{
|
||||
if (isOpeningOrHidingSettingsPane) return;
|
||||
HideSubPanels();
|
||||
try
|
||||
{
|
||||
try
|
||||
if (SecurityManager.IsPasswordRequiredForEnterSettings(Settings))
|
||||
{
|
||||
if (SecurityManager.IsPasswordRequiredForEnterSettings(Settings))
|
||||
{
|
||||
bool ok = await SecurityManager.PromptAndVerifyAsync(Settings, this, "进入设置", "请输入安全密码以进入设置。");
|
||||
if (!ok) return;
|
||||
}
|
||||
bool ok = await SecurityManager.PromptAndVerifyAsync(Settings, this, "进入设置", "请输入安全密码以进入设置。");
|
||||
if (!ok) return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"安全密码校验失败: {ex}", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var settingsWindow = new SettingsWindow();
|
||||
settingsWindow.Owner = this;
|
||||
settingsWindow.ShowDialog();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"安全密码校验失败: {ex}", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var settingsWindow = new SettingsWindow();
|
||||
settingsWindow.Owner = this;
|
||||
settingsWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 新设置窗口
|
||||
|
||||
@@ -3267,7 +3398,12 @@ namespace Ink_Canvas
|
||||
|
||||
/// <summary>
|
||||
/// 在笔工具菜单中隐藏墨迹渐隐控制开关切换事件处理
|
||||
/// <summary>
|
||||
/// 切换“在笔工具菜单中隐藏墨迹渐隐控制开关”设置并立即应用该更改。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当控件切换时,方法会更新 Settings.Canvas.HideInkFadeControlInPenMenu 的值、将设置写回配置文件、刷新墨迹渐隐控件的可见性,并记录事件日志或错误日志。
|
||||
/// </remarks>
|
||||
private void ToggleSwitchHideInkFadeControlInPenMenu_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -3289,6 +3425,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据开关状态启用或禁用画笔自动恢复:更新设置并保存,启用时初始化并安排恢复定时器,禁用时停止计时器。
|
||||
/// </summary>
|
||||
private void ToggleSwitchBrushAutoRestore_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -3316,6 +3455,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当“画笔自动恢复次数”文本改变时更新并保存设置;若启用画笔自动恢复,则重新调度自动恢复定时器。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在窗口未完成加载或 Settings.Canvas 为 null 时不执行任何操作;方法内部会捕获并记录异常,不向调用方抛出异常。
|
||||
/// </remarks>
|
||||
private void BrushAutoRestoreTimesTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -3336,6 +3481,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 响应画笔自动恢复颜色下拉框的选择变更并将选中项保存为设置中的目标颜色。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当窗口已加载且 Settings.Canvas 可用时,将选中 ComboBoxItem 的 Tag(十六进制颜色字符串)写入 Settings.Canvas.BrushAutoRestoreColor 并持久化到设置文件;若发生异常则记录错误日志。
|
||||
/// </remarks>
|
||||
private void ComboBoxBrushAutoRestoreColor_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -3356,6 +3507,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将画笔自动恢复的目标粗细设置为滑块的新值并将更改保存到设置文件。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的滑块控件(通常为 BrushAutoRestoreWidthSlider)。</param>
|
||||
/// <param name="e">包含滑块的新值的事件参数;使用 <c>e.NewValue</c> 作为目标粗细。</param>
|
||||
/// <remarks>
|
||||
/// 如果窗口尚未完成加载或 Settings.Canvas 为 null,则不执行任何操作。
|
||||
/// </remarks>
|
||||
private void BrushAutoRestoreWidthSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
try
|
||||
@@ -3372,6 +3531,10 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在画笔自动恢复透明度滑块的值发生变化时,将新的透明度值保存到 Settings.Canvas.BrushAutoRestoreAlpha 并持久化到设置文件。
|
||||
/// </summary>
|
||||
/// <param name="e">来自滑块的事件参数;使用 <c>e.NewValue</c> 的整数值作为新的透明度目标。</param>
|
||||
private void BrushAutoRestoreAlphaSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
try
|
||||
@@ -3718,7 +3881,12 @@ namespace Ink_Canvas
|
||||
|
||||
/// <summary>
|
||||
/// 为所有滑块控件添加触摸和手写笔事件支持
|
||||
/// <summary>
|
||||
/// 为窗口中预定义的一组滑块控件注册触摸交互支持并记录操作结果。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果在添加触摸支持过程中发生错误,会捕获异常并将错误信息记录到日志中。
|
||||
/// </remarks>
|
||||
private void AddTouchSupportToSliders()
|
||||
{
|
||||
try
|
||||
@@ -4330,4 +4498,4 @@ namespace Ink_Canvas
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
namespace Ink_Canvas
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置助手类(预留),用于未来扩展配置相关的辅助方法。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该类目前为预留类,用于未来扩展配置相关的辅助方法。
|
||||
/// </remarks>
|
||||
internal class ConfigHelper
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using System;
|
||||
using System.Threading;
|
||||
@@ -13,9 +13,31 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 浮动栏是否折叠的标志。
|
||||
/// </summary>
|
||||
public bool isFloatingBarFolded;
|
||||
|
||||
/// <summary>
|
||||
/// 浮动栏正在改变隐藏模式的标志,用于防止重复操作。
|
||||
/// </summary>
|
||||
private bool isFloatingBarChangingHideMode;
|
||||
|
||||
/// <summary>
|
||||
/// 立即关闭白板模式,恢复到批注模式。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 检查是否正在显示或隐藏黑板,如果是则直接返回
|
||||
/// 2. 设置显示/隐藏黑板的标志为true
|
||||
/// 3. 立即隐藏所有子面板
|
||||
/// 4. 如果启用了自动切换多指手势,则关闭多指平移
|
||||
/// 5. 隐藏所有水印
|
||||
/// 6. 切换到批注模式
|
||||
/// 7. 设置退出按钮前景色为白色
|
||||
/// 8. 设置应用主题为深色
|
||||
/// 9. 200毫秒后重置显示/隐藏黑板的标志为false
|
||||
/// </remarks>
|
||||
private void CloseWhiteboardImmediately()
|
||||
{
|
||||
if (isDisplayingOrHidingBlackboard) return;
|
||||
@@ -38,11 +60,35 @@ namespace Ink_Canvas
|
||||
}).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理折叠浮动栏的鼠标点击事件。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="e">鼠标按钮事件参数。</param>
|
||||
public async void FoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
await FoldFloatingBar(sender);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 折叠浮动栏,将其收纳到侧边栏。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="isAutoFoldCommand">是否为自动折叠命令。</param>
|
||||
/// <returns>表示异步操作的任务。</returns>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 检查是否应该拒绝操作(如点击了折叠图标但上次鼠标按下的对象不是折叠图标)
|
||||
/// 2. 设置折叠/展开标志
|
||||
/// 3. 检查浮动栏是否已经折叠或正在改变隐藏模式,如果是则直接返回
|
||||
/// 4. 处理墨迹重放相关的UI元素
|
||||
/// 5. 设置浮动栏状态标志,关闭白板模式(如果当前在白板模式)
|
||||
/// 6. 如果是用户手动折叠且画布上有较多墨迹,显示通知
|
||||
/// 7. 清空画布墨迹
|
||||
/// 8. 隐藏PPT导航面板和浮动栏拖动网格
|
||||
/// 9. 执行浮动栏和侧边栏的动画
|
||||
/// 10. 如果开启了彻底隐藏,则隐藏主窗口
|
||||
/// </remarks>
|
||||
public async Task FoldFloatingBar(object sender, bool isAutoFoldCommand = false)
|
||||
{
|
||||
var isShouldRejectAction = false;
|
||||
@@ -122,6 +168,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理左侧展开按钮显示快捷面板的鼠标点击事件。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="e">鼠标按钮事件参数。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 检查是否显示快捷面板
|
||||
/// 2. 如果显示快捷面板,则隐藏右侧快捷面板,显示左侧快捷面板并执行动画
|
||||
/// 3. 否则,调用展开浮动栏的方法
|
||||
/// </remarks>
|
||||
private async void LeftUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Settings.Appearance.IsShowQuickPanel)
|
||||
@@ -152,6 +209,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理右侧展开按钮显示快捷面板的鼠标点击事件。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="e">鼠标按钮事件参数。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 检查是否显示快捷面板
|
||||
/// 2. 如果显示快捷面板,则隐藏左侧快捷面板,显示右侧快捷面板并执行动画
|
||||
/// 3. 否则,调用展开浮动栏的方法
|
||||
/// </remarks>
|
||||
private async void RightUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Settings.Appearance.IsShowQuickPanel)
|
||||
@@ -182,6 +250,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏左侧快捷面板。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 检查左侧快捷面板是否可见,如果不可见则直接返回
|
||||
/// 2. 执行左侧快捷面板的隐藏动画
|
||||
/// 3. 等待动画完成后,设置左侧快捷面板的边距并将其折叠
|
||||
/// </remarks>
|
||||
private async void HideLeftQuickPanel()
|
||||
{
|
||||
if (LeftUnFoldButtonQuickPanel.Visibility == Visibility.Visible)
|
||||
@@ -207,6 +284,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏右侧快捷面板。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 检查右侧快捷面板是否可见,如果不可见则直接返回
|
||||
/// 2. 执行右侧快捷面板的隐藏动画
|
||||
/// 3. 等待动画完成后,设置右侧快捷面板的边距并将其折叠
|
||||
/// </remarks>
|
||||
private async void HideRightQuickPanel()
|
||||
{
|
||||
if (RightUnFoldButtonQuickPanel.Visibility == Visibility.Visible)
|
||||
@@ -232,17 +318,50 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理隐藏快捷面板的鼠标点击事件。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="e">鼠标按钮事件参数。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 隐藏左侧快捷面板
|
||||
/// 2. 隐藏右侧快捷面板
|
||||
/// </remarks>
|
||||
private void HideQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
HideLeftQuickPanel();
|
||||
HideRightQuickPanel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理展开浮动栏的鼠标点击事件。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="e">鼠标按钮事件参数。</param>
|
||||
public async void UnFoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
await UnFoldFloatingBar(sender);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 展开浮动栏,将其从侧边栏恢复到正常状态。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <returns>表示异步操作的任务。</returns>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 如果之前彻底隐藏了主窗口,先恢复显示
|
||||
/// 2. 隐藏左右侧快捷面板
|
||||
/// 3. 设置展开/折叠标志
|
||||
/// 4. 检查浮动栏是否正在改变隐藏模式,如果是则直接返回
|
||||
/// 5. 设置浮动栏状态标志,标记为未折叠
|
||||
/// 6. 根据设置决定是否自动切换至批注模式
|
||||
/// 7. 根据PPT放映模式和设置显示或隐藏翻页按钮
|
||||
/// 8. 在屏幕模式下显示浮动栏并执行动画
|
||||
/// 9. 执行侧边栏动画
|
||||
/// 10. 等待UI完全更新后,重新设置当前选中模式的按钮高亮状态
|
||||
/// </remarks>
|
||||
public async Task UnFoldFloatingBar(object sender)
|
||||
{
|
||||
// 新增:如果之前彻底隐藏了,先恢复显示
|
||||
@@ -356,6 +475,21 @@ namespace Ink_Canvas
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行侧边栏边距动画,用于折叠或展开侧边栏。
|
||||
/// </summary>
|
||||
/// <param name="MarginFromEdge">侧边栏距边缘的边距值。可能的值:-50(完全折叠), -10(半展开)</param>
|
||||
/// <param name="isNoAnimation">是否禁用动画效果。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 如果边距值为-10(半展开),则显示左侧边栏
|
||||
/// 2. 创建并执行左侧边栏的边距动画
|
||||
/// 3. 创建并执行右侧边栏的边距动画
|
||||
/// 4. 等待600毫秒让动画完成
|
||||
/// 5. 直接设置侧边栏的最终边距值
|
||||
/// 6. 如果边距值为-50(完全折叠),则隐藏左侧边栏
|
||||
/// 7. 重置浮动栏正在改变隐藏模式的标志为false
|
||||
/// </remarks>
|
||||
private async void SidePannelMarginAnimation(int MarginFromEdge, bool isNoAnimation = false) // Possible value: -50, -10
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using IWshRuntimeLibrary;
|
||||
using IWshRuntimeLibrary;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Application = System.Windows.Forms.Application;
|
||||
@@ -8,6 +8,22 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建开机自启动快捷方式。
|
||||
/// </summary>
|
||||
/// <param name="exeName">可执行文件名,用于命名快捷方式。</param>
|
||||
/// <returns>创建成功返回true,失败返回false。</returns>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 创建Windows Shell对象
|
||||
/// 2. 在启动文件夹中创建快捷方式
|
||||
/// 3. 设置快捷方式的目标路径为当前可执行文件路径
|
||||
/// 4. 设置工作目录为当前目录
|
||||
/// 5. 设置窗口样式为普通窗口
|
||||
/// 6. 设置快捷方式描述
|
||||
/// 7. 保存快捷方式
|
||||
/// 8. 捕获可能的异常,确保方法不会因异常而崩溃
|
||||
/// </remarks>
|
||||
public static bool StartAutomaticallyCreate(string exeName)
|
||||
{
|
||||
try
|
||||
@@ -34,6 +50,16 @@ namespace Ink_Canvas
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除开机自启动快捷方式。
|
||||
/// </summary>
|
||||
/// <param name="exeName">可执行文件名,用于定位要删除的快捷方式。</param>
|
||||
/// <returns>删除成功返回true,失败返回false。</returns>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 在启动文件夹中删除指定名称的快捷方式
|
||||
/// 2. 捕获可能的异常,确保方法不会因异常而崩溃
|
||||
/// </remarks>
|
||||
public static bool StartAutomaticallyDel(string exeName)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -14,8 +14,16 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 浮动栏前景色,根据当前主题动态更新。
|
||||
/// </summary>
|
||||
private Color FloatBarForegroundColor;
|
||||
|
||||
/// <summary>
|
||||
/// 应用并切换到指定的主题("Light" 或 "Dark"),更新主题资源并刷新相关 UI 元素以反映主题变化。
|
||||
/// </summary>
|
||||
/// <param name="theme">主题标识,支持 "Light" 或 "Dark"(区分大小写)。</param>
|
||||
/// <param name="autoSwitchIcon">若为 true,则根据主题自动切换并保存浮动工具栏的图标设置。</param>
|
||||
private void SetTheme(string theme, bool autoSwitchIcon = false)
|
||||
{
|
||||
// 清理现有的主题资源
|
||||
@@ -328,6 +336,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理系统主题偏好变化事件,根据当前设置更新应用主题。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="e">用户偏好变化事件参数。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 根据当前主题设置(Settings.Appearance.Theme)决定使用哪种主题
|
||||
/// 2. 如果设置为0(浅色主题),则设置为Light主题
|
||||
/// 3. 如果设置为1(深色主题),则设置为Dark主题
|
||||
/// 4. 如果设置为2(跟随系统主题),则根据系统主题设置应用相应的主题
|
||||
/// </remarks>
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
switch (Settings.Appearance.Theme)
|
||||
@@ -345,6 +365,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查系统主题是否为浅色主题。
|
||||
/// </summary>
|
||||
/// <returns>系统主题为浅色返回true,深色返回false。</returns>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 从注册表中读取系统主题设置
|
||||
/// 2. 检查"SystemUsesLightTheme"键的值
|
||||
/// 3. 如果值为1,则表示系统使用浅色主题
|
||||
/// 4. 捕获可能的异常,确保方法不会因异常而崩溃
|
||||
/// </remarks>
|
||||
private bool IsSystemThemeLight()
|
||||
{
|
||||
var light = false;
|
||||
|
||||
@@ -12,15 +12,51 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储每个白板页面的墨迹集合
|
||||
/// </summary>
|
||||
private StrokeCollection[] strokeCollections = new StrokeCollection[101];
|
||||
|
||||
/// <summary>
|
||||
/// 存储每个白板页面的最后操作模式是否为重做
|
||||
/// </summary>
|
||||
private bool[] whiteboadLastModeIsRedo = new bool[101];
|
||||
|
||||
/// <summary>
|
||||
/// 存储最后一次触摸按下时的墨迹集合
|
||||
/// </summary>
|
||||
private StrokeCollection lastTouchDownStrokeCollection = new StrokeCollection();
|
||||
|
||||
/// <summary>
|
||||
/// 当前白板页面索引
|
||||
/// </summary>
|
||||
private int CurrentWhiteboardIndex = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 白板页面总数
|
||||
/// </summary>
|
||||
private int WhiteboardTotalCount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 存储每个白板页面的时间机器历史记录
|
||||
/// </summary>
|
||||
private TimeMachineHistory[][] TimeMachineHistories = new TimeMachineHistory[101][];
|
||||
|
||||
/// <summary>
|
||||
/// 存储每个白板页面的多指书写模式状态
|
||||
/// </summary>
|
||||
private bool[] savedMultiTouchModeStates = new bool[101];
|
||||
|
||||
// 保存每页白板图片信息
|
||||
/// <summary>
|
||||
/// 将当前画布上的所有未保存的图片/媒体和墨迹提交到时间机器历史并将导出结果保存为指定页的快照。
|
||||
/// </summary>
|
||||
/// <param name="isBackupMain">为 true 时将导出结果保存到主备份槽(索引 0);为 false 时保存到当前白板索引。</param>
|
||||
/// <remarks>
|
||||
/// - 会提交画布上缺失于历史记录的 Image/MediaElement(但跳过 Tag 等于 VideoPresenterLiveFrameTag 的 Image)和缺失的墨迹;
|
||||
/// - 导出后把结果存入 TimeMachineHistories 的相应索引,并保存当前多指书写模式到 savedMultiTouchModeStates;
|
||||
/// - 导出后会清除时间机器的临时墨迹历史以释放内存。
|
||||
/// - 此方法有副作用:修改 TimeMachineHistories、savedMultiTouchModeStates,并通过 timeMachine 的提交方法改变其内部历史状态。
|
||||
/// </remarks>
|
||||
private void SaveStrokes(bool isBackupMain = false)
|
||||
{
|
||||
// 确保画布上的所有UI元素都被保存到时间机器历史记录中
|
||||
@@ -119,6 +155,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除画布上的所有墨迹并执行内存清理
|
||||
/// </summary>
|
||||
/// <param name="isErasedByCode">是否由代码触发的清除操作</param>
|
||||
/// <remarks>
|
||||
/// - 根据参数设置当前提交类型
|
||||
/// - 清除画布上的所有墨迹
|
||||
/// - 执行轻量级内存清理
|
||||
/// - 恢复当前提交类型为用户输入
|
||||
/// </remarks>
|
||||
private void ClearStrokes(bool isErasedByCode)
|
||||
{
|
||||
_currentCommitType = CommitReason.ClearingCanvas;
|
||||
@@ -143,7 +189,17 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
|
||||
// 恢复每页白板图片信息
|
||||
/// <summary>
|
||||
/// 恢复指定白板页面的墨迹和元素信息
|
||||
/// </summary>
|
||||
/// <param name="isBackupMain">是否恢复主备份页面</param>
|
||||
/// <remarks>
|
||||
/// - 隐藏图片选择工具栏
|
||||
/// - 清空当前画布的墨迹和所有内容
|
||||
/// - 从时间机器历史记录中恢复页面内容
|
||||
/// - 恢复多指书写模式状态
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void RestoreStrokes(bool isBackupMain = false)
|
||||
{
|
||||
try
|
||||
@@ -232,6 +288,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理白板页面索引按钮点击事件,显示或隐藏侧边页面列表
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 处理左侧页面列表按钮点击:显示或隐藏左侧页面列表
|
||||
/// - 处理右侧页面列表按钮点击:显示或隐藏右侧页面列表
|
||||
/// - 显示页面列表时会刷新列表内容并滚动到当前页面
|
||||
/// </remarks>
|
||||
private async void BtnWhiteBoardPageIndex_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (sender == BtnLeftPageListWB)
|
||||
@@ -277,6 +343,12 @@ namespace Ink_Canvas
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到前一白板页并在切换过程中保存与恢复画布和相关状态(如果当前已是第一页则不执行任何操作)。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该方法在切换前会取消当前选中元素(同时保留并恢复编辑模式)、调用视频呈现器的离开页前钩子、保存当前页的笔迹与元素、清空画布;切换到前一页后恢复该页内容、调用视频呈现器的页已更改钩子并刷新页面索引显示。
|
||||
/// </remarks>
|
||||
private void BtnWhiteBoardSwitchPrevious_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (CurrentWhiteboardIndex <= 1) return;
|
||||
@@ -304,6 +376,11 @@ namespace Ink_Canvas
|
||||
UpdateIndexInfoDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到白板的下一页;在到达最后一页时会新增一页。方法在切页前保存当前页面的笔迹/多媒体状态,在切页后恢复目标页面的内容并更新界面状态。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的源对象(通常为按钮)。</param>
|
||||
/// <param name="e">事件参数。</param>
|
||||
private void BtnWhiteBoardSwitchNext_Click(object sender, EventArgs e)
|
||||
{
|
||||
Trace.WriteLine("113223234");
|
||||
@@ -340,6 +417,16 @@ namespace Ink_Canvas
|
||||
UpdateIndexInfoDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在白板集合中添加一个新页面:在切换前保存并清除当前页面的笔迹与状态,插入新空白页面,恢复并刷新与页面相关的 UI 状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 在达到最大页面数(99)时不执行任何操作。
|
||||
/// - 在切换前若启用了自动保存且笔迹数量超过阈值,会保存当前画面截图。
|
||||
/// - 若有选中元素,会取消选中并恢复编辑模式。
|
||||
/// - 将当前页面的历史保存到时间轴并清空画布,然后在白板集合中插入一个空白页面(其历史为 null),随后恢复该页面并触发页面变更回调。
|
||||
/// - 更新页码显示并在达到上限时禁用添加按钮;若侧边页列表可见,则刷新该列表。
|
||||
/// </remarks>
|
||||
private void BtnWhiteBoardAdd_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (WhiteboardTotalCount >= 99) return;
|
||||
@@ -385,6 +472,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理白板页面删除按钮点击事件,删除当前白板页面
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 隐藏图片选择工具栏
|
||||
/// - 清除当前画布内容
|
||||
/// - 重新排列剩余页面的历史记录
|
||||
/// - 更新当前页面索引和页面总数
|
||||
/// - 恢复剩余页面内容
|
||||
/// - 更新页码显示
|
||||
/// - 启用添加按钮(如果页面总数小于99)
|
||||
/// </remarks>
|
||||
private void BtnWhiteBoardDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 隐藏图片选择工具栏
|
||||
@@ -415,6 +516,17 @@ namespace Ink_Canvas
|
||||
if (WhiteboardTotalCount < 99) BtnWhiteBoardAdd.IsEnabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新白板页码信息显示和按钮状态
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 更新页码显示文本
|
||||
/// - 设置下一页按钮文本(根据是否为最后一页)
|
||||
/// - 启用或禁用下一页按钮(根据是否为最后一页和最大页面数)
|
||||
/// - 设置按钮颜色和透明度
|
||||
/// - 启用或禁用上一页按钮(根据是否为第一页)
|
||||
/// - 设置删除按钮状态(根据页面总数)
|
||||
/// </remarks>
|
||||
private void UpdateIndexInfoDisplay()
|
||||
{
|
||||
TextBlockWhiteBoardIndexInfo.Text =
|
||||
|
||||
@@ -12,6 +12,19 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理背景颜色按钮点击事件,显示或隐藏背景颜色选项面板
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查应用是否已加载
|
||||
/// - 创建背景选项面板(如果不存在)
|
||||
/// - 显示或隐藏背景选项面板
|
||||
/// - 隐藏其他可能显示的面板
|
||||
/// - 处理白板/黑板模式切换
|
||||
/// - 更新背景颜色和墨迹颜色
|
||||
/// </remarks>
|
||||
private void BoardChangeBackgroundColorBtn_MouseUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -124,7 +137,18 @@ namespace Ink_Canvas
|
||||
CheckColorTheme(true);
|
||||
}
|
||||
|
||||
// 创建背景选项面板
|
||||
/// <summary>
|
||||
/// 创建背景颜色选项面板
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 加载自定义背景色
|
||||
/// - 创建背景选项面板UI
|
||||
/// - 添加标题栏和关闭按钮
|
||||
/// - 添加白板/黑板模式选择按钮
|
||||
/// - 添加RGB颜色选择器
|
||||
/// - 添加颜色预览和应用按钮
|
||||
/// - 将面板添加到主网格
|
||||
/// </remarks>
|
||||
private void CreateBackgroundPalette()
|
||||
{
|
||||
// 确保加载自定义背景色
|
||||
@@ -543,7 +567,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 更新背景按钮状态
|
||||
/// <summary>
|
||||
/// 更新背景颜色选项面板中的按钮状态
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 更新白板和黑板按钮的背景和前景色
|
||||
/// - 根据当前使用的模式设置按钮状态
|
||||
/// </remarks>
|
||||
private void UpdateBackgroundButtonsState()
|
||||
{
|
||||
if (BackgroundPalette != null && BackgroundPalette.Child is StackPanel stackPanel)
|
||||
@@ -582,10 +612,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 添加成员变量保存背景面板引用
|
||||
/// <summary>
|
||||
/// 背景颜色选项面板
|
||||
/// </summary>
|
||||
private Border BackgroundPalette { get; set; }
|
||||
|
||||
// 添加成员变量保存当前自定义背景色
|
||||
/// <summary>
|
||||
/// 当前自定义背景色
|
||||
/// </summary>
|
||||
private Color? CustomBackgroundColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -702,6 +736,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理套索工具图标点击事件,切换到选择模式
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 禁用橡皮擦模式
|
||||
/// - 禁用形状绘制模式
|
||||
/// - 设置当前工具模式为选择模式
|
||||
/// - 根据编辑模式设置光标
|
||||
/// </remarks>
|
||||
private void BoardLassoIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
forceEraser = false;
|
||||
@@ -712,6 +757,22 @@ namespace Ink_Canvas
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理橡皮擦图标点击事件,切换到按笔画擦除模式
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 禁用高级橡皮擦系统
|
||||
/// - 启用橡皮擦模式
|
||||
/// - 设置橡皮擦形状为圆形
|
||||
/// - 设置当前工具模式为按笔画擦除
|
||||
/// - 禁用形状绘制模式
|
||||
/// - 重置钢笔类型和属性
|
||||
/// - 触发编辑模式变更事件
|
||||
/// - 取消单指拖动模式
|
||||
/// - 隐藏子面板
|
||||
/// </remarks>
|
||||
private void BoardEraserIconByStrokes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//if (BoardEraserByStrokes.Background.ToString() == "#FF679CF4") {
|
||||
@@ -740,6 +801,18 @@ namespace Ink_Canvas
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理删除图标点击事件,清空画布内容
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 调用钢笔图标点击事件
|
||||
/// - 调用符号删除鼠标抬起事件
|
||||
/// - 根据设置决定是否清空图片
|
||||
/// - 如果设置为清空图片,则清空所有子元素
|
||||
/// - 否则,保存非笔画元素并在清空后恢复
|
||||
/// </remarks>
|
||||
private void BoardSymbolIconDelete_MouseUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PenIcon_Click(null, null);
|
||||
@@ -764,6 +837,19 @@ namespace Ink_Canvas
|
||||
Debug.WriteLine($"BoardSymbolIconDelete: inkCanvas.Children.Count after restore: {inkCanvas.Children.Count}");
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 处理删除墨迹和历史记录图标点击事件,清空画布内容和时间机器历史
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 调用钢笔图标点击事件
|
||||
/// - 调用符号删除鼠标抬起事件
|
||||
/// - 根据设置决定是否清空时间机器历史
|
||||
/// - 根据设置决定是否清空图片
|
||||
/// - 如果设置为清空图片,则清空所有子元素
|
||||
/// - 否则,保存非笔画元素并在清空后恢复
|
||||
/// </remarks>
|
||||
private void BoardSymbolIconDeleteInkAndHistories_MouseUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PenIcon_Click(null, null);
|
||||
@@ -790,12 +876,31 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理启动希沃视频展台图标点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 调用图片黑板鼠标抬起事件
|
||||
/// - 启动希沃视频展台软件
|
||||
/// </remarks>
|
||||
private void BoardLaunchEasiCamera_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ImageBlackboard_MouseUp(null, null);
|
||||
SoftwareLauncher.LaunchEasiCamera("希沃视频展台");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理启动Desmos计算器图标点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 立即隐藏所有子面板
|
||||
/// - 调用图片黑板鼠标抬起事件
|
||||
/// - 打开Desmos计算器网页
|
||||
/// </remarks>
|
||||
private void BoardLaunchDesmos_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
HideSubPanelsImmediately();
|
||||
|
||||
@@ -21,23 +21,60 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 剪贴板更新消息常量
|
||||
/// </summary>
|
||||
private const int WM_CLIPBOARDUPDATE = 0x031D;
|
||||
|
||||
/// <summary>
|
||||
/// 添加剪贴板格式监听器
|
||||
/// </summary>
|
||||
/// <param name="hwnd">窗口句柄</param>
|
||||
/// <returns>操作是否成功</returns>
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool AddClipboardFormatListener(IntPtr hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// 移除剪贴板格式监听器
|
||||
/// </summary>
|
||||
/// <param name="hwnd">窗口句柄</param>
|
||||
/// <returns>操作是否成功</returns>
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// 剪贴板监控启用状态
|
||||
/// </summary>
|
||||
private bool isClipboardMonitoringEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// 最后一次剪贴板图像
|
||||
/// </summary>
|
||||
private BitmapSource lastClipboardImage;
|
||||
|
||||
/// <summary>
|
||||
/// 剪贴板窗口句柄源
|
||||
/// </summary>
|
||||
private HwndSource _clipboardHwndSource;
|
||||
|
||||
/// <summary>
|
||||
/// 最后一次粘贴通知时间
|
||||
/// </summary>
|
||||
private DateTime _lastPasteNotificationTime = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// 粘贴通知防抖时间(秒)
|
||||
/// </summary>
|
||||
private const int PasteNotificationDebounceSeconds = 4;
|
||||
|
||||
// 初始化剪贴板监控
|
||||
/// <summary>
|
||||
/// 启用并初始化对系统剪贴板变更的监控,确保窗口消息钩子在可用时安装并订阅剪贴板更新事件。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在首次调用时订阅内部的 ClipboardNotification.ClipboardUpdate 事件、将监控标志设为已启用,并在窗口句柄可用时安装窗口消息钩子;若句柄尚不可用则延迟到 SourceInitialized 事件完成后安装。此方法会异步调度 EnsureClipboardHookInstalled 以在加载优先级下最终确认钩子已安装。发生异常时记录错误但不会抛出。
|
||||
/// </remarks>
|
||||
private void InitializeClipboardMonitoring()
|
||||
{
|
||||
try
|
||||
@@ -60,6 +97,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// — 在窗口句柄可用且尚未安装钩子时,为接收剪贴板更新消息安装窗口消息钩子。
|
||||
/// </summary>
|
||||
private void EnsureClipboardHookInstalled()
|
||||
{
|
||||
if (_clipboardHwndSource != null) return;
|
||||
@@ -68,6 +108,12 @@ namespace Ink_Canvas
|
||||
OnSourceInitializedForClipboard(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在窗口初始化后安装用于接收系统剪贴板更改消息的窗口钩子并注册剪贴板格式监听器。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将当前窗口的 HwndSource 与 ClipboardWndProc 消息钩子关联,并调用 AddClipboardFormatListener 注册剪贴板更新通知;若无法获取窗口句柄则不执行任何操作。
|
||||
/// </remarks>
|
||||
private void OnSourceInitializedForClipboard(object sender, EventArgs e)
|
||||
{
|
||||
SourceInitialized -= OnSourceInitializedForClipboard;
|
||||
@@ -88,6 +134,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理窗口消息,响应剪贴板更新事件
|
||||
/// </summary>
|
||||
/// <param name="hwnd">窗口句柄</param>
|
||||
/// <param name="msg">消息类型</param>
|
||||
/// <param name="wParam">消息参数W</param>
|
||||
/// <param name="lParam">消息参数L</param>
|
||||
/// <param name="handled">消息是否已处理</param>
|
||||
/// <returns>处理结果</returns>
|
||||
/// <remarks>
|
||||
/// - 当收到剪贴板更新消息时,通知剪贴板变更
|
||||
/// - 标记消息为已处理
|
||||
/// </remarks>
|
||||
private IntPtr ClipboardWndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
if (msg == WM_CLIPBOARDUPDATE)
|
||||
@@ -98,7 +157,12 @@ namespace Ink_Canvas
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
// 剪贴板内容变化事件处理
|
||||
/// <summary>
|
||||
/// 在剪贴板内容变化时检查剪贴板是否包含图像并缓存该图像。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果剪贴板包含图像,则读取该图像并更新字段 <c>lastClipboardImage</c>;否则不做任何操作。方法内部会捕获异常并记录日志,不会向上抛出。
|
||||
/// </remarks>
|
||||
private void OnClipboardUpdate()
|
||||
{
|
||||
try
|
||||
@@ -116,6 +180,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在进入白板时检查系统剪贴板是否包含图片;如果存在图片且与上次提示间隔超过预设节流时间,则显示粘贴提示。
|
||||
/// </summary>
|
||||
public void CheckClipboardImageAndShowPasteNotificationWhenEnteringBoard()
|
||||
{
|
||||
try
|
||||
@@ -136,7 +203,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 显示粘贴提示
|
||||
/// <summary>
|
||||
/// 在界面上显示提示,告知用户剪贴板中存在图片并可在白板上右键粘贴。
|
||||
/// </summary>
|
||||
private void ShowPasteNotification()
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
@@ -152,7 +221,10 @@ namespace Ink_Canvas
|
||||
}), DispatcherPriority.Normal);
|
||||
}
|
||||
|
||||
// 处理右键菜单显示
|
||||
/// <summary>
|
||||
/// 在指定位置显示包含“粘贴图片”项的右键菜单(仅在剪贴板包含图片时显示)。
|
||||
/// </summary>
|
||||
/// <param name="position">右键菜单应定位的画布坐标;该位置会传递给粘贴操作以确定图片粘贴位置。</param>
|
||||
private void ShowPasteContextMenu(Point position)
|
||||
{
|
||||
try
|
||||
@@ -181,7 +253,26 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 从剪贴板粘贴图片
|
||||
/// <summary>
|
||||
/// 从剪贴板粘贴图片到画布
|
||||
/// </summary>
|
||||
/// <param name="position">粘贴位置(可选)</param>
|
||||
/// <returns>异步任务</returns>
|
||||
/// <remarks>
|
||||
/// - 检查剪贴板是否包含图片
|
||||
/// - 创建Image控件并设置属性
|
||||
/// - 生成唯一名称
|
||||
/// - 初始化变换组
|
||||
/// - 设置图片属性,避免被InkCanvas选择系统处理
|
||||
/// - 添加到画布
|
||||
/// - 等待图片加载完成后进行居中处理
|
||||
/// - 如果有指定位置,调整到指定位置
|
||||
/// - 绑定事件处理器
|
||||
/// - 提交到历史记录
|
||||
/// - 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
/// - 显示通知
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private async Task PasteImageFromClipboard(Point? position = null)
|
||||
{
|
||||
try
|
||||
@@ -297,7 +388,17 @@ namespace Ink_Canvas
|
||||
|
||||
|
||||
|
||||
// 处理白板右键事件
|
||||
/// <summary>
|
||||
/// 处理白板右键事件,显示粘贴图片菜单
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 只在白板模式下处理
|
||||
/// - 检查是否有图片在剪贴板中
|
||||
/// - 显示粘贴上下文菜单
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void InkCanvas_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -318,7 +419,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 处理全局粘贴快捷键
|
||||
/// <summary>
|
||||
/// 处理全局粘贴快捷键,粘贴剪贴板中的图片
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 只在白板模式下处理
|
||||
/// - 检查剪贴板是否包含图片
|
||||
/// - 从剪贴板粘贴图片
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
internal async void HandleGlobalPaste(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -337,7 +448,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 清理剪贴板监控
|
||||
/// <summary>
|
||||
/// 清理剪贴板监控资源
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 取消订阅剪贴板更新事件
|
||||
/// - 移除剪贴板格式监听器
|
||||
/// - 移除窗口消息钩子
|
||||
/// - 重置相关变量
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void CleanupClipboardMonitoring()
|
||||
{
|
||||
try
|
||||
@@ -362,14 +482,32 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 剪贴板通知类
|
||||
/// <summary>
|
||||
/// 剪贴板通知类,用于监控剪贴板变化
|
||||
/// </summary>
|
||||
public static class ClipboardNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// 剪贴板更新事件
|
||||
/// </summary>
|
||||
public static event Action ClipboardUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// 最后一次剪贴板文本
|
||||
/// </summary>
|
||||
private static string lastClipboardText = "";
|
||||
|
||||
/// <summary>
|
||||
/// 最后一次是否有图片
|
||||
/// </summary>
|
||||
private static bool lastHadImage;
|
||||
|
||||
/// <summary>
|
||||
/// 检查当前系统剪贴板的文本与图像状态,并在检测到相关变化或存在图像时触发 <see cref="ClipboardUpdate"/> 事件以通知订阅者。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 会比较当前剪贴板的图像存在性与文本内容与内部缓存的上一状态;若图像存在性发生变化、文本内容发生变化,或当前存在图像,则更新缓存并调用 <see cref="ClipboardUpdate"/>。方法内部捕获异常并将错误记录到日志,而不是向调用方抛出异常。
|
||||
/// </remarks>
|
||||
public static void NotifyFromMessage()
|
||||
{
|
||||
try
|
||||
@@ -390,8 +528,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止剪贴板监控
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当前实现为空方法,预留用于未来扩展
|
||||
/// </remarks>
|
||||
public static void Stop()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,24 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前墨水颜色
|
||||
/// </summary>
|
||||
private int inkColor = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 颜色切换检查,处理颜色变更和相关UI状态
|
||||
/// </summary>
|
||||
/// <param name="hidePanels">是否隐藏面板</param>
|
||||
/// <remarks>
|
||||
/// - 隐藏相关面板
|
||||
/// - 处理透明背景情况
|
||||
/// - 处理选中笔画的颜色更新
|
||||
/// - 提交笔画属性历史记录
|
||||
/// - 设置工具模式为墨水模式
|
||||
/// - 取消单指拖动模式
|
||||
/// - 检查颜色主题
|
||||
/// </remarks>
|
||||
private void ColorSwitchCheck(bool hidePanels = true)
|
||||
{
|
||||
if (hidePanels)
|
||||
@@ -76,11 +92,40 @@ namespace Ink_Canvas
|
||||
isLongPressSelected = false;
|
||||
}
|
||||
|
||||
private bool isUselightThemeColor, isDesktopUselightThemeColor;
|
||||
private int penType; // 0是签字笔,1是荧光笔
|
||||
private int lastDesktopInkColor = 1, lastBoardInkColor = 5;
|
||||
/// <summary>
|
||||
/// 是否使用亮色主题颜色
|
||||
/// </summary>
|
||||
private bool isUselightThemeColor;
|
||||
|
||||
/// <summary>
|
||||
/// 桌面模式是否使用亮色主题颜色
|
||||
/// </summary>
|
||||
private bool isDesktopUselightThemeColor;
|
||||
|
||||
/// <summary>
|
||||
/// 笔类型(0是签字笔,1是荧光笔)
|
||||
/// </summary>
|
||||
private int penType;
|
||||
|
||||
/// <summary>
|
||||
/// 桌面模式最后使用的墨水颜色
|
||||
/// </summary>
|
||||
private int lastDesktopInkColor = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 白板模式最后使用的墨水颜色
|
||||
/// </summary>
|
||||
private int lastBoardInkColor = 5;
|
||||
|
||||
/// <summary>
|
||||
/// 荧光笔颜色
|
||||
/// </summary>
|
||||
private int highlighterColor = 102;
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前模式、画笔类型与主题设置,应用并同步画布颜色、笔触颜色与界面配色指示器。
|
||||
/// </summary>
|
||||
/// <param name="changeColorTheme">为 true 时(且非桌面模式)根据白板/黑板设置刷新背景色、水印色和亮/暗主题标志;为 false 则仅同步颜色相关状态。</param>
|
||||
private void CheckColorTheme(bool changeColorTheme = false)
|
||||
{
|
||||
if (changeColorTheme)
|
||||
@@ -438,6 +483,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并更新最后使用的颜色
|
||||
/// </summary>
|
||||
/// <param name="inkColor">墨水颜色</param>
|
||||
/// <param name="isHighlighter">是否为荧光笔</param>
|
||||
/// <remarks>
|
||||
/// - 如果是荧光笔,更新荧光笔颜色
|
||||
/// - 否则,根据当前模式更新相应的最后使用颜色
|
||||
/// </remarks>
|
||||
private void CheckLastColor(int inkColor, bool isHighlighter = false)
|
||||
{
|
||||
if (isHighlighter)
|
||||
@@ -451,6 +505,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并更新笔类型UI状态
|
||||
/// </summary>
|
||||
/// <returns>异步任务</returns>
|
||||
/// <remarks>
|
||||
/// - 根据笔类型显示或隐藏相应的面板
|
||||
/// - 更新标签按钮的样式和状态
|
||||
/// - 执行面板动画
|
||||
/// - 处理签字笔和荧光笔的不同UI状态
|
||||
/// </remarks>
|
||||
private async void CheckPenTypeUIState()
|
||||
{
|
||||
if (penType == 0)
|
||||
@@ -590,6 +654,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到默认签字笔
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 设置笔类型为0(签字笔)
|
||||
/// - 更新笔类型UI状态
|
||||
/// - 检查颜色主题
|
||||
/// - 设置画笔属性(宽度、高度、笔尖形状、是否为荧光笔)
|
||||
/// </remarks>
|
||||
private void SwitchToDefaultPen(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
penType = 0;
|
||||
@@ -601,6 +676,18 @@ namespace Ink_Canvas
|
||||
drawingAttributes.IsHighlighter = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到荧光笔
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 设置笔类型为1(荧光笔)
|
||||
/// - 更新笔类型UI状态
|
||||
/// - 检查颜色主题
|
||||
/// - 设置画笔属性(宽度、高度、笔尖形状、是否为荧光笔)
|
||||
/// - 确保荧光笔模式切换后正确更新颜色和快捷调色板指示器
|
||||
/// </remarks>
|
||||
private void SwitchToHighlighterPen(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
penType = 1;
|
||||
@@ -615,60 +702,110 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理黑色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnColorBlack_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(0);
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理红色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnColorRed_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(1);
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理绿色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnColorGreen_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(2);
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理蓝色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnColorBlue_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(3);
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理黄色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnColorYellow_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(4);
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理白色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnColorWhite_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(5);
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理粉色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnColorPink_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(6);
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理橙色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnColorOrange_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(8);
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理青色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnColorTeal_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(7);
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔黑色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorBlack_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(100, true);
|
||||
@@ -677,6 +814,11 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔白色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorWhite_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(101, true);
|
||||
@@ -685,6 +827,11 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔红色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorRed_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(102, true);
|
||||
@@ -693,6 +840,11 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔黄色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorYellow_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(103, true);
|
||||
@@ -701,6 +853,11 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔绿色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorGreen_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(104, true);
|
||||
@@ -709,6 +866,11 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔锌色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorZinc_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(105, true);
|
||||
@@ -717,6 +879,11 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔蓝色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorBlue_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(106, true);
|
||||
@@ -725,6 +892,11 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔紫色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorPurple_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(107, true);
|
||||
@@ -733,6 +905,11 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔青色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorTeal_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(108, true);
|
||||
@@ -741,6 +918,11 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理荧光笔橙色按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
private void BtnHighlighterColorOrange_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(109, true);
|
||||
@@ -749,6 +931,15 @@ namespace Ink_Canvas
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为颜色对象
|
||||
/// </summary>
|
||||
/// <param name="colorStr">颜色字符串(格式:#FFFFFFFF)</param>
|
||||
/// <returns>颜色对象</returns>
|
||||
/// <remarks>
|
||||
/// - 解析颜色字符串为ARGB值
|
||||
/// - 转换为Color对象返回
|
||||
/// </remarks>
|
||||
private Color StringToColor(string colorStr)
|
||||
{
|
||||
var argb = new byte[4];
|
||||
@@ -763,6 +954,15 @@ namespace Ink_Canvas
|
||||
return Color.FromArgb(argb[0], argb[1], argb[2], argb[3]); //#FFFFFFFF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符转换为字节
|
||||
/// </summary>
|
||||
/// <param name="c">字符</param>
|
||||
/// <returns>字节值</returns>
|
||||
/// <remarks>
|
||||
/// - 将十六进制字符转换为对应的字节值
|
||||
/// - 支持0-9和A-F字符
|
||||
/// </remarks>
|
||||
private static byte toByte(char c)
|
||||
{
|
||||
var b = (byte)"0123456789ABCDEF".IndexOf(c);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
@@ -19,12 +19,40 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
// 当前选中的可操作元素
|
||||
/// <summary>
|
||||
/// 当前选中的可操作元素
|
||||
/// </summary>
|
||||
private FrameworkElement currentSelectedElement;
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在拖动
|
||||
/// </summary>
|
||||
private bool isDragging;
|
||||
|
||||
/// <summary>
|
||||
/// 拖动起始点
|
||||
/// </summary>
|
||||
private Point dragStartPoint;
|
||||
|
||||
#region Image
|
||||
/// <summary>
|
||||
/// 处理图片插入按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 打开文件选择对话框,选择图片文件
|
||||
/// - 创建并压缩图片
|
||||
/// - 设置图片属性,避免被InkCanvas选择系统处理
|
||||
/// - 初始化InkCanvas选择设置
|
||||
/// - 添加图片到画布
|
||||
/// - 等待图片加载完成后进行后续处理
|
||||
/// - 初始化TransformGroup
|
||||
/// - 居中缩放图片
|
||||
/// - 绑定事件处理器
|
||||
/// - 提交到时间机器历史记录
|
||||
/// - 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
/// </remarks>
|
||||
private async void BtnImageInsert_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenFileDialog openFileDialog = new OpenFileDialog();
|
||||
@@ -79,7 +107,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化元素的TransformGroup
|
||||
/// <summary>
|
||||
/// 初始化元素的TransformGroup
|
||||
/// </summary>
|
||||
/// <param name="element">要初始化的元素</param>
|
||||
/// <remarks>
|
||||
/// - 创建TransformGroup
|
||||
/// - 添加ScaleTransform、TranslateTransform和RotateTransform
|
||||
/// - 设置元素的RenderTransform
|
||||
/// </remarks>
|
||||
private void InitializeElementTransform(FrameworkElement element)
|
||||
{
|
||||
var transformGroup = new TransformGroup();
|
||||
@@ -89,7 +125,17 @@ namespace Ink_Canvas
|
||||
element.RenderTransform = transformGroup;
|
||||
}
|
||||
|
||||
// 绑定元素事件处理器
|
||||
/// <summary>
|
||||
/// 绑定元素事件处理器
|
||||
/// </summary>
|
||||
/// <param name="element">要绑定事件的元素</param>
|
||||
/// <remarks>
|
||||
/// - 绑定鼠标事件(MouseLeftButtonDown、MouseLeftButtonUp、MouseMove、MouseWheel)
|
||||
/// - 启用触摸操作
|
||||
/// - 绑定触摸事件(ManipulationDelta、ManipulationCompleted)
|
||||
/// - 设置光标为手形
|
||||
/// - 禁用InkCanvas对图片的选择处理
|
||||
/// </remarks>
|
||||
private void BindElementEvents(FrameworkElement element)
|
||||
{
|
||||
// 鼠标事件
|
||||
@@ -111,7 +157,19 @@ namespace Ink_Canvas
|
||||
element.Focusable = false;
|
||||
}
|
||||
|
||||
// 鼠标左键按下事件
|
||||
/// <summary>
|
||||
/// 处理元素鼠标左键按下事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查编辑模式是否为选择模式
|
||||
/// - 取消之前选中的元素
|
||||
/// - 选中当前元素
|
||||
/// - 开始拖动
|
||||
/// - 捕获鼠标
|
||||
/// - 设置光标为全尺寸光标
|
||||
/// </remarks>
|
||||
private void Element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
@@ -145,7 +203,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标左键释放事件
|
||||
/// <summary>
|
||||
/// 处理元素鼠标左键释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 停止拖动
|
||||
/// - 释放鼠标捕获
|
||||
/// - 恢复光标为手形
|
||||
/// </remarks>
|
||||
private void Element_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
@@ -158,7 +225,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸释放事件
|
||||
/// <summary>
|
||||
/// 处理元素触摸释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 停止拖动
|
||||
/// - 释放触摸捕获
|
||||
/// - 恢复光标为手形
|
||||
/// </remarks>
|
||||
private void Element_TouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
@@ -171,7 +247,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标移动事件
|
||||
/// <summary>
|
||||
/// 处理元素鼠标移动事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查是否正在拖动且鼠标已捕获
|
||||
/// - 获取当前鼠标位置
|
||||
/// - 应用鼠标拖动变换
|
||||
/// - 如果是图片元素,更新工具栏位置
|
||||
/// - 如果是图片元素,更新选择点位置
|
||||
/// - 更新拖动起始点
|
||||
/// </remarks>
|
||||
private void Element_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element && isDragging && element.IsMouseCaptured)
|
||||
@@ -198,7 +286,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标滚轮事件 - 缩放
|
||||
/// <summary>
|
||||
/// 处理元素鼠标滚轮事件 - 缩放
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 应用滚轮缩放变换
|
||||
/// - 如果是图片元素,更新工具栏位置
|
||||
/// - 如果是图片元素,更新选择点位置
|
||||
/// </remarks>
|
||||
private void Element_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
@@ -224,7 +321,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸按下事件
|
||||
/// <summary>
|
||||
/// 处理元素触摸按下事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查编辑模式是否为选择模式
|
||||
/// - 取消之前选中的元素
|
||||
/// - 选中当前元素
|
||||
/// - 开始拖动
|
||||
/// - 捕获触摸
|
||||
/// - 设置光标为全尺寸光标
|
||||
/// </remarks>
|
||||
private void Element_TouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
@@ -258,7 +367,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸操作事件
|
||||
/// <summary>
|
||||
/// 处理元素触摸操作事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查是否是双指手势
|
||||
/// - 双指手势时,让画布级别的手势处理
|
||||
/// - 单指手势时,应用触摸拖动变换
|
||||
/// - 如果是图片元素,更新工具栏位置
|
||||
/// - 如果是图片元素,更新选择点位置
|
||||
/// </remarks>
|
||||
private void Element_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
@@ -291,13 +411,30 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸操作完成事件
|
||||
/// <summary>
|
||||
/// 处理元素触摸操作完成事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 可以在这里添加操作完成后的处理逻辑
|
||||
/// </remarks>
|
||||
private void Element_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
|
||||
{
|
||||
// 可以在这里添加操作完成后的处理逻辑
|
||||
}
|
||||
|
||||
// 应用平移变换
|
||||
/// <summary>
|
||||
/// 应用平移变换到元素
|
||||
/// </summary>
|
||||
/// <param name="element">要变换的元素</param>
|
||||
/// <param name="deltaX">X轴偏移量</param>
|
||||
/// <param name="deltaY">Y轴偏移量</param>
|
||||
/// <remarks>
|
||||
/// - 获取元素的TransformGroup
|
||||
/// - 查找TranslateTransform
|
||||
/// - 应用平移变换
|
||||
/// </remarks>
|
||||
private void ApplyTranslateTransform(FrameworkElement element, double deltaX, double deltaY)
|
||||
{
|
||||
if (element.RenderTransform is TransformGroup transformGroup)
|
||||
@@ -311,7 +448,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 应用缩放变换
|
||||
/// <summary>
|
||||
/// 应用缩放变换到元素
|
||||
/// </summary>
|
||||
/// <param name="element">要变换的元素</param>
|
||||
/// <param name="scaleFactor">缩放因子</param>
|
||||
/// <param name="center">缩放中心</param>
|
||||
/// <remarks>
|
||||
/// - 获取元素的TransformGroup
|
||||
/// - 查找ScaleTransform
|
||||
/// - 设置缩放中心
|
||||
/// - 应用缩放
|
||||
/// - 限制缩放范围(0.1到5.0)
|
||||
/// </remarks>
|
||||
private void ApplyScaleTransform(FrameworkElement element, double scaleFactor, Point center)
|
||||
{
|
||||
if (element.RenderTransform is TransformGroup transformGroup)
|
||||
@@ -334,7 +483,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 应用旋转变换
|
||||
/// <summary>
|
||||
/// 应用旋转变换到元素
|
||||
/// </summary>
|
||||
/// <param name="element">要变换的元素</param>
|
||||
/// <param name="angle">旋转角度</param>
|
||||
/// <remarks>
|
||||
/// - 获取元素的TransformGroup
|
||||
/// - 查找RotateTransform
|
||||
/// - 应用旋转变换
|
||||
/// </remarks>
|
||||
private void ApplyRotateTransform(FrameworkElement element, double angle)
|
||||
{
|
||||
if (element.RenderTransform is TransformGroup transformGroup)
|
||||
@@ -347,7 +505,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 选中元素
|
||||
/// <summary>
|
||||
/// 选中元素
|
||||
/// </summary>
|
||||
/// <param name="element">要选中的元素</param>
|
||||
/// <remarks>
|
||||
/// - 设置当前选中元素
|
||||
/// - 根据元素类型显示不同的选择工具栏
|
||||
/// - 如果是图片元素,显示图片选择工具栏和缩放选择点
|
||||
/// - 如果不是图片元素,隐藏图片选择工具栏和缩放选择点
|
||||
/// - 确保选择框不显示,避免蓝色边框
|
||||
/// - 禁用InkCanvas的选择功能,去除控制点
|
||||
/// - 保持选择模式,这样用户可以直接点击墨迹来选择
|
||||
/// </remarks>
|
||||
private void SelectElement(FrameworkElement element)
|
||||
{
|
||||
currentSelectedElement = element;
|
||||
@@ -400,7 +570,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 取消选中元素
|
||||
/// <summary>
|
||||
/// 取消选中元素
|
||||
/// </summary>
|
||||
/// <param name="element">要取消选中的元素</param>
|
||||
/// <remarks>
|
||||
/// - 隐藏图片选择工具栏
|
||||
/// - 隐藏图片缩放选择点
|
||||
/// - 确保选择框隐藏
|
||||
/// - 确保InkCanvas处于选择模式,这样用户可以直接点击墨迹来选择
|
||||
/// </remarks>
|
||||
private void UnselectElement(FrameworkElement element)
|
||||
{
|
||||
// 去除选中效果
|
||||
@@ -430,7 +609,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 应用矩阵变换到元素
|
||||
/// <summary>
|
||||
/// 应用矩阵变换到元素
|
||||
/// </summary>
|
||||
/// <param name="element">要变换的元素</param>
|
||||
/// <param name="matrix">变换矩阵</param>
|
||||
/// <remarks>
|
||||
/// - 获取元素的RenderTransform,如果不存在则创建新的TransformGroup
|
||||
/// - 创建MatrixTransform
|
||||
/// - 将MatrixTransform添加到TransformGroup
|
||||
/// </remarks>
|
||||
private void ApplyElementMatrixTransform(FrameworkElement element, Matrix matrix)
|
||||
{
|
||||
if (element.RenderTransform is TransformGroup transformGroup)
|
||||
@@ -443,7 +631,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 滚轮缩放的核心机制
|
||||
/// <summary>
|
||||
/// 处理滚轮缩放的核心机制
|
||||
/// </summary>
|
||||
/// <param name="element">要缩放的元素</param>
|
||||
/// <param name="e">鼠标滚轮事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 根据滚轮方向确定缩放比例(向上1.1倍,向下0.9倍)
|
||||
/// - 计算选中元素的中心点作为缩放中心
|
||||
/// - 创建 Matrix 对象并应用 ScaleAt 变换
|
||||
/// - 对选中的图片元素应用矩阵变换
|
||||
/// - 对选中的笔画应用 Transform 方法
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void ApplyWheelScaleTransform(FrameworkElement element, MouseWheelEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -476,7 +676,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 矩阵变换的完整实现
|
||||
/// <summary>
|
||||
/// 矩阵变换的完整实现
|
||||
/// </summary>
|
||||
/// <param name="element">要变换的元素</param>
|
||||
/// <param name="matrix">变换矩阵</param>
|
||||
/// <param name="saveHistory">是否保存历史记录</param>
|
||||
/// <remarks>
|
||||
/// - 获取元素的 RenderTransform,如果不存在则创建新的 TransformGroup
|
||||
/// - 保存初始变换状态用于历史记录
|
||||
/// - 创建新的 TransformGroup 并添加 MatrixTransform
|
||||
/// - 将新的变换组添加到现有的变换组中
|
||||
/// - 如果启用了历史记录,提交变换历史
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void ApplyMatrixTransformToElement(FrameworkElement element, Matrix matrix, bool saveHistory = true)
|
||||
{
|
||||
try
|
||||
@@ -513,7 +726,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标拖动的完整实现机制
|
||||
/// <summary>
|
||||
/// 鼠标拖动的完整实现机制
|
||||
/// </summary>
|
||||
/// <param name="element">要拖动的元素</param>
|
||||
/// <param name="currentPoint">当前鼠标位置</param>
|
||||
/// <param name="startPoint">起始鼠标位置</param>
|
||||
/// <remarks>
|
||||
/// - 计算鼠标移动的位移向量
|
||||
/// - 创建 Matrix 对象并应用 Translate 变换
|
||||
/// - 对选中的图片元素应用矩阵变换
|
||||
/// - 对选中的笔画应用变换
|
||||
/// - 更新选择框的位置(如果有选择框)
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void ApplyMouseDragTransform(FrameworkElement element, Point currentPoint, Point startPoint)
|
||||
{
|
||||
try
|
||||
@@ -546,7 +772,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 更新选择框位置
|
||||
/// <summary>
|
||||
/// 更新选择框位置
|
||||
/// </summary>
|
||||
/// <param name="delta">位移向量</param>
|
||||
/// <remarks>
|
||||
/// - 更新选择框位置的逻辑
|
||||
/// - 更新 BorderStrokeSelectionControl 的位置
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void UpdateSelectionBorderPosition(Vector delta)
|
||||
{
|
||||
try
|
||||
@@ -570,7 +804,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 提交变换历史
|
||||
/// <summary>
|
||||
/// 提交变换历史
|
||||
/// </summary>
|
||||
/// <param name="element">变换的元素</param>
|
||||
/// <param name="initialTransform">初始变换</param>
|
||||
/// <param name="finalTransform">最终变换</param>
|
||||
/// <remarks>
|
||||
/// - 提交变换历史到时间机器的逻辑
|
||||
/// - 记录变换前后的状态
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void CommitTransformHistory(FrameworkElement element, TransformGroup initialTransform, TransformGroup finalTransform)
|
||||
{
|
||||
try
|
||||
@@ -585,7 +829,21 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸拖动的完整实现
|
||||
/// <summary>
|
||||
/// 触摸拖动的完整实现
|
||||
/// </summary>
|
||||
/// <param name="element">要操作的元素</param>
|
||||
/// <param name="e">操作事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 支持单指拖动和多指手势
|
||||
/// - 可以同时进行平移、旋转和缩放
|
||||
/// - 通过 ManipulationDelta 获取手势变化信息
|
||||
/// - 应用平移
|
||||
/// - 支持两指缩放和旋转操作
|
||||
/// - 应用变换到元素
|
||||
/// - 应用变换到选中的笔画
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void ApplyTouchManipulationTransform(FrameworkElement element, ManipulationDeltaEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -643,6 +901,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并压缩图片
|
||||
/// </summary>
|
||||
/// <param name="filePath">图片文件路径</param>
|
||||
/// <returns>创建的Image对象</returns>
|
||||
/// <remarks>
|
||||
/// - 创建文件依赖目录
|
||||
/// - 复制文件到依赖目录
|
||||
/// - 创建BitmapImage
|
||||
/// - 如果图片尺寸大于1920x1080且设置了压缩图片,则压缩图片
|
||||
/// - 否则使用原始尺寸
|
||||
/// - 返回创建的Image对象
|
||||
/// </remarks>
|
||||
private async Task<Image> CreateAndCompressImageAsync(string filePath)
|
||||
{
|
||||
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
|
||||
@@ -697,6 +968,21 @@ namespace Ink_Canvas
|
||||
#endregion
|
||||
|
||||
#region Media
|
||||
/// <summary>
|
||||
/// 处理媒体插入按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 打开文件选择对话框,选择媒体文件
|
||||
/// - 读取媒体文件字节
|
||||
/// - 创建MediaElement
|
||||
/// - 居中缩放MediaElement
|
||||
/// - 设置位置并添加到画布
|
||||
/// - 设置LoadedBehavior和UnloadedBehavior为Manual
|
||||
/// - 媒体加载完成后播放并立即暂停
|
||||
/// - 提交到时间机器历史记录
|
||||
/// </remarks>
|
||||
private async void BtnMediaInsert_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenFileDialog openFileDialog = new OpenFileDialog();
|
||||
@@ -732,6 +1018,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建MediaElement
|
||||
/// </summary>
|
||||
/// <param name="filePath">媒体文件路径</param>
|
||||
/// <returns>创建的MediaElement对象</returns>
|
||||
/// <remarks>
|
||||
/// - 创建文件依赖目录
|
||||
/// - 创建MediaElement
|
||||
/// - 设置Source、名称、LoadedBehavior和UnloadedBehavior
|
||||
/// - 设置宽度和高度
|
||||
/// - 复制文件到依赖目录
|
||||
/// - 更新Source为新文件路径
|
||||
/// - 返回创建的MediaElement对象
|
||||
/// </remarks>
|
||||
private async Task<MediaElement> CreateMediaElementAsync(string filePath)
|
||||
{
|
||||
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
|
||||
@@ -988,6 +1288,29 @@ namespace Ink_Canvas
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 居中并缩放元素
|
||||
/// </summary>
|
||||
/// <param name="element">要居中缩放的元素</param>
|
||||
/// <remarks>
|
||||
/// - 确保元素已加载且有有效尺寸
|
||||
/// - 如果元素尺寸无效,等待加载完成后再处理
|
||||
/// - 获取画布的实际尺寸
|
||||
/// - 如果画布尺寸为0,使用窗口尺寸作为备选
|
||||
/// - 如果仍然为0,使用屏幕尺寸
|
||||
/// - 计算最大允许尺寸(画布的70%)
|
||||
/// - 获取元素的当前尺寸
|
||||
/// - 计算缩放比例
|
||||
/// - 如果元素本身比最大尺寸小,不进行缩放
|
||||
/// - 计算新的尺寸
|
||||
/// - 设置元素尺寸
|
||||
/// - 计算居中位置
|
||||
/// - 确保位置不为负数
|
||||
/// - 设置位置
|
||||
/// - 保持TransformGroup,不清除RenderTransform
|
||||
/// - 只有在没有TransformGroup时才创建
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void CenterAndScaleElement(FrameworkElement element)
|
||||
{
|
||||
try
|
||||
@@ -1079,7 +1402,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
/// <summary>
|
||||
/// 初始化InkCanvas选择设置
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 清除当前选择,避免显示控制点
|
||||
/// - 设置编辑模式为非选择模式
|
||||
/// </remarks>
|
||||
private void InitializeInkCanvasSelectionSettings()
|
||||
{
|
||||
if (inkCanvas != null)
|
||||
@@ -1091,7 +1420,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图片选择工具栏位置
|
||||
/// <summary>
|
||||
/// 更新图片选择工具栏位置
|
||||
/// </summary>
|
||||
/// <param name="element">图片元素</param>
|
||||
/// <remarks>
|
||||
/// - 获取元素的实际边界(考虑变换)
|
||||
/// - 计算工具栏位置(显示在图片下方)
|
||||
/// - 确保工具栏不超出画布边界
|
||||
/// - 设置工具栏位置
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void UpdateImageSelectionToolbarPosition(FrameworkElement element)
|
||||
{
|
||||
try
|
||||
@@ -1121,7 +1460,22 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 获取元素的实际边界(考虑变换)
|
||||
/// <summary>
|
||||
/// 获取元素的实际边界(考虑变换)
|
||||
/// </summary>
|
||||
/// <param name="element">要获取边界的元素</param>
|
||||
/// <returns>元素的实际边界</returns>
|
||||
/// <remarks>
|
||||
/// - 获取元素的Left和Top位置
|
||||
/// - 如果值为NaN,设为0
|
||||
/// - 获取元素的宽度和高度
|
||||
/// - 检查是否有RenderTransform
|
||||
/// - 如果有变换,使用变换后的边界
|
||||
/// - 变换失败时回退到简单计算
|
||||
/// - 没有变换时直接使用位置和大小
|
||||
/// - 包含异常处理
|
||||
/// - 回退到基本计算
|
||||
/// </remarks>
|
||||
private Rect GetElementActualBounds(FrameworkElement element)
|
||||
{
|
||||
try
|
||||
@@ -1170,7 +1524,20 @@ namespace Ink_Canvas
|
||||
|
||||
#region Image Selection Toolbar Event Handlers
|
||||
|
||||
// 图片克隆功能
|
||||
/// <summary>
|
||||
/// 处理图片克隆功能
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查当前选中元素是否为图片
|
||||
/// - 创建克隆图片
|
||||
/// - 添加到画布
|
||||
/// - 初始化变换
|
||||
/// - 绑定事件
|
||||
/// - 记录历史
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void BorderImageClone_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1201,7 +1568,22 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 图片克隆到新页面
|
||||
/// <summary>
|
||||
/// 处理图片克隆到新页面功能
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查当前选中元素是否为图片
|
||||
/// - 创建新页面
|
||||
/// - 创建克隆图片
|
||||
/// - 设置图片属性,避免被InkCanvas选择系统处理
|
||||
/// - 初始化变换
|
||||
/// - 绑定事件
|
||||
/// - 添加到新页面的画布
|
||||
/// - 记录历史
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void BorderImageCloneToNewBoard_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1242,7 +1624,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 图片左旋转
|
||||
/// <summary>
|
||||
/// 处理图片左旋转功能
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查当前是否有选中元素
|
||||
/// - 应用旋转变换(向左旋转45度)
|
||||
/// - 如果是图片元素,更新工具栏位置
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void BorderImageRotateLeft_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1266,7 +1658,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 图片右旋转
|
||||
/// <summary>
|
||||
/// 处理图片右旋转功能
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查当前是否有选中元素
|
||||
/// - 应用旋转变换(向右旋转45度)
|
||||
/// - 如果是图片元素,更新工具栏位置
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void BorderImageRotateRight_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1290,7 +1692,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 图片缩放减小
|
||||
/// <summary>
|
||||
/// 处理图片缩放减小功能
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查当前是否有选中元素
|
||||
/// - 计算元素中心点
|
||||
/// - 应用缩放变换(缩小到0.9倍)
|
||||
/// - 如果是图片元素,更新工具栏位置
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void GridImageScaleDecrease_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1315,7 +1728,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 图片缩放增大
|
||||
/// <summary>
|
||||
/// 处理图片缩放增大功能
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查当前是否有选中元素
|
||||
/// - 计算元素中心点
|
||||
/// - 应用缩放变换(放大到1.1倍)
|
||||
/// - 如果是图片元素,更新工具栏位置
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void GridImageScaleIncrease_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1340,7 +1764,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 图片删除
|
||||
/// <summary>
|
||||
/// 处理图片删除功能
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查当前是否有选中元素
|
||||
/// - 保存删除前的编辑模式
|
||||
/// - 记录删除历史
|
||||
/// - 从画布中移除
|
||||
/// - 清除选中状态
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void BorderImageDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前工具模式
|
||||
/// </summary>
|
||||
private string _currentToolMode = "cursor";
|
||||
|
||||
#region "手勢"按鈕
|
||||
@@ -180,12 +183,32 @@ namespace Ink_Canvas
|
||||
|
||||
#region 浮動工具欄的拖動實現
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在拖动浮动工具栏
|
||||
/// </summary>
|
||||
private bool isDragDropInEffect;
|
||||
/// <summary>
|
||||
/// 当前位置
|
||||
/// </summary>
|
||||
private Point pos;
|
||||
/// <summary>
|
||||
/// 按下鼠标时的位置
|
||||
/// </summary>
|
||||
private Point downPos;
|
||||
private Point pointDesktop = new Point(-1, -1); //用于记录上次在桌面时的坐标
|
||||
private Point pointPPT = new Point(-1, -1); //用于记录上次在PPT中的坐标
|
||||
/// <summary>
|
||||
/// 用于记录上次在桌面时的坐标
|
||||
/// </summary>
|
||||
private Point pointDesktop = new Point(-1, -1);
|
||||
/// <summary>
|
||||
/// 用于记录上次在PPT中的坐标
|
||||
/// </summary>
|
||||
private Point pointPPT = new Point(-1, -1);
|
||||
|
||||
/// <summary>
|
||||
/// 浮动工具栏移动事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标事件参数</param>
|
||||
private void SymbolIconEmoji_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (isDragDropInEffect)
|
||||
@@ -202,6 +225,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浮动工具栏鼠标按下事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void SymbolIconEmoji_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (isViewboxFloatingBarMarginAnimationRunning)
|
||||
@@ -216,6 +244,11 @@ namespace Ink_Canvas
|
||||
GridForFloatingBarDraging.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浮动工具栏鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
internal void SymbolIconEmoji_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
isDragDropInEffect = false;
|
||||
@@ -243,7 +276,7 @@ namespace Ink_Canvas
|
||||
#region 隱藏子面板和按鈕背景高亮
|
||||
|
||||
/// <summary>
|
||||
/// 隱藏形狀繪製面板
|
||||
/// 隐藏形状绘制面板
|
||||
/// </summary>
|
||||
private void CollapseBorderDrawShape()
|
||||
{
|
||||
@@ -252,7 +285,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>HideSubPanels</c>的青春版。目前需要修改<c>BorderSettings</c>的關閉機制(改為動畫關閉)。
|
||||
/// HideSubPanels的简化版,立即隐藏所有子面板,无动画效果
|
||||
/// </summary>
|
||||
private void HideSubPanelsImmediately()
|
||||
{
|
||||
@@ -587,6 +620,11 @@ namespace Ink_Canvas
|
||||
|
||||
#region 撤銷重做按鈕
|
||||
|
||||
/// <summary>
|
||||
/// 撤销按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
internal void SymbolIconUndo_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
//if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -600,6 +638,11 @@ namespace Ink_Canvas
|
||||
HideSubPanels();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重做按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
internal void SymbolIconRedo_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
//if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -617,9 +660,16 @@ namespace Ink_Canvas
|
||||
|
||||
#region 白板按鈕和退出白板模式按鈕
|
||||
|
||||
//private bool Not_Enter_Blackboard_fir_Mouse_Click = true;
|
||||
/// <summary>
|
||||
/// 是否正在显示或隐藏黑板
|
||||
/// </summary>
|
||||
private bool isDisplayingOrHidingBlackboard;
|
||||
|
||||
/// <summary>
|
||||
/// 白板按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
internal void ImageBlackboard_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
|
||||
@@ -868,6 +918,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// 光标图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private async void SymbolIconCursor_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (currentMode != 0)
|
||||
@@ -888,6 +943,11 @@ namespace Ink_Canvas
|
||||
|
||||
#region 清空畫布按鈕
|
||||
|
||||
/// <summary>
|
||||
/// 清空画布按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
internal void SymbolIconDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
|
||||
@@ -935,10 +995,10 @@ namespace Ink_Canvas
|
||||
#region 主要的工具按鈕事件
|
||||
|
||||
/// <summary>
|
||||
/// 浮動工具欄的"套索選"按鈕事件,重定向到舊UI的<c>BtnSelect_Click</c>方法
|
||||
/// 浮动工具栏的"套索选"按钮事件,重定向到旧UI的BtnSelect_Click方法
|
||||
/// </summary>
|
||||
/// <param name="sender">sender</param>
|
||||
/// <param name="e">MouseButtonEventArgs</param>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
internal void SymbolIconSelect_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
|
||||
@@ -957,6 +1017,11 @@ namespace Ink_Canvas
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 浮动工具栏按钮鼠标按下反馈效果处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void FloatingBarToolBtnMouseDownFeedback_Panel(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is Panel panel)
|
||||
@@ -984,6 +1049,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浮动工具栏按钮鼠标离开反馈效果处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标事件参数</param>
|
||||
private void FloatingBarToolBtnMouseLeaveFeedback_Panel(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (sender is Panel panel)
|
||||
@@ -1039,12 +1109,23 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private void SymbolIconSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isOpeningOrHidingSettingsPane) return;
|
||||
HideSubPanels();
|
||||
BtnSettings_Click(null, null);
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 截图图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private async void SymbolIconScreenshot_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
HideSubPanelsImmediately();
|
||||
@@ -1052,6 +1133,11 @@ namespace Ink_Canvas
|
||||
SaveScreenShotToDesktop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 倒计时计时器图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void ImageCountdownTimer_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
LeftUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
@@ -1101,6 +1187,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作指南窗口图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void OperatingGuideWindowIcon_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BorderTools);
|
||||
@@ -1110,6 +1201,11 @@ namespace Ink_Canvas
|
||||
new OperatingGuideWindow().Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 随机点名图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void SymbolIconRand_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 如果控件被隐藏,不处理事件
|
||||
@@ -1175,6 +1271,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并更新橡皮擦类型标签的状态
|
||||
/// </summary>
|
||||
public void CheckEraserTypeTab()
|
||||
{
|
||||
if (Settings.Canvas.EraserShapeType == 0)
|
||||
@@ -1235,6 +1334,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单次点名图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void SymbolIconRandOne_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 如果控件被隐藏,不处理事件
|
||||
@@ -1306,6 +1410,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void GridInkReplayButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
//if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -1455,11 +1564,28 @@ namespace Ink_Canvas
|
||||
}).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否停止墨迹重播
|
||||
/// </summary>
|
||||
private bool isStopInkReplay;
|
||||
/// <summary>
|
||||
/// 是否暂停墨迹重播
|
||||
/// </summary>
|
||||
private bool isPauseInkReplay;
|
||||
/// <summary>
|
||||
/// 是否重新开始墨迹重播
|
||||
/// </summary>
|
||||
private bool isRestartInkReplay;
|
||||
/// <summary>
|
||||
/// 墨迹重播速度
|
||||
/// </summary>
|
||||
private double inkReplaySpeed = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播画布鼠标按下事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void InkCanvasForInkReplay_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ClickCount == 2)
|
||||
@@ -1485,11 +1611,21 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播播放/暂停按钮鼠标按下事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void InkReplayPlayPauseBorder_OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
InkReplayPlayPauseBorder.Background = new SolidColorBrush(Color.FromArgb(34, 9, 9, 11));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播播放/暂停按钮鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void InkReplayPlayPauseBorder_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
InkReplayPlayPauseBorder.Background = new SolidColorBrush(Colors.Transparent);
|
||||
@@ -1499,11 +1635,21 @@ namespace Ink_Canvas
|
||||
InkReplayPauseButtonImage.Visibility = !isPauseInkReplay ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播停止按钮鼠标按下事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void InkReplayStopButtonBorder_OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
InkReplayStopButtonBorder.Background = new SolidColorBrush(Color.FromArgb(34, 9, 9, 11));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播停止按钮鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void InkReplayStopButtonBorder_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
InkReplayStopButtonBorder.Background = new SolidColorBrush(Colors.Transparent);
|
||||
@@ -1518,11 +1664,21 @@ namespace Ink_Canvas
|
||||
isStopInkReplay = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播重新开始按钮鼠标按下事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void InkReplayReplayButtonBorder_OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
InkReplayReplayButtonBorder.Background = new SolidColorBrush(Color.FromArgb(34, 9, 9, 11));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播重新开始按钮鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void InkReplayReplayButtonBorder_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
InkReplayReplayButtonBorder.Background = new SolidColorBrush(Colors.Transparent);
|
||||
@@ -1533,11 +1689,21 @@ namespace Ink_Canvas
|
||||
InkReplayPauseButtonImage.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播速度按钮鼠标按下事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void InkReplaySpeedButtonBorder_OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
InkReplaySpeedButtonBorder.Background = new SolidColorBrush(Color.FromArgb(34, 9, 9, 11));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹重播速度按钮鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void InkReplaySpeedButtonBorder_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
InkReplaySpeedButtonBorder.Background = new SolidColorBrush(Colors.Transparent);
|
||||
@@ -1548,6 +1714,11 @@ namespace Ink_Canvas
|
||||
InkReplaySpeedTextBlock.Text = inkReplaySpeed + "x";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工具图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void SymbolIconTools_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
|
||||
@@ -1573,8 +1744,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浮动工具栏边距动画是否正在运行
|
||||
/// </summary>
|
||||
private bool isViewboxFloatingBarMarginAnimationRunning;
|
||||
|
||||
/// <summary>
|
||||
/// 浮动工具栏边距动画处理
|
||||
/// </summary>
|
||||
/// <param name="MarginFromEdge">边缘边距</param>
|
||||
/// <param name="PosXCaculatedWithTaskbarHeight">是否考虑任务栏高度计算位置</param>
|
||||
public async void ViewboxFloatingBarMarginAnimation(int MarginFromEdge,
|
||||
bool PosXCaculatedWithTaskbarHeight = false)
|
||||
{
|
||||
@@ -1722,6 +1901,9 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 桌面模式下的浮动工具栏边距动画处理
|
||||
/// </summary>
|
||||
public async void PureViewboxFloatingBarMarginAnimationInDesktopMode()
|
||||
{
|
||||
// 在白板模式下不执行浮动栏动画
|
||||
@@ -1826,6 +2008,10 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PPT模式下的浮动工具栏边距动画处理
|
||||
/// </summary>
|
||||
/// <param name="isRetry">是否为重试操作</param>
|
||||
public async void PureViewboxFloatingBarMarginAnimationInPPTMode(bool isRetry = false)
|
||||
{
|
||||
// 新增:在白板模式下不执行浮动栏动画
|
||||
@@ -1943,6 +2129,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 光标图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
internal async void CursorIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
@@ -2054,6 +2245,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 画笔图标点击事件处理,用于切换到批注模式或显示画笔调色盘
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
internal void PenIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
@@ -2280,6 +2476,11 @@ namespace Ink_Canvas
|
||||
drawingShapeMode = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 颜色主题切换鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private void ColorThemeSwitch_MouseUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
isUselightThemeColor = !isUselightThemeColor;
|
||||
@@ -2287,6 +2488,11 @@ namespace Ink_Canvas
|
||||
CheckColorTheme();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 橡皮擦图标点击事件处理,用于切换到橡皮擦模式或显示橡皮擦尺寸面板
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
internal void EraserIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool isAlreadyEraser = inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint;
|
||||
@@ -2338,6 +2544,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 白板模式下的橡皮擦图标点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private void BoardEraserIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool isAlreadyEraser = inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint;
|
||||
@@ -2376,6 +2587,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹擦除图标点击事件处理,用于切换到按笔画擦除模式
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private void EraserIconByStrokes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
@@ -2408,6 +2624,11 @@ namespace Ink_Canvas
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 光标删除图标点击事件处理,用于删除选中内容并切换到光标模式
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private void CursorWithDelIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
@@ -2419,55 +2640,90 @@ namespace Ink_Canvas
|
||||
CursorIcon_Click(null, null);
|
||||
}
|
||||
|
||||
// 快捷调色盘事件处理方法
|
||||
/// <summary>
|
||||
/// 将当前绘笔颜色设置为白色并安排在短时间后自动恢复到之前的笔刷。
|
||||
/// </summary>
|
||||
private void QuickColorWhite_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetQuickColor(Colors.White);
|
||||
ScheduleBrushAutoRestore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将快速颜色设置为橙色,并安排稍后自动恢复到先前的画笔颜色。
|
||||
/// </summary>
|
||||
private void QuickColorOrange_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetQuickColor(Color.FromRgb(251, 150, 80)); // 橙色
|
||||
ScheduleBrushAutoRestore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将画笔颜色切换为黄色并安排自动恢复为先前的画笔设置。
|
||||
/// </summary>
|
||||
private void QuickColorYellow_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetQuickColor(Colors.Yellow);
|
||||
ScheduleBrushAutoRestore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将快速颜色设置为黑色并安排在稍后自动恢复为先前的画笔颜色。
|
||||
/// </summary>
|
||||
private void QuickColorBlack_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetQuickColor(Colors.Black);
|
||||
ScheduleBrushAutoRestore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前画笔颜色设置为蓝色并安排在一段时间后自动恢复到之前的画笔颜色。
|
||||
/// </summary>
|
||||
private void QuickColorBlue_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetQuickColor(Color.FromRgb(37, 99, 235)); // 蓝色
|
||||
ScheduleBrushAutoRestore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将快速颜色切换为红色,并安排稍后自动恢复为先前的画笔颜色。
|
||||
/// </summary>
|
||||
private void QuickColorRed_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetQuickColor(Colors.Red);
|
||||
ScheduleBrushAutoRestore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将快速颜色切换为绿色并安排在一段时间后自动恢复先前画笔颜色。
|
||||
/// </summary>
|
||||
private void QuickColorGreen_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetQuickColor(Color.FromRgb(22, 163, 74));
|
||||
ScheduleBrushAutoRestore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前画笔颜色切换为紫色快捷色并安排自动恢复先前画笔设置。
|
||||
/// </summary>
|
||||
private void QuickColorPurple_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetQuickColor(Color.FromRgb(147, 51, 234));
|
||||
ScheduleBrushAutoRestore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置并应用快速颜色到当前画笔与相关状态,包括必要时切换到批注模式、更新荧光笔属性与颜色索引、记录桌面/白板的最近颜色,以及刷新调色盘指示器和颜色显示。
|
||||
/// </summary>
|
||||
/// <param name="color">要应用的颜色。</param>
|
||||
/// <remarks>
|
||||
/// 此方法会:
|
||||
/// - 在非批注模式时切换到绘制(Ink)模式;
|
||||
/// - 将指定颜色应用到绘图属性和 InkCanvas 的默认绘图属性;
|
||||
/// - 在荧光笔模式下更新荧光笔的内部颜色索引与绘图属性(宽度、笔尖形状、IsHighlighter 等);
|
||||
/// - 根据当前模式(桌面或白板)记录最近使用的颜色索引;
|
||||
/// - 更新快速调色盘的选中指示器并刷新颜色显示状态。
|
||||
/// </remarks>
|
||||
private void SetQuickColor(Color color)
|
||||
{
|
||||
// 确保当前处于批注模式
|
||||
@@ -2577,6 +2833,10 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新快速调色盘的选中指示器,根据当前选中的颜色显示对应的勾选图标
|
||||
/// </summary>
|
||||
/// <param name="selectedColor">当前选中的颜色</param>
|
||||
private void UpdateQuickColorPaletteIndicator(Color selectedColor)
|
||||
{
|
||||
// 隐藏所有check图标(双行显示)
|
||||
@@ -2665,6 +2925,11 @@ namespace Ink_Canvas
|
||||
return rDiff <= tolerance && gDiff <= tolerance && bDiff <= tolerance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选择工具图标鼠标释放事件处理,用于切换到选择模式或选择所有墨迹
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private void SelectIcon_MouseUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 禁用高级橡皮擦系统
|
||||
@@ -2688,6 +2953,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从图形绘制模式切换到画笔模式的提示处理
|
||||
/// </summary>
|
||||
private void DrawShapePromptToPen()
|
||||
{
|
||||
if (isLongPressSelected)
|
||||
@@ -2704,6 +2972,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭工具面板鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void CloseBordertools_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
HideSubPanels();
|
||||
@@ -2711,6 +2984,11 @@ namespace Ink_Canvas
|
||||
|
||||
#region Left Side Panel
|
||||
|
||||
/// <summary>
|
||||
/// 手指拖动模式切换按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private void BtnFingerDragMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isSingleFingerDragMode)
|
||||
@@ -2725,6 +3003,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 撤销按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private void BtnUndo_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (inkCanvas.GetSelectedStrokes().Count != 0)
|
||||
@@ -2737,6 +3020,11 @@ namespace Ink_Canvas
|
||||
ApplyHistoryToCanvas(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重做按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private void BtnRedo_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (inkCanvas.GetSelectedStrokes().Count != 0)
|
||||
@@ -2749,6 +3037,11 @@ namespace Ink_Canvas
|
||||
ApplyHistoryToCanvas(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按钮启用状态变更事件处理,用于更新按钮内容的透明度
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">依赖属性变更事件参数</param>
|
||||
private void Btn_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2768,6 +3061,11 @@ namespace Ink_Canvas
|
||||
|
||||
public static bool CloseIsFromButton;
|
||||
|
||||
/// <summary>
|
||||
/// 退出按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
public void BtnExit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 如果当前在设置面板中,需要先恢复无焦点模式状态
|
||||
@@ -2786,6 +3084,11 @@ namespace Ink_Canvas
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重启按钮点击事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
public void BtnRestart_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (BorderSettings.Visibility == Visibility.Visible)
|
||||
@@ -2804,6 +3107,11 @@ namespace Ink_Canvas
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置覆盖层点击事件处理,用于点击设置面板外部时关闭设置面板
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
private void SettingsOverlayClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (isOpeningOrHidingSettingsPane) return;
|
||||
@@ -2830,6 +3138,11 @@ namespace Ink_Canvas
|
||||
private bool isOpeningOrHidingSettingsPane;
|
||||
private bool wasNoFocusModeBeforeSettings;
|
||||
|
||||
/// <summary>
|
||||
/// 切换并打开设置面板;在需要时先进行安全密码校验,然后显示设置面板并启动打开动画,同时根据设置暂时调整无焦点模式与遮罩交互状态。
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private async void BtnSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (BorderSettings.Visibility == Visibility.Visible)
|
||||
@@ -3004,6 +3317,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在屏幕模式、白板与黑板模式之间切换并同步相关的 UI 状态与资源处理。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 切换过程中会保存/清理/恢复画笔轨迹,显示或隐藏白板/黑板面板、手势面板与 PPT 控件,调整主题与悬浮工具栏可见性,处理全屏/工作区尺寸恢复或进入全屏,以及在进入白板时检查剪贴板并显示粘贴提示。该方法还会触发隐藏/显示墨迹画布的逻辑(通过调用 BtnHideInkCanvas_Click)。
|
||||
/// </remarks>
|
||||
private void BtnSwitch_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (GridTransparencyFakeBackground.Background == Brushes.Transparent)
|
||||
|
||||
@@ -296,6 +296,8 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 设置拦截规则
|
||||
/// </summary>
|
||||
/// <param name="type">拦截类型</param>
|
||||
/// <param name="enabled">是否启用拦截</param>
|
||||
private void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -6,6 +6,11 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 鼠标滚轮事件处理,用于PPT翻页
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标滚轮事件参数</param>
|
||||
private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (BtnPPTSlideShowEnd.Visibility != Visibility.Visible || currentMode != 0) return;
|
||||
@@ -19,6 +24,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 键盘按键预览事件处理,用于PPT翻页
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">键盘事件参数</param>
|
||||
private void Main_Grid_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (BtnPPTSlideShowEnd.Visibility != Visibility.Visible || currentMode != 0) return;
|
||||
@@ -34,6 +44,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 撤销操作热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
private void HotKey_Undo(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -43,6 +58,11 @@ namespace Ink_Canvas
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重做操作热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
private void HotKey_Redo(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -52,22 +72,46 @@ namespace Ink_Canvas
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空画布热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
private void HotKey_Clear(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
SymbolIconDelete_MouseUp(lastBorderMouseDownObject, null);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 退出PPT放映热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
internal void KeyExit(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到绘图工具热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
private void KeyChangeToDrawTool(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
PenIcon_Click(lastBorderMouseDownObject, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 退出绘图工具热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 在白板模式下,alt+q 退出白板模式
|
||||
/// 在非白板模式下,alt+q 切换到鼠标模式
|
||||
/// </remarks>
|
||||
internal void KeyChangeToQuitDrawTool(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
if (currentMode != 0)
|
||||
@@ -82,12 +126,24 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到选择工具热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
/// <remarks>仅当画布控件面板可见时生效</remarks>
|
||||
private void KeyChangeToSelect(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
if (StackPanelCanvasControls.Visibility == Visibility.Visible)
|
||||
SymbolIconSelect_MouseUp(lastBorderMouseDownObject, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到橡皮擦工具热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
/// <remarks>仅当画布控件面板可见时生效,根据当前橡皮擦状态选择相应的橡皮擦模式</remarks>
|
||||
private void KeyChangeToEraser(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
if (StackPanelCanvasControls.Visibility == Visibility.Visible)
|
||||
@@ -99,21 +155,42 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到白板模式热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
private void KeyChangeToBoard(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ImageBlackboard_MouseUp(lastBorderMouseDownObject, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 屏幕截图热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
private void KeyCapture(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
SaveScreenShotToDesktop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制直线热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
/// <remarks>仅当画布控件面板可见时生效</remarks>
|
||||
private void KeyDrawLine(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
if (StackPanelCanvasControls.Visibility == Visibility.Visible) BtnDrawLine_Click(lastMouseDownSender, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏工具栏热键处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
private void KeyHide(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
SymbolIconEmoji_MouseUp(null, null);
|
||||
|
||||
@@ -1,74 +1,179 @@
|
||||
namespace Ink_Canvas
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 图标几何路径定义类,包含各种工具图标的XAML几何路径
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该类定义了应用程序中使用的所有工具图标,包括:
|
||||
/// - 光标图标
|
||||
/// - 画笔图标
|
||||
/// - 橡皮擦图标
|
||||
/// - 选择工具图标
|
||||
/// - 手势图标
|
||||
/// - 老版本浮动栏图标
|
||||
/// 每个图标都有实线(Lined)和虚线(Solid)两种变体
|
||||
/// </remarks>
|
||||
public static class XamlGraphicsIconGeometries
|
||||
{
|
||||
/// <summary>
|
||||
/// 虚线光标图标
|
||||
/// </summary>
|
||||
/// <remarks>用于表示鼠标模式的图标</remarks>
|
||||
public static string LinedCursorIcon =
|
||||
"F1 M24,24z M0,0z M5.72106,15.9716L3.71327,3.00395C3.6389,2.6693 3.65747,2.41831 3.76902,2.25099 3.88057,2.08366 4.0479,2 4.271,2 4.4941,2 4.71711,2.07437 4.94021,2.2231 6.72502,3.39438 9.28149,5.10481 12.6094,7.3544 15.677,9.45526 18.1125,11.1285 19.9159,12.3742 20.1204,12.5229 20.2505,12.6995 20.3062,12.904 20.362,13.1085 20.3249,13.2944 20.1947,13.4618 20.0832,13.6105 19.8973,13.6849 19.637,13.6849L13.3902,13.6849 17.6291,19.7365C17.722,19.8666 17.75,20.0153 17.7128,20.1827 17.6942,20.3314 17.6198,20.4522 17.4897,20.5452L15.5654,21.8838C15.4353,21.9768 15.2865,22.0139 15.1192,21.9953 14.9704,21.9582 14.8496,21.8745 14.7566,21.7444L10.2389,15.2745 7.58956,19.9038C7.45942,20.1269 7.30144,20.2756 7.11552,20.35 6.92961,20.4058 6.75292,20.3872 6.5856,20.2942 6.43686,20.2013 6.34392,20.0339 6.30673,19.7922L6.00007,17.8959C5.88852,17.0779,5.79543,16.4364,5.72106,15.9716z";
|
||||
|
||||
/// <summary>
|
||||
/// 实线光标图标
|
||||
/// </summary>
|
||||
/// <remarks>用于表示鼠标模式的图标</remarks>
|
||||
public static string SolidCursorIcon =
|
||||
"F1 M24,24z M0,0z M5.72106,15.9716L3.71327,3.00395C3.6389,2.6693 3.65747,2.41831 3.76902,2.25099 3.88057,2.08366 4.0479,2 4.271,2 4.4941,2 4.71711,2.07437 4.94021,2.2231 6.72502,3.39438 9.28149,5.10481 12.6094,7.3544 15.677,9.45526 18.1125,11.1285 19.9159,12.3742 20.1204,12.5229 20.2505,12.6995 20.3062,12.904 20.362,13.1085 20.3249,13.2944 20.1947,13.4618 20.0832,13.6105 19.8973,13.6849 19.637,13.6849L13.3902,13.6849 17.6291,19.7365C17.722,19.8666 17.75,20.0153 17.7128,20.1827 17.6942,20.3314 17.6198,20.4522 17.4897,20.5452L15.5654,21.8838C15.4353,21.9768 15.2865,22.0139 15.1192,21.9953 14.9704,21.9582 14.8496,21.8745 14.7566,21.7444L10.2389,15.2745 7.58956,19.9038C7.45942,20.1269 7.30144,20.2756 7.11552,20.35 6.92961,20.4058 6.75292,20.3872 6.5856,20.2942 6.43686,20.2013 6.34392,20.0339 6.30673,19.7922L6.00007,17.8959C5.88852,17.0779,5.79543,16.4364,5.72106,15.9716z";
|
||||
|
||||
/// <summary>
|
||||
/// 虚线画笔图标
|
||||
/// </summary>
|
||||
/// <remarks>用于表示画笔工具的图标</remarks>
|
||||
public static string LinedPenIcon =
|
||||
"F1 M24,24z M0,0z M16.996,2.34419L21.6823,7.00397C21.8941,7.23343 22,7.49819 22,7.79825 22,8.09831 21.8941,8.35425 21.6823,8.56606L10.8271,19.4212 4.57877,13.1994 15.4339,2.34419C15.6457,2.11473 15.9017,2 16.2018,2 16.5195,2 16.7842,2.11473 16.996,2.34419z M9.63571,20.5862L9.50333,20.6391 2.6725,21.9894C2.47834,22.0247 2.31066,21.9718 2.16946,21.8306 2.02825,21.707 1.97529,21.5481 2.01059,21.354L3.38736,14.5232C3.38736,14.4879,3.40502,14.4349,3.44032,14.3643L9.63571,20.5862z";
|
||||
|
||||
/// <summary>
|
||||
/// 实线画笔图标
|
||||
/// </summary>
|
||||
/// <remarks>用于表示画笔工具的图标</remarks>
|
||||
public static string SolidPenIcon =
|
||||
"F1 M24,24z M0,0z M16.996,2.34419L21.6823,7.00397C21.8941,7.23343 22,7.49819 22,7.79825 22,8.09831 21.8941,8.35425 21.6823,8.56606L10.8271,19.4212 4.57877,13.1994 15.4339,2.34419C15.6457,2.11473 15.9017,2 16.2018,2 16.5195,2 16.7842,2.11473 16.996,2.34419z M9.63571,20.5862L9.50333,20.6391 2.6725,21.9894C2.47834,22.0247 2.31066,21.9718 2.16946,21.8306 2.02825,21.707 1.97529,21.5481 2.01059,21.354L3.38736,14.5232C3.38736,14.4879,3.40502,14.4349,3.44032,14.3643L9.63571,20.5862z";
|
||||
|
||||
/// <summary>
|
||||
/// 虚线橡皮擦图标(按笔画擦除模式)
|
||||
/// </summary>
|
||||
/// <remarks>用于表示橡皮擦工具的按笔画擦除模式的图标</remarks>
|
||||
public static string LinedEraserStrokeIcon =
|
||||
"F1 M24,24z M0,0z M19.0625,7.99501C19.2863,7.99501 19.4861,8.06695 19.662,8.21083 19.8538,8.35471 19.9897,8.53856 20.0696,8.76237 20.4054,9.94539 20.7011,11.2563 20.9569,12.6951 21.1967,14.0859 21.3646,15.4368 21.4605,16.7477L21.4845,17.2993C21.5005,17.5551 21.5084,17.9068 21.5084,18.3544 21.5084,19.2017 21.2926,19.8412 20.861,20.2728 20.4453,20.7044 19.7099,21.0482 18.6548,21.3039 18.3191,21.3679 17.7836,20.4327 17.0482,18.4983 16.5845,20.8643 16.1129,22.0313 15.6333,21.9994 13.9227,21.9674 12.468,21.8954 11.269,21.7835L10.8134,21.7356C9.83817,21.6077 8.86297,21.4398 7.88778,21.232 7.1524,21.0721 6.32109,20.8563 5.39386,20.5845 4.61051,20.3447 3.97904,20.089 3.49944,19.8172 3.01984,19.5454 2.70809,19.2337 2.56421,18.882 2.40434,18.4823 2.50827,18.0746 2.87596,17.659L3.09176,17.3952 3.18769,17.2273 3.23565,17.1074 3.37954,16.8197 3.47544,16.5559 3.6433,15.9324 3.73923,15.6207 3.85912,15.1411 4.17088,13.5824 4.57853,11.1844 4.98621,9.05013C5.03417,8.79435 5.16203,8.55455 5.36986,8.33073 5.59367,8.10692 5.8255,7.99501 6.0653,7.99501L19.0625,7.99501z M14.4823,2C14.7861,2 15.0418,2.08793 15.2496,2.26378 15.4575,2.43963 15.5614,2.64746 15.5614,2.88726L15.5614,4.99751 19.0625,4.99751C19.3343,4.99751 19.5661,5.10142 19.7579,5.30925 19.9498,5.50109 20.0457,5.73289 20.0457,6.00467 20.0457,6.27644 19.9498,6.51624 19.7579,6.72407 19.5661,6.91591 19.3343,7.01183 19.0625,7.01183L6.0653,7.01183C5.79352,7.01183 5.55372,6.91591 5.34589,6.72407 5.15405,6.51624 5.05814,6.27644 5.05814,6.00467 5.05814,5.73289 5.15405,5.50109 5.34589,5.30925 5.55372,5.10142 5.79352,4.99751 6.0653,4.99751L9.56638,4.99751 9.56638,2.88726C9.56638,2.66345 9.65432,2.47161 9.83017,2.31174 10.022,2.15187 10.2538,2.05595 10.5256,2.02398L10.6455,2 14.4823,2z";
|
||||
|
||||
/// <summary>
|
||||
/// 实线橡皮擦图标(按笔画擦除模式)
|
||||
/// </summary>
|
||||
/// <remarks>用于表示橡皮擦工具的按笔画擦除模式的图标</remarks>
|
||||
public static string SolidEraserStrokeIcon =
|
||||
"F1 M24,24z M0,0z M19.0625,7.99501C19.2863,7.99501 19.4861,8.06695 19.662,8.21083 19.8538,8.35471 19.9897,8.53856 20.0696,8.76237 20.4054,9.94539 20.7011,11.2563 20.9569,12.6951 21.1967,14.0859 21.3646,15.4368 21.4605,16.7477L21.4845,17.2993C21.5005,17.5551 21.5084,17.9068 21.5084,18.3544 21.5084,19.2017 21.2926,19.8412 20.861,20.2728 20.4453,20.7044 19.7099,21.0482 18.6548,21.3039 18.3191,21.3679 17.7836,20.4327 17.0482,18.4983 16.5845,20.8643 16.1129,22.0313 15.6333,21.9994 13.9227,21.9674 12.468,21.8954 11.269,21.7835L10.8134,21.7356C9.83817,21.6077 8.86297,21.4398 7.88778,21.232 7.1524,21.0721 6.32109,20.8563 5.39386,20.5845 4.61051,20.3447 3.97904,20.089 3.49944,19.8172 3.01984,19.5454 2.70809,19.2337 2.56421,18.882 2.40434,18.4823 2.50827,18.0746 2.87596,17.659L3.09176,17.3952 3.18769,17.2273 3.23565,17.1074 3.37954,16.8197 3.47544,16.5559 3.6433,15.9324 3.73923,15.6207 3.85912,15.1411 4.17088,13.5824 4.57853,11.1844 4.98621,9.05013C5.03417,8.79435 5.16203,8.55455 5.36986,8.33073 5.59367,8.10692 5.8255,7.99501 6.0653,7.99501L19.0625,7.99501z M14.4823,2C14.7861,2 15.0418,2.08793 15.2496,2.26378 15.4575,2.43963 15.5614,2.64746 15.5614,2.88726L15.5614,4.99751 19.0625,4.99751C19.3343,4.99751 19.5661,5.10142 19.7579,5.30925 19.9498,5.50109 20.0457,5.73289 20.0457,6.00467 20.0457,6.27644 19.9498,6.51624 19.7579,6.72407 19.5661,6.91591 19.3343,7.01183 19.0625,7.01183L6.0653,7.01183C5.79352,7.01183 5.55372,6.91591 5.34589,6.72407 5.15405,6.51624 5.05814,6.27644 5.05814,6.00467 5.05814,5.73289 5.15405,5.50109 5.34589,5.30925 5.55372,5.10142 5.79352,4.99751 6.0653,4.99751L9.56638,4.99751 9.56638,2.88726C9.56638,2.66345 9.65432,2.47161 9.83017,2.31174 10.022,2.15187 10.2538,2.05595 10.5256,2.02398L10.6455,2 14.4823,2z";
|
||||
|
||||
/// <summary>
|
||||
/// 虚线橡皮擦图标(圆形擦除模式)
|
||||
/// </summary>
|
||||
/// <remarks>用于表示橡皮擦工具的圆形擦除模式的图标</remarks>
|
||||
public static string LinedEraserCircleIcon =
|
||||
"F1 M24,24z M0,0z M15.0665,2.29557L21.6921,8.92118C21.8892,9.11823 21.9877,9.36453 21.9877,9.6601 21.9877,9.93924 21.8892,10.1773 21.6921,10.3744L10.3621,21.6798C10.165,21.8933 9.92694,22 9.6478,22 9.36865,22 9.12235,21.8933 8.90888,21.6798L2.30789,15.0788C2.11085,14.8654 2.01233,14.619 2.01233,14.3399 2.01233,14.0608 2.11085,13.8227 2.30789,13.6256L13.6133,2.29557C13.8103,2.09852 14.0485,2 14.3276,2 14.6232,2 14.8695,2.09852 15.0665,2.29557z M8.19458,11.5813C8.09606,11.4828 7.97292,11.4335 7.82514,11.4335 7.69377,11.4335 7.57883,11.4828 7.48031,11.5813L5.28818,13.7734C5.18965,13.8719 5.14041,13.9951 5.14041,14.1429 5.14041,14.2906 5.18965,14.4138 5.28818,14.5123L9.47539,18.6995C9.59033,18.8144 9.71347,18.8719 9.84483,18.8719 9.99261,18.8719 10.1158,18.8144 10.2143,18.6995L12.4064,16.5074C12.5049,16.4089 12.5542,16.2939 12.5542,16.1626 12.5542,16.0148 12.5049,15.8916 12.4064,15.7931L8.19458,11.5813z";
|
||||
|
||||
/// <summary>
|
||||
/// 实线橡皮擦图标(圆形擦除模式)
|
||||
/// </summary>
|
||||
/// <remarks>用于表示橡皮擦工具的圆形擦除模式的图标</remarks>
|
||||
public static string SolidEraserCircleIcon =
|
||||
"F1 M24,24z M0,0z M15.0665,2.29557L21.6921,8.92118C21.8892,9.11823 21.9877,9.36453 21.9877,9.6601 21.9877,9.93924 21.8892,10.1773 21.6921,10.3744L10.3621,21.6798C10.165,21.8933 9.92694,22 9.6478,22 9.36865,22 9.12235,21.8933 8.90888,21.6798L2.30789,15.0788C2.11085,14.8654 2.01233,14.619 2.01233,14.3399 2.01233,14.0608 2.11085,13.8227 2.30789,13.6256L13.6133,2.29557C13.8103,2.09852 14.0485,2 14.3276,2 14.6232,2 14.8695,2.09852 15.0665,2.29557z M8.19458,11.5813C8.09606,11.4828 7.97292,11.4335 7.82514,11.4335 7.69377,11.4335 7.57883,11.4828 7.48031,11.5813L5.28818,13.7734C5.18965,13.8719 5.14041,13.9951 5.14041,14.1429 5.14041,14.2906 5.18965,14.4138 5.28818,14.5123L9.47539,18.6995C9.59033,18.8144 9.71347,18.8719 9.84483,18.8719 9.99261,18.8719 10.1158,18.8144 10.2143,18.6995L12.4064,16.5074C12.5049,16.4089 12.5542,16.2939 12.5542,16.1626 12.5542,16.0148 12.5049,15.8916 12.4064,15.7931L8.19458,11.5813z";
|
||||
|
||||
/// <summary>
|
||||
/// 虚线套索选择工具图标
|
||||
/// </summary>
|
||||
/// <remarks>用于表示套索选择工具的图标</remarks>
|
||||
public static string LinedLassoSelectIcon =
|
||||
"F0 M24,24z M0,0z M21.1749,3.19033C21.2959,3.31432,21.3512,3.45083,21.3512,3.62667L21.3512,15.7344 18.9141,14.0608 18.9141,5.43716 5.30084,5.43716 5.30084,19.0505 14.7641,19.0505 15.0947,21.4876 3.49029,21.4876C3.31451,21.4876 3.178,21.4323 3.05397,21.3113 2.92046,21.1636 2.86368,21.0107 2.86368,20.8334L2.86368,3.62667C2.86368,3.44751 2.92108,3.30918 3.04695,3.18331 3.17285,3.0574 3.31118,3 3.49029,3L20.697,3C20.8743,3,21.0272,3.0568,21.1749,3.19033z M15.042,13.7475L16.02,20.0637C16.0562,20.2901,16.1015,20.6026,16.1559,21.001L16.3052,21.9247C16.3234,22.0424 16.3686,22.1239 16.4411,22.1692 16.5226,22.2144 16.6086,22.2235 16.6992,22.1963 16.7897,22.1601 16.8667,22.0877 16.9301,21.979L18.2205,19.7242 20.421,22.8755C20.4663,22.9389 20.5251,22.9796 20.5976,22.9978 20.6791,23.0068 20.7515,22.9887 20.8149,22.9434L21.7522,22.2914C21.8156,22.2461 21.8518,22.1873 21.8609,22.1148 21.879,22.0333 21.8654,21.9609 21.8201,21.8975L19.7555,18.9499 22.7981,18.9499C22.9249,18.9499 23.0154,18.9137 23.0698,18.8412 23.1332,18.7597 23.1512,18.6692 23.1241,18.5696 23.0969,18.47 23.0336,18.3839 22.934,18.3115 22.0556,17.7048 20.8693,16.8898 19.3751,15.8665 17.7542,14.7708 16.509,13.9377 15.6396,13.3672 15.531,13.2947 15.4224,13.2585 15.3137,13.2585 15.205,13.2585 15.1235,13.2992 15.0692,13.3807 15.0149,13.4622 15.0058,13.5845 15.042,13.7475z";
|
||||
|
||||
/// <summary>
|
||||
/// 实线套索选择工具图标
|
||||
/// </summary>
|
||||
/// <remarks>用于表示套索选择工具的图标</remarks>
|
||||
public static string SolidLassoSelectIcon =
|
||||
"F0 M24,24z M0,0z M21.1749,3.19033C21.2959,3.31432,21.3512,3.45083,21.3512,3.62667L21.3512,15.7344 18.9141,14.0608 18.9141,5.43716 5.30084,5.43716 5.30084,19.0505 14.7641,19.0505 15.0947,21.4876 3.49029,21.4876C3.31451,21.4876 3.178,21.4323 3.05397,21.3113 2.92046,21.1636 2.86368,21.0107 2.86368,20.8334L2.86368,3.62667C2.86368,3.44751 2.92108,3.30918 3.04695,3.18331 3.17285,3.0574 3.31118,3 3.49029,3L20.697,3C20.8743,3,21.0272,3.0568,21.1749,3.19033z M15.042,13.7475L16.02,20.0637C16.0562,20.2901,16.1015,20.6026,16.1559,21.001L16.3052,21.9247C16.3234,22.0424 16.3686,22.1239 16.4411,22.1692 16.5226,22.2144 16.6086,22.2235 16.6992,22.1963 16.7897,22.1601 16.8667,22.0877 16.9301,21.979L18.2205,19.7242 20.421,22.8755C20.4663,22.9389 20.5251,22.9796 20.5976,22.9978 20.6791,23.0068 20.7515,22.9887 20.8149,22.9434L21.7522,22.2914C21.8156,22.2461 21.8518,22.1873 21.8609,22.1148 21.879,22.0333 21.8654,21.9609 21.8201,21.8975L19.7555,18.9499 22.7981,18.9499C22.9249,18.9499 23.0154,18.9137 23.0698,18.8412 23.1332,18.7597 23.1512,18.6692 23.1241,18.5696 23.0969,18.47 23.0336,18.3839 22.934,18.3115 22.0556,17.7048 20.8693,16.8898 19.3751,15.8665 17.7542,14.7708 16.509,13.9377 15.6396,13.3672 15.531,13.2947 15.4224,13.2585 15.3137,13.2585 15.205,13.2585 15.1235,13.2992 15.0692,13.3807 15.0149,13.4622 15.0058,13.5845 15.042,13.7475z";
|
||||
|
||||
/// <summary>
|
||||
/// 禁用手势图标
|
||||
/// </summary>
|
||||
/// <remarks>用于表示手势功能已禁用的图标</remarks>
|
||||
public static string DisabledGestureIcon =
|
||||
"F0 M24,24z M0,0z M7.82154,10.0753L7.82154,3.74613C7.82154,3.06603 8.08946,2.40655 8.57377,1.92224 9.05808,1.43793 9.70726,1.17001 10.3977,1.17001 11.0881,1.17001 11.7372,1.43793 12.2216,1.92224 12.7059,2.40655 12.9738,3.05573 12.9738,3.74613L12.9738,6.37308C13.1415,6.33947 13.3139,6.32225 13.489,6.32225 14.1794,6.32225 14.8286,6.59016 15.3129,7.07447 15.4484,7.21001 15.567,7.35845 15.6675,7.5171 15.9551,7.40916 16.2634,7.35269 16.5803,7.35269 17.2707,7.35269 17.9199,7.62061 18.4042,8.10492 18.5461,8.24683 18.6695,8.4029 18.7729,8.57001 19.6856,8.26338 20.7674,8.45871 21.4647,9.15599 21.949,9.6403 22.2169,10.2998 22.2169,10.9799L22.2169,15.6169C22.2169,17.5438 21.4647,19.3574 20.1045,20.7176 18.7443,22.0778 16.9307,22.83 15.0038,22.83L13.149,22.83 13.1799,22.8094 12.8398,22.8094C11.7682,22.7579 10.7068,22.4694 9.75878,21.9541 8.70773,21.3874 7.81124,20.563 7.15175,19.5738L6.94566,19.2647C6.60562,18.7494 5.49273,16.8019 3.52458,13.3087 3.19484,12.7213 3.1021,12.0412 3.27727,11.3818 3.45245,10.7326 3.86463,10.1761 4.44168,9.83608 5.00842,9.49604 5.66791,9.35177 6.31709,9.43421 6.86548,9.50385 7.39181,9.7279 7.82154,10.0753z M10.037,3.38547C10.1297,3.28243 10.2637,3.23091 10.3977,3.23091 10.5316,3.23091 10.6656,3.29273 10.7583,3.38547 10.8614,3.47821 10.9129,3.61217 10.9129,3.74613L10.9129,11.4745C10.9129,12.0412 11.3766,12.5049 11.9433,12.5049 12.5101,12.5049 12.9738,12.0412 12.9738,11.4745L12.9738,8.89836C12.9738,8.7644 13.0356,8.63045 13.1283,8.53771 13.2211,8.43466 13.355,8.38314 13.489,8.38314 13.623,8.38314 13.7569,8.44497 13.8497,8.53771 13.9527,8.63045 14.0042,8.7644 14.0042,8.89836L14.0042,11.4745C14.0042,12.0412 14.4679,12.5049 15.0347,12.5049 15.6014,12.5049 16.0651,12.0412 16.0651,11.4745L16.0651,9.92881C16.0651,9.79485 16.1269,9.66089 16.2197,9.56815 16.3124,9.46511 16.4464,9.41359 16.5803,9.41359 16.7143,9.41359 16.8483,9.47541 16.941,9.56815 17.044,9.66089 17.0956,9.79485 17.0956,9.92881L17.0956,10.5869C17.0752,10.7163 17.0646,10.8477 17.0646,10.9799 17.0646,11.0661 17.0754,11.1499 17.0956,11.2301L17.0956,11.4745C17.0956,12.0412 17.5593,12.5049 18.126,12.5049 18.6928,12.5049 19.1565,12.0412 19.1565,11.4745L19.1565,10.8128C19.1834,10.7399 19.2266,10.6727 19.2801,10.6192 19.4759,10.4234 19.8159,10.4234 20.0117,10.6192 20.1148,10.712 20.1663,10.8459 20.1663,10.9799L20.1663,15.6169C20.1663,16.9977 19.6408,18.296 18.6618,19.2647 17.6829,20.2333 16.3949,20.7691 15.0141,20.7691L13.1593,20.7691C12.3143,20.7691 11.4796,20.5527 10.7274,20.1509 9.98548,19.749 9.3363,19.1616 8.8726,18.4506L8.66651,18.1415C8.35737,17.6675 7.23419,15.7096 5.31756,12.2988 5.24543,12.1752 5.23512,12.0412 5.26604,11.9073 5.30725,11.7733 5.38969,11.6703 5.50304,11.5981 5.66791,11.4951 5.874,11.4539 6.06978,11.4745 6.26557,11.5054 6.45105,11.5878 6.59531,11.7321L8.11007,13.2469C8.49419,13.631 9.10425,13.648 9.50833,13.2978 9.73651,13.1084 9.88244,12.8229 9.88244,12.5049L9.88244,3.74613C9.88244,3.61217,9.94426,3.47821,10.037,3.38547z M2.99905,6.31195L1.78313,4.65293 2.61779,4.04497C3.46275,3.4267,4.37985,2.89087,5.33817,2.46838L6.27587,2.0459 7.12084,3.93162 6.18313,4.3541C5.35878,4.72506,4.56533,5.17846,3.83372,5.71429L2.99905,6.32225 2.99905,6.31195z M18.2806,5.20935L19.1565,5.75549 20.259,4.01404 19.3831,3.4679C18.1157,2.67446,16.7452,2.0768,15.3026,1.68523L14.303,1.41731 13.7672,3.40607 14.7667,3.67399C16.0033,4.00373,17.1883,4.51895,18.2806,5.20935z";
|
||||
|
||||
/// <summary>
|
||||
/// 启用手势图标
|
||||
/// </summary>
|
||||
/// <remarks>用于表示手势功能已启用的图标</remarks>
|
||||
public static string EnabledGestureIcon =
|
||||
"F1 M24,24z M0,0z M7.29844,9.85586L7.29844,3.52668C7.29844,2.84658 7.56636,2.1871 8.05067,1.70279 8.53498,1.21848 9.18416,0.950562 9.87456,0.950562 10.565,0.950562 11.2141,1.21848 11.6984,1.70279 12.1828,2.1871 12.4507,2.83628 12.4507,3.52668L12.4507,6.15363C12.6184,6.12002 12.7908,6.10279 12.9659,6.10279 13.6563,6.10279 14.3055,6.37071 14.7898,6.85502 14.9253,6.99055 15.0439,7.139 15.1444,7.29765 15.432,7.18971 15.7403,7.13324 16.0572,7.13324 16.7476,7.13324 17.3968,7.40116 17.8811,7.88547 18.023,8.02738 18.1464,8.18344 18.2498,8.35055 19.1625,8.04393 20.2443,8.23925 20.9416,8.93654 21.4259,9.42085 21.6938,10.0803 21.6938,10.7604L21.6938,14.2958C21.1174,13.741,20.4192,13.3118,19.6432,13.0524L19.6432,10.7604C19.6432,10.6265 19.5917,10.4925 19.4886,10.3998 19.2928,10.204 18.9528,10.204 18.757,10.3998 18.7035,10.4532 18.6603,10.5204 18.6334,10.5934L18.6334,11.255C18.6334,11.8218 18.1697,12.2855 17.6029,12.2855 17.0362,12.2855 16.5725,11.8218 16.5725,11.255L16.5725,11.0106C16.5523,10.9304 16.5415,10.8466 16.5415,10.7604 16.5415,10.6283 16.5521,10.4969 16.5725,10.3674L16.5725,9.70936C16.5725,9.5754 16.5209,9.44144 16.4179,9.3487 16.3252,9.25596 16.1912,9.19413 16.0572,9.19413 15.9233,9.19413 15.7893,9.24566 15.6966,9.3487 15.6038,9.44144 15.542,9.5754 15.542,9.70936L15.542,11.255C15.542,11.8218 15.0783,12.2855 14.5116,12.2855 13.9448,12.2855 13.4811,11.8218 13.4811,11.255L13.4811,8.67891C13.4811,8.54495 13.4296,8.41099 13.3266,8.31825 13.2338,8.22551 13.0999,8.16369 12.9659,8.16369 12.8319,8.16369 12.698,8.21521 12.6052,8.31825 12.5125,8.41099 12.4507,8.54495 12.4507,8.67891L12.4507,11.255C12.4507,11.8218 11.987,12.2855 11.4202,12.2855 10.8535,12.2855 10.3898,11.8218 10.3898,11.255L10.3898,3.52668C10.3898,3.39272 10.3383,3.25876 10.2352,3.16602 10.1425,3.07328 10.0085,3.01145 9.87456,3.01145 9.7406,3.01145 9.60664,3.06298 9.5139,3.16602 9.42116,3.25876 9.35933,3.39272 9.35933,3.52668L9.35933,12.2855C9.35933,12.6034 9.21341,12.8889 8.98523,13.0783 8.58114,13.4285 7.97109,13.4115 7.58697,13.0274L6.07221,11.5127C5.92795,11.3684 5.74247,11.286 5.54668,11.255 5.3509,11.2344 5.14481,11.2756 4.97994,11.3787 4.86659,11.4508 4.78415,11.5539 4.74293,11.6878 4.71202,11.8218 4.72232,11.9557 4.79446,12.0794 6.71109,15.4902 7.83427,17.448 8.14341,17.922L8.3495,18.2312C8.8132,18.9422 9.46238,19.5295 10.2043,19.9314 10.9565,20.3333 11.7912,20.5497 12.6362,20.5497L12.9829,20.5497C13.3696,21.3681 13.9542,22.0748 14.6748,22.608 14.6102,22.6097 14.5455,22.6106 14.4807,22.6106L12.6258,22.6106 12.6568,22.59 12.3167,22.59C11.2451,22.5384 10.1837,22.2499 9.23568,21.7347 8.18463,21.1679 7.28814,20.3436 6.62865,19.3544L6.42256,19.0452C6.08251,18.53 4.96963,16.5824 3.00148,13.0892 2.67174,12.5019 2.579,11.8218 2.75417,11.1623 2.92935,10.5131 3.34153,9.95668 3.91858,9.61663 4.48532,9.27658 5.14481,9.13232 5.79399,9.21476 6.34238,9.28439 6.86871,9.50845 7.29844,9.85586z M2.47595,6.0925L1.26003,4.43348 2.09469,3.82551C2.93965,3.20725,3.85675,2.67141,4.81507,2.24893L5.75277,1.82645 6.59774,3.71216 5.66003,4.13465C4.83567,4.50561,4.04223,4.959,3.31061,5.49484L2.47595,6.1028 2.47595,6.0925z M17.7575,4.9899L18.6334,5.53604 19.7359,3.79458 18.86,3.24845C17.5926,2.455,16.2221,1.85734,14.7795,1.46577L13.7799,1.19786 13.2441,3.18662 14.2436,3.45454C15.4802,3.78428,16.6652,4.2995,17.7575,4.9899z";
|
||||
|
||||
/// <summary>
|
||||
/// 启用手势图标徽章(带勾选标记)
|
||||
/// </summary>
|
||||
/// <remarks>用于表示手势功能已启用并带有勾选标记的图标</remarks>
|
||||
public static string EnabledGestureIconBadgeCheck =
|
||||
"M22.74,18.2234C22.74,20.8888 20.5793,23.0494 17.914,23.0494 15.2487,23.0494 13.088,20.8888 13.088,18.2234 13.088,15.5581 15.2487,13.3975 17.914,13.3975 20.5793,13.3975 22.74,15.5581 22.74,18.2234z M21.1673,15.8009C21.4651,16.0889,21.473,16.5637,21.1851,16.8614L17.5425,20.6282C17.4012,20.7743 17.2066,20.8568 17.0034,20.8568 16.8001,20.8568 16.6055,20.7743 16.4642,20.6282L14.6429,18.7448C14.355,18.447 14.3629,17.9722 14.6607,17.6843 14.9585,17.3963 15.4333,17.4043 15.7212,17.7021L17.0034,19.0279 20.1068,15.8187C20.3947,15.5209,20.8695,15.513,21.1673,15.8009z";
|
||||
|
||||
// 老版浮动栏按钮图标
|
||||
/// <summary>
|
||||
/// 老版虚线光标图标
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示鼠标模式的图标</remarks>
|
||||
public static string LegacyLinedCursorIcon =
|
||||
"F0 M24,24z M0,0z M3.85151,2.7073C3.52422,2.57095 3.147,2.64558 2.89629,2.89629 2.64558,3.147 2.57095,3.52422 2.7073,3.85151L9.7773,20.8215C9.91729,21.1575 10.2507,21.3718 10.6145,21.3595 10.9783,21.3473 11.2965,21.1111 11.4135,20.7664L13.4711,14.7085 18.8963,20.1337C19.238,20.4754 19.792,20.4754 20.1337,20.1337 20.4754,19.792 20.4754,19.238 20.1337,18.8963L14.7085,13.4711 20.7664,11.4135C21.1111,11.2965 21.3473,10.9783 21.3595,10.6145 21.3718,10.2507 21.1575,9.91729 20.8215,9.7773L3.85151,2.7073z M10.5017,18.0097L5.13984,5.13984 18.0097,10.5017 12.8136,12.2665C12.5561,12.3539,12.3539,12.5561,12.2665,12.8136L10.5017,18.0097z";
|
||||
|
||||
/// <summary>
|
||||
/// 老版实线光标图标
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示鼠标模式的图标</remarks>
|
||||
public static string LegacySolidCursorIcon =
|
||||
"F0 M24,24z M0,0z M2.89629,2.89629C3.147,2.64558,3.52422,2.57095,3.85151,2.7073L20.8215,9.7773C21.1575,9.91729 21.3718,10.2507 21.3595,10.6145 21.3473,10.9783 21.1111,11.2965 20.7664,11.4135L14.7085,13.4711 20.1337,18.8963C20.4754,19.238 20.4754,19.792 20.1337,20.1337 19.792,20.4754 19.238,20.4754 18.8963,20.1337L13.4711,14.7085 11.4135,20.7664C11.2965,21.1111 10.9783,21.3473 10.6145,21.3595 10.2507,21.3718 9.91729,21.1575 9.7773,20.8215L2.7073,3.85151C2.57095,3.52422,2.64558,3.147,2.89629,2.89629z";
|
||||
|
||||
/// <summary>
|
||||
/// 老版虚线画笔图标
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示画笔工具的图标</remarks>
|
||||
public static string LegacyLinedPenIcon =
|
||||
"F0 M24,24z M0,0z M18.7033,4.39761C18.4948,4.31644 18.2714,4.27922 18.0473,4.28846 17.8233,4.29771 17.6038,4.3532 17.403,4.4512 17.2022,4.54919 17.0246,4.68744 16.8813,4.8568 16.8665,4.87422 16.8511,4.89102 16.8349,4.90716L15.7108,6.03131 17.9591,8.27962 19.0832,7.15546C19.1021,7.13662 19.1218,7.11869 19.1424,7.10176 19.3143,6.96037 19.4543,6.7853 19.5537,6.58793 19.6531,6.39058 19.7099,6.1751 19.7207,5.95519 19.7314,5.73528 19.6959,5.51545 19.6163,5.30962 19.5367,5.10378 19.4147,4.91625 19.2576,4.75914 19.1004,4.60201 18.9117,4.47877 18.7033,4.39761z M16.7944,9.44428L14.5461,7.19597 5.47079,16.2713 4.62767,19.3627 7.7191,18.5196 16.7944,9.44428z M13.9636,5.44913L4.15148,15.2613C4.05014,15.3626,3.977,15.4886,3.93929,15.6269L2.65942,20.3198C2.58166,20.6049 2.66264,20.9098 2.87161,21.1188 3.08059,21.3277 3.38551,21.4087 3.67063,21.331L8.36347,20.0511C8.50174,20.0134,8.62777,19.9402,8.72911,19.8389L20.2217,8.34636C20.5551,8.06468 20.8283,7.71873 21.0247,7.3289 21.2275,6.92628 21.3437,6.48586 21.3658,6.03572 21.3878,5.58559 21.3151,5.13594 21.1525,4.71552 20.99,4.29512 20.7411,3.91338 20.4222,3.59447 20.1033,3.27558 19.7214,3.0265 19.3009,2.86277 18.8804,2.69905 18.4304,2.62417 17.9794,2.64278 17.5285,2.66139 17.0862,2.77308 16.6807,2.97095 16.2862,3.16344 15.9348,3.43348 15.6478,3.76494L13.9636,5.44913z";
|
||||
|
||||
/// <summary>
|
||||
/// 老版实线画笔图标
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示画笔工具的图标</remarks>
|
||||
public static string LegacySolidPenIcon =
|
||||
"F1 M24,24z M0,0z M19.3332,2.85933C18.9193,2.69814 18.4762,2.62442 18.0322,2.64274 17.5882,2.66106 17.1527,2.77103 16.7535,2.96583 16.3643,3.15575 16.0177,3.42232 15.7349,3.74956L14.5672,4.91725 19.0731,9.4231 20.2373,8.25888C20.5666,7.98121 20.8364,7.63993 21.0302,7.25528 21.2298,6.85899 21.3442,6.42551 21.3659,5.98249 21.3876,5.53947 21.3161,5.09692 21.1561,4.68313 20.996,4.26934 20.7511,3.89359 20.4372,3.57966 20.1232,3.26574 19.7472,3.02052 19.3332,2.85933z M18.0085,10.4877L13.5026,5.98183 4.14128,15.3432C4.04864,15.4358,3.98179,15.551,3.94732,15.6774L2.65684,20.4091C2.58577,20.6698 2.65979,20.9485 2.8508,21.1395 3.04182,21.3305 3.32054,21.4045 3.58117,21.3335L8.3129,20.043C8.43929,20.0085,8.5545,19.9417,8.64713,19.849L18.0085,10.4877z";
|
||||
|
||||
/// <summary>
|
||||
/// 老版虚线橡皮擦图标(按笔画擦除模式)
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示橡皮擦工具的按笔画擦除模式的图标</remarks>
|
||||
public static string LegacyLinedEraserStrokeIcon =
|
||||
"F0 M25,24z M0,0z M7.32029,21.36L13.0098,21.36 13.0122,21.36 21.5471,21.36C21.989,21.36 22.3473,21.0017 22.3473,20.5598 22.3473,20.1179 21.989,19.7596 21.5471,19.7596L14.9429,19.7596 21.4352,13.2673C22.7372,12.0786,22.6872,10.1353,21.449,8.89707L16.1515,3.59952C14.9628,2.29751,13.0195,2.34754,11.7813,3.58572L2.68992,12.6771C1.3879,13.8657,1.43793,15.8091,2.67611,17.0473L6.75447,21.1256C6.90453,21.2757,7.10807,21.36,7.32029,21.36z M14.9771,4.68685C14.4571,4.10907,13.5664,4.06392,12.9129,4.71737L6.55503,11.0753 13.9595,18.4797 20.3174,12.1218C20.3273,12.1119 20.3375,12.1022 20.3479,12.0929 20.9257,11.5729 20.9708,10.6822 20.3174,10.0287L15.006,4.71737C14.9961,4.70745,14.9864,4.69727,14.9771,4.68685z M12.8278,19.6114L5.42338,12.2069 3.80776,13.8225C3.79784,13.8324 3.78766,13.8421 3.77724,13.8515 3.19947,14.3715 3.15431,15.2622 3.80776,15.9156L7.65174,19.7596 12.6796,19.7596 12.8278,19.6114z";
|
||||
|
||||
/// <summary>
|
||||
/// 老版实线橡皮擦图标(按笔画擦除模式)
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示橡皮擦工具的按笔画擦除模式的图标</remarks>
|
||||
public static string LegacySolidEraserStrokeIcon =
|
||||
"F1 M24,24z M0,0z M11.6199,3.61372C12.8916,2.34202,14.8995,2.2837,16.1307,3.62964L21.3433,8.84225C22.615,10.1139,22.6733,12.1218,21.3274,13.353L15.1877,19.4927 5.46434,9.76928 11.6199,3.61372z M7.33167,21.36C7.08919,21.36 6.86831,21.2676 6.70232,21.116 6.69184,21.1064 6.68155,21.0966 6.67147,21.0865L2.65671,17.0718C1.385,15.8001,1.32668,13.7922,2.67262,12.561L4.14394,11.0897 12.5469,19.4927 21.3367,19.4927C21.8523,19.4927 22.2703,19.9107 22.2703,20.4263 22.2703,20.942 21.8523,21.36 21.3367,21.36L7.33167,21.36z";
|
||||
|
||||
/// <summary>
|
||||
/// 老版虚线橡皮擦图标(圆形擦除模式)
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示橡皮擦工具的圆形擦除模式的图标</remarks>
|
||||
public static string LegacyLinedEraserCircleIcon =
|
||||
"F0 M25,24z M0,0z M2.47995,17.1206L6.56736,21.208C6.57733,21.218 6.58749,21.2277 6.59783,21.237 6.66429,21.2971 6.7405,21.3466 6.82381,21.3829L6.83712,21.3885C6.84698,21.3926 6.85693,21.3965 6.86698,21.4003 6.86818,21.4007 6.86937,21.4011 6.87057,21.4016 6.94576,21.4289 7.02412,21.4451 7.10303,21.45L7.12183,21.451 7.13076,21.4513 7.13345,21.4514 7.15549,21.4517 17.0847,21.4517C17.5973,22.3438 18.5597,22.9445 19.6624,22.9445 21.3031,22.9445 22.6332,21.6144 22.6332,19.9737 22.6332,18.3329 21.3031,17.0028 19.6624,17.0028 18.0839,17.0028 16.793,18.2338 16.6972,19.7882L14.8669,19.7882 21.3224,13.3327C22.6404,12.1289,22.5884,10.1619,21.3367,8.91021L16.0278,3.60138C14.8241,2.28336,12.8571,2.33535,11.6053,3.58706L2.49426,12.6981C1.17625,13.9019,1.22824,15.8689,2.47995,17.1206z M14.8072,4.7316C14.2984,4.16633,13.4255,4.11939,12.7816,4.76332L6.43063,11.1143 13.8094,18.4931 20.1604,12.1421C20.1707,12.1318 20.1813,12.1218 20.1921,12.112 20.7574,11.6033 20.8043,10.7304 20.1604,10.0865L14.8373,4.76332C14.8269,4.75301,14.8169,4.74243,14.8072,4.7316z M3.65621,13.8887C3.6459,13.899 3.63532,13.9091 3.62448,13.9188 3.05922,14.4276 3.01228,15.3004 3.65621,15.9444L7.50001,19.7882 12.752,19.7882 5.25437,12.2906 3.65621,13.8887z";
|
||||
|
||||
/// <summary>
|
||||
/// 老版实线橡皮擦图标(圆形擦除模式)
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示橡皮擦工具的圆形擦除模式的图标</remarks>
|
||||
public static string LegacySolidEraserCircleIcon =
|
||||
"F1 M24,24z M0,0z M15.0919,19.6686L21.4282,13.3322C22.7462,12.1285,22.6942,10.1616,21.4426,8.90993L16.134,3.60133C14.9303,2.28338,12.9633,2.33537,11.7117,3.58702L5.36097,9.93771 15.0919,19.6686z M6.67201,21.2053C6.82267,21.3569,7.03137,21.4508,7.26201,21.4508L17.1907,21.4508C17.7033,22.3429 18.6657,22.9437 19.7683,22.9437 21.409,22.9437 22.7391,21.6136 22.7391,19.9729 22.7391,18.3322 21.409,17.0022 19.7683,17.0022 18.19,17.0022 16.8991,18.2331 16.8033,19.7874L12.8583,19.7874 4.18476,11.1139 2.60098,12.6977C1.28303,13.9014,1.33502,15.8683,2.58667,17.12L6.67201,21.2053z";
|
||||
|
||||
/// <summary>
|
||||
/// 老版虚线套索选择工具图标
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示套索选择工具的图标</remarks>
|
||||
public static string LegacyLinedLassoSelectIcon =
|
||||
"F0 M24,24z M0,0z M14.4715,12.7092L14.4715,18.7882 15.8291,16.7546C15.9688,16.5453,16.2038,16.4196,16.4554,16.4196L19.0106,16.4196 14.4715,12.7092z M14.6618,10.9193C14.4981,10.784 14.2733,10.6788 14.0025,10.6788 13.9951,10.6788 13.9877,10.6789 13.9803,10.6791 13.7083,10.6872 13.4502,10.8008 13.2607,10.9961 13.0712,11.1913 12.9653,11.4526 12.9653,11.7246L12.9653,20.3314C12.9653,20.3403 12.9655,20.3491 12.9658,20.358 12.9734,20.5733 13.0468,20.7811 13.176,20.9534 13.3053,21.1258 13.4842,21.2544 13.6888,21.3219 13.765,21.3471 13.8447,21.36 13.925,21.36L14.0025,21.36C14.1661,21.36 14.3276,21.3218 14.474,21.2486 14.6204,21.1754 14.7477,21.0692 14.8459,20.9382 14.8542,20.9272 14.8622,20.9159 14.8698,20.9045L16.8582,17.9258 20.3145,17.9258C20.5287,17.9281 20.7384,17.8641 20.9149,17.7424 21.0941,17.6187 21.2299,17.4417 21.3029,17.2365 21.3759,17.0313 21.3825,16.8084 21.3217,16.5993 21.262,16.3936 21.14,16.2117 20.9729,16.0782L14.6618,10.9193z M8.14548,20.0044C7.70454,19.6737 7.34665,19.2448 7.10016,18.7519 6.94658,18.4447 6.83887,18.1179 6.7795,17.7818 7.131,17.6605 7.45404,17.4604 7.72196,17.1924 7.74959,17.1648 7.7765,17.1366 7.80267,17.1078 8.5567,17.4118 9.3392,17.6365 10.1444,17.7694 10.5548,17.8372 10.9424,17.5594 11.0101,17.149 11.0779,16.7387 10.8001,16.3511 10.3897,16.2833 9.72172,16.1731 9.06686,15.9883 8.42926,15.7362 8.44084,15.6393 8.44672,15.5413 8.44672,15.4427 8.44672,14.7865 8.18602,14.1571 7.72196,13.693 7.25791,13.229 6.62852,12.9682 5.97224,12.9682 5.65536,12.9682 5.34474,13.029 5.05598,13.1441 4.47073,12.3026 4.15196,11.303 4.14328,10.2756 4.14532,7.03688 7.49758,4.1462 11.9971,4.1462 16.4941,4.1462 19.8451,7.03371 19.8508,10.2703 19.8388,10.7807 19.7549,11.2869 19.6016,11.7739 19.4767,12.1706 19.697,12.5934 20.0938,12.7183 20.4905,12.8432 20.9134,12.6228 21.0383,12.2261 21.2351,11.6008 21.3424,10.9507 21.3568,10.2952L21.357,10.2786C21.357,5.91009 16.9982,2.64 11.9971,2.64 6.9959,2.64 2.63705,5.91009 2.63705,10.2786L2.6371,10.2845C2.6479,11.6579 3.08647,12.993 3.89074,14.1047 3.63615,14.5007 3.49777,14.9646 3.49777,15.4427 3.49777,16.099 3.75847,16.7284 4.22252,17.1924 4.51465,17.4846 4.87229,17.6961 5.26092,17.8128 5.33333,18.3726 5.49917,18.9179 5.75297,19.4255 6.10404,20.1276 6.61375,20.7383 7.24176,21.2093 7.5745,21.4589 8.04654,21.3915 8.2961,21.0587 8.54566,20.726 8.47822,20.2539 8.14548,20.0044z M5.97224,14.4745C5.71544,14.4745 5.46916,14.5765 5.28757,14.7581 5.10598,14.9396 5.00397,15.1859 5.00397,15.4427 5.00397,15.6995 5.10598,15.9458 5.28757,16.1274 5.46916,16.309 5.71544,16.411 5.97224,16.411 6.22904,16.411 6.47533,16.309 6.65692,16.1274 6.8385,15.9458 6.94052,15.6995 6.94052,15.4427 6.94052,15.1859 6.8385,14.9396 6.65692,14.7581 6.47533,14.5765 6.22904,14.4745 5.97224,14.4745z";
|
||||
|
||||
/// <summary>
|
||||
/// 老版实线套索选择工具图标
|
||||
/// </summary>
|
||||
/// <remarks>老版浮动栏中用于表示套索选择工具的图标</remarks>
|
||||
public static string LegacySolidLassoSelectIcon =
|
||||
"F1 M24,24z M0,0z M14.6618,10.9193C14.4981,10.784 14.2733,10.6788 14.0025,10.6788 13.9951,10.6788 13.9877,10.6789 13.9803,10.6791 13.7083,10.6872 13.4502,10.8008 13.2607,10.9961 13.0712,11.1913 12.9653,11.4526 12.9653,11.7246L12.9653,20.3314C12.9653,20.3403 12.9655,20.3491 12.9658,20.358 12.9734,20.5733 13.0468,20.7811 13.176,20.9534 13.3053,21.1258 13.4842,21.2544 13.6888,21.3219 13.765,21.3471 13.8447,21.36 13.925,21.36L14.0025,21.36C14.1661,21.36 14.3276,21.3218 14.474,21.2486 14.6204,21.1754 14.7477,21.0692 14.8459,20.9382 14.8542,20.9272 14.8622,20.9159 14.8698,20.9045L16.8582,17.9258 20.3145,17.9258C20.5287,17.9281 20.7384,17.8641 20.9149,17.7424 21.0941,17.6187 21.2299,17.4417 21.3029,17.2365 21.3759,17.0313 21.3825,16.8084 21.3217,16.5993 21.262,16.3936 21.14,16.2117 20.9729,16.0782L14.6618,10.9193z M8.14548,20.0044C7.70454,19.6737 7.34665,19.2448 7.10016,18.7519 6.94658,18.4447 6.83888,18.1179 6.7795,17.7818 7.131,17.6605 7.45404,17.4604 7.72196,17.1924 7.74959,17.1648 7.77649,17.1366 7.80267,17.1078 8.5567,17.4118 9.3392,17.6365 10.1444,17.7694 10.5548,17.8372 10.9424,17.5594 11.0101,17.149 11.0779,16.7387 10.8001,16.3511 10.3897,16.2833 9.72172,16.1731 9.06686,15.9883 8.42926,15.7362 8.44084,15.6393 8.44672,15.5413 8.44672,15.4427 8.44672,14.7865 8.18602,14.1571 7.72196,13.693 7.25791,13.229 6.62852,12.9682 5.97224,12.9682 5.65536,12.9682 5.34474,13.029 5.05598,13.1441 4.47073,12.3026 4.15196,11.303 4.14328,10.2756 4.14532,7.03688 7.49758,4.1462 11.9971,4.1462 16.4941,4.1462 19.8451,7.03371 19.8508,10.2703 19.8388,10.7807 19.7549,11.2869 19.6016,11.7739 19.4767,12.1706 19.697,12.5934 20.0938,12.7183 20.4905,12.8432 20.9134,12.6228 21.0383,12.2261 21.2351,11.6008 21.3424,10.9507 21.3568,10.2952L21.357,10.2786C21.357,5.91009 16.9982,2.64 11.9971,2.64 6.9959,2.64 2.63705,5.91009 2.63705,10.2786L2.6371,10.2845C2.6479,11.6579 3.08647,12.993 3.89074,14.1047 3.63615,14.5007 3.49777,14.9646 3.49777,15.4427 3.49777,16.099 3.75847,16.7284 4.22252,17.1924 4.51465,17.4846 4.87229,17.6961 5.26092,17.8128 5.33333,18.3726 5.49917,18.9178 5.75297,19.4255 6.10404,20.1276 6.61375,20.7383 7.24176,21.2093 7.5745,21.4589 8.04654,21.3915 8.2961,21.0587 8.54566,20.726 8.47822,20.2539 8.14548,20.0044z";
|
||||
}
|
||||
|
||||
@@ -41,7 +41,18 @@ namespace Ink_Canvas
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
// 截图并插入到画布
|
||||
/// <summary>
|
||||
/// 截图并插入到画布
|
||||
/// </summary>
|
||||
/// <returns>异步任务</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 隐藏主窗口以避免截图包含窗口本身
|
||||
/// 2. 启动区域选择截图
|
||||
/// 3. 恢复窗口显示
|
||||
/// 4. 处理截图结果并插入到画布
|
||||
/// 5. 支持摄像头截图和区域截图
|
||||
/// </remarks>
|
||||
private async Task CaptureScreenshotAndInsert()
|
||||
{
|
||||
try
|
||||
@@ -118,7 +129,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 直接全屏截图并插入到画布
|
||||
/// <summary>
|
||||
/// 直接全屏截图并插入到画布
|
||||
/// </summary>
|
||||
/// <returns>异步任务</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 隐藏主窗口以避免截图包含窗口本身
|
||||
/// 2. 获取虚拟屏幕边界
|
||||
/// 3. 截取全屏
|
||||
/// 4. 将截图转换为WPF Image并插入到画布
|
||||
/// 5. 恢复窗口显示
|
||||
/// </remarks>
|
||||
private async Task CaptureFullScreenAndInsert()
|
||||
{
|
||||
try
|
||||
@@ -158,7 +180,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 显示截图区域选择器
|
||||
/// <summary>
|
||||
/// 显示截图区域选择器
|
||||
/// </summary>
|
||||
/// <returns>截图结果,包含区域、路径和摄像头截图信息</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 显示截图选择器窗口
|
||||
/// 2. 获取用户选择的区域或摄像头截图
|
||||
/// 3. 返回截图结果
|
||||
/// </remarks>
|
||||
private async Task<ScreenshotResult?> ShowScreenshotSelector()
|
||||
{
|
||||
ScreenshotResult? result = null;
|
||||
@@ -206,7 +237,19 @@ namespace Ink_Canvas
|
||||
return result;
|
||||
}
|
||||
|
||||
// 截取指定屏幕区域
|
||||
/// <summary>
|
||||
/// 截取指定屏幕区域
|
||||
/// </summary>
|
||||
/// <param name="area">要截取的屏幕区域</param>
|
||||
/// <returns>截取的位图</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 确保区域在有效范围内
|
||||
/// 2. 调整区域边界,确保不超出屏幕范围
|
||||
/// 3. 创建支持透明度的位图
|
||||
/// 4. 设置高质量渲染
|
||||
/// 5. 截取屏幕区域
|
||||
/// </remarks>
|
||||
private Bitmap CaptureScreenArea(Rectangle area)
|
||||
{
|
||||
try
|
||||
@@ -246,7 +289,25 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 将截图插入到画布
|
||||
/// <summary>
|
||||
/// 将截图插入到画布
|
||||
/// </summary>
|
||||
/// <param name="bitmap">要插入的位图</param>
|
||||
/// <returns>异步任务</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 验证位图有效性
|
||||
/// 2. 将Bitmap转换为WPF BitmapSource
|
||||
/// 3. 创建WPF Image控件
|
||||
/// 4. 生成唯一名称
|
||||
/// 5. 初始化TransformGroup
|
||||
/// 6. 设置截图属性,避免被InkCanvas选择系统处理
|
||||
/// 7. 初始化InkCanvas选择设置
|
||||
/// 8. 等待图片加载完成后进行居中处理
|
||||
/// 9. 添加到画布
|
||||
/// 10. 提交历史记录
|
||||
/// 11. 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
/// </remarks>
|
||||
private async Task InsertScreenshotToCanvas(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
@@ -325,7 +386,23 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 将BitmapSource插入到画布(用于摄像头截图)
|
||||
/// <summary>
|
||||
/// 将BitmapSource插入到画布(用于摄像头截图)
|
||||
/// </summary>
|
||||
/// <param name="bitmapSource">要插入的BitmapSource</param>
|
||||
/// <returns>异步任务</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 创建WPF Image控件
|
||||
/// 2. 生成唯一名称
|
||||
/// 3. 初始化TransformGroup
|
||||
/// 4. 设置截图属性,避免被InkCanvas选择系统处理
|
||||
/// 5. 初始化InkCanvas选择设置
|
||||
/// 6. 等待图片加载完成后进行居中处理
|
||||
/// 7. 添加到画布
|
||||
/// 8. 提交历史记录
|
||||
/// 9. 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
/// </remarks>
|
||||
private async Task InsertBitmapSourceToCanvas(BitmapSource bitmapSource)
|
||||
{
|
||||
try
|
||||
@@ -384,7 +461,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化截图的TransformGroup
|
||||
/// <summary>
|
||||
/// 初始化截图的TransformGroup
|
||||
/// </summary>
|
||||
/// <param name="image">要初始化的Image控件</param>
|
||||
/// <remarks>
|
||||
/// 该方法会为截图创建一个包含缩放、平移和旋转变换的TransformGroup。
|
||||
/// </remarks>
|
||||
private void InitializeScreenshotTransform(Image image)
|
||||
{
|
||||
var transformGroup = new TransformGroup();
|
||||
@@ -394,7 +477,17 @@ namespace Ink_Canvas
|
||||
image.RenderTransform = transformGroup;
|
||||
}
|
||||
|
||||
// 绑定截图事件处理器
|
||||
/// <summary>
|
||||
/// 绑定截图事件处理器
|
||||
/// </summary>
|
||||
/// <param name="image">要绑定事件的Image控件</param>
|
||||
/// <remarks>
|
||||
/// 该方法会为截图绑定以下事件:
|
||||
/// 1. 鼠标事件(按下、释放、移动、滚轮)
|
||||
/// 2. 触摸事件(按下、释放、操作)
|
||||
/// 3. 设置光标为手形
|
||||
/// 4. 禁用InkCanvas对截图的选择处理
|
||||
/// </remarks>
|
||||
private void BindScreenshotEvents(Image image)
|
||||
{
|
||||
// 鼠标事件
|
||||
@@ -418,7 +511,27 @@ namespace Ink_Canvas
|
||||
image.Focusable = false;
|
||||
}
|
||||
|
||||
// 专门为截图优化的居中缩放方法
|
||||
/// <summary>
|
||||
/// 专门为截图优化的居中缩放方法
|
||||
/// </summary>
|
||||
/// <param name="image">要居中缩放的Image控件</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 确保图片已加载
|
||||
/// 2. 获取画布的实际尺寸
|
||||
/// 3. 如果画布尺寸为0,使用窗口尺寸作为备选
|
||||
/// 4. 如果仍然为0,使用屏幕尺寸
|
||||
/// 5. 计算最大允许尺寸(画布的80%)
|
||||
/// 6. 获取图片的原始尺寸
|
||||
/// 7. 计算缩放比例
|
||||
/// 8. 如果图片本身比最大尺寸小,不进行缩放
|
||||
/// 9. 计算新的尺寸
|
||||
/// 10. 设置图片尺寸
|
||||
/// 11. 计算居中位置
|
||||
/// 12. 确保位置不为负数
|
||||
/// 13. 设置位置
|
||||
/// 14. 保持滚轮缩放和拖动功能
|
||||
/// </remarks>
|
||||
private void CenterAndScaleScreenshot(Image image)
|
||||
{
|
||||
try
|
||||
@@ -502,7 +615,27 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 应用形状遮罩到截图
|
||||
/// <summary>
|
||||
/// 应用形状遮罩到截图
|
||||
/// </summary>
|
||||
/// <param name="bitmap">要应用遮罩的位图</param>
|
||||
/// <param name="path">遮罩路径</param>
|
||||
/// <param name="area">截图区域</param>
|
||||
/// <returns>应用遮罩后的位图</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 验证路径参数
|
||||
/// 2. 获取DPI缩放比例
|
||||
/// 3. 创建结果位图,确保支持透明度
|
||||
/// 4. 首先将整个位图设置为透明
|
||||
/// 5. 创建路径
|
||||
/// 6. 转换WPF坐标到GDI+坐标,考虑DPI缩放和屏幕偏移
|
||||
/// 7. 添加路径
|
||||
/// 8. 验证路径是否有效
|
||||
/// 9. 设置裁剪区域为路径内部
|
||||
/// 10. 在裁剪区域内绘制原始图像
|
||||
/// 11. 重置裁剪区域,确保后续操作不受影响
|
||||
/// </remarks>
|
||||
private Bitmap ApplyShapeMask(Bitmap bitmap, List<Point> path, Rectangle area)
|
||||
{
|
||||
try
|
||||
@@ -588,7 +721,21 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 将System.Drawing.Bitmap转换为WPF BitmapSource
|
||||
/// <summary>
|
||||
/// 将System.Drawing.Bitmap转换为WPF BitmapSource
|
||||
/// </summary>
|
||||
/// <param name="bitmap">要转换的位图</param>
|
||||
/// <returns>转换后的BitmapSource</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 验证位图有效性
|
||||
/// 2. 验证位图尺寸
|
||||
/// 3. 使用更安全的方法转换位图
|
||||
/// 4. 根据像素格式选择合适的WPF像素格式
|
||||
/// 5. 创建BitmapSource
|
||||
/// 6. 冻结BitmapSource以提高性能
|
||||
/// 7. 如果转换失败,尝试使用备用方法
|
||||
/// </remarks>
|
||||
private BitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
@@ -673,7 +820,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 备用的位图转换方法(使用内存流)
|
||||
/// <summary>
|
||||
/// 备用的位图转换方法(使用内存流)
|
||||
/// </summary>
|
||||
/// <param name="bitmap">要转换的位图</param>
|
||||
/// <returns>转换后的BitmapSource</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 验证位图有效性
|
||||
/// 2. 创建一个新的位图,确保格式正确
|
||||
/// 3. 在内存流中保存为PNG格式
|
||||
/// 4. 创建BitmapImage并加载内存流中的数据
|
||||
/// 5. 冻结BitmapImage以提高性能
|
||||
/// </remarks>
|
||||
private BitmapSource ConvertBitmapToBitmapSourceFallback(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
@@ -713,7 +872,21 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 最简单的位图转换方法
|
||||
/// <summary>
|
||||
/// 最简单的位图转换方法
|
||||
/// </summary>
|
||||
/// <param name="bitmap">要转换的位图</param>
|
||||
/// <returns>转换后的BitmapSource</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 验证位图有效性
|
||||
/// 2. 使用最基础的方法:直接保存为PNG然后加载
|
||||
/// 3. 创建临时文件
|
||||
/// 4. 将位图保存为PNG格式到临时文件
|
||||
/// 5. 创建BitmapImage并加载临时文件
|
||||
/// 6. 冻结BitmapImage以提高性能
|
||||
/// 7. 清理临时文件
|
||||
/// </remarks>
|
||||
private BitmapSource ConvertBitmapToBitmapSourceSimple(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
@@ -760,7 +933,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 获取DPI缩放比例
|
||||
/// <summary>
|
||||
/// 获取DPI缩放比例
|
||||
/// </summary>
|
||||
/// <returns>DPI缩放比例</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会从当前窗口的PresentationSource获取DPI缩放比例。
|
||||
/// 如果无法获取,则返回默认值1.0。
|
||||
/// </remarks>
|
||||
private double GetDpiScale()
|
||||
{
|
||||
var source = PresentationSource.FromVisual(this);
|
||||
|
||||
@@ -11,12 +11,27 @@ namespace Ink_Canvas
|
||||
private int lastNotificationShowTime;
|
||||
private int notificationShowTime = 2500;
|
||||
|
||||
/// <summary>
|
||||
/// 静态方法,用于在主窗口中显示通知
|
||||
/// </summary>
|
||||
/// <param name="notice">要显示的通知文本</param>
|
||||
/// <param name="isShowImmediately">指示是否应立即显示通知</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 获取应用程序中的主窗口实例
|
||||
/// 2. 调用主窗口的ShowNotification方法显示通知
|
||||
/// </remarks>
|
||||
public static void ShowNewMessage(string notice, bool isShowImmediately = true)
|
||||
{
|
||||
(Application.Current?.Windows.Cast<Window>().FirstOrDefault(window => window is MainWindow) as MainWindow)
|
||||
?.ShowNotification(notice, isShowImmediately);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在窗口中显示带从底部滑入并淡入的通知文本,并在配置的时长后自动隐藏(若未被新通知覆盖)。
|
||||
/// </summary>
|
||||
/// <param name="notice">要显示的通知文本。</param>
|
||||
/// <param name="isShowImmediately">指示是否应立即显示通知;当前实现默认立即显示。</param>
|
||||
public void ShowNotification(string notice, bool isShowImmediately = true)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -70,53 +70,138 @@ namespace Ink_Canvas
|
||||
#endregion
|
||||
|
||||
#region PPT Application Variables
|
||||
/// <summary>
|
||||
/// PowerPoint应用程序实例,用于与PowerPoint进行交互。
|
||||
/// </summary>
|
||||
public static Microsoft.Office.Interop.PowerPoint.Application pptApplication;
|
||||
|
||||
/// <summary>
|
||||
/// 当前活动的PowerPoint演示文稿。
|
||||
/// </summary>
|
||||
public static Presentation presentation;
|
||||
|
||||
/// <summary>
|
||||
/// 当前演示文稿的幻灯片集合。
|
||||
/// </summary>
|
||||
public static Slides slides;
|
||||
|
||||
/// <summary>
|
||||
/// 当前活动的幻灯片。
|
||||
/// </summary>
|
||||
public static Slide slide;
|
||||
|
||||
/// <summary>
|
||||
/// 当前演示文稿的幻灯片总数。
|
||||
/// </summary>
|
||||
public static int slidescount;
|
||||
#endregion
|
||||
|
||||
#region PPT State Management
|
||||
/// <summary>
|
||||
/// 幻灯片放映结束事件重入保护标志,防止重复处理放映结束事件。
|
||||
/// </summary>
|
||||
private bool isEnteredSlideShowEndEvent;
|
||||
|
||||
/// <summary>
|
||||
/// 演示文稿是否有黑边的指示标志。
|
||||
/// </summary>
|
||||
private bool isPresentationHaveBlackSpace;
|
||||
|
||||
// 长按翻页相关字段
|
||||
/// <summary>
|
||||
/// 用于处理长按翻页功能的定时器。
|
||||
/// </summary>
|
||||
private DispatcherTimer _longPressTimer;
|
||||
|
||||
/// <summary>
|
||||
/// 长按翻页方向标志,true表示下一页,false表示上一页。
|
||||
/// </summary>
|
||||
private bool _isLongPressNext = true; // true为下一页,false为上一页
|
||||
|
||||
/// <summary>
|
||||
/// 长按延迟时间(毫秒),即用户需要按住按钮多长时间才开始连续翻页。
|
||||
/// </summary>
|
||||
private const int LongPressDelay = 500; // 长按延迟时间(毫秒)
|
||||
|
||||
/// <summary>
|
||||
/// 长按翻页间隔(毫秒),即连续翻页的时间间隔。
|
||||
/// </summary>
|
||||
private const int LongPressInterval = 50; // 长按翻页间隔(毫秒)
|
||||
|
||||
// PowerPoint应用程序守护相关字段
|
||||
/// <summary>
|
||||
/// 用于监控PowerPoint应用程序状态的定时器。
|
||||
/// </summary>
|
||||
private DispatcherTimer _powerPointProcessMonitorTimer;
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序监控间隔(毫秒),即每隔多长时间检查一次PowerPoint应用程序状态。
|
||||
/// </summary>
|
||||
private const int ProcessMonitorInterval = 1000; // 应用程序监控间隔(毫秒)
|
||||
|
||||
// 上次播放位置相关字段
|
||||
/// <summary>
|
||||
/// 上次播放的幻灯片页码。
|
||||
/// </summary>
|
||||
private int _lastPlaybackPage = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 是否应该导航到上次播放页码的标志。
|
||||
/// </summary>
|
||||
private bool _shouldNavigateToLastPage = false;
|
||||
|
||||
// 当前播放页码跟踪
|
||||
/// <summary>
|
||||
/// 当前幻灯片放映的位置(页码)。
|
||||
/// </summary>
|
||||
private int _currentSlideShowPosition = 0;
|
||||
|
||||
private Dictionary<int, MemoryStream> _memoryStreams = new Dictionary<int, MemoryStream>();
|
||||
private int _previousSlideID = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 用于在PowerPoint连接断开后延迟退出PPT模式的定时器。
|
||||
/// </summary>
|
||||
private DispatcherTimer _exitPPTModeAfterDisconnectTimer;
|
||||
|
||||
/// <summary>
|
||||
/// 断开连接后退出PPT模式的延迟时间(毫秒),即连接断开后多长时间才退出PPT模式。
|
||||
/// </summary>
|
||||
private const int ExitPPTModeAfterDisconnectDelayMs = 1200;
|
||||
#endregion
|
||||
|
||||
#region PPT Managers
|
||||
/// <summary>
|
||||
/// PPT链接管理器,用于管理与PowerPoint的连接和事件处理。
|
||||
/// </summary>
|
||||
private IPPTLinkManager _pptManager;
|
||||
|
||||
/// <summary>
|
||||
/// PPT墨迹管理器,用于管理PowerPoint幻灯片上的墨迹。
|
||||
/// </summary>
|
||||
private PPTInkManager _singlePPTInkManager;
|
||||
|
||||
/// <summary>
|
||||
/// PPT UI管理器,用于管理与PowerPoint相关的用户界面元素。
|
||||
/// </summary>
|
||||
private PPTUIManager _pptUIManager;
|
||||
|
||||
/// <summary>
|
||||
/// 获取PPT管理器实例
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 提供对内部PPT链接管理器的公共访问,用于外部代码与PowerPoint进行交互。
|
||||
/// </remarks>
|
||||
public IPPTLinkManager PPTManager => _pptManager;
|
||||
#endregion
|
||||
|
||||
#region PPT Manager Initialization
|
||||
/// <summary>
|
||||
/// 初始化并配置用于 PowerPoint 集成的管理器与相关状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 清理并释放现有的 PPT 管理器与 COM/Interop 状态,创建并配置新的 PPT 管理器(ROT 或 COM 实现,取决于设置)、单一的 PPT 墨迹管理器及其自动保存行为,以及 PPT UI 管理器与其显示/按钮位置选项。方法内部会订阅必要的 PPT 事件并记录初始化过程中的错误或警告。同时初始化长按页翻页定时器以支持长按翻页功能。
|
||||
/// </remarks>
|
||||
private void InitializePPTManagers()
|
||||
{
|
||||
try
|
||||
@@ -194,6 +279,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动PPT监控:当PowerPoint支持功能启用时,启动PPT管理器的监控功能。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 只有当Settings.PowerPointSettings.PowerPointSupport为true时才会启动监控,并记录启动事件日志。
|
||||
/// </remarks>
|
||||
private void StartPPTMonitoring()
|
||||
{
|
||||
if (Settings.PowerPointSettings.PowerPointSupport)
|
||||
@@ -203,6 +294,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止 PowerPoint 相关的监控:停止并清除用于延迟退出 PPT 模式的定时器,并停止 PPT 管理器的监控,同时记录事件日志。
|
||||
/// </summary>
|
||||
private void StopPPTMonitoring()
|
||||
{
|
||||
try
|
||||
@@ -221,7 +315,12 @@ namespace Ink_Canvas
|
||||
#region PowerPoint Application Management
|
||||
/// <summary>
|
||||
/// 启动PowerPoint应用程序守护
|
||||
/// <summary>
|
||||
/// 启动对本地 PowerPoint 应用实例的守护监控并在需要时创建应用程序实例。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 仅在 PowerPoint 增强功能已启用且未使用 ROT 链接时生效;方法将创建 PowerPoint 应用(若不存在)并启动用于定期检查应用状态的定时器。
|
||||
/// </remarks>
|
||||
private void StartPowerPointProcessMonitoring()
|
||||
{
|
||||
try
|
||||
@@ -272,7 +371,12 @@ namespace Ink_Canvas
|
||||
|
||||
/// <summary>
|
||||
/// 创建PowerPoint应用程序实例
|
||||
/// <summary>
|
||||
/// 创建并初始化一个隐藏的 PowerPoint 应用程序 COM 实例,并在可用时将该实例注入到当前的 PPT 管理器中。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果配置为使用 ROT 链接或已有有效的 PowerPoint 实例,则不会创建新实例。创建的实例会被设置为不可见并最小化;在实例准备就绪后会通过延迟调用将其设置到 PPT 管理器(SetPPTManagerApplication)。任何创建或注入失败的情况会被记录日志,但不会抛出异常给调用者。
|
||||
/// </remarks>
|
||||
private void CreatePowerPointApplication()
|
||||
{
|
||||
try
|
||||
@@ -325,7 +429,11 @@ namespace Ink_Canvas
|
||||
|
||||
/// <summary>
|
||||
/// 设置PPTManager的PowerPoint应用程序实例
|
||||
/// <summary>
|
||||
/// 将给定的 PowerPoint 应用实例注入到当前的 PPT 管理器中,若管理器为 null 或启用 ROT 链接则不做任何操作。
|
||||
/// 尝试使用非公开的 `ConnectToPPT` 方法进行绑定,若不可用则回退到写入公共 `PPTApplication` 属性;操作结果和异常通过日志记录。
|
||||
/// </summary>
|
||||
/// <param name="app">要注入的 PowerPoint 应用实例(Microsoft.Office.Interop.PowerPoint.Application)。</param>
|
||||
private void SetPPTManagerApplication(Microsoft.Office.Interop.PowerPoint.Application app)
|
||||
{
|
||||
try
|
||||
@@ -399,6 +507,10 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 关闭PowerPoint应用程序
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 关闭当前的 PowerPoint 应用程序及其所有打开的演示文稿,释放相关 COM 资源并清理静态互操作状态。</summary>
|
||||
/// 会尝试关闭所有打开的演示文稿、退出 PowerPoint 进程、释放 COM 对象引用,并将内部 PowerPoint 互操作状态重置为初始值;操作结果会被记录到日志,发生异常时会记录错误并仍然尝试清理互操作状态。
|
||||
/// </remarks>
|
||||
private void ClosePowerPointApplication()
|
||||
{
|
||||
try
|
||||
@@ -436,6 +548,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放并清理与 PowerPoint COM 互操作相关的引用(演示文稿、Slides、当前幻灯片),并将幻灯片计数重置为 0。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在释放过程中若发生异常会被捕获并以警告级别记录日志,不会抛出异常到调用者。
|
||||
/// </remarks>
|
||||
private void ClearStaticInteropState()
|
||||
{
|
||||
try
|
||||
@@ -466,6 +584,9 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// PowerPoint应用程序监控定时器事件
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 周期性监控嵌入的 PowerPoint 应用实例的可用性,并在检测到失效时尝试重建实例;当增强功能被禁用时停止监控,并在使用 ROT 链接时不进行检查。
|
||||
/// </remarks>
|
||||
private void OnPowerPointApplicationMonitorTick(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -491,6 +612,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 释放并停止所有与 PowerPoint 集成相关的管理器与资源,恢复和清理应用的 PPT 相关运行状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 操作包括停止并释放 PPT 管理器、墨迹管理器和长按计时器,停止 PowerPoint 进程监控,关闭 PowerPoint 应用并清除静态 COM/互操作状态;所有异常会被捕获并记录为错误日志。
|
||||
/// </remarks>
|
||||
private void DisposePPTManagers()
|
||||
{
|
||||
try
|
||||
@@ -573,6 +700,10 @@ namespace Ink_Canvas
|
||||
#endregion
|
||||
|
||||
#region New PPT Event Handlers
|
||||
/// <summary>
|
||||
/// 处理 PowerPoint 连接状态的变更:更新界面连接/放映状态,并在断开时启动一个短延迟以安全退出 PPT 模式。
|
||||
/// </summary>
|
||||
/// <param name="isConnected">指示当前是否已与 PowerPoint 建立连接;`true` 表示已连接,`false` 表示已断开。</param>
|
||||
private void OnPPTConnectionChanged(bool isConnected)
|
||||
{
|
||||
try
|
||||
@@ -618,6 +749,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 PowerPoint 演示文稿打开事件:清理画布墨迹、初始化墨迹管理器、处理导航逻辑、检查隐藏幻灯片和自动播放设置,并更新连接状态。
|
||||
/// </summary>
|
||||
/// <param name="pres">已打开的 PowerPoint 演示文稿(Presentation)实例。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:清理画布墨迹和备份历史记录,初始化墨迹管理器,处理跳转到首页或上次播放页的逻辑,检查隐藏幻灯片和自动播放设置,更新UI连接状态,并记录事件日志。
|
||||
/// 所有操作在UI线程异步执行,异常会被捕获并记录为错误日志。
|
||||
/// </remarks>
|
||||
private void OnPPTPresentationOpen(Presentation pres)
|
||||
{
|
||||
try
|
||||
@@ -701,6 +840,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 PowerPoint 幻灯片放映状态变化事件:更新UI管理器的放映状态并检查主窗口可见性。
|
||||
/// </summary>
|
||||
/// <param name="isInSlideShow">指示当前是否处于幻灯片放映状态;`true` 表示正在放映,`false` 表示已退出放映。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:在UI线程异步通知UI管理器放映状态变化,检查并更新主窗口的可见性(用于仅PPT模式)。
|
||||
/// 异常会被捕获并记录为错误日志,确保方法执行不会中断。
|
||||
/// </remarks>
|
||||
private void OnPPTSlideShowStateChanged(bool isInSlideShow)
|
||||
{
|
||||
try
|
||||
@@ -724,6 +871,31 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 PowerPoint 幻灯片放映开始事件:根据设置折叠或展开浮动栏,初始化放映状态,更新UI,加载当前页墨迹,并设置相关参数。
|
||||
/// </summary>
|
||||
/// <param name="wn">PowerPoint 幻灯片放映窗口(SlideShowWindow)实例,包含当前放映状态和视图信息。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 根据设置自动折叠或展开浮动栏
|
||||
/// 2. 停止墨迹重放
|
||||
/// 3. 获取当前活动演示文稿、当前幻灯片和总幻灯片数
|
||||
/// 4. 初始化墨迹管理器
|
||||
/// 5. 处理跳转到首页或上次播放位置的逻辑
|
||||
/// 6. 更新UI状态,包括放映状态、当前幻灯片编号
|
||||
/// 7. 设置浮动栏透明度和边距
|
||||
/// 8. 显示侧边栏退出按钮
|
||||
/// 9. 处理画板显示
|
||||
/// 10. 关闭白板模式(如果当前在白板模式)
|
||||
/// 11. 显示浮动栏主控件
|
||||
/// 12. 根据设置隐藏或显示手势面板和按钮
|
||||
/// 13. 如果设置了在新放映时显示画布,则进入批注模式并显示调色盘
|
||||
/// 14. 重置幻灯片放映结束事件标志
|
||||
/// 15. 加载当前页墨迹
|
||||
/// 16. 调整浮动栏边距动画
|
||||
///
|
||||
/// 所有UI操作在UI线程异步执行,异常会被捕获并记录为错误日志。
|
||||
/// </remarks>
|
||||
private async void OnPPTSlideShowBegin(SlideShowWindow wn)
|
||||
{
|
||||
try
|
||||
@@ -947,6 +1119,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理幻灯片放映中的切换:在幻灯片变更时保存当前页墨迹、加载目标页墨迹并更新界面状态。
|
||||
/// </summary>
|
||||
/// <param name="wn">当前的幻灯片放映窗口;若为 null 或其 View/Presentation 无效则方法不执行。</param>
|
||||
/// <remarks>
|
||||
/// - 如果收到与当前记录相同的页码或已有切换正在处理,则忽略该事件。
|
||||
/// - 在切换过程中会保存前一页的墨迹(如存在)、清空画布与历史、加载新页的墨迹、锁定新页墨迹并刷新当前页显示序号,同时更新内部的当前播放位置状态。
|
||||
/// </remarks>
|
||||
private void OnPPTSlideShowNextSlide(SlideShowWindow wn)
|
||||
{
|
||||
try
|
||||
@@ -1018,6 +1198,10 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 PowerPoint 幻灯片放映结束时的清理与界面恢复,包括保存当前幻灯片墨迹、重置墨迹管理器状态、恢复主题与工具栏显示,并根据配置折叠或展示浮动工具栏等 UI 调整。
|
||||
/// </summary>
|
||||
/// <param name="pres">触发结束事件的 PowerPoint 演示文稿(Presentation)实例,用于保存墨迹并尝试读取放映时的当前页码。</param>
|
||||
private async void OnPPTSlideShowEnd(Presentation pres)
|
||||
{
|
||||
try
|
||||
@@ -1227,6 +1411,16 @@ namespace Ink_Canvas
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
/// <summary>
|
||||
/// 处理演示文稿打开时的导航逻辑:根据设置决定跳转到首页或显示上次播放页通知。
|
||||
/// </summary>
|
||||
/// <param name="pres">当前打开的 PowerPoint 演示文稿(Presentation)实例。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 如果设置了总是跳转到首页,则尝试导航到第1页
|
||||
/// 2. 否则,如果设置了显示上次播放页通知,则显示上次播放页通知
|
||||
/// 异常会被捕获并记录为错误日志,确保方法执行不会中断。
|
||||
/// </remarks>
|
||||
private void HandlePresentationOpenNavigation(Presentation pres)
|
||||
{
|
||||
try
|
||||
@@ -1246,6 +1440,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示上次播放页通知:检查演示文稿的上次播放位置并显示跳转提示。
|
||||
/// </summary>
|
||||
/// <param name="pres">当前打开的 PowerPoint 演示文稿(Presentation)实例。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 检查演示文稿是否为null
|
||||
/// 2. 获取演示文稿路径并计算文件哈希值
|
||||
/// 3. 构建保存位置文件夹路径和位置文件路径
|
||||
/// 4. 检查位置文件是否存在
|
||||
/// 5. 尝试解析位置文件中的页码
|
||||
/// 6. 如果解析成功且页码大于0,则保存上次播放页码并显示跳转提示窗口
|
||||
/// 异常会被捕获并记录为错误日志,确保方法执行不会中断。
|
||||
/// </remarks>
|
||||
private void ShowPreviousPageNotification(Presentation pres)
|
||||
{
|
||||
try
|
||||
@@ -1290,6 +1498,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并通知隐藏幻灯片:扫描演示文稿中的所有幻灯片,检测隐藏幻灯片并显示取消隐藏的提示。
|
||||
/// </summary>
|
||||
/// <param name="pres">要检查的 PowerPoint 演示文稿(Presentation)实例。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 检查演示文稿及其幻灯片集合是否为null
|
||||
/// 2. 遍历所有幻灯片,检测是否存在隐藏的幻灯片
|
||||
/// 3. 如果存在隐藏幻灯片且未显示过恢复隐藏幻灯片窗口,则显示确认窗口
|
||||
/// 4. 如果用户确认,则取消所有幻灯片的隐藏状态
|
||||
/// 5. 无论用户选择如何,都会重置IsShowingRestoreHiddenSlidesWindow标志
|
||||
/// 异常会被捕获并记录为错误日志,确保方法执行不会中断。
|
||||
/// </remarks>
|
||||
private void CheckAndNotifyHiddenSlides(Presentation pres)
|
||||
{
|
||||
try
|
||||
@@ -1343,6 +1564,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并通知自动播放设置:扫描演示文稿中的所有幻灯片,检测自动播放或排练计时设置并显示取消提示。
|
||||
/// </summary>
|
||||
/// <param name="pres">要检查的 PowerPoint 演示文稿(Presentation)实例。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 检查是否正在显示PPT放映结束按钮,如果是则直接返回
|
||||
/// 2. 检查演示文稿及其幻灯片集合是否为null
|
||||
/// 3. 遍历所有幻灯片,检测是否存在自动播放或排练计时设置
|
||||
/// 4. 如果存在自动播放设置且未显示过自动播放提示窗口,则显示确认窗口
|
||||
/// 5. 如果用户确认,则将演示文稿的放映设置设置为手动播放模式
|
||||
/// 6. 无论用户选择如何,都会重置IsShowingAutoplaySlidesWindow标志
|
||||
/// 异常会被捕获并记录为错误日志,确保方法执行不会中断。
|
||||
/// </remarks>
|
||||
private void CheckAndNotifyAutoPlaySettings(Presentation pres)
|
||||
{
|
||||
try
|
||||
@@ -1395,6 +1630,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载当前幻灯片的墨迹:清空画布和历史记录,然后加载指定幻灯片的墨迹。
|
||||
/// </summary>
|
||||
/// <param name="slideIndex">要加载墨迹的幻灯片索引。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 清空画布上的所有墨迹
|
||||
/// 2. 清空时间机器的墨迹历史记录
|
||||
/// 3. 从墨迹管理器加载指定幻灯片的墨迹
|
||||
/// 4. 如果加载到墨迹且墨迹集合不为空,则将墨迹添加到画布
|
||||
/// 异常会被捕获并记录为错误日志,确保方法执行不会中断。
|
||||
/// </remarks>
|
||||
private void LoadCurrentSlideInk(int slideIndex)
|
||||
{
|
||||
try
|
||||
@@ -1447,6 +1694,21 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 重置PPT相关的状态变量,当PPT自动收纳设置变更时调用
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将与 PowerPoint 播放和状态追踪相关的内部字段重置为初始默认值。
|
||||
/// 具体重置的字段包括:
|
||||
/// 1. 播放结束重入保护标志(isEnteredSlideShowEndEvent)
|
||||
/// 2. 演示文稿黑边指示(isPresentationHaveBlackSpace)
|
||||
/// 3. 上次播放页码(_lastPlaybackPage)
|
||||
/// 4. 导航标志(_shouldNavigateToLastPage)
|
||||
/// 5. 当前放映位置(_currentSlideShowPosition)
|
||||
/// 6. 滑动切换处理状态(_isProcessingSlideSwitch)
|
||||
///
|
||||
/// 该方法在执行过程中会:
|
||||
/// - 使用线程安全的方式重置滑动切换处理状态
|
||||
/// - 成功时记录追踪日志
|
||||
/// - 发生异常时记录错误日志并继续执行
|
||||
/// </remarks>
|
||||
public void ResetPPTStateVariables()
|
||||
{
|
||||
try
|
||||
@@ -1481,6 +1743,14 @@ namespace Ink_Canvas
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 发起一次手动的 PowerPoint 连接检查并在短延迟后报告结果。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果尚未初始化 PPT 管理器则先进行初始化,然后重载连接并启动监控;
|
||||
/// 延迟约 800 毫秒后在 UI 线程上检查连接状态:若已连接仅记录事件日志,若未连接则弹出提示并记录警告;
|
||||
/// 若过程中抛出异常则记录错误日志、将 UI 连接状态置为断开并提示用户未找到幻灯片。
|
||||
/// </remarks>
|
||||
private void BtnCheckPPT_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1518,6 +1788,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PowerPoint增强功能开关的切换事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当PowerPoint增强功能被启用时:
|
||||
/// 1. 禁用WPS支持
|
||||
/// 2. 更新PPT管理器的WPS支持设置
|
||||
/// 3. 启动PowerPoint进程守护
|
||||
/// 当PowerPoint增强功能被禁用时:
|
||||
/// 1. 停止PowerPoint进程守护
|
||||
/// 无论开关状态如何变化,都会保存设置到文件
|
||||
/// </remarks>
|
||||
private void ToggleSwitchPowerPointEnhancement_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -1549,6 +1833,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理WPS支持开关的切换事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当WPS支持被启用时:
|
||||
/// 1. 如果PowerPoint支持未启用,则启用PowerPoint支持
|
||||
/// 2. 启动PPT监控
|
||||
/// 3. 如果PowerPoint增强功能已启用,则禁用它并停止PowerPoint进程守护
|
||||
/// 无论开关状态如何变化,都会:
|
||||
/// 1. 更新PPT管理器的WPS支持设置
|
||||
/// 2. 保存设置到文件
|
||||
/// </remarks>
|
||||
private void ToggleSwitchSupportWPS_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -1587,11 +1885,27 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否启用了WPS支持
|
||||
/// </summary>
|
||||
/// <value>如果启用了WPS支持,则为true;否则为false</value>
|
||||
private static bool isWPSSupportOn => Settings.PowerPointSettings.IsSupportWPS;
|
||||
|
||||
/// <summary>
|
||||
/// 指示是否正在显示恢复隐藏幻灯片的窗口
|
||||
/// </summary>
|
||||
public static bool IsShowingRestoreHiddenSlidesWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 指示是否正在显示自动播放提示窗口
|
||||
/// </summary>
|
||||
private static bool IsShowingAutoplaySlidesWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 处理“上一页”按钮的点击操作:在满足自动保存条件时保存当前幻灯片截图并尝试切换到上一张幻灯片;在切换失败或发生异常时记录日志并更新连接状态。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象(通常是触发按钮)。</param>
|
||||
/// <param name="e">路由事件参数。</param>
|
||||
private void BtnPPTSlidesUp_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
@@ -1624,6 +1938,12 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理“下一页”按钮点击:在满足自动保存条件时保存当前幻灯片的截图并尝试切换到下一张幻灯片。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果切换操作失败或发生异常,会写入日志并将 PPT 连接状态更新为断开。
|
||||
/// </remarks>
|
||||
private void BtnPPTSlidesDown_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
@@ -1656,6 +1976,17 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT导航按钮的鼠标按下事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户按下PPT导航按钮时执行以下操作:
|
||||
/// 1. 记录按下的按钮对象
|
||||
/// 2. 检查是否启用了PPT按钮页码点击功能
|
||||
/// 3. 根据按下的按钮设置相应的反馈边框透明度
|
||||
/// </remarks>
|
||||
private void PPTNavigationBtn_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
lastBorderMouseDownObject = sender;
|
||||
@@ -1678,6 +2009,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT导航按钮的鼠标离开事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户鼠标离开PPT导航按钮时执行以下操作:
|
||||
/// 1. 重置按下的按钮对象为null
|
||||
/// 2. 根据离开的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
||||
/// </remarks>
|
||||
private void PPTNavigationBtn_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
lastBorderMouseDownObject = null;
|
||||
@@ -1699,6 +2040,23 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT导航按钮的鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户释放PPT导航按钮时执行以下操作:
|
||||
/// 1. 检查释放的按钮是否与按下的按钮一致
|
||||
/// 2. 隐藏按钮的反馈效果
|
||||
/// 3. 检查是否启用了PPT按钮页码点击功能
|
||||
/// 4. 检查PPT是否已连接且在放映状态
|
||||
/// 5. 设置背景透明度和颜色
|
||||
/// 6. 切换到光标模式
|
||||
/// 7. 尝试显示PPT幻灯片导航
|
||||
/// 8. 如果浮动栏未折叠,则调整其位置
|
||||
/// 9. 捕获并记录可能的异常
|
||||
/// </remarks>
|
||||
private async void PPTNavigationBtn_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -1758,6 +2116,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理“开始幻灯片放映”按钮的点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户点击“开始幻灯片放映”按钮时执行以下操作:
|
||||
/// 1. 在新线程中尝试启动PPT幻灯片放映
|
||||
/// 2. 如果启动失败,记录警告日志
|
||||
/// 3. 捕获并记录可能的异常
|
||||
/// </remarks>
|
||||
private void BtnPPTSlideShow_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
new Thread(() =>
|
||||
@@ -1884,6 +2253,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT上一页控制按钮的鼠标按下事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户按下PPT上一页控制按钮时执行以下操作:
|
||||
/// 1. 记录按下的按钮对象
|
||||
/// 2. 根据按下的按钮设置相应的反馈边框透明度
|
||||
/// 3. 如果启用了PPT按钮长按翻页功能,则启动长按检测
|
||||
/// </remarks>
|
||||
private void GridPPTControlPrevious_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
lastBorderMouseDownObject = sender;
|
||||
@@ -1910,6 +2290,17 @@ namespace Ink_Canvas
|
||||
StartLongPressDetection(sender, false);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 处理PPT上一页控制按钮的鼠标离开事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户鼠标离开PPT上一页控制按钮时执行以下操作:
|
||||
/// 1. 重置按下的按钮对象为null
|
||||
/// 2. 根据离开的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
||||
/// 3. 停止长按检测
|
||||
/// </remarks>
|
||||
private void GridPPTControlPrevious_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
lastBorderMouseDownObject = null;
|
||||
@@ -1933,6 +2324,18 @@ namespace Ink_Canvas
|
||||
// 停止长按检测
|
||||
StopLongPressDetection();
|
||||
}
|
||||
/// <summary>
|
||||
/// 处理PPT上一页控制按钮的鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户释放PPT上一页控制按钮时执行以下操作:
|
||||
/// 1. 检查释放的按钮是否与按下的按钮一致
|
||||
/// 2. 根据释放的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
||||
/// 3. 停止长按检测
|
||||
/// 4. 调用上一页按钮的点击事件处理方法,实现切换到上一页的功能
|
||||
/// </remarks>
|
||||
private void GridPPTControlPrevious_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -1960,6 +2363,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT下一页控制按钮的鼠标按下事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户按下PPT下一页控制按钮时执行以下操作:
|
||||
/// 1. 记录按下的按钮对象
|
||||
/// 2. 根据按下的按钮设置相应的反馈边框透明度
|
||||
/// 3. 如果启用了PPT按钮长按翻页功能,则启动长按检测
|
||||
/// </remarks>
|
||||
private void GridPPTControlNext_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
lastBorderMouseDownObject = sender;
|
||||
@@ -1986,6 +2400,17 @@ namespace Ink_Canvas
|
||||
StartLongPressDetection(sender, true);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 处理PPT下一页控制按钮的鼠标离开事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户鼠标离开PPT下一页控制按钮时执行以下操作:
|
||||
/// 1. 重置按下的按钮对象为null
|
||||
/// 2. 根据离开的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
||||
/// 3. 停止长按检测
|
||||
/// </remarks>
|
||||
private void GridPPTControlNext_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
lastBorderMouseDownObject = null;
|
||||
@@ -2009,6 +2434,18 @@ namespace Ink_Canvas
|
||||
// 停止长按检测
|
||||
StopLongPressDetection();
|
||||
}
|
||||
/// <summary>
|
||||
/// 处理PPT下一页控制按钮的鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户释放PPT下一页控制按钮时执行以下操作:
|
||||
/// 1. 检查释放的按钮是否与按下的按钮一致
|
||||
/// 2. 根据释放的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
||||
/// 3. 停止长按检测
|
||||
/// 4. 调用下一页按钮的点击事件处理方法,实现切换到下一页的功能
|
||||
/// </remarks>
|
||||
private void GridPPTControlNext_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -2035,9 +2472,17 @@ namespace Ink_Canvas
|
||||
BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT结束控制按钮的鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件的来源对象</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法在用户释放PPT结束控制按钮时调用BtnPPTSlideShowEnd_Click方法,实现结束幻灯片放映的功能
|
||||
/// </remarks>
|
||||
private void ImagePPTControlEnd_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,11 @@ namespace Ink_Canvas
|
||||
ObservableCollection<PageListViewItem> blackBoardSidePageListViewObservableCollection = new ObservableCollection<PageListViewItem>();
|
||||
|
||||
/// <summary>
|
||||
/// <para>刷新白板的缩略图页面列表。</para>
|
||||
/// 刷新白板的缩略图页面列表,更新左右侧缩略页列表,使其与当前白板页及历史快照一致,并将左右列表的选中项同步到当前白板页。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 为每页生成或更新对应的 PageListViewItem(通过应用时间线历史并裁剪到画布边界),用当前画布的笔迹替换当前页的条目,并将两个侧边 ListView 的 SelectedIndex 设置为当前白板索引 - 1。
|
||||
/// </remarks>
|
||||
private void RefreshBlackBoardSidePageListView()
|
||||
{
|
||||
if (blackBoardSidePageListViewObservableCollection.Count == WhiteboardTotalCount)
|
||||
@@ -67,6 +70,16 @@ namespace Ink_Canvas
|
||||
BlackBoardRightSidePageListView.SelectedIndex = CurrentWhiteboardIndex - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据传入相对于 <paramref name="scrollViewer"/> 的点,查找并选中列表中对应的缩略图项;在需要时切换当前白板页并更新画布状态与左右侧缩略图选择状态。
|
||||
/// </summary>
|
||||
/// <param name="listView">承载页面缩略图的 ListView。</param>
|
||||
/// <param name="scrollViewer">包含该 ListView 的 ScrollViewer,用于将触点坐标从滚动视图坐标系转换到 ListView。</param>
|
||||
/// <param name="pointInScrollViewer">相对于 <paramref name="scrollViewer"/> 的触点坐标(用于命中测试)。</param>
|
||||
/// <remarks>
|
||||
/// - 如果命中到 ListViewItem,会隐藏左右侧页面边框、在必要时保存/清空/恢复画笔笔迹并更新 CurrentWhiteboardIndex 与显示信息;还会将左右两侧 ListView 的 SelectedIndex 同步为命中项索引。
|
||||
/// - 在查找命中或切换过程中发生的异常将被捕获并忽略,不会向上抛出。
|
||||
/// </remarks>
|
||||
private void TrySwitchWhiteboardPageByTouchPoint(ListView listView, ScrollViewer scrollViewer, Point pointInScrollViewer)
|
||||
{
|
||||
if (listView == null || scrollViewer == null) return;
|
||||
@@ -109,6 +122,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在视觉树中自下而上查找并返回第一个匹配指定类型的祖先元素。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要查找的祖先类型,必须继承自 <see cref="DependencyObject"/>。</typeparam>
|
||||
/// <param name="current">起始节点;从此节点开始向上遍历视觉树。</param>
|
||||
/// <returns>找到的第一个类型为 <typeparamref name="T"/> 的祖先元素,未找到时返回 <c>null</c>。</returns>
|
||||
private static T FindAncestorOfType<T>(DependencyObject current) where T : DependencyObject
|
||||
{
|
||||
while (current != null)
|
||||
@@ -119,6 +138,11 @@ namespace Ink_Canvas
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定元素在给定 ScrollViewer 中滚动,使该元素与可视区域的顶部对齐。
|
||||
/// </summary>
|
||||
/// <param name="element">要对齐到顶部的元素。</param>
|
||||
/// <param name="scrollViewer">包含该元素的目标 ScrollViewer。</param>
|
||||
public static void ScrollViewToVerticalTop(FrameworkElement element, ScrollViewer scrollViewer)
|
||||
{
|
||||
if (element == null || scrollViewer == null)
|
||||
@@ -139,6 +163,24 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 左侧页面列表视图的鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 隐藏左右侧页面边框
|
||||
/// 2. 获取选中的项目和索引
|
||||
/// 3. 只有当选择的页面与当前页面不同时才进行切换
|
||||
/// 4. 如果有选中的元素,先取消选择
|
||||
/// 5. 保存当前页面的笔画
|
||||
/// 6. 清空画布
|
||||
/// 7. 更新当前白板索引
|
||||
/// 8. 恢复新页面的笔画
|
||||
/// 9. 更新索引信息显示
|
||||
/// 10. 更新选择索引
|
||||
/// </remarks>
|
||||
private void BlackBoardLeftSidePageListView_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView);
|
||||
@@ -172,6 +214,24 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 右侧页面列表视图的鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 隐藏左右侧页面边框
|
||||
/// 2. 获取选中的项目和索引
|
||||
/// 3. 只有当选择的页面与当前页面不同时才进行切换
|
||||
/// 4. 如果有选中的元素,先取消选择
|
||||
/// 5. 保存当前页面的笔画
|
||||
/// 6. 清空画布
|
||||
/// 7. 更新当前白板索引
|
||||
/// 8. 恢复新页面的笔画
|
||||
/// 9. 更新索引信息显示
|
||||
/// 10. 更新选择索引
|
||||
/// </remarks>
|
||||
private void BlackBoardRightSidePageListView_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView);
|
||||
@@ -206,4 +266,4 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 保存墨迹的鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 检查是否是当前按下的对象,且墨迹画布是否可见
|
||||
/// 2. 隐藏工具面板
|
||||
/// 3. 隐藏通知面板
|
||||
/// 4. 调用SaveInkCanvasStrokes方法保存墨迹
|
||||
/// </remarks>
|
||||
private void SymbolIconSaveStrokes_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender || inkCanvas.Visibility != Visibility.Visible) return;
|
||||
@@ -50,6 +62,23 @@ namespace Ink_Canvas
|
||||
SaveInkCanvasStrokes(true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存墨迹画布的墨迹
|
||||
/// </summary>
|
||||
/// <param name="newNotice">是否显示新的通知</param>
|
||||
/// <param name="saveByUser">是否是用户手动保存</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 根据保存类型和模式确定保存路径
|
||||
/// 2. 创建保存目录
|
||||
/// 3. 根据当前模式生成保存文件名
|
||||
/// 4. 根据设置选择保存模式:
|
||||
/// - 全页面保存模式:保存为图像或压缩包
|
||||
/// - XML保存模式:保存为XML文件或压缩包
|
||||
/// - 常规保存模式:保存为二进制格式或XML格式
|
||||
/// 5. 异步上传保存的文件到Dlass
|
||||
/// 6. 保存元素信息
|
||||
/// </remarks>
|
||||
private void SaveInkCanvasStrokes(bool newNotice = true, bool saveByUser = false)
|
||||
{
|
||||
try
|
||||
@@ -673,6 +702,22 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开墨迹文件的鼠标释放事件处理
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 检查是否是当前按下的对象
|
||||
/// 2. 隐藏工具面板
|
||||
/// 3. 打开文件选择对话框
|
||||
/// 4. 根据文件扩展名选择不同的打开方式:
|
||||
/// - .zip:处理ICC压缩包
|
||||
/// - .xml:处理XML格式墨迹文件
|
||||
/// - 其他:处理单个墨迹文件(二进制格式)
|
||||
/// 5. 如果墨迹画布不可见,切换到鼠标模式
|
||||
/// </remarks>
|
||||
private void SymbolIconOpenStrokes_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -1157,6 +1202,15 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 打开单个墨迹文件
|
||||
/// </summary>
|
||||
/// <param name="filePath">墨迹文件的路径</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 打开墨迹文件并加载墨迹
|
||||
/// 2. 检查文件是否包含墨迹
|
||||
/// 3. 如果包含墨迹,清空当前墨迹并添加新墨迹
|
||||
/// 4. 恢复元素信息
|
||||
/// 5. 如果文件流中没有墨迹,尝试从内存流中加载
|
||||
/// </remarks>
|
||||
public void OpenSingleStrokeFile(string filePath)
|
||||
{
|
||||
var fileStreamHasNoStroke = false;
|
||||
|
||||
@@ -11,6 +11,17 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 保存截图
|
||||
/// </summary>
|
||||
/// <param name="isHideNotification">是否隐藏通知</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 根据设置确定保存路径
|
||||
/// 2. 调用CaptureAndSaveScreenshot方法捕获并保存截图
|
||||
/// 3. 如果设置了自动保存墨迹,调用SaveInkCanvasStrokes方法保存墨迹
|
||||
/// </remarks>
|
||||
private void SaveScreenShot(bool isHideNotification, string fileName = null)
|
||||
{
|
||||
var savePath = Settings.Automation.IsSaveScreenshotsInDateFolders
|
||||
@@ -23,6 +34,15 @@ namespace Ink_Canvas
|
||||
SaveInkCanvasStrokes(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存截图到桌面
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 生成桌面路径和文件名
|
||||
/// 2. 调用CaptureAndSaveScreenshot方法捕获并保存截图到桌面
|
||||
/// 3. 如果设置了自动保存墨迹,调用SaveInkCanvasStrokes方法保存墨迹
|
||||
/// </remarks>
|
||||
internal void SaveScreenShotToDesktop()
|
||||
{
|
||||
var desktopPath = Path.Combine(
|
||||
@@ -35,7 +55,21 @@ namespace Ink_Canvas
|
||||
SaveInkCanvasStrokes(false);
|
||||
}
|
||||
|
||||
// 提取公共的截图和保存逻辑
|
||||
/// <summary>
|
||||
/// 提取公共的截图和保存逻辑
|
||||
/// </summary>
|
||||
/// <param name="savePath">保存路径</param>
|
||||
/// <param name="isHideNotification">是否隐藏通知</param>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 获取虚拟屏幕边界
|
||||
/// 2. 创建位图并设置高质量渲染
|
||||
/// 3. 从屏幕复制内容到位图
|
||||
/// 4. 确保保存目录存在
|
||||
/// 5. 保存为PNG格式
|
||||
/// 6. 如果不隐藏通知,显示保存成功通知
|
||||
/// 7. 异步上传截图到Dlass
|
||||
/// </remarks>
|
||||
private void CaptureAndSaveScreenshot(string savePath, bool isHideNotification)
|
||||
{
|
||||
var rc = SystemInformation.VirtualScreen;
|
||||
@@ -84,7 +118,17 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
|
||||
// 获取日期文件夹路径
|
||||
/// <summary>
|
||||
/// 获取日期文件夹路径
|
||||
/// </summary>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <returns>日期文件夹路径</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 如果文件名为空,使用当前时间作为文件名
|
||||
/// 2. 获取基础路径和日期文件夹名
|
||||
/// 3. 组合路径并返回
|
||||
/// </remarks>
|
||||
private string GetDateFolderPath(string fileName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
@@ -102,7 +146,17 @@ namespace Ink_Canvas
|
||||
$"{fileName}.png");
|
||||
}
|
||||
|
||||
// 获取默认文件夹路径
|
||||
/// <summary>
|
||||
/// 获取默认文件夹路径
|
||||
/// </summary>
|
||||
/// <returns>默认文件夹路径</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 获取基础路径
|
||||
/// 2. 组合截图文件夹路径
|
||||
/// 3. 确保截图文件夹存在
|
||||
/// 4. 生成文件名并组合完整路径返回
|
||||
/// </remarks>
|
||||
private string GetDefaultFolderPath()
|
||||
{
|
||||
var basePath = Settings.Automation.AutoSavedStrokesLocation;
|
||||
|
||||
@@ -17,8 +17,20 @@ namespace Ink_Canvas
|
||||
{
|
||||
#region Floating Control
|
||||
|
||||
/// <summary>
|
||||
/// 存储最后一次鼠标按下的边界对象
|
||||
/// </summary>
|
||||
private object lastBorderMouseDownObject;
|
||||
|
||||
/// <summary>
|
||||
/// 处理边界鼠标按下事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 如果发送者是 RandomDrawPanel 或 SingleDrawPanel,且它们被隐藏,则不处理事件
|
||||
/// 否则存储当前鼠标按下的对象
|
||||
/// </remarks>
|
||||
private void Border_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 如果发送者是 RandomDrawPanel 或 SingleDrawPanel,且它们被隐藏,则不处理事件
|
||||
@@ -34,7 +46,15 @@ namespace Ink_Canvas
|
||||
lastBorderMouseDownObject = sender;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨迹选择克隆鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 执行墨迹克隆操作并记录日志
|
||||
/// </remarks>
|
||||
private void BorderStrokeSelectionClone_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -55,6 +75,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨迹选择克隆到新画板鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 克隆选中的墨迹到新画板并清除当前选择
|
||||
/// </remarks>
|
||||
private void BorderStrokeSelectionCloneToNewBoard_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -64,24 +93,60 @@ namespace Ink_Canvas
|
||||
CloneStrokesToNewBoard(strokes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨迹选择删除鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 调用 SymbolIconDelete_MouseUp 方法执行删除操作
|
||||
/// </remarks>
|
||||
private void BorderStrokeSelectionDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
SymbolIconDelete_MouseUp(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理笔宽减小鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 调用 ChangeStrokeThickness 方法减小笔宽
|
||||
/// </remarks>
|
||||
private void GridPenWidthDecrease_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
ChangeStrokeThickness(0.8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理笔宽增大鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 调用 ChangeStrokeThickness 方法增大笔宽
|
||||
/// </remarks>
|
||||
private void GridPenWidthIncrease_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
ChangeStrokeThickness(1.25);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更改选中墨迹的粗细
|
||||
/// </summary>
|
||||
/// <param name="multipler">缩放倍数</param>
|
||||
/// <remarks>
|
||||
/// 对选中的每个墨迹应用缩放倍数
|
||||
/// 确保新的粗细在允许的范围内
|
||||
/// 如果有 DrawingAttributesHistory,则提交历史记录
|
||||
/// </remarks>
|
||||
private void ChangeStrokeThickness(double multipler)
|
||||
{
|
||||
foreach (var stroke in inkCanvas.GetSelectedStrokes())
|
||||
@@ -107,6 +172,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理笔宽恢复默认鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 将选中墨迹的粗细恢复为默认值
|
||||
/// </remarks>
|
||||
private void GridPenWidthRestore_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -118,6 +192,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理水平翻转鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 对选中的墨迹应用水平翻转变换
|
||||
/// 如果有 DrawingAttributesHistory,则提交历史记录
|
||||
/// </remarks>
|
||||
private void ImageFlipHorizontal_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -155,6 +239,16 @@ namespace Ink_Canvas
|
||||
//updateBorderStrokeSelectionControlLocation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理垂直翻转鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 对选中的墨迹应用垂直翻转变换
|
||||
/// 如果有 DrawingAttributesHistory,则提交历史记录
|
||||
/// </remarks>
|
||||
private void ImageFlipVertical_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -186,6 +280,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// ... existing code ...
|
||||
/// <summary>
|
||||
/// 处理顺时针旋转45度鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 对选中的墨迹应用45度旋转变换
|
||||
/// 如果有 DrawingAttributesHistory,则提交历史记录
|
||||
/// </remarks>
|
||||
private void ImageRotate45_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -216,6 +320,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理顺时针旋转90度鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 只有当鼠标按下和释放的是同一个对象时才处理
|
||||
/// 对选中的墨迹应用90度旋转变换
|
||||
/// 如果有 DrawingAttributesHistory,则提交历史记录
|
||||
/// </remarks>
|
||||
private void ImageRotate90_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
@@ -254,17 +368,51 @@ namespace Ink_Canvas
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹选择覆盖层鼠标按下状态
|
||||
/// </summary>
|
||||
private bool isGridInkCanvasSelectionCoverMouseDown;
|
||||
/// <summary>
|
||||
/// 墨迹拖动状态
|
||||
/// </summary>
|
||||
private bool isStrokeDragging = false;
|
||||
/// <summary>
|
||||
/// 墨迹拖动起始点
|
||||
/// </summary>
|
||||
private Point strokeDragStartPoint;
|
||||
/// <summary>
|
||||
/// 墨迹选择克隆集合
|
||||
/// </summary>
|
||||
private StrokeCollection StrokesSelectionClone = new StrokeCollection();
|
||||
|
||||
// 选择框和选择点相关变量
|
||||
/// <summary>
|
||||
/// 调整大小状态
|
||||
/// </summary>
|
||||
private bool isResizing = false;
|
||||
/// <summary>
|
||||
/// 当前调整把手
|
||||
/// </summary>
|
||||
private string currentResizeHandle = "";
|
||||
/// <summary>
|
||||
/// 调整起始点
|
||||
/// </summary>
|
||||
private Point resizeStartPoint;
|
||||
/// <summary>
|
||||
/// 原始选择边界
|
||||
/// </summary>
|
||||
private Rect originalSelectionBounds;
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨迹选择覆盖层鼠标按下事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 如果有选中的墨迹,检查点击位置是否在选择框边界内
|
||||
/// 如果在边界内,开始拖动墨迹
|
||||
/// 如果在边界外,取消选择
|
||||
/// </remarks>
|
||||
private void GridInkCanvasSelectionCover_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
isGridInkCanvasSelectionCoverMouseDown = true;
|
||||
@@ -297,6 +445,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨迹选择覆盖层鼠标移动事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标事件参数</param>
|
||||
/// <remarks>
|
||||
/// 如果正在拖动墨迹,执行拖动操作
|
||||
/// 如果鼠标在选中区域移动,更新墨迹选中栏位置
|
||||
/// </remarks>
|
||||
private void GridInkCanvasSelectionCover_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isGridInkCanvasSelectionCoverMouseDown) return;
|
||||
@@ -331,6 +488,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨迹选择覆盖层鼠标释放事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 结束墨迹拖动
|
||||
/// 只有在没有选中墨迹时才隐藏选中栏
|
||||
/// </remarks>
|
||||
private void GridInkCanvasSelectionCover_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!isGridInkCanvasSelectionCoverMouseDown) return;
|
||||
@@ -352,6 +518,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理选择按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 如果当前是选择模式,检查是否全选
|
||||
/// 如果全选,则切换到墨迹模式再切换回选择模式
|
||||
/// 如果不是全选,则选择所有有效墨迹
|
||||
/// 如果当前不是选择模式,则切换到选择模式
|
||||
/// </remarks>
|
||||
private void BtnSelect_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
forceEraser = true;
|
||||
@@ -381,10 +558,30 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹选择控件宽度
|
||||
/// </summary>
|
||||
private double BorderStrokeSelectionControlWidth = 490.0;
|
||||
/// <summary>
|
||||
/// 墨迹选择控件高度
|
||||
/// </summary>
|
||||
private double BorderStrokeSelectionControlHeight = 80.0;
|
||||
/// <summary>
|
||||
/// 程序更改墨迹选择状态
|
||||
/// </summary>
|
||||
private bool isProgramChangeStrokeSelection;
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨迹画布选择更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 优先检查墨迹选择状态
|
||||
/// 如果有墨迹被选中,显示墨迹选择栏和选择框
|
||||
/// 如果有图片元素被选中,不显示选择框
|
||||
/// 如果没有选中任何内容,隐藏选择框
|
||||
/// </remarks>
|
||||
private void inkCanvas_SelectionChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (isProgramChangeStrokeSelection) return;
|
||||
@@ -438,6 +635,14 @@ namespace Ink_Canvas
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 更新墨迹选中栏位置
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 计算墨迹选中栏的位置,确保在墨迹下方显示
|
||||
/// 如果选中栏会超出屏幕底部,则显示在墨迹上方
|
||||
/// 如果上方也没有空间,则显示在顶部
|
||||
/// </remarks>
|
||||
private void updateBorderStrokeSelectionControlLocation()
|
||||
{
|
||||
var borderLeft = (inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Right -
|
||||
@@ -461,11 +666,28 @@ namespace Ink_Canvas
|
||||
BorderStrokeSelectionControl.Margin = new Thickness(borderLeft, borderTop, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨迹选择覆盖层操作开始事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">操作开始事件参数</param>
|
||||
/// <remarks>
|
||||
/// 设置操作模式为所有模式
|
||||
/// </remarks>
|
||||
private void GridInkCanvasSelectionCover_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
|
||||
{
|
||||
e.Mode = ManipulationModes.All;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨迹选择覆盖层操作完成事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">操作完成事件参数</param>
|
||||
/// <remarks>
|
||||
/// 如果有 StrokeManipulationHistory,则提交历史记录
|
||||
/// 如果有 DrawingAttributesHistory,则提交历史记录
|
||||
/// </remarks>
|
||||
private void GridInkCanvasSelectionCover_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
|
||||
{
|
||||
if (StrokeManipulationHistory?.Count > 0)
|
||||
@@ -490,6 +712,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理选择覆盖层的操作增量事件,将来自触摸或操作的平移、缩放和旋转应用到当前选中的墨迹上并更新选择控件位置。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的元素(通常为选择覆盖层)。</param>
|
||||
/// <param name="e">包含平移、缩放和旋转增量的 ManipulationDeltaEventArgs。</param>
|
||||
/// <remarks>
|
||||
/// - 当只有单指触摸且已有选中墨迹时,不在此处处理拖动(由 TouchMove 处理)。
|
||||
/// - 三指及以上触摸时禁用缩放。
|
||||
/// - 若 StrokesSelectionClone 非空,则对其内的墨迹应用变换;否则在允许两指旋转时也会应用旋转变换。
|
||||
/// - 处理完成后会刷新并更新边框/选择控件的位置。
|
||||
/// </remarks>
|
||||
private void GridInkCanvasSelectionCover_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -543,6 +776,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理选区覆盖层的触摸按下事件:记录触摸设备 ID,并在第一个触点时保存选择中心与用于拖拽的初始触点位置。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件源(触摸事件的发送者)。</param>
|
||||
/// <param name="e">触摸事件参数,包含触点位置与设备 ID。</param>
|
||||
private void GridInkCanvasSelectionCover_TouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
dec.Add(e.TouchDevice.Id);
|
||||
@@ -558,6 +796,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理选区覆盖层的触摸结束事件:更新触摸跟踪状态并根据触摸位置和当前选区状态显示或隐藏选区覆盖层与克隆集合。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的对象(通常为选区覆盖层)。</param>
|
||||
/// <param name="e">触摸事件参数,包含触点信息。</param>
|
||||
private void GridInkCanvasSelectionCover_TouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
dec.Remove(e.TouchDevice.Id);
|
||||
@@ -597,6 +840,10 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理触摸移动事件,按单指拖动并平移当前选中的墨迹,同时更新选择控件的位置。
|
||||
/// </summary>
|
||||
/// <remarks>仅在存在选中墨迹且当前仅有一个触摸点时生效;若起始触摸点未记录(lastDragPointInCanvas 为 (0,0))则不移动。只有当触摸位移在任一方向超过 1 像素时,才对每条选中墨迹应用平移变换,并更新选择控件位置与最后触摸点。</remarks>
|
||||
private void GridInkCanvasSelectionCover_TouchMove(object sender, TouchEventArgs e)
|
||||
{
|
||||
// 处理触摸移动事件 - 用于拖动选中的墨迹
|
||||
@@ -636,6 +883,12 @@ namespace Ink_Canvas
|
||||
private Point lastTouchPointOnGridInkCanvasCover = new Point(0, 0);
|
||||
private Point lastDragPointInCanvas = new Point(0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 切换到选择(套索)工具模式并同步光标显示。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 同时取消强制橡皮擦和点式橡皮擦状态,并将绘制形状模式重置为默认(0)。
|
||||
/// </remarks>
|
||||
private void LassoSelect_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
forceEraser = false;
|
||||
@@ -646,6 +899,16 @@ namespace Ink_Canvas
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理套索选择按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 设置工具模式为选择模式
|
||||
/// 启用墨迹画布的操作支持
|
||||
/// 设置光标为选择模式光标
|
||||
/// </remarks>
|
||||
private void BtnLassoSelect_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
forceEraser = false;
|
||||
@@ -659,6 +922,17 @@ namespace Ink_Canvas
|
||||
|
||||
#region UIElement Selection and Resize
|
||||
|
||||
/// <summary>
|
||||
/// 获取UI元素的边界
|
||||
/// </summary>
|
||||
/// <param name="element">UI元素</param>
|
||||
/// <returns>UI元素的边界矩形</returns>
|
||||
/// <remarks>
|
||||
/// 如果元素是FrameworkElement,获取其位置和大小
|
||||
/// 如果元素有RenderTransform,尝试使用变换后的边界
|
||||
/// 如果变换失败,回退到简单计算
|
||||
/// 如果元素不是FrameworkElement,返回空矩形
|
||||
/// </remarks>
|
||||
private Rect GetUIElementBounds(UIElement element)
|
||||
{
|
||||
if (element is FrameworkElement fe)
|
||||
@@ -701,6 +975,12 @@ namespace Ink_Canvas
|
||||
|
||||
#region Selection Display and Resize Handles
|
||||
|
||||
/// <summary>
|
||||
/// 在画布上显示当前选中墨迹的可视选择框与调整控件(如有选中墨迹则显示,否则隐藏)。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果没有选中任何墨迹,隐藏选择显示;否则计算选区边界,将选择框在所有方向各扩展 8 像素,设置选择框的位置与尺寸,并更新调整句柄的位置后显示句柄画布。
|
||||
/// </remarks>
|
||||
private void UpdateSelectionDisplay()
|
||||
{
|
||||
if (inkCanvas.GetSelectedStrokes().Count == 0)
|
||||
@@ -725,12 +1005,22 @@ namespace Ink_Canvas
|
||||
SelectionHandlesCanvas.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏选择显示
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 隐藏选择矩形和选择把手画布
|
||||
/// </remarks>
|
||||
private void HideSelectionDisplay()
|
||||
{
|
||||
SelectionRectangle.Visibility = Visibility.Collapsed;
|
||||
SelectionHandlesCanvas.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据给定的选择边界定位并设置八个缩放/移动把手的位置,四个把手在各边外扩展 8 像素,四个角把手完全位于外部。
|
||||
/// </summary>
|
||||
/// <param name="bounds">当前选区在画布坐标系中的边界矩形(未包含用于显示的 8 像素外扩展)。</param>
|
||||
private void UpdateSelectionHandles(Rect bounds)
|
||||
{
|
||||
// 四个边选择点,向外扩展8像素
|
||||
@@ -746,6 +1036,14 @@ namespace Ink_Canvas
|
||||
BottomRightHandle.Margin = new Thickness(bounds.Right + 4, bounds.Bottom + 4, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在用户按下选择框的缩放把手时开始缩放操作。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 记录缩放开始位置和当前选区的初始边界,设置当前活动把手并捕获鼠标以便后续移动/释放事件处理。
|
||||
/// </remarks>
|
||||
/// <param name="sender">触发事件的缩放把手,预期为一个 Rectangle。</param>
|
||||
/// <param name="e">包含鼠标按下事件的位置信息和处理标志的 <see cref="MouseButtonEventArgs"/> 实例。</param>
|
||||
private void SelectionHandle_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is Rectangle handle)
|
||||
@@ -759,6 +1057,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理选择把手鼠标移动事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标事件参数</param>
|
||||
/// <remarks>
|
||||
/// 如果正在调整大小,计算新的边界
|
||||
/// 应用新的边界到选中的墨迹
|
||||
/// 更新选择框显示
|
||||
/// </remarks>
|
||||
private void SelectionHandle_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isResizing || !(sender is Rectangle handle)) return;
|
||||
@@ -775,6 +1083,11 @@ namespace Ink_Canvas
|
||||
UpdateSelectionDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在选择框的大小调整句柄上释放鼠标时结束调整操作、释放句柄的鼠标捕获并将事件标记为已处理。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的对象,应为表示调整句柄的 <see cref="System.Windows.Shapes.Rectangle"/>。</param>
|
||||
/// <param name="e">鼠标事件参数;在处理后该事件会被标记为已处理。</param>
|
||||
private void SelectionHandle_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is Rectangle handle)
|
||||
@@ -786,6 +1099,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在触摸按下选择调整句柄时开始调整操作并记录初始状态(活动句柄、起始点和当前选区边界)。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的调整句柄(应为 Rectangle)。</param>
|
||||
/// <param name="e">触摸事件数据;方法会标记事件为已处理并使用其触点相对于 inkCanvas 的位置。</param>
|
||||
private void SelectionHandle_TouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (sender is Rectangle handle)
|
||||
@@ -799,6 +1117,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在触摸移动时根据拖动更新选区的边界并将其应用到当前所选的墨迹。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的调整句柄(应为一个 Rectangle)。</param>
|
||||
/// <param name="e">包含触摸位置和状态的事件参数。</param>
|
||||
/// <remarks>计算新的选择边界、将其应用到所选墨迹并刷新选择显示;将事件标记为已处理。</remarks>
|
||||
private void SelectionHandle_TouchMove(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (!isResizing || !(sender is Rectangle handle)) return;
|
||||
@@ -818,6 +1142,11 @@ namespace Ink_Canvas
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束触摸对选择框调整大小的交互,重置调整状态并标记事件已处理。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的调整句柄(Rectangle)。</param>
|
||||
/// <param name="e">触摸事件数据;方法会将其标记为已处理。</param>
|
||||
private void SelectionHandle_TouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (sender is Rectangle handle)
|
||||
@@ -828,6 +1157,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的拖动增量和所操作的调整柄,计算并返回调整后的边界矩形。
|
||||
/// </summary>
|
||||
/// <param name="originalBounds">调整前的原始边界矩形。</param>
|
||||
/// <param name="delta">从初始位置到当前拖动位置的偏移量,用于更新对应边或角的位置和尺寸。</param>
|
||||
/// <param name="handleName">被拖动的调整柄的名称,支持的值:`TopLeftHandle`、`TopRightHandle`、`BottomLeftHandle`、`BottomRightHandle`、`TopHandle`、`BottomHandle`、`LeftHandle`、`RightHandle`。</param>
|
||||
/// <returns>应用偏移并强制最小宽高限制(10×10)后的新的边界矩形。</returns>
|
||||
private Rect CalculateNewBounds(Rect originalBounds, Point delta, string handleName)
|
||||
{
|
||||
var newBounds = originalBounds;
|
||||
@@ -884,6 +1220,15 @@ namespace Ink_Canvas
|
||||
return newBounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用新的边界到选中的墨迹
|
||||
/// </summary>
|
||||
/// <param name="newBounds">新的边界矩形</param>
|
||||
/// <remarks>
|
||||
/// 计算缩放比例和平移量
|
||||
/// 创建变换矩阵
|
||||
/// 应用变换到选中的墨迹
|
||||
/// </remarks>
|
||||
private void ApplyBoundsToStrokes(Rect newBounds)
|
||||
{
|
||||
var selectedStrokes = inkCanvas.GetSelectedStrokes();
|
||||
@@ -914,4 +1259,3 @@ namespace Ink_Canvas
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,27 @@ namespace Ink_Canvas
|
||||
{
|
||||
#region Behavior
|
||||
|
||||
/// <summary>
|
||||
/// 内部标记:是否正在内部更改更新通道
|
||||
/// </summary>
|
||||
private bool _isChangingUpdateChannelInternally;
|
||||
/// <summary>
|
||||
/// 内部标记:是否正在内部更改遥测设置
|
||||
/// </summary>
|
||||
private bool _isChangingTelemetryInternally;
|
||||
/// <summary>
|
||||
/// 内部标记:是否正在内部更改遥测隐私设置
|
||||
/// </summary>
|
||||
private bool _isChangingTelemetryPrivacyInternally;
|
||||
|
||||
/// <summary>
|
||||
/// 检查隐私文件是否存在
|
||||
/// </summary>
|
||||
/// <returns>如果隐私文件存在则返回true,否则返回false</returns>
|
||||
/// <remarks>
|
||||
/// 尝试从程序集资源中查找privacy.txt文件
|
||||
/// 如果找到则返回true,否则返回false
|
||||
/// </remarks>
|
||||
private static bool PrivacyFileExists()
|
||||
{
|
||||
try
|
||||
@@ -55,6 +72,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找隐私文件
|
||||
/// </summary>
|
||||
/// <returns>隐私文件路径,如果是嵌入资源则返回"embedded",如果未找到则返回null</returns>
|
||||
/// <remarks>
|
||||
/// 先尝试从文件系统读取,搜索路径包括:
|
||||
/// 1. App.RootPath
|
||||
/// 2. 程序集所在目录
|
||||
/// 3. 程序集所在目录的上一级目录
|
||||
/// 如果文件系统中未找到,则回退到嵌入资源
|
||||
/// </remarks>
|
||||
private static string FindPrivacyFile()
|
||||
{
|
||||
// 先尝试从文件系统读取
|
||||
@@ -305,6 +333,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理遥测上传级别选择更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 根据选择的遥测级别执行相应操作:
|
||||
/// 1. 如果选择关闭遥测且当前不是正式通道,提示用户并切换到正式通道
|
||||
/// 2. 如果选择开启遥测但未同意隐私说明,提示用户需要先同意隐私说明
|
||||
/// 3. 保存设置并显示通知
|
||||
/// </remarks>
|
||||
private void ComboBoxTelemetryUploadLevel_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -421,6 +460,16 @@ namespace Ink_Canvas
|
||||
ShowNotification("匿名使用数据上传设置已保存");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理遥测隐私同意复选框状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当用户勾选或取消勾选隐私同意复选框时执行:
|
||||
/// 1. 勾选时:显示隐私协议窗口,用户同意后保存设置
|
||||
/// 2. 取消勾选时:提示用户会关闭遥测并切回正式通道,用户确认后执行相应操作
|
||||
/// </remarks>
|
||||
private void CheckBoxTelemetryPrivacyAccepted_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -551,6 +600,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理自动更新开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当自动更新开关状态更改时:
|
||||
/// 1. 保存自动更新设置
|
||||
/// 2. 自动更新关闭时隐藏静默更新选项
|
||||
/// 3. 如果关闭了自动更新,同时也关闭静默更新
|
||||
/// 4. 根据静默更新设置显示或隐藏静默更新时间区域
|
||||
/// </remarks>
|
||||
private void ToggleSwitchIsAutoUpdate_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -575,6 +636,16 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理静默自动更新开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当静默自动更新开关状态更改时:
|
||||
/// 1. 保存静默自动更新设置
|
||||
/// 2. 根据静默更新设置显示或隐藏静默更新时间区域
|
||||
/// </remarks>
|
||||
private void ToggleSwitchIsAutoUpdateWithSilence_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -587,6 +658,14 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理静默自动更新开始时间选择更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当选择静默自动更新开始时间时,保存设置到文件
|
||||
/// </remarks>
|
||||
private void AutoUpdateWithSilenceStartTimeComboBox_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -595,6 +674,14 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理静默自动更新结束时间选择更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当选择静默自动更新结束时间时,保存设置到文件
|
||||
/// </remarks>
|
||||
private void AutoUpdateWithSilenceEndTimeComboBox_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -602,6 +689,16 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理开机启动开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当开机启动开关状态更改时:
|
||||
/// 1. 如果开启,删除旧的启动项并创建新的启动项
|
||||
/// 2. 如果关闭,删除所有启动项
|
||||
/// </remarks>
|
||||
private void ToggleSwitchRunAtStartup_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -617,6 +714,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理启动时折叠开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当启动时折叠开关状态更改时,保存设置到文件
|
||||
/// </remarks>
|
||||
private void ToggleSwitchFoldAtStartup_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -624,6 +729,18 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PowerPoint支持开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当PowerPoint支持开关状态更改时:
|
||||
/// 1. 保存PowerPoint支持设置
|
||||
/// 2. 如果关闭PowerPoint支持,同时也关闭WPS支持
|
||||
/// 3. 如果开启PowerPoint支持,初始化PPT管理器并开始监控
|
||||
/// 4. 如果关闭PowerPoint支持,停止监控
|
||||
/// </remarks>
|
||||
private void ToggleSwitchSupportPowerPoint_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -661,6 +778,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理使用ROT PPT链接开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当使用ROT PPT链接开关状态更改时:
|
||||
/// 1. 保存ROT PPT链接设置
|
||||
/// 2. 停止PPT监控
|
||||
/// 3. 如果开启ROT PPT链接且启用了PowerPoint增强,关闭PowerPoint增强
|
||||
/// 4. 初始化PPT管理器
|
||||
/// 5. 如果启用了PowerPoint支持,开始PPT监控
|
||||
/// 6. 记录切换PPT联动架构的日志
|
||||
/// </remarks>
|
||||
private void ToggleSwitchUseRotPptLink_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -701,6 +832,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理新幻灯片放映时显示画布开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当新幻灯片放映时显示画布开关状态更改时,保存设置到文件
|
||||
/// </remarks>
|
||||
private void ToggleSwitchShowCanvasAtNewSlideShow_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -713,6 +852,17 @@ namespace Ink_Canvas
|
||||
|
||||
#region Startup
|
||||
|
||||
/// <summary>
|
||||
/// 处理笔尖模式开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当笔尖模式开关状态更改时:
|
||||
/// 1. 同步更新两个笔尖模式开关的状态
|
||||
/// 2. 保存笔尖模式设置
|
||||
/// 3. 根据笔尖模式设置更新边界宽度
|
||||
/// </remarks>
|
||||
private void ToggleSwitchEnableNibMode_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -733,6 +883,16 @@ namespace Ink_Canvas
|
||||
|
||||
#region Appearance
|
||||
|
||||
/// <summary>
|
||||
/// 处理显示笔尖模式切换开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当显示笔尖模式切换开关状态更改时:
|
||||
/// 1. 保存显示笔尖模式切换设置
|
||||
/// 2. 根据设置显示或隐藏笔尖模式切换面板
|
||||
/// </remarks>
|
||||
private void ToggleSwitchEnableDisPlayNibModeToggle_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -756,6 +916,14 @@ namespace Ink_Canvas
|
||||
// SaveSettingsToFile();
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 处理快速面板开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当快速面板开关状态更改时,保存设置到文件
|
||||
/// </remarks>
|
||||
private void ToggleSwitchEnableQuickPanel_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -763,6 +931,14 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理启动屏幕开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当启动屏幕开关状态更改时,保存设置到文件
|
||||
/// </remarks>
|
||||
private void ToggleSwitchEnableSplashScreen_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -770,6 +946,14 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理启动屏幕样式选择更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当选择启动屏幕样式时,保存设置到文件
|
||||
/// </remarks>
|
||||
private void ComboBoxSplashScreenStyle_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -777,6 +961,18 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理浮动栏缩放值滑块值更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当浮动栏缩放值滑块值更改时:
|
||||
/// 1. 保存浮动栏缩放设置
|
||||
/// 2. 应用缩放值到浮动栏(限制在0.5-1.25范围内)
|
||||
/// 3. 等待UI更新后重新计算浮动栏位置,确保居中计算准确
|
||||
/// 4. 只在屏幕模式下重新计算浮动栏位置
|
||||
/// </remarks>
|
||||
private void ViewboxFloatingBarScaleTransformValueSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -816,6 +1012,16 @@ namespace Ink_Canvas
|
||||
}), DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理浮动栏透明度值滑块值更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当浮动栏透明度值滑块值更改时:
|
||||
/// 1. 保存浮动栏透明度设置
|
||||
/// 2. 应用透明度值到浮动栏
|
||||
/// </remarks>
|
||||
private void ViewboxFloatingBarOpacityValueSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -824,6 +1030,14 @@ namespace Ink_Canvas
|
||||
ViewboxFloatingBar.Opacity = Settings.Appearance.ViewboxFloatingBarOpacityValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT中浮动栏透明度值滑块值更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当PPT中浮动栏透明度值滑块值更改时,保存设置到文件
|
||||
/// </remarks>
|
||||
private void ViewboxFloatingBarOpacityInPPTValueSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -831,6 +1045,17 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理托盘图标开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当托盘图标开关状态更改时:
|
||||
/// 1. 保存托盘图标设置
|
||||
/// 2. 根据设置显示或隐藏托盘图标示例图像
|
||||
/// 3. 根据设置显示或隐藏系统托盘图标
|
||||
/// </remarks>
|
||||
private void ToggleSwitchEnableTrayIcon_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -841,6 +1066,17 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理展开按钮图像选择更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当选择展开按钮图像类型时:
|
||||
/// 1. 保存展开按钮图像类型设置
|
||||
/// 2. 根据选择的图像类型更新左右展开按钮的图标
|
||||
/// 3. 为不同的图像类型设置不同的大小和旋转角度
|
||||
/// </remarks>
|
||||
private void ComboBoxUnFoldBtnImg_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -876,6 +1112,16 @@ namespace Ink_Canvas
|
||||
|
||||
private static readonly Lazy<object> HitokotoHttpClient = new Lazy<object>(CreateHitokotoClient, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
/// <summary>
|
||||
/// 创建用于获取一言(Hitokoto)数据的HttpClient
|
||||
/// </summary>
|
||||
/// <returns>创建的HttpClient实例,如果创建失败则返回null</returns>
|
||||
/// <remarks>
|
||||
/// 创建HttpClient时:
|
||||
/// 1. 设置超时时间为5秒
|
||||
/// 2. 尝试设置User-Agent头
|
||||
/// 3. 捕获并记录创建过程中的异常
|
||||
/// </remarks>
|
||||
private static object CreateHitokotoClient()
|
||||
{
|
||||
try
|
||||
@@ -906,6 +1152,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前外观设置更新白板水印的名言文本。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当配置为内置来源时(0:OSUPlayer、1:名言警句、2:高考俗语)从对应数组中随机选择一条并设置为水印文本;
|
||||
/// 当配置为一言(3)时会异步请求 Hitokoto API 并在请求中显示占位提示,成功时将返回文本设为水印,失败时记录警告日志并设置可读的失败提示文本。此方法会修改 BlackBoardWaterMark.Text,并在发生异常时记录日志且设置合适的回退文本。
|
||||
/// </remarks>
|
||||
private async Task UpdateChickenSoupTextAsync()
|
||||
{
|
||||
try
|
||||
@@ -999,6 +1252,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理名言来源选择更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当选择名言来源时:
|
||||
/// 1. 保存名言来源设置
|
||||
/// 2. 异步更新白板水印的名言文本
|
||||
/// </remarks>
|
||||
private async void ComboBoxChickenSoupSource_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -1176,6 +1439,17 @@ namespace Ink_Canvas
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理浮动栏图标选择更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当选择浮动栏图标时:
|
||||
/// 1. 保存浮动栏图标设置
|
||||
/// 2. 更新浮动栏图标
|
||||
/// 3. 保存设置到文件
|
||||
/// </remarks>
|
||||
public void ComboBoxFloatingBarImg_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -1184,6 +1458,16 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据设置更新浮动栏图标
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 根据设置的浮动栏图标索引更新图标:
|
||||
/// 1. 为不同的图标索引设置不同的图标源
|
||||
/// 2. 为不同的图标设置不同的边距
|
||||
/// 3. 支持自定义图标
|
||||
/// 4. 自定义图标加载失败时使用默认图标
|
||||
/// </remarks>
|
||||
public void UpdateFloatingBarIcon()
|
||||
{
|
||||
int index = Settings.Appearance.FloatingBarImg;
|
||||
@@ -1279,6 +1563,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新组合框中的自定义图标选项
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 更新自定义图标选项时:
|
||||
/// 1. 保留前12个内置图标选项
|
||||
/// 2. 移除所有现有的自定义图标选项
|
||||
/// 3. 添加新的自定义图标选项
|
||||
/// 4. 为自定义图标选项设置字体
|
||||
/// </remarks>
|
||||
public void UpdateCustomIconsInComboBox()
|
||||
{
|
||||
// 保留前12个内置图标选项
|
||||
@@ -1297,6 +1591,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理添加自定义图标按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当点击添加自定义图标按钮时:
|
||||
/// 1. 显示添加自定义图标窗口
|
||||
/// 2. 如果添加成功,自动选中新添加的图标
|
||||
/// </remarks>
|
||||
private void ButtonAddCustomIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AddCustomIconWindow dialog = new AddCustomIconWindow(this);
|
||||
@@ -1310,6 +1614,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理管理自定义图标按钮点击事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当点击管理自定义图标按钮时,显示自定义图标管理窗口
|
||||
/// </remarks>
|
||||
private void ButtonManageCustomIcons_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CustomIconWindow dialog = new CustomIconWindow(this);
|
||||
@@ -1317,6 +1629,18 @@ namespace Ink_Canvas
|
||||
dialog.ShowDialog();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理白板模式下时间显示开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当白板模式下时间显示开关状态更改时:
|
||||
/// 1. 保存时间显示设置
|
||||
/// 2. 如果当前是白板模式,根据设置显示或隐藏时间和日期水印
|
||||
/// 3. 保存设置到文件
|
||||
/// 4. 重新加载设置以应用更改
|
||||
/// </remarks>
|
||||
private void ToggleSwitchEnableTimeDisplayInWhiteboardMode_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -1339,6 +1663,18 @@ namespace Ink_Canvas
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理白板模式下名言显示开关状态更改事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当白板模式下名言显示开关状态更改时:
|
||||
/// 1. 保存名言显示设置
|
||||
/// 2. 如果当前是白板模式,根据时间显示设置显示或隐藏名言水印
|
||||
/// 3. 保存设置到文件
|
||||
/// 4. 重新加载设置以应用更改
|
||||
/// </remarks>
|
||||
private void ToggleSwitchEnableChickenSoupInWhiteboardMode_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2508,6 +2844,12 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将画笔不透明度更新为滑块的当前值,并保存到设置中。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 使用滑块的当前值作为 alpha 通道更新 drawingAttributes.Color,同时将该值写入 Settings.Canvas.InkAlpha 并持久化配置文件。
|
||||
/// </remarks>
|
||||
private void InkAlphaSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2522,6 +2864,9 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据组合框的当前选择更新双曲线渐近线选项(Settings.Canvas.HyperbolaAsymptoteOption),并将更改保存到设置文件。
|
||||
/// </summary>
|
||||
private void ComboBoxHyperbolaAsymptoteOption_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -3350,6 +3695,13 @@ namespace Ink_Canvas
|
||||
|
||||
#region Reset
|
||||
|
||||
/// <summary>
|
||||
/// 将应用设置重置为推荐的默认配置。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该方法会重新创建全局 Settings 实例并应用推荐值,覆盖大部分子模块配置(如外观、画布、自动化、PPT、手势、高级选项等)。
|
||||
/// 在重置过程中会保留并恢复当前 Settings.Automation 中的 AutoDelSavedFiles 与 AutoDelSavedFilesDaysThreshold 两项值以避免意外删除策略变化。
|
||||
/// </remarks>
|
||||
public static void SetSettingsToRecommendation()
|
||||
{
|
||||
var AutoDelSavedFilesDays = Settings.Automation.AutoDelSavedFiles;
|
||||
@@ -3497,6 +3849,12 @@ namespace Ink_Canvas
|
||||
Settings.Startup.IsFoldAtStartup = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将应用设置重置为推荐的默认值,并保存与重新加载配置以应用更改。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果配置重置受安全密码保护,则会提示用户输入密码;在验证失败时中止重置。方法会暂时停止加载标志以避免触发事件、将“开机启动”切换置为关闭,并在完成后显示一条通知。任何内部异常将被吞噬以保证流程不中断。
|
||||
/// </remarks>
|
||||
public async void BtnResetToSuggestion_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -4586,6 +4944,12 @@ namespace Ink_Canvas
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 将当前内存中的 Settings 序列化为格式化的 JSON 并写入应用程序配置文件(位于 App.RootPath 下的 Configs 目录或根设置文件)。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在写入前会确保目标目录/文件具有写入权限(使用 ProcessProtectionManager)。任何写入失败或异常都会被吞掉,调用方不会收到异常抛出。
|
||||
/// </remarks>
|
||||
public static void SaveSettingsToFile()
|
||||
{
|
||||
var text = JsonConvert.SerializeObject(Settings, Formatting.Indented);
|
||||
@@ -5279,4 +5643,4 @@ namespace Ink_Canvas
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,11 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 从配置文件加载用户设置并将其应用到主窗口和相关控件的状态(包括启动、外观、画布、手势、PPT、自动化等各项配置)。
|
||||
/// </summary>
|
||||
/// <param name="isStartup">指示当前为应用启动阶段;为 true 时按启动流程应用启动相关设置(例如触发启动专用动作和启动时的行为)。</param>
|
||||
/// <param name="skipAutoUpdateCheck">指示是否跳过自动更新检查;为 true 时不会在加载设置后执行自动更新检测。</param>
|
||||
private void LoadSettings(bool isStartup = false, bool skipAutoUpdateCheck = false)
|
||||
{
|
||||
AppVersionTextBlock.Text = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
@@ -1241,6 +1246,12 @@ namespace Ink_Canvas
|
||||
LoadBrushAutoRestoreSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将画笔自动恢复相关的设置应用到界面控件并在启用时初始化自动恢复定时器。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 会将 Settings.Canvas 中的 BrushAutoRestore 配置同步到对应的切换开关、时间文本框、颜色下拉框、宽度和透明度滑块;当颜色缺失时会使用默认值 `#FFFF0000`,当宽度无效时使用默认值 `5`。若功能被启用,会初始化并启动定时器以执行自动恢复任务。方法执行过程中会记录加载结果或错误信息到日志。
|
||||
/// </remarks>
|
||||
private void LoadBrushAutoRestoreSettings()
|
||||
{
|
||||
try
|
||||
@@ -1385,7 +1396,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理配置文件中的过期设置
|
||||
/// </summary>
|
||||
/// <param name="userConfigJson">用户配置的JSON字符串</param>
|
||||
/// <remarks>
|
||||
/// 清理过期设置时:
|
||||
/// 1. 创建默认配置对象
|
||||
/// 2. 将默认配置和用户配置都序列化为JObject
|
||||
/// 3. 递归比较并删除用户配置中多余的键
|
||||
/// 4. 如果有清理操作,重新反序列化并保存
|
||||
/// 5. 记录清理结果到日志
|
||||
/// </remarks>
|
||||
private void CleanupObsoleteSettings(string userConfigJson)
|
||||
{
|
||||
try
|
||||
@@ -1418,9 +1440,23 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归删除用户配置中多余的属性
|
||||
/// </summary>
|
||||
/// <param name="userObj">用户配置的JObject</param>
|
||||
/// <param name="defaultObj">默认配置的JObject</param>
|
||||
/// <param name="hasChanges">是否有变更的引用标志</param>
|
||||
/// <remarks>
|
||||
/// 递归删除多余属性时:
|
||||
/// 1. 检查用户配置和默认配置是否为空
|
||||
/// 2. 获取需要删除的键列表
|
||||
/// 3. 遍历用户配置的所有属性
|
||||
/// 4. 如果默认配置中不存在该属性,标记为删除
|
||||
/// 5. 如果两个属性都是对象类型,递归比较
|
||||
/// 6. 处理数组中的对象(如自定义图标列表等)
|
||||
/// 7. 删除标记的键
|
||||
/// 8. 设置变更标志
|
||||
/// </remarks>
|
||||
private void RemoveObsoleteProperties(JObject userObj, JObject defaultObj, ref bool hasChanges)
|
||||
{
|
||||
if (userObj == null || defaultObj == null)
|
||||
@@ -1482,4 +1518,4 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,26 +14,94 @@ using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 主窗口类的部分类,包含压感模拟和墨水到形状识别的功能
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 本文件主要包含以下功能:
|
||||
/// 1. 压感模拟:根据输入设备类型和设置模拟不同的压感效果
|
||||
/// 2. 墨水到形状识别:将手绘墨迹转换为规则形状(直线、圆形、椭圆、三角形、矩形等)
|
||||
/// 3. 直线自动拉直:将近似直线的墨迹自动拉成直线
|
||||
/// 4. 端点吸附:将直线端点吸附到其他直线的端点
|
||||
/// 5. 矩形参考线系统:通过多条直线构成矩形
|
||||
/// 6. 高级贝塞尔曲线平滑:对墨迹进行平滑处理
|
||||
/// 7. 异步墨水处理:提高性能的异步墨水处理机制
|
||||
/// </remarks>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储新的笔画集合,用于形状识别
|
||||
/// </summary>
|
||||
private StrokeCollection newStrokes = new StrokeCollection();
|
||||
private List<Circle> circles = new List<Circle>();
|
||||
private const double LINE_STRAIGHTEN_THRESHOLD = 0.20;
|
||||
|
||||
/// <summary>
|
||||
/// 存储圆形形状的列表
|
||||
/// </summary>
|
||||
private List<Circle> circles = new List<Circle>();
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 矩形参考线的列表
|
||||
/// </summary>
|
||||
private List<RectangleGuideLine> rectangleGuideLines = new List<RectangleGuideLine>();
|
||||
|
||||
/// <summary>
|
||||
/// 矩形端点的阈值
|
||||
/// </summary>
|
||||
private const double RECTANGLE_ENDPOINT_THRESHOLD = 30.0;
|
||||
|
||||
/// <summary>
|
||||
/// 矩形角度的阈值
|
||||
/// </summary>
|
||||
private const double RECTANGLE_ANGLE_THRESHOLD = 15.0;
|
||||
|
||||
/// <summary>
|
||||
/// 矩形参考线类,用于辅助矩形绘制
|
||||
/// </summary>
|
||||
private class RectangleGuideLine
|
||||
{
|
||||
/// <summary>
|
||||
/// 原始笔画
|
||||
/// </summary>
|
||||
public Stroke OriginalStroke { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 起始点
|
||||
/// </summary>
|
||||
public Point StartPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束点
|
||||
/// </summary>
|
||||
public Point EndPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角度
|
||||
/// </summary>
|
||||
public double Angle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为水平线
|
||||
/// </summary>
|
||||
public bool IsHorizontal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为垂直线
|
||||
/// </summary>
|
||||
public bool IsVertical { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="stroke">原始笔画</param>
|
||||
/// <param name="start">起始点</param>
|
||||
/// <param name="end">结束点</param>
|
||||
public RectangleGuideLine(Stroke stroke, Point start, Point end)
|
||||
{
|
||||
OriginalStroke = stroke;
|
||||
@@ -53,6 +121,27 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理墨水画布的笔画收集事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">笔画收集事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当用户在墨水画布上完成一笔绘制后:
|
||||
/// 1. 检查是否启用墨迹渐隐功能,如果启用则添加到墨迹渐隐管理器
|
||||
/// 2. 根据设置处理压感:
|
||||
/// - 如果禁用压感,统一压感值为0.5
|
||||
/// - 如果启用触摸压感模式,根据速度模拟压感
|
||||
/// 3. 如果启用了形状识别:
|
||||
/// - 检查是否启用了直线自动拉直功能,如果是则尝试拉直线条
|
||||
/// - 处理形状识别,包括圆形、椭圆、三角形、矩形等
|
||||
/// 4. 检查是否是压感笔书写,如果是则返回
|
||||
/// 5. 根据墨水风格设置模拟压感
|
||||
/// 6. 应用高级贝塞尔曲线平滑(仅在未进行直线拉直时)
|
||||
/// <para>
|
||||
/// 注意:形状识别(圆形、椭圆、三角形、矩形等)仅在32位进程中可用。当 Environment.Is64BitProcess 为 true 时,形状识别功能会被禁用。
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private void inkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
|
||||
{
|
||||
// 检查是否启用墨迹渐隐功能
|
||||
@@ -775,6 +864,15 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 异步处理笔画平滑
|
||||
/// </summary>
|
||||
/// <param name="originalStroke">原始笔画</param>
|
||||
/// <returns>返回一个表示异步操作的Task</returns>
|
||||
/// <remarks>
|
||||
/// 异步处理笔画平滑的流程:
|
||||
/// 1. 调用墨迹平滑管理器的SmoothStrokeAsync方法
|
||||
/// 2. 在平滑完成后,在UI线程上执行笔画替换
|
||||
/// 3. 如果原始笔画仍然存在于画布中且平滑后的笔画不同,则替换原始笔画
|
||||
/// 4. 捕获并记录可能的异常
|
||||
/// </remarks>
|
||||
private async Task ProcessStrokeAsync(Stroke originalStroke)
|
||||
{
|
||||
try
|
||||
@@ -808,7 +906,21 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// New method: Checks if a stroke is potentially a straight line
|
||||
/// <summary>
|
||||
/// 检查一笔墨迹是否可能是直线
|
||||
/// </summary>
|
||||
/// <param name="stroke">要检查的笔画</param>
|
||||
/// <returns>如果可能是直线则返回true,否则返回false</returns>
|
||||
/// <remarks>
|
||||
/// 检查一笔墨迹是否可能是直线的流程:
|
||||
/// 1. 确保有足够的点来进行线条分析(至少5个点)
|
||||
/// 2. 计算线条长度,确保线条足够长(使用分辨率自适应阈值)
|
||||
/// 3. 检查墨迹复杂度,避免将复杂图形拉直
|
||||
/// 4. 检查是否为明显的曲线
|
||||
/// 5. 根据用户设置的灵敏度值计算阈值
|
||||
/// 6. 快速检查:计算几个关键点与直线的距离
|
||||
/// 7. 根据偏差阈值判断是否可能是直线
|
||||
/// </remarks>
|
||||
private bool IsPotentialStraightLine(Stroke stroke)
|
||||
{
|
||||
// 确保有足够的点来进行线条分析
|
||||
@@ -910,6 +1022,15 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 检查墨迹是否为复杂形状
|
||||
/// </summary>
|
||||
/// <param name="stroke">要检查的笔画</param>
|
||||
/// <returns>如果是复杂形状则返回true,否则返回false</returns>
|
||||
/// <remarks>
|
||||
/// 检查墨迹是否为复杂形状的流程:
|
||||
/// 1. 确保有足够的点来进行分析(至少10个点)
|
||||
/// 2. 计算直线距离和实际路径长度
|
||||
/// 3. 如果实际路径长度远大于直线距离(2.5倍以上),说明是复杂形状
|
||||
/// 4. 检查方向变化次数,如果超过动态阈值,说明是复杂形状
|
||||
/// </remarks>
|
||||
private bool IsComplexShape(Stroke stroke)
|
||||
{
|
||||
if (stroke.StylusPoints.Count < 10) return false;
|
||||
@@ -950,6 +1071,17 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 检查是否为明显的曲线(如圆弧、抛物线等)
|
||||
/// </summary>
|
||||
/// <param name="stroke">要检查的笔画</param>
|
||||
/// <returns>如果是明显的曲线则返回true,否则返回false</returns>
|
||||
/// <remarks>
|
||||
/// 检查墨迹是否为明显的曲线的流程:
|
||||
/// 1. 确保有足够的点来进行分析(至少10个点)
|
||||
/// 2. 计算线条长度
|
||||
/// 3. 检查曲率一致性,如果一致则认为是明显的曲线
|
||||
/// 4. 检查中点偏移(对圆弧特别有效):
|
||||
/// - 计算中点到直线的距离
|
||||
/// - 如果中点偏移超过线长的15%,且偏移方向一致,可能是圆弧
|
||||
/// </remarks>
|
||||
private bool IsObviousCurve(Stroke stroke)
|
||||
{
|
||||
if (stroke.StylusPoints.Count < 10) return false;
|
||||
@@ -987,6 +1119,17 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 计算方向变化次数
|
||||
/// </summary>
|
||||
/// <param name="stroke">要检查的笔画</param>
|
||||
/// <returns>返回方向变化的次数</returns>
|
||||
/// <remarks>
|
||||
/// 计算方向变化次数的流程:
|
||||
/// 1. 确保有足够的点来进行分析(至少3个点)
|
||||
/// 2. 遍历笔画中的每个点(除了第一个和最后一个)
|
||||
/// 3. 计算每个点前后线段的角度变化
|
||||
/// 4. 处理角度跨越问题(超过180度的情况)
|
||||
/// 5. 如果角度变化超过30度,且与上一次角度变化的差异超过15度,认为是方向变化
|
||||
/// 6. 返回方向变化的总次数
|
||||
/// </remarks>
|
||||
private int CountDirectionChanges(Stroke stroke)
|
||||
{
|
||||
if (stroke.StylusPoints.Count < 3) return 0;
|
||||
@@ -1027,6 +1170,17 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 检查曲率是否一致(用于识别圆弧等规则曲线)
|
||||
/// </summary>
|
||||
/// <param name="stroke">要检查的笔画</param>
|
||||
/// <returns>如果曲率一致则返回true,否则返回false</returns>
|
||||
/// <remarks>
|
||||
/// 检查曲率是否一致的流程:
|
||||
/// 1. 确保有足够的点来进行分析(至少15个点)
|
||||
/// 2. 计算每个点的曲率(使用前后各两个点)
|
||||
/// 3. 过滤掉无效的曲率值(NaN或无穷大),并取绝对值
|
||||
/// 4. 确保有足够的有效曲率值(至少5个)
|
||||
/// 5. 计算曲率的平均值和标准差
|
||||
/// 6. 如果平均曲率大于0.001且标准差与平均值的比例小于0.5,认为曲率一致
|
||||
/// </remarks>
|
||||
private bool HasConsistentCurvature(Stroke stroke)
|
||||
{
|
||||
if (stroke.StylusPoints.Count < 15) return false;
|
||||
@@ -1106,7 +1260,8 @@ namespace Ink_Canvas
|
||||
|
||||
// 使用海伦公式计算面积
|
||||
double s = (a + b + c) / 2;
|
||||
double area = Math.Sqrt(s * (s - a) * (s - b) * (s - c));
|
||||
double areaSquared = s * (s - a) * (s - b) * (s - c);
|
||||
double area = areaSquared > 0 ? Math.Sqrt(areaSquared) : 0;
|
||||
|
||||
// 曲率 = 4 * 面积 / (a * b * c)
|
||||
return 4 * area / (a * b * c);
|
||||
@@ -1128,6 +1283,25 @@ namespace Ink_Canvas
|
||||
lineEnd.X * lineStart.Y - lineEnd.Y * lineStart.X) / lineLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取直线的端点
|
||||
/// </summary>
|
||||
/// <param name="stroke">要分析的笔画</param>
|
||||
/// <param name="endpoint1">输出参数:直线的第一个端点</param>
|
||||
/// <param name="endpoint2">输出参数:直线的第二个端点</param>
|
||||
/// <returns>如果成功获取直线端点则返回true,否则返回false</returns>
|
||||
/// <remarks>
|
||||
/// 尝试获取直线端点的流程:
|
||||
/// 1. 确保笔画有足够的点(至少10个点)
|
||||
/// 2. 如果启用高精度直线拉直,则对点数进行采样
|
||||
/// 3. 使用总最小二乘法(TLS/PCA)进行直线拟合
|
||||
/// 4. 计算中心点和协方差矩阵
|
||||
/// 5. 计算特征值和特征向量,确定直线方向
|
||||
/// 6. 计算解释方差比例(拟合优度)
|
||||
/// 7. 计算所有点在直线方向上的投影,找到最小和最大投影值
|
||||
/// 8. 根据投影值计算端点坐标
|
||||
/// 9. 根据解释方差比例判断是否为直线
|
||||
/// </remarks>
|
||||
private bool TryGetStraightLineEndpoints(Stroke stroke, out Point endpoint1, out Point endpoint2)
|
||||
{
|
||||
endpoint1 = new Point();
|
||||
@@ -1188,7 +1362,8 @@ namespace Ink_Canvas
|
||||
// 计算特征值和特征向量
|
||||
double trace = covXX + covYY;
|
||||
double determinant = covXX * covYY - covXY * covXY;
|
||||
double discriminant = Math.Sqrt(trace * trace - 4 * determinant);
|
||||
double discriminantSquared = trace * trace - 4 * determinant;
|
||||
double discriminant = discriminantSquared > 0 ? Math.Sqrt(discriminantSquared) : 0;
|
||||
|
||||
double eigenvalue1 = (trace + discriminant) / 2;
|
||||
double eigenvalue2 = (trace - discriminant) / 2;
|
||||
@@ -1291,7 +1466,19 @@ namespace Ink_Canvas
|
||||
return explainedVarianceRatio > threshold;
|
||||
}
|
||||
|
||||
// New method: Determines if a stroke should be straightened into a line
|
||||
/// <summary>
|
||||
/// 确定笔画是否应该被拉成直线
|
||||
/// </summary>
|
||||
/// <param name="stroke">要分析的笔画</param>
|
||||
/// <returns>如果笔画应该被拉成直线则返回true,否则返回false</returns>
|
||||
/// <remarks>
|
||||
/// 确定笔画是否应该被拉成直线的流程:
|
||||
/// 1. 计算线条长度和分辨率自适应阈值
|
||||
/// 2. 如果线条太短,不进行拉直处理
|
||||
/// 3. 检查线条复杂度,如果是复杂形状,不进行拉直处理
|
||||
/// 4. 尝试获取直线端点,判断是否满足直线条件
|
||||
/// 5. 根据判断结果返回相应的布尔值
|
||||
/// </remarks>
|
||||
private bool ShouldStraightenLine(Stroke stroke)
|
||||
{
|
||||
// 分辨率自适应阈值
|
||||
@@ -1332,6 +1519,28 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 计算墨迹的直线度评分(0-1,1表示完美直线)
|
||||
/// </summary>
|
||||
/// <param name="stroke">要分析的笔画</param>
|
||||
/// <returns>返回直线度评分,范围为0到1,1表示完美直线</returns>
|
||||
/// <remarks>
|
||||
/// 计算墨迹直线度评分的流程:
|
||||
/// 1. 确保笔画有足够的点(至少3个点)
|
||||
/// 2. 计算线条长度
|
||||
/// 3. 计算偏差评分(基于点到直线的距离):
|
||||
/// - 计算所有点到直线的平均偏差和最大偏差
|
||||
/// - 根据偏差计算评分
|
||||
/// 4. 计算方向一致性评分:
|
||||
/// - 计算每个线段与目标方向的角度差
|
||||
/// - 将角度差转换为评分
|
||||
/// 5. 计算路径效率评分:
|
||||
/// - 计算实际路径长度与直线距离的比例
|
||||
/// 6. 计算端点连接度评分(默认满分)
|
||||
/// 7. 综合评分(加权平均):
|
||||
/// - 偏差评分:40%
|
||||
/// - 方向一致性评分:30%
|
||||
/// - 路径效率评分:20%
|
||||
/// - 端点连接度评分:10%
|
||||
/// 8. 返回最终评分,确保在0到1之间
|
||||
/// </remarks>
|
||||
private double CalculateStraightnessScore(Stroke stroke)
|
||||
{
|
||||
if (stroke.StylusPoints.Count < 3) return 0;
|
||||
@@ -1388,6 +1597,24 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 计算方向一致性评分
|
||||
/// </summary>
|
||||
/// <param name="stroke">要分析的笔画</param>
|
||||
/// <returns>返回方向一致性评分,范围为0到1,1表示方向完全一致</returns>
|
||||
/// <remarks>
|
||||
/// 计算方向一致性评分的流程:
|
||||
/// 1. 确保笔画有足够的点(至少5个点)
|
||||
/// 2. 计算目标方向(从起点到终点的方向)
|
||||
/// 3. 计算每个线段与目标方向的角度差:
|
||||
/// - 遍历笔画中的每个线段
|
||||
/// - 忽略太短的线段(长度小于2)
|
||||
/// - 计算线段的角度
|
||||
/// - 计算与目标方向的角度差
|
||||
/// - 处理角度跨越问题(超过180度的情况)
|
||||
/// 4. 计算平均角度差
|
||||
/// 5. 将角度差转换为评分(0-1):
|
||||
/// - 0度差 = 1分
|
||||
/// - 90度差 = 0分
|
||||
/// 6. 返回方向一致性评分
|
||||
/// </remarks>
|
||||
private double CalculateDirectionConsistency(Stroke stroke)
|
||||
{
|
||||
if (stroke.StylusPoints.Count < 5) return 1.0;
|
||||
@@ -1431,7 +1658,23 @@ namespace Ink_Canvas
|
||||
return directionScore;
|
||||
}
|
||||
|
||||
// New method: Creates a straight line stroke between two points
|
||||
/// <summary>
|
||||
/// 在两点之间创建直线笔画
|
||||
/// </summary>
|
||||
/// <param name="start">直线的起始点</param>
|
||||
/// <param name="end">直线的结束点</param>
|
||||
/// <returns>返回包含直线点集的StylusPointCollection</returns>
|
||||
/// <remarks>
|
||||
/// 在两点之间创建直线笔画的流程:
|
||||
/// 1. 根据是否启用压感触屏模式决定如何设置压感:
|
||||
/// - 如果未启用压感触屏模式、禁用压感、启用无压感矩形或使用钢笔类型1,则使用均匀粗细(压感值0.5)
|
||||
/// - 否则,创建带有压感变化的直线:
|
||||
/// - 计算中点
|
||||
/// - 从起点到中点:压感从0.4渐变到0.8
|
||||
/// - 从中点到终点:压感从0.8渐变到0.4
|
||||
/// 2. 使用GeneratePointsBetween方法生成点集
|
||||
/// 3. 返回生成的点集
|
||||
/// </remarks>
|
||||
private StylusPointCollection CreateStraightLine(Point start, Point end)
|
||||
{
|
||||
StylusPointCollection points = new StylusPointCollection();
|
||||
@@ -1468,8 +1711,21 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 高精度模式
|
||||
/// 根据距离对点数进行采样
|
||||
/// </summary>
|
||||
/// <param name="points">原始点列表</param>
|
||||
/// <param name="sampleInterval">采样间隔,默认为10.0</param>
|
||||
/// <returns>返回采样后的点列表</returns>
|
||||
/// <remarks>
|
||||
/// 根据距离对点数进行采样的流程:
|
||||
/// 1. 确保原始点列表不为空且至少有2个点
|
||||
/// 2. 总是包含起点
|
||||
/// 3. 遍历原始点列表,计算累积距离:
|
||||
/// - 当累积距离达到采样间隔时,添加当前点
|
||||
/// - 重置累积距离
|
||||
/// 4. 总是包含终点(如果还没有包含)
|
||||
/// 5. 返回采样后的点列表
|
||||
/// </remarks>
|
||||
private List<Point> SamplePointsByDistance(List<Point> points, double sampleInterval = 10.0)
|
||||
{
|
||||
if (points == null || points.Count < 2)
|
||||
@@ -1504,7 +1760,20 @@ namespace Ink_Canvas
|
||||
return sampledPoints;
|
||||
}
|
||||
|
||||
// New method: Gets distance from point to a line defined by two points
|
||||
/// <summary>
|
||||
/// 计算点到直线的距离
|
||||
/// </summary>
|
||||
/// <param name="lineStart">直线的起始点</param>
|
||||
/// <param name="lineEnd">直线的结束点</param>
|
||||
/// <param name="point">要计算距离的点</param>
|
||||
/// <returns>返回点到直线的距离</returns>
|
||||
/// <remarks>
|
||||
/// 计算点到直线距离的流程:
|
||||
/// 1. 计算直线的长度
|
||||
/// 2. 如果直线长度为0(即两个点重合),则返回点到该点的距离
|
||||
/// 3. 否则,使用叉积计算点到直线的垂直距离
|
||||
/// 4. 返回计算得到的距离
|
||||
/// </remarks>
|
||||
private double DistanceFromLineToPoint(Point lineStart, Point lineEnd, Point point)
|
||||
{
|
||||
// Calculate distance from point to line defined by lineStart and lineEnd
|
||||
@@ -1523,6 +1792,23 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
/// <param name="stroke">要检查的 stroke</param>
|
||||
/// <returns>如果是直线返回 true,否则返回 false</returns>
|
||||
/// <remarks>
|
||||
/// 判断一个 stroke 是否是直线的流程:
|
||||
/// 1. 检查 stroke 是否为空或没有点,如果是则返回 false
|
||||
/// 2. 检查点的数量:
|
||||
/// - 如果只有1个点,返回 false
|
||||
/// - 如果有2个点:
|
||||
/// - 计算两点之间的距离
|
||||
/// - 如果距离小于10,返回 false
|
||||
/// - 否则返回 true
|
||||
/// - 如果有3个点:
|
||||
/// - 计算第一个点和第三个点之间的距离
|
||||
/// - 如果距离小于10,返回 false
|
||||
/// - 计算第二个点到由第一个点和第三个点组成的直线的距离
|
||||
/// - 如果距离相对于线段长度很小(小于1%),认为是直线,返回 true
|
||||
/// - 否则返回 false
|
||||
/// - 如果点的数量大于3,返回 false
|
||||
/// </remarks>
|
||||
private bool IsStraightLine(Stroke stroke)
|
||||
{
|
||||
if (stroke == null || stroke.StylusPoints.Count == 0)
|
||||
@@ -1575,7 +1861,26 @@ namespace Ink_Canvas
|
||||
return false;
|
||||
}
|
||||
|
||||
// New method: Attempts to snap endpoints to existing stroke endpoints
|
||||
/// <summary>
|
||||
/// 尝试将直线端点吸附到现有笔画的端点
|
||||
/// </summary>
|
||||
/// <param name="start">直线的起始点</param>
|
||||
/// <param name="end">直线的结束点</param>
|
||||
/// <returns>返回吸附后的端点数组,如果没有发生吸附则返回null</returns>
|
||||
/// <remarks>
|
||||
/// 尝试将直线端点吸附到现有笔画端点的流程:
|
||||
/// 1. 检查是否启用了线段端点吸附功能,如果没有启用则返回null
|
||||
/// 2. 初始化吸附状态和吸附后的点
|
||||
/// 3. 获取设置中的吸附距离阈值
|
||||
/// 4. 遍历画布中的所有笔画:
|
||||
/// - 跳过没有点的笔画
|
||||
/// - 只对直线进行端点吸附,跳过虚线和点线
|
||||
/// - 获取笔画的起点和终点
|
||||
/// - 检查起点是否应该吸附到现有笔画的端点
|
||||
/// - 检查终点是否应该吸附到现有笔画的端点
|
||||
/// - 如果两个端点都已经吸附,结束遍历
|
||||
/// 5. 如果发生了吸附,返回吸附后的端点数组,否则返回null
|
||||
/// </remarks>
|
||||
private Point[] GetSnappedEndpoints(Point start, Point end)
|
||||
{
|
||||
if (!Settings.Canvas.LineEndpointSnapping)
|
||||
@@ -1645,6 +1950,16 @@ namespace Ink_Canvas
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置新的笔画备份
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 设置新的笔画备份的流程:
|
||||
/// 1. 克隆当前墨水画布的笔画集合
|
||||
/// 2. 获取当前白板索引
|
||||
/// 3. 如果当前模式为0,则将白板索引设置为0
|
||||
/// 4. 将克隆的笔画集合存储到strokeCollections中对应索引的位置
|
||||
/// </remarks>
|
||||
private void SetNewBackupOfStroke()
|
||||
{
|
||||
lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone();
|
||||
@@ -1654,12 +1969,36 @@ namespace Ink_Canvas
|
||||
strokeCollections[whiteboardIndex] = lastTouchDownStrokeCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算两点之间的距离
|
||||
/// </summary>
|
||||
/// <param name="point1">第一个点</param>
|
||||
/// <param name="point2">第二个点</param>
|
||||
/// <returns>返回两点之间的距离</returns>
|
||||
/// <remarks>
|
||||
/// 使用欧几里得距离公式计算两点之间的距离:
|
||||
/// distance = √[(x2 - x1)² + (y2 - y1)²]
|
||||
/// </remarks>
|
||||
public double GetDistance(Point point1, Point point2)
|
||||
{
|
||||
return Math.Sqrt((point1.X - point2.X) * (point1.X - point2.X) +
|
||||
(point1.Y - point2.Y) * (point1.Y - point2.Y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算点的速度
|
||||
/// </summary>
|
||||
/// <param name="point1">第一个点</param>
|
||||
/// <param name="point2">第二个点(当前点)</param>
|
||||
/// <param name="point3">第三个点</param>
|
||||
/// <returns>返回点的速度</returns>
|
||||
/// <remarks>
|
||||
/// 计算点速度的流程:
|
||||
/// 1. 计算第一个点到第二个点的距离
|
||||
/// 2. 计算第三个点到第二个点的距离
|
||||
/// 3. 将两个距离相加
|
||||
/// 4. 除以20,得到速度值
|
||||
/// </remarks>
|
||||
public double GetPointSpeed(Point point1, Point point2, Point point3)
|
||||
{
|
||||
return (Math.Sqrt((point1.X - point2.X) * (point1.X - point2.X) +
|
||||
@@ -1671,10 +2010,13 @@ namespace Ink_Canvas
|
||||
|
||||
public Point[] FixPointsDirection(Point p1, Point p2)
|
||||
{
|
||||
if (Math.Abs(p1.X - p2.X) / Math.Abs(p1.Y - p2.Y) > 8)
|
||||
double deltaY = Math.Abs(p1.Y - p2.Y);
|
||||
double deltaX = Math.Abs(p1.X - p2.X);
|
||||
|
||||
if (deltaY < 1e-10 || deltaX / deltaY > 8)
|
||||
{
|
||||
//水平
|
||||
var x = Math.Abs(p1.Y - p2.Y) / 2;
|
||||
var x = deltaY / 2;
|
||||
if (p1.Y > p2.Y)
|
||||
{
|
||||
p1.Y -= x;
|
||||
@@ -1686,10 +2028,10 @@ namespace Ink_Canvas
|
||||
p2.Y -= x;
|
||||
}
|
||||
}
|
||||
else if (Math.Abs(p1.Y - p2.Y) / Math.Abs(p1.X - p2.X) > 8)
|
||||
else if (deltaX < 1e-10 || deltaY / deltaX > 8)
|
||||
{
|
||||
//垂直
|
||||
var x = Math.Abs(p1.X - p2.X) / 2;
|
||||
var x = deltaX / 2;
|
||||
if (p1.X > p2.X)
|
||||
{
|
||||
p1.X -= x;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -12,29 +12,70 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 提交原因枚举,用于标识不同类型的操作
|
||||
/// </summary>
|
||||
private enum CommitReason
|
||||
{
|
||||
/// <summary>用户输入操作</summary>
|
||||
UserInput,
|
||||
/// <summary>代码输入操作</summary>
|
||||
CodeInput,
|
||||
/// <summary>形状绘制操作</summary>
|
||||
ShapeDrawing,
|
||||
/// <summary>形状识别操作</summary>
|
||||
ShapeRecognition,
|
||||
/// <summary>清除画布操作</summary>
|
||||
ClearingCanvas,
|
||||
/// <summary>笔画操作操作</summary>
|
||||
Manipulation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前提交类型
|
||||
/// </summary>
|
||||
private CommitReason _currentCommitType = CommitReason.UserInput;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为点橡皮擦模式
|
||||
/// </summary>
|
||||
private bool IsEraseByPoint => inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 替换的笔画集合
|
||||
/// </summary>
|
||||
private StrokeCollection ReplacedStroke;
|
||||
|
||||
/// <summary>
|
||||
/// 添加的笔画集合
|
||||
/// </summary>
|
||||
private StrokeCollection AddedStroke;
|
||||
|
||||
/// <summary>
|
||||
/// 长方体笔画集合
|
||||
/// </summary>
|
||||
private StrokeCollection CuboidStrokeCollection;
|
||||
|
||||
/// <summary>
|
||||
/// 笔画操作历史记录
|
||||
/// </summary>
|
||||
private Dictionary<Stroke, Tuple<StylusPointCollection, StylusPointCollection>> StrokeManipulationHistory;
|
||||
|
||||
/// <summary>
|
||||
/// 笔画初始状态历史记录
|
||||
/// </summary>
|
||||
private Dictionary<Stroke, StylusPointCollection> StrokeInitialHistory =
|
||||
new Dictionary<Stroke, StylusPointCollection>();
|
||||
|
||||
/// <summary>
|
||||
/// 绘制属性历史记录
|
||||
/// </summary>
|
||||
private Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>> DrawingAttributesHistory =
|
||||
new Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>>();
|
||||
|
||||
/// <summary>
|
||||
/// 绘制属性历史记录标志
|
||||
/// </summary>
|
||||
private Dictionary<Guid, List<Stroke>> DrawingAttributesHistoryFlag = new Dictionary<Guid, List<Stroke>> {
|
||||
{ DrawingAttributeIds.Color, new List<Stroke>() },
|
||||
{ DrawingAttributeIds.DrawingFlags, new List<Stroke>() },
|
||||
@@ -45,8 +86,25 @@ namespace Ink_Canvas
|
||||
{ DrawingAttributeIds.StylusWidth, new List<Stroke>() }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 时间机器实例,用于撤销/重做操作
|
||||
/// </summary>
|
||||
private TimeMachine timeMachine = new TimeMachine();
|
||||
|
||||
/// <summary>
|
||||
/// 将历史记录应用到画布
|
||||
/// </summary>
|
||||
/// <param name="item">时间机器历史记录项</param>
|
||||
/// <param name="applyCanvas">要应用的画布,默认为null(使用主画布)</param>
|
||||
/// <remarks>
|
||||
/// 根据历史记录类型执行不同的操作:
|
||||
/// 1. UserInput: 处理用户输入的笔画
|
||||
/// 2. ShapeRecognition: 处理形状识别的笔画
|
||||
/// 3. Manipulation: 处理笔画操作
|
||||
/// 4. DrawingAttributes: 处理绘制属性变化
|
||||
/// 5. Clear: 处理清除画布操作
|
||||
/// 6. ElementInsert: 处理元素插入操作
|
||||
/// </remarks>
|
||||
private void ApplyHistoryToCanvas(TimeMachineHistory item, InkCanvas applyCanvas = null)
|
||||
{
|
||||
_currentCommitType = CommitReason.CodeInput;
|
||||
@@ -226,6 +284,15 @@ namespace Ink_Canvas
|
||||
_currentCommitType = CommitReason.UserInput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将历史记录应用到新的笔画集合
|
||||
/// </summary>
|
||||
/// <param name="items">时间机器历史记录数组</param>
|
||||
/// <returns>返回应用历史记录后的笔画集合</returns>
|
||||
/// <remarks>
|
||||
/// 创建一个临时画布,应用历史记录,然后返回画布中的笔画集合
|
||||
/// 只处理笔画历史,不处理图片元素历史
|
||||
/// </remarks>
|
||||
private StrokeCollection ApplyHistoriesToNewStrokeCollection(TimeMachineHistory[] items)
|
||||
{
|
||||
InkCanvas fakeInkCanv = new InkCanvas
|
||||
@@ -251,7 +318,14 @@ namespace Ink_Canvas
|
||||
return fakeInkCanv.Strokes;
|
||||
}
|
||||
|
||||
// 新增:获取页面的所有图片元素
|
||||
/// <summary>
|
||||
/// 获取页面的所有图片元素
|
||||
/// </summary>
|
||||
/// <param name="items">时间机器历史记录数组</param>
|
||||
/// <returns>返回页面的图片元素列表</returns>
|
||||
/// <remarks>
|
||||
/// 遍历历史记录,收集所有插入的图片元素
|
||||
/// </remarks>
|
||||
private List<UIElement> GetPageImageElements(TimeMachineHistory[] items)
|
||||
{
|
||||
var imageElements = new List<UIElement>();
|
||||
@@ -272,6 +346,13 @@ namespace Ink_Canvas
|
||||
return imageElements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理撤销状态变化事件
|
||||
/// </summary>
|
||||
/// <param name="status">撤销状态</param>
|
||||
/// <remarks>
|
||||
/// 根据撤销状态更新撤销按钮的可见性和启用状态
|
||||
/// </remarks>
|
||||
private void TimeMachine_OnUndoStateChanged(bool status)
|
||||
{
|
||||
var result = status ? Visibility.Visible : Visibility.Collapsed;
|
||||
@@ -279,6 +360,13 @@ namespace Ink_Canvas
|
||||
BtnUndo.IsEnabled = status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理重做状态变化事件
|
||||
/// </summary>
|
||||
/// <param name="status">重做状态</param>
|
||||
/// <remarks>
|
||||
/// 根据重做状态更新重做按钮的可见性和启用状态
|
||||
/// </remarks>
|
||||
private void TimeMachine_OnRedoStateChanged(bool status)
|
||||
{
|
||||
var result = status ? Visibility.Visible : Visibility.Collapsed;
|
||||
@@ -286,6 +374,18 @@ namespace Ink_Canvas
|
||||
BtnRedo.IsEnabled = status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理笔画集合变化事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">笔画集合变化事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当笔画集合发生变化时:
|
||||
/// 1. 书写时自动隐藏二级菜单
|
||||
/// 2. 处理移除的笔画:移除事件处理器,从历史记录中移除
|
||||
/// 3. 处理添加的笔画:添加事件处理器,记录初始状态
|
||||
/// 4. 根据不同的提交类型处理历史记录
|
||||
/// </remarks>
|
||||
private void StrokesOnStrokesChanged(object sender, StrokeCollectionChangedEventArgs e)
|
||||
{
|
||||
if (!isHidingSubPanelsWhenInking)
|
||||
@@ -347,6 +447,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理笔画绘制属性变化事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">属性数据变化事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当笔画的绘制属性发生变化时,记录变化历史
|
||||
/// </remarks>
|
||||
private void Stroke_DrawingAttributesChanged(object sender, PropertyDataChangedEventArgs e)
|
||||
{
|
||||
var key = sender as Stroke;
|
||||
@@ -399,11 +507,31 @@ namespace Ink_Canvas
|
||||
new Tuple<DrawingAttributes, DrawingAttributes>(previousValue, currentValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理笔画触笔点替换事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">触笔点替换事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当笔画的触笔点被替换时,更新初始状态历史
|
||||
/// </remarks>
|
||||
private void Stroke_StylusPointsReplaced(object sender, StylusPointsReplacedEventArgs e)
|
||||
{
|
||||
StrokeInitialHistory[sender as Stroke] = e.NewStylusPoints.Clone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理笔画触笔点变化事件
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当笔画的触笔点发生变化时:
|
||||
/// 1. 获取选中的笔画数量
|
||||
/// 2. 初始化笔画操作历史记录
|
||||
/// 3. 记录笔画的初始状态和当前状态
|
||||
/// 4. 当所有选中的笔画都已处理时,提交操作历史
|
||||
/// </remarks>
|
||||
private void Stroke_StylusPointsChanged(object sender, EventArgs e)
|
||||
{
|
||||
var selectedStrokes = inkCanvas.GetSelectedStrokes();
|
||||
|
||||
@@ -16,11 +16,23 @@ using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 时间视图模型类,用于绑定显示时间和日期
|
||||
/// </summary>
|
||||
public class TimeViewModel : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前时间字符串
|
||||
/// </summary>
|
||||
private string _nowTime;
|
||||
/// <summary>
|
||||
/// 当前日期字符串
|
||||
/// </summary>
|
||||
private string _nowDate;
|
||||
|
||||
/// <summary>
|
||||
/// 当前时间属性
|
||||
/// </summary>
|
||||
public string nowTime
|
||||
{
|
||||
get => _nowTime;
|
||||
@@ -34,6 +46,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前日期属性
|
||||
/// </summary>
|
||||
public string nowDate
|
||||
{
|
||||
get => _nowDate;
|
||||
@@ -47,8 +62,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 属性变化事件
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 触发属性变化事件
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
@@ -57,27 +79,91 @@ namespace Ink_Canvas
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 进程终止定时器
|
||||
/// </summary>
|
||||
private Timer timerKillProcess = new Timer();
|
||||
/// <summary>
|
||||
/// 统一的主窗口定时器
|
||||
/// </summary>
|
||||
private Timer _unifiedMainWindowTimer;
|
||||
/// <summary>
|
||||
/// 可用的最新版本号
|
||||
/// </summary>
|
||||
private string AvailableLatestVersion;
|
||||
/// <summary>
|
||||
/// 静默更新检查定时器
|
||||
/// </summary>
|
||||
private Timer timerCheckAutoUpdateWithSilence = new Timer();
|
||||
/// <summary>
|
||||
/// 更新检查重试定时器
|
||||
/// </summary>
|
||||
private Timer timerCheckAutoUpdateRetry = new Timer();
|
||||
private bool isHidingSubPanelsWhenInking; // 避免书写时触发二次关闭二级菜单导致动画不连续
|
||||
/// <summary>
|
||||
/// 避免书写时触发二次关闭二级菜单导致动画不连续
|
||||
/// </summary>
|
||||
private bool isHidingSubPanelsWhenInking;
|
||||
/// <summary>
|
||||
/// 更新检查重试计数
|
||||
/// </summary>
|
||||
private int updateCheckRetryCount = 0;
|
||||
/// <summary>
|
||||
/// 最大更新检查重试次数
|
||||
/// </summary>
|
||||
private const int MAX_UPDATE_CHECK_RETRIES = 6;
|
||||
/// <summary>
|
||||
/// 时间显示定时器
|
||||
/// </summary>
|
||||
private Timer timerDisplayTime = new Timer();
|
||||
/// <summary>
|
||||
/// 日期显示定时器
|
||||
/// </summary>
|
||||
private Timer timerDisplayDate = new Timer();
|
||||
/// <summary>
|
||||
/// NTP时间同步定时器
|
||||
/// </summary>
|
||||
private Timer timerNtpSync = new Timer();
|
||||
|
||||
/// <summary>
|
||||
/// 时间视图模型实例
|
||||
/// </summary>
|
||||
private TimeViewModel nowTimeVM = new TimeViewModel();
|
||||
/// <summary>
|
||||
/// 缓存的网络时间
|
||||
/// </summary>
|
||||
private DateTime cachedNetworkTime = DateTime.Now;
|
||||
/// <summary>
|
||||
/// 上次NTP同步时间
|
||||
/// </summary>
|
||||
private DateTime lastNtpSyncTime = DateTime.MinValue;
|
||||
/// <summary>
|
||||
/// 上次显示的时间字符串
|
||||
/// </summary>
|
||||
private string lastDisplayedTime = "";
|
||||
/// <summary>
|
||||
/// 是否使用网络时间
|
||||
/// </summary>
|
||||
private bool useNetworkTime = false;
|
||||
/// <summary>
|
||||
/// 网络时间与本地时间的偏移量
|
||||
/// </summary>
|
||||
private TimeSpan networkTimeOffset = TimeSpan.Zero;
|
||||
private DateTime lastLocalTime = DateTime.Now; // 记录上次的本地时间,用于检测时间跳跃
|
||||
private bool isNtpSyncing = false; // 防止重复NTP同步的标志
|
||||
/// <summary>
|
||||
/// 记录上次的本地时间,用于检测时间跳跃
|
||||
/// </summary>
|
||||
private DateTime lastLocalTime = DateTime.Now;
|
||||
/// <summary>
|
||||
/// 防止重复NTP同步的标志
|
||||
/// </summary>
|
||||
private bool isNtpSyncing = false;
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取网络时间
|
||||
/// </summary>
|
||||
/// <returns>返回网络时间,如果获取失败则返回本地时间</returns>
|
||||
/// <remarks>
|
||||
/// 使用NTP协议从国家授时中心服务器获取网络时间
|
||||
/// </remarks>
|
||||
private async Task<DateTime> GetNetworkTimeAsync()
|
||||
{
|
||||
try
|
||||
@@ -107,7 +193,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 修改InitTimers方法中的初始时间和日期格式
|
||||
/// <summary>
|
||||
/// 初始化所有定时器
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 初始化以下定时器:
|
||||
/// 1. timerKillProcess: 进程终止定时器,每2秒执行一次
|
||||
/// 2. _unifiedMainWindowTimer: 统一的主窗口定时器,每500毫秒执行一次
|
||||
/// 3. timerCheckAutoUpdateWithSilence: 静默更新检查定时器,每10分钟执行一次
|
||||
/// 4. timerCheckAutoUpdateRetry: 更新检查重试定时器,每10分钟执行一次
|
||||
/// 5. timerDisplayTime: 时间显示定时器,每秒执行一次
|
||||
/// 6. timerDisplayDate: 日期显示定时器,每小时执行一次
|
||||
/// 7. timerNtpSync: NTP时间同步定时器,每2小时执行一次
|
||||
/// 同时初始化定时保存墨迹定时器
|
||||
/// </remarks>
|
||||
private void InitTimers()
|
||||
{
|
||||
timerKillProcess.Elapsed += TimerKillProcess_Elapsed;
|
||||
@@ -151,12 +250,26 @@ namespace Ink_Canvas
|
||||
InitAutoSaveStrokesTimer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一主窗口定时器事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 调用timerCheckAutoFold_Elapsed方法处理自动收纳逻辑
|
||||
/// </remarks>
|
||||
private void OnUnifiedMainWindowTimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
timerCheckAutoFold_Elapsed(sender, e);
|
||||
}
|
||||
|
||||
// 初始化定时保存墨迹定时器
|
||||
/// <summary>
|
||||
/// 初始化定时保存墨迹定时器
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 初始化DispatcherTimer实例并绑定AutoSaveStrokesTimer_Tick事件处理方法
|
||||
/// 然后调用UpdateAutoSaveStrokesTimer方法根据设置更新定时器状态
|
||||
/// </remarks>
|
||||
private void InitAutoSaveStrokesTimer()
|
||||
{
|
||||
if (autoSaveStrokesTimer == null)
|
||||
@@ -169,7 +282,14 @@ namespace Ink_Canvas
|
||||
UpdateAutoSaveStrokesTimer();
|
||||
}
|
||||
|
||||
// 更新定时保存墨迹定时器状态
|
||||
/// <summary>
|
||||
/// 更新定时保存墨迹定时器状态
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 根据Settings.Automation.IsEnableAutoSaveStrokes设置决定是否启用定时器
|
||||
/// 如果启用,则根据Settings.Automation.AutoSaveStrokesIntervalMinutes设置定时器间隔
|
||||
/// 最小间隔为1分钟
|
||||
/// </remarks>
|
||||
private void UpdateAutoSaveStrokesTimer()
|
||||
{
|
||||
if (autoSaveStrokesTimer == null) return;
|
||||
@@ -185,7 +305,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 定时保存墨迹定时器事件处理
|
||||
/// <summary>
|
||||
/// 定时保存墨迹定时器事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 当定时器触发时,检查画布是否可见且有墨迹
|
||||
/// 如果满足条件,则调用SaveInkCanvasStrokes方法进行静默保存
|
||||
/// </remarks>
|
||||
private void AutoSaveStrokesTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -202,7 +330,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// NTP同步定时器事件处理
|
||||
/// <summary>
|
||||
/// NTP同步定时器事件处理方法
|
||||
/// </summary>
|
||||
/// <returns>异步任务</returns>
|
||||
/// <remarks>
|
||||
/// 异步执行NTP时间同步,包括以下步骤:
|
||||
/// 1. 防止重复同步(使用isNtpSyncing标志)
|
||||
/// 2. 添加10秒超时机制
|
||||
/// 3. 调用GetNetworkTimeAsync获取网络时间
|
||||
/// 4. 计算网络时间与本地时间的偏移量
|
||||
/// 5. 如果时间差超过3分钟,则使用网络时间
|
||||
/// 6. 处理异常情况,确保即使同步失败也能恢复到使用本地时间
|
||||
/// </remarks>
|
||||
private async Task TimerNtpSync_ElapsedAsync()
|
||||
{
|
||||
// 防止重复同步
|
||||
@@ -256,7 +396,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 优化后的时间显示方法,仅在NTP同步时计算网络时间偏移
|
||||
/// <summary>
|
||||
/// 优化后的时间显示方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理时间显示逻辑,包括以下步骤:
|
||||
/// 1. 获取当前本地时间
|
||||
/// 2. 检测系统时间是否发生重大跳跃(超过3分钟),如果是则触发NTP同步
|
||||
/// 3. 如果启用网络时间且偏移量已计算,则应用偏移量
|
||||
/// 4. 格式化时间字符串
|
||||
/// 5. 只有当时间字符串发生变化时才更新UI,避免不必要的UI刷新
|
||||
/// 6. 使用BeginInvoke异步更新UI,避免阻塞
|
||||
/// </remarks>
|
||||
private void TimerDisplayTime_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
DateTime localTime = DateTime.Now;
|
||||
@@ -307,12 +460,34 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 修改TimerDisplayDate_Elapsed方法中的日期格式
|
||||
/// <summary>
|
||||
/// 日期显示定时器事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 更新nowTimeVM的nowDate属性,设置为当前日期的格式化字符串
|
||||
/// 格式为:yyyy年MM月dd日 星期几
|
||||
/// </remarks>
|
||||
private void TimerDisplayDate_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd");
|
||||
// 使用BeginInvoke异步更新UI,避免阻塞
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd");
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进程终止定时器事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 根据设置终止指定的进程,包括PPTService、EasiNote、HiteAnnotation等
|
||||
/// 对于每个终止的进程,会显示相应的通知
|
||||
/// 对于HiteAnnotation进程,还会根据设置决定是否自动进入批注状态
|
||||
/// </remarks>
|
||||
private void TimerKillProcess_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -821,6 +996,19 @@ namespace Ink_Canvas
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动收纳定时器事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 检查是否需要自动收纳浮动栏,包括以下逻辑:
|
||||
/// 1. 检查是否有全屏窗口需要收纳
|
||||
/// 2. 检查是否有应用程序需要自动收纳
|
||||
/// 3. 对于EasiNote应用,根据版本和窗口类型决定是否收纳
|
||||
/// 4. 对于其他应用程序,根据设置决定是否收纳
|
||||
/// 5. 当没有需要收纳的应用程序时,根据设置决定是否展开浮动栏
|
||||
/// </remarks>
|
||||
private void timerCheckAutoFold_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (isFloatingBarChangingHideMode) return;
|
||||
@@ -911,6 +1099,24 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 静默更新检查定时器事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理静默更新的检查和安装逻辑,包括以下步骤:
|
||||
/// 1. 停止计时器,避免重复触发
|
||||
/// 2. 检查是否有可用的更新版本
|
||||
/// 3. 检查是否启用了静默更新
|
||||
/// 4. 检查更新文件是否已下载
|
||||
/// 5. 如果未下载,尝试使用多线路组下载更新文件
|
||||
/// 6. 检查是否在静默更新时间段内
|
||||
/// 7. 检查应用程序状态,确保可以安全更新
|
||||
/// 8. 如果可以安全更新,执行更新安装并关闭应用程序
|
||||
/// 9. 如果不能安全更新,重新启动计时器,稍后再检查
|
||||
/// 10. 处理异常情况,确保计时器能够重新启动
|
||||
/// </remarks>
|
||||
private void timerCheckAutoUpdateWithSilence_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
// 停止计时器,避免重复触发
|
||||
@@ -1064,7 +1270,23 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 检查更新失败重试定时器事件处理
|
||||
/// <summary>
|
||||
/// 检查更新失败重试定时器事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// 异步处理更新检查失败后的重试逻辑,包括以下步骤:
|
||||
/// 1. 停止计时器,避免重复触发
|
||||
/// 2. 检查是否启用了自动更新
|
||||
/// 3. 增加重试计数
|
||||
/// 4. 检查是否超过最大重试次数
|
||||
/// 5. 清除之前的更新状态
|
||||
/// 6. 使用当前选择的更新通道检查更新
|
||||
/// 7. 如果检查成功,重置重试计数并停止重试定时器
|
||||
/// 8. 如果检查失败,重新启动定时器,10分钟后再次尝试
|
||||
/// 9. 处理异常情况,确保定时器能够重新启动
|
||||
/// </remarks>
|
||||
private async void timerCheckAutoUpdateRetry_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
// 停止定时器,避免重复触发
|
||||
@@ -1132,7 +1354,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 重置更新检查重试状态
|
||||
/// <summary>
|
||||
/// 重置更新检查重试状态方法
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 重置更新检查的重试状态,包括以下步骤:
|
||||
/// 1. 停止重试定时器
|
||||
/// 2. 重置重试计数为0
|
||||
/// 3. 记录日志
|
||||
/// 4. 处理异常情况
|
||||
/// </remarks>
|
||||
public void ResetUpdateCheckRetry()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -17,17 +17,39 @@ namespace Ink_Canvas
|
||||
{
|
||||
#region Multi-Touch
|
||||
|
||||
/// <summary>
|
||||
/// 是否处于多点触控模式
|
||||
/// </summary>
|
||||
private bool isInMultiTouchMode;
|
||||
/// <summary>
|
||||
/// 存储触摸设备ID的列表
|
||||
/// </summary>
|
||||
private List<int> dec = new List<int>();
|
||||
/// <summary>
|
||||
/// 是否处于单指拖动模式
|
||||
/// </summary>
|
||||
private bool isSingleFingerDragMode;
|
||||
/// <summary>
|
||||
/// 中心点坐标
|
||||
/// </summary>
|
||||
private Point centerPoint = new Point(0, 0);
|
||||
/// <summary>
|
||||
/// 上次的InkCanvas编辑模式
|
||||
/// </summary>
|
||||
private InkCanvasEditingMode lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
|
||||
/// <summary>
|
||||
/// 上次触摸按下的时间
|
||||
/// </summary>
|
||||
private DateTime lastTouchDownTime = DateTime.MinValue;
|
||||
/// <summary>
|
||||
/// 多点触控延迟时间(毫秒)
|
||||
/// </summary>
|
||||
private const double MULTI_TOUCH_DELAY_MS = 100;
|
||||
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 保存画布上的非笔画元素(如图片、媒体元素等)
|
||||
/// </summary>
|
||||
/// <returns>返回保存的非笔画元素列表</returns>
|
||||
private List<UIElement> PreserveNonStrokeElements()
|
||||
{
|
||||
var preservedElements = new List<UIElement>();
|
||||
@@ -164,6 +186,27 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多点触控模式切换按钮的鼠标抬起事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">鼠标按钮事件参数</param>
|
||||
/// <remarks>
|
||||
/// 切换多点触控模式和单点触控模式,包括以下步骤:
|
||||
/// 1. 如果当前处于多点触控模式,则切换到单点触控模式
|
||||
/// - 移除手写笔和触摸事件处理程序
|
||||
/// - 添加触摸事件处理程序
|
||||
/// - 设置InkCanvas编辑模式为Ink(如果当前不是橡皮擦模式)
|
||||
/// - 保存并恢复非笔画元素
|
||||
/// - 设置isInMultiTouchMode为false
|
||||
/// 2. 如果当前处于单点触控模式,则切换到多点触控模式
|
||||
/// - 添加手写笔事件处理程序
|
||||
/// - 添加触摸事件处理程序
|
||||
/// - 移除触摸事件处理程序
|
||||
/// - 设置InkCanvas编辑模式为None(如果当前不是橡皮擦模式)
|
||||
/// - 保存并恢复非笔画元素
|
||||
/// - 设置isInMultiTouchMode为true
|
||||
/// </remarks>
|
||||
private void BorderMultiTouchMode_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (isInMultiTouchMode)
|
||||
@@ -208,6 +251,24 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主窗口的触摸按下事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">触摸事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理触摸按下事件,包括以下逻辑:
|
||||
/// 1. 如果当前处于橡皮擦模式或选择模式,则直接返回
|
||||
/// 2. 如果当前没有隐藏子面板,则隐藏子面板
|
||||
/// 3. 如果当前处于图形绘制模式,则:
|
||||
/// - 设置InkCanvas编辑模式为None
|
||||
/// - 设置触摸状态为按下
|
||||
/// - 禁用浮动栏和黑板UI网格的命中测试
|
||||
/// - 设置起始点坐标
|
||||
/// - 直接返回
|
||||
/// 4. 否则,设置触摸按下点的编辑模式为None
|
||||
/// 5. 如果当前不是橡皮擦模式,则设置InkCanvas编辑模式为None
|
||||
/// </remarks>
|
||||
private void MainWindow_TouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
|
||||
@@ -244,6 +305,26 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主窗口的手写笔按下事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">手写笔按下事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理手写笔按下事件,包括以下逻辑:
|
||||
/// 1. 检查手写笔点击是否发生在浮动栏区域,如果是则允许事件传播到浮动栏按钮并返回
|
||||
/// 2. 根据手写笔是否倒置自动切换橡皮擦/画笔模式:
|
||||
/// - 如果手写笔倒置,设置编辑模式为EraseByPoint
|
||||
/// - 如果手写笔正常:
|
||||
/// - 如果当前处于图形绘制模式,设置编辑模式为None,设置触摸状态为按下,禁用浮动栏和黑板UI网格的命中测试,设置起始点坐标并返回
|
||||
/// - 如果当前不是线擦模式,设置编辑模式为Ink
|
||||
/// - 否则,保持当前线擦模式
|
||||
/// 3. 捕获手写笔输入
|
||||
/// 4. 禁用浮动栏和黑板UI网格的命中测试
|
||||
/// 5. 根据编辑模式设置光标
|
||||
/// 6. 如果当前处于橡皮擦模式或选择模式,则直接返回
|
||||
/// 7. 设置触摸按下点的编辑模式为None
|
||||
/// </remarks>
|
||||
private void MainWindow_StylusDown(object sender, StylusDownEventArgs e)
|
||||
{
|
||||
// 检查手写笔点击是否发生在浮动栏区域,如果是则允许事件传播到浮动栏按钮
|
||||
@@ -301,6 +382,30 @@ namespace Ink_Canvas
|
||||
TouchDownPointsList[e.StylusDevice.Id] = InkCanvasEditingMode.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主窗口的手写笔抬起事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">手写笔事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理手写笔抬起事件,包括以下逻辑:
|
||||
/// 1. 如果当前处于图形绘制模式:
|
||||
/// - 重置触摸状态
|
||||
/// - 启用浮动栏和黑板UI网格的命中测试
|
||||
/// - 对于双曲线等需要多步绘制的图形,根据当前步骤决定是进入下一步还是完成绘制
|
||||
/// - 对于其他单步绘制的图形,直接完成绘制
|
||||
/// - 直接返回
|
||||
/// 2. 否则,尝试获取并处理笔画:
|
||||
/// - 获取笔画视觉对象的笔画
|
||||
/// - 如果笔画不为空,将其添加到InkCanvas,移除视觉画布,并触发笔画收集事件
|
||||
/// - 如果笔画为空,仅移除视觉画布
|
||||
/// 3. 清理相关资源:
|
||||
/// - 从StrokeVisualList、VisualCanvasList和TouchDownPointsList中移除当前手写笔设备ID
|
||||
/// - 如果列表为空,清除所有手写笔预览相关的Canvas并清空列表
|
||||
/// 4. 释放手写笔捕获
|
||||
/// 5. 启用浮动栏和黑板UI网格的命中测试
|
||||
/// 6. 根据编辑模式设置光标
|
||||
/// </remarks>
|
||||
private async void MainWindow_StylusUp(object sender, StylusEventArgs e)
|
||||
{
|
||||
if (drawingShapeMode != 0)
|
||||
@@ -396,6 +501,22 @@ namespace Ink_Canvas
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主窗口的手写笔移动事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">手写笔事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理手写笔移动事件,包括以下逻辑:
|
||||
/// 1. 如果当前处于图形绘制模式且触摸状态为按下:
|
||||
/// - 获取手写笔在InkCanvas上的位置
|
||||
/// - 调用MouseTouchMove方法处理移动
|
||||
/// - 直接返回
|
||||
/// 2. 如果触摸按下点的编辑模式不是None,则直接返回
|
||||
/// 3. 尝试检查手写笔按钮状态,如果第二个按钮被按下,则直接返回
|
||||
/// 4. 否则,获取笔画视觉对象,添加手写笔点,并重新绘制
|
||||
/// 5. 捕获并忽略所有异常
|
||||
/// </remarks>
|
||||
private void MainWindow_StylusMove(object sender, StylusEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -427,6 +548,20 @@ namespace Ink_Canvas
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取笔画视觉对象方法
|
||||
/// </summary>
|
||||
/// <param name="id">设备ID</param>
|
||||
/// <returns>返回笔画视觉对象</returns>
|
||||
/// <remarks>
|
||||
/// 根据设备ID获取笔画视觉对象,如果不存在则创建新的:
|
||||
/// 1. 尝试从StrokeVisualList中获取笔画视觉对象
|
||||
/// 2. 如果不存在,创建新的StrokeVisual实例,使用InkCanvas的默认绘制属性的克隆
|
||||
/// 3. 将新的笔画视觉对象添加到StrokeVisualList
|
||||
/// 4. 创建新的VisualCanvas实例,将其设置为笔画视觉对象的视觉画布
|
||||
/// 5. 将新的视觉画布添加到VisualCanvasList和InkCanvas的子元素中
|
||||
/// 6. 返回笔画视觉对象
|
||||
/// </remarks>
|
||||
private StrokeVisual GetStrokeVisual(int id)
|
||||
{
|
||||
if (StrokeVisualList.TryGetValue(id, out var visual)) return visual;
|
||||
@@ -441,20 +576,45 @@ namespace Ink_Canvas
|
||||
return strokeVisual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取视觉画布方法
|
||||
/// </summary>
|
||||
/// <param name="id">设备ID</param>
|
||||
/// <returns>返回视觉画布对象,如果不存在则返回null</returns>
|
||||
/// <remarks>
|
||||
/// 根据设备ID从VisualCanvasList中获取视觉画布对象
|
||||
/// </remarks>
|
||||
private VisualCanvas GetVisualCanvas(int id)
|
||||
{
|
||||
return VisualCanvasList.TryGetValue(id, out var visualCanvas) ? visualCanvas : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取触摸按下点的编辑模式方法
|
||||
/// </summary>
|
||||
/// <param name="id">设备ID</param>
|
||||
/// <returns>返回触摸按下点的编辑模式,如果不存在则返回InkCanvas的当前编辑模式</returns>
|
||||
/// <remarks>
|
||||
/// 根据设备ID从TouchDownPointsList中获取触摸按下点的编辑模式
|
||||
/// </remarks>
|
||||
private InkCanvasEditingMode GetTouchDownPointsList(int id)
|
||||
{
|
||||
return TouchDownPointsList.TryGetValue(id, out var inkCanvasEditingMode) ? inkCanvasEditingMode : inkCanvas.EditingMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触摸按下点的编辑模式字典,键为设备ID,值为编辑模式
|
||||
/// </summary>
|
||||
private Dictionary<int, InkCanvasEditingMode> TouchDownPointsList { get; } =
|
||||
new Dictionary<int, InkCanvasEditingMode>();
|
||||
|
||||
/// <summary>
|
||||
/// 笔画视觉对象字典,键为设备ID,值为笔画视觉对象
|
||||
/// </summary>
|
||||
private Dictionary<int, StrokeVisual> StrokeVisualList { get; } = new Dictionary<int, StrokeVisual>();
|
||||
/// <summary>
|
||||
/// 视觉画布字典,键为设备ID,值为视觉画布对象
|
||||
/// </summary>
|
||||
private Dictionary<int, VisualCanvas> VisualCanvasList { get; } = new Dictionary<int, VisualCanvas>();
|
||||
|
||||
#endregion
|
||||
@@ -464,6 +624,26 @@ namespace Ink_Canvas
|
||||
|
||||
private Point iniP = new Point(0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 主网格的触摸按下事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">触摸事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理主网格的触摸按下事件,包括以下逻辑:
|
||||
/// 1. 检查触摸是否发生在浮动栏区域,如果是则允许事件传播到浮动栏按钮并返回
|
||||
/// 2. 根据编辑模式设置光标
|
||||
/// 3. 捕获触摸输入
|
||||
/// 4. 如果当前处于点擦模式,则直接返回
|
||||
/// 5. 如果当前处于图形绘制模式:
|
||||
/// - 设置编辑模式为None
|
||||
/// - 设置触摸状态为按下
|
||||
/// - 禁用浮动栏和黑板UI网格的命中测试
|
||||
/// - 设置起始点坐标
|
||||
/// - 直接返回
|
||||
/// 6. 如果当前处于选择模式、墨水模式或线擦模式,则直接返回
|
||||
/// 7. 如果当前不是橡皮擦模式,则设置编辑模式为Ink
|
||||
/// </remarks>
|
||||
private void Main_Grid_TouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
// 检查触摸是否发生在浮动栏区域,如果是则允许事件传播到浮动栏按钮
|
||||
@@ -518,6 +698,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取触摸边界宽度方法
|
||||
/// </summary>
|
||||
/// <param name="e">触摸事件参数</param>
|
||||
/// <returns>返回触摸边界宽度</returns>
|
||||
/// <remarks>
|
||||
/// 根据触摸事件参数计算触摸边界宽度,包括以下逻辑:
|
||||
/// 1. 获取触摸点的边界
|
||||
/// 2. 如果不是四边红外屏幕,使用边界宽度
|
||||
/// 3. 如果是四边红外屏幕,使用边界宽度和高度的平方根
|
||||
/// 4. 如果是特殊屏幕,乘以触摸倍数
|
||||
/// 5. 返回计算得到的触摸边界宽度
|
||||
/// </remarks>
|
||||
public double GetTouchBoundWidth(TouchEventArgs e)
|
||||
{
|
||||
var args = e.GetTouchPoint(null).Bounds;
|
||||
@@ -528,6 +721,25 @@ namespace Ink_Canvas
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// InkCanvas的预览触摸按下事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">触摸事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理InkCanvas的预览触摸按下事件,包括以下逻辑:
|
||||
/// 1. 捕获触摸输入
|
||||
/// 2. 禁用浮动栏和黑板UI网格的命中测试
|
||||
/// 3. 将触摸设备ID添加到dec列表中
|
||||
/// 4. 当只有一个触摸设备时:
|
||||
/// - 记录中心点坐标
|
||||
/// - 记录第一根手指点击时的StrokeCollection
|
||||
/// 5. 当有两个或以上触摸设备,或者处于单指拖动模式,或者禁用了双指手势时:
|
||||
/// - 如果处于多点触控模式或禁用了双指手势,则直接返回
|
||||
/// - 如果当前编辑模式为None或Select,则直接返回
|
||||
/// - 记录当前的编辑模式
|
||||
/// - 设置编辑模式为None,关闭画笔功能
|
||||
/// </remarks>
|
||||
private void InkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
inkCanvas.CaptureTouch(e.TouchDevice);
|
||||
@@ -555,10 +767,40 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// InkCanvas的预览触摸移动事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">触摸事件参数</param>
|
||||
/// <remarks>
|
||||
/// 空方法,预留用于处理InkCanvas的预览触摸移动事件
|
||||
/// </remarks>
|
||||
private void InkCanvas_PreviewTouchMove(object sender, TouchEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// InkCanvas的预览触摸抬起事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">触摸事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理InkCanvas的预览触摸抬起事件,包括以下逻辑:
|
||||
/// 1. 释放所有触摸捕获
|
||||
/// 2. 启用浮动栏和黑板UI网格的命中测试
|
||||
/// 3. 如果有多个触摸设备且当前编辑模式为None,则切回之前的编辑模式
|
||||
/// 4. 从dec列表中移除当前触摸设备ID
|
||||
/// 5. 当没有触摸设备时:
|
||||
/// - 重置单指拖动模式和等待下一次触摸按下的标志
|
||||
/// - 如果当前不是图形绘制模式且编辑模式不是橡皮擦或选择模式,则切回之前的编辑模式
|
||||
/// 6. 如果当前处于图形绘制模式:
|
||||
/// - 重置触摸状态
|
||||
/// - 启用浮动栏和黑板UI网格的命中测试
|
||||
/// - 对于双曲线等需要多步绘制的图形,根据当前步骤决定是进入下一步还是完成绘制
|
||||
/// - 对于其他单步绘制的图形,直接完成绘制
|
||||
/// 7. 设置InkCanvas的透明度为1
|
||||
/// 8. 当没有触摸设备且笔画数量发生变化,且不是绘制长方体的第一次触摸时,保存笔画集合
|
||||
/// </remarks>
|
||||
private void InkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
inkCanvas.ReleaseAllTouchCaptures();
|
||||
@@ -635,13 +877,41 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// InkCanvas的操作开始事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">操作开始事件参数</param>
|
||||
/// <remarks>
|
||||
/// 设置操作模式为所有模式
|
||||
/// </remarks>
|
||||
private void InkCanvas_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
|
||||
{
|
||||
e.Mode = ManipulationModes.All;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// InkCanvas的操作惯性开始事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">操作惯性开始事件参数</param>
|
||||
/// <remarks>
|
||||
/// 空方法,预留用于处理InkCanvas的操作惯性开始事件
|
||||
/// </remarks>
|
||||
private void InkCanvas_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e) { }
|
||||
|
||||
/// <summary>
|
||||
/// 主网格的操作完成事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">操作完成事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理主网格的操作完成事件,包括以下逻辑:
|
||||
/// 1. 当没有操作器时:
|
||||
/// - 清除dec列表
|
||||
/// - 重置单指拖动模式标志
|
||||
/// - 如果当前不是图形绘制模式且编辑模式不是橡皮擦或选择模式,则设置编辑模式为Ink
|
||||
/// </remarks>
|
||||
private void Main_Grid_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
|
||||
{
|
||||
if (e.Manipulators.Count() == 0)
|
||||
@@ -663,6 +933,33 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主网格的操作增量事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">操作增量事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理主网格的操作增量事件,包括以下逻辑:
|
||||
/// 1. 如果当前处于多点触控模式或禁用了双指手势,则直接返回
|
||||
/// 2. 检查是否有多个操作器
|
||||
/// 3. 检查是否应该使用双指手势
|
||||
/// 4. 如果应该使用双指手势:
|
||||
/// - 获取位移矢量
|
||||
/// - 创建矩阵变换
|
||||
/// - 如果启用了双指平移,则应用平移变换
|
||||
/// - 计算中心点(用于缩放和旋转)
|
||||
/// - 如果启用了双指平移或旋转,则应用旋转变换
|
||||
/// - 如果启用了双指缩放,则应用缩放变换
|
||||
/// - 处理选中的笔画:
|
||||
/// - 对每个选中的笔画应用变换
|
||||
/// - 对圆形笔画更新半径和中心点
|
||||
/// - 如果启用了双指缩放,更新笔画的宽度和高度
|
||||
/// - 处理未选中的笔画:
|
||||
/// - 对所有笔画应用变换
|
||||
/// - 如果启用了双指缩放,更新笔画的宽度和高度
|
||||
/// - 同时变换画布上的图片元素
|
||||
/// - 对所有圆形笔画更新半径和中心点
|
||||
/// </remarks>
|
||||
private void Main_Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
|
||||
{
|
||||
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using System;
|
||||
@@ -16,15 +16,26 @@ namespace Ink_Canvas
|
||||
public partial class App : Application
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 系统托盘菜单打开时的事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理系统托盘菜单打开时的逻辑,包括以下步骤:
|
||||
/// 1. 获取系统托盘菜单及其相关菜单项和图标
|
||||
/// 2. 获取主窗口实例
|
||||
/// 3. 如果主窗口已加载:
|
||||
/// - 在无焦点模式下,暂时取消主窗口置顶,让系统菜单能够正常显示
|
||||
/// - 根据浮动栏是否处于收纳模式,更新菜单项图标和文本
|
||||
/// - 根据浮动栏状态和主窗口是否隐藏,更新重置浮动栏位置菜单项的启用状态
|
||||
/// </remarks>
|
||||
private void SysTrayMenu_Opened(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var s = (ContextMenu)sender;
|
||||
var FoldFloatingBarTrayIconMenuItemIconEyeOff =
|
||||
(Image)((Grid)((MenuItem)s.Items[s.Items.Count - 5]).Icon).Children[0];
|
||||
var FoldFloatingBarTrayIconMenuItemIconEyeOn =
|
||||
(Image)((Grid)((MenuItem)s.Items[s.Items.Count - 5]).Icon).Children[1];
|
||||
var FoldFloatingBarTrayIconMenuItemHeaderText =
|
||||
(TextBlock)((SimpleStackPanel)((MenuItem)s.Items[s.Items.Count - 5]).Header).Children[0];
|
||||
var FoldFloatingBarTrayIconMenuItemIconEyeOff = (Image)((Grid)((MenuItem)s.Items[s.Items.Count - 5]).Icon).Children[0];
|
||||
var FoldFloatingBarTrayIconMenuItemIconEyeOn = (Image)((Grid)((MenuItem)s.Items[s.Items.Count - 5]).Icon).Children[1];
|
||||
var FoldFloatingBarTrayIconMenuItemHeaderText = (TextBlock)((SimpleStackPanel)((MenuItem)s.Items[s.Items.Count - 5]).Header).Children[0];
|
||||
var ResetFloatingBarPositionTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 4];
|
||||
var HideICCMainWindowTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 9];
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
@@ -63,6 +74,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 系统托盘菜单关闭时的事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理系统托盘菜单关闭时的逻辑,包括以下步骤:
|
||||
/// 1. 获取主窗口实例
|
||||
/// 2. 如果主窗口已加载,且在无焦点模式下启用了始终置顶,则恢复主窗口的置顶状态
|
||||
/// </remarks>
|
||||
private void SysTrayMenu_Closed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
@@ -76,6 +97,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭应用程序托盘菜单项点击事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理关闭应用程序托盘菜单项的点击事件,包括以下步骤:
|
||||
/// 1. 获取主窗口实例
|
||||
/// 2. 如果主窗口已加载:
|
||||
/// - 设置IsAppExitByUser为true,表示用户主动退出
|
||||
/// - 关闭应用程序
|
||||
/// </remarks>
|
||||
private void CloseAppTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
@@ -87,6 +120,20 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重启应用程序托盘菜单项点击事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理重启应用程序托盘菜单项的点击事件,包括以下步骤:
|
||||
/// 1. 获取主窗口实例
|
||||
/// 2. 如果主窗口已加载:
|
||||
/// - 设置IsAppExitByUser为true,表示用户主动退出
|
||||
/// - 尝试启动应用程序的新实例,带延迟参数
|
||||
/// - 捕获并记录启动新实例时可能出现的异常
|
||||
/// - 关闭当前应用程序实例
|
||||
/// </remarks>
|
||||
private void RestartAppTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
@@ -115,6 +162,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制全屏化托盘菜单项点击事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理强制全屏化托盘菜单项的点击事件,包括以下步骤:
|
||||
/// 1. 获取主窗口实例
|
||||
/// 2. 如果主窗口已加载:
|
||||
/// - 调用MoveWindow方法将主窗口移动到屏幕左上角并设置为全屏大小
|
||||
/// - 显示强制全屏化的消息,包含屏幕分辨率和缩放比例信息
|
||||
/// </remarks>
|
||||
private void ForceFullScreenTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
@@ -126,6 +185,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换浮动栏收纳模式托盘菜单项点击事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理切换浮动栏收纳模式托盘菜单项的点击事件,包括以下步骤:
|
||||
/// 1. 获取主窗口实例
|
||||
/// 2. 如果主窗口已加载:
|
||||
/// - 如果浮动栏当前处于收纳模式,则调用UnFoldFloatingBar_MouseUp方法退出收纳模式
|
||||
/// - 否则,调用FoldFloatingBar_MouseUp方法进入收纳模式
|
||||
/// </remarks>
|
||||
private void FoldFloatingBarTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
@@ -134,6 +205,20 @@ namespace Ink_Canvas
|
||||
else mainWin.FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置浮动栏位置托盘菜单项点击事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理重置浮动栏位置托盘菜单项的点击事件,包括以下步骤:
|
||||
/// 1. 获取主窗口实例
|
||||
/// 2. 如果主窗口已加载:
|
||||
/// - 检查是否处于PPT演示模式
|
||||
/// - 如果浮动栏当前未处于收纳模式:
|
||||
/// - 如果不处于PPT演示模式,调用PureViewboxFloatingBarMarginAnimationInDesktopMode方法重置浮动栏位置
|
||||
/// - 否则,调用PureViewboxFloatingBarMarginAnimationInPPTMode方法重置浮动栏位置
|
||||
/// </remarks>
|
||||
private void ResetFloatingBarPositionTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
@@ -152,6 +237,23 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏主窗口托盘菜单项选中事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理隐藏主窗口托盘菜单项的选中事件,包括以下步骤:
|
||||
/// 1. 获取菜单项和主窗口实例
|
||||
/// 2. 如果主窗口已加载:
|
||||
/// - 隐藏主窗口
|
||||
/// - 获取系统托盘菜单
|
||||
/// - 禁用并设置半透明效果给以下菜单项:
|
||||
/// - 重置浮动栏位置
|
||||
/// - 切换浮动栏收纳模式
|
||||
/// - 强制全屏化
|
||||
/// 3. 否则,取消菜单项的选中状态
|
||||
/// </remarks>
|
||||
private void HideICCMainWindowTrayIconMenuItem_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mi = (MenuItem)sender;
|
||||
@@ -177,6 +279,23 @@ namespace Ink_Canvas
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示主窗口托盘菜单项取消选中事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理显示主窗口托盘菜单项的取消选中事件,包括以下步骤:
|
||||
/// 1. 获取菜单项和主窗口实例
|
||||
/// 2. 如果主窗口已加载:
|
||||
/// - 显示主窗口
|
||||
/// - 获取系统托盘菜单
|
||||
/// - 启用并设置正常透明度给以下菜单项:
|
||||
/// - 重置浮动栏位置
|
||||
/// - 切换浮动栏收纳模式
|
||||
/// - 强制全屏化
|
||||
/// 3. 否则,取消菜单项的选中状态
|
||||
/// </remarks>
|
||||
private void HideICCMainWindowTrayIconMenuItem_UnChecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mi = (MenuItem)sender;
|
||||
@@ -201,6 +320,24 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用/启用所有快捷键托盘菜单项点击事件处理方法
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
/// <remarks>
|
||||
/// 处理禁用/启用所有快捷键托盘菜单项的点击事件,包括以下步骤:
|
||||
/// 1. 获取主窗口实例
|
||||
/// 2. 如果主窗口已加载,尝试:
|
||||
/// - 通过反射获取全局快捷键管理器
|
||||
/// - 如果获取成功:
|
||||
/// - 禁用快捷键注册
|
||||
/// - 更新菜单项文本和状态:
|
||||
/// - 如果当前文本是"禁用所有快捷键",则更改为"启用所有快捷键"并记录日志
|
||||
/// - 否则,更改为"禁用所有快捷键",重新启用快捷键注册并记录日志
|
||||
/// - 如果获取失败,记录错误日志
|
||||
/// 3. 捕获并记录可能出现的异常
|
||||
/// </remarks>
|
||||
private void DisableAllHotkeysMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
@@ -209,8 +346,7 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
// 获取全局快捷键管理器
|
||||
var hotkeyManagerField = typeof(MainWindow).GetField("_globalHotkeyManager",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var hotkeyManagerField = typeof(MainWindow).GetField("_globalHotkeyManager", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var hotkeyManager = hotkeyManagerField?.GetValue(mainWin) as GlobalHotkeyManager;
|
||||
|
||||
if (hotkeyManager != null)
|
||||
|
||||
@@ -6,6 +6,29 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理URI命令方法
|
||||
/// </summary>
|
||||
/// <param name="uri">URI命令字符串</param>
|
||||
/// <remarks>
|
||||
/// 处理ICC协议的URI命令,包括以下步骤:
|
||||
/// 1. 检查URI是否为空
|
||||
/// 2. 检查是否启用了外部协议
|
||||
/// 3. 记录处理URI命令的日志
|
||||
/// 4. 解析URI获取命令
|
||||
/// 5. 根据命令执行相应的操作:
|
||||
/// - fold: 进入收纳模式
|
||||
/// - unfold/show: 退出收纳模式
|
||||
/// - toggle: 切换收纳模式
|
||||
/// - thoroughhideon: 开启收起时彻底隐藏
|
||||
/// - thoroughhideoff: 关闭收起时彻底隐藏
|
||||
/// - thoroughhidetoggle: 切换收起时彻底隐藏状态
|
||||
/// - randone: 随机一个
|
||||
/// - rand: 随机
|
||||
/// - timer: 计时器
|
||||
/// - whiteboard/board: 白板
|
||||
/// 6. 捕获并记录可能出现的异常
|
||||
/// </remarks>
|
||||
public void HandleUriCommand(string uri)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -40,11 +40,23 @@ namespace Ink_Canvas
|
||||
|
||||
private const int CorrectedPaperHeight = 600;
|
||||
|
||||
/// <summary>
|
||||
/// 切换视频呈现侧边栏的显示状态(显示或隐藏)。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的源对象。</param>
|
||||
/// <param name="e">鼠标按钮事件的参数。</param>
|
||||
private void BtnToggleVideoPresenter_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
ToggleVideoPresenterSidebar();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换视频演示侧栏的显示状态并在显示时初始化相关控件与状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当侧栏被显示时:确保摄像头服务已初始化、暂时禁用拍照按钮、刷新可用摄像头列表,并将“照片校正”和当前页面的“上屏(live on canvas)”开关同步为保存的设置或页面状态;
|
||||
/// 当侧栏被隐藏时:将其折叠并停止进一步初始化操作。
|
||||
/// </remarks>
|
||||
private void ToggleVideoPresenterSidebar()
|
||||
{
|
||||
if (VideoPresenterSidebar == null) return;
|
||||
@@ -72,6 +84,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭视频呈现侧边栏(将其可见性设为 Collapsed)。
|
||||
/// </summary>
|
||||
private void BtnCloseVideoPresenter_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (VideoPresenterSidebar != null)
|
||||
@@ -80,6 +95,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 延迟初始化摄像头服务并订阅其帧和错误事件;如果服务已存在则不做任何操作。
|
||||
/// </summary>
|
||||
private void EnsureCameraService()
|
||||
{
|
||||
if (_cameraService != null) return;
|
||||
@@ -89,6 +107,10 @@ namespace Ink_Canvas
|
||||
_cameraService.ErrorOccurred += CameraService_ErrorOccurred;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在相机服务发生错误时将错误信息写入错误日志文件。
|
||||
/// </summary>
|
||||
/// <param name="e">来自相机服务的错误描述,会被写入错误日志。</param>
|
||||
private void CameraService_ErrorOccurred(object sender, string e)
|
||||
{
|
||||
try
|
||||
@@ -98,6 +120,13 @@ namespace Ink_Canvas
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理来自摄像头的单帧图像,用于更新预览、缓存最新帧并刷新当前页的实时画面显示。
|
||||
/// </summary>
|
||||
/// <param name="frame">来自摄像头的位图帧;为 null 时忽略。</param>
|
||||
/// <remarks>
|
||||
/// 缓存该帧为最新帧、更新预览控件的图像来源、启用拍照按钮并尝试在当前白板页上刷新实时画面。
|
||||
/// </remarks>
|
||||
private void CameraService_FrameReceived(object sender, Bitmap frame)
|
||||
{
|
||||
if (frame == null) return;
|
||||
@@ -147,11 +176,21 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前白板页索引(确保返回值至少为 1)。
|
||||
/// </summary>
|
||||
/// <returns>当前白板页索引;如果内部索引小于 1,则返回 1。</returns>
|
||||
private int GetCurrentPageIndex()
|
||||
{
|
||||
return Math.Max(1, CurrentWhiteboardIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在当前白板页面(若已启用)将给定预览图像应用到页面上的实时摄像框元素,并确保该元素已添加到画布且可见。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果当前页面未启用实时显示,或画布/对应图像元素不可用,则函数不执行任何操作。
|
||||
/// </remarks>
|
||||
private void TryUpdateLiveFrameOnCanvas(BitmapImage preview)
|
||||
{
|
||||
try
|
||||
@@ -174,6 +213,11 @@ namespace Ink_Canvas
|
||||
|
||||
private const double VideoPresenterLiveFrameScreenRatio = 0.75;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建并缓存用于指定白板页的实时视频帧 Image 元素。
|
||||
/// </summary>
|
||||
/// <param name="page">白板页索引(页面编号,用于在每页间区分并缓存元素)。</param>
|
||||
/// <returns>返回指定页对应的 Image 元素;若已存在则返回已缓存实例,否则创建新的 Image(根据画布大小设置默认宽高、标记为实时帧并初始化变换与交互绑定)并将其缓存后返回。</returns>
|
||||
private System.Windows.Controls.Image EnsureLiveFrameElementForPage(int page)
|
||||
{
|
||||
if (_liveFrameImageByPage.TryGetValue(page, out var existing) && existing != null) return existing;
|
||||
@@ -207,6 +251,14 @@ namespace Ink_Canvas
|
||||
return img;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将已保存的布局(或默认布局)应用到指定白板页面上的直播帧 Image 元素,设置其位置和尺寸并确保坐标有效。
|
||||
/// </summary>
|
||||
/// <param name="page">目标白板页面的索引。</param>
|
||||
/// <param name="img">要应用布局的 Image 元素;为 null 时不执行任何操作。</param>
|
||||
/// <remarks>
|
||||
/// 如果存在为该页面保存的布局则使用其宽度和左/上坐标;否则将 Image 调整为画布尺寸的 75% 并居中。最终位置会限制为不小于 0 的坐标,且对无效计算结果使用合理的默认偏移。
|
||||
/// </remarks>
|
||||
private void ApplyLiveFrameLayoutForPage(int page, System.Windows.Controls.Image img)
|
||||
{
|
||||
if (img == null) return;
|
||||
@@ -235,6 +287,12 @@ namespace Ink_Canvas
|
||||
InkCanvas.SetTop(img, Math.Max(0, y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新视频呈现器侧栏中的摄像头设备列表并在界面上显示可选项。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 若未检测到摄像头,会在面板中显示提示文本;若存在设备,则为每个设备创建一个用于选择的单选按钮,选择某项会启动对应的摄像头预览。函数在列表生成后会尝试恢复并启动当前页面在 _cameraIndexByPage 中存储的摄像头索引,仅当没有保存的索引时才会选择并启动第一个可用设备。保存的每页选择优先于默认选择第一个设备。
|
||||
/// </remarks>
|
||||
private void RefreshVideoPresenterDeviceList()
|
||||
{
|
||||
if (_cameraService == null) return;
|
||||
@@ -294,6 +352,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为当前白板页开始指定摄像头的预览并保存该页的摄像头选择。
|
||||
/// </summary>
|
||||
/// <param name="cameraIndex">要启动的摄像头在设备列表中的索引。</param>
|
||||
/// <remarks>预览成功时会允许拍照按钮可用。</remarks>
|
||||
private void StartVideoPresenterPreview(int cameraIndex)
|
||||
{
|
||||
try
|
||||
@@ -311,6 +374,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在当前白板页面启用“在画布上显示实时视频”功能并将对应的实时画面元素添加到画布上。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 确保为当前页面创建并应用已保存的布局,将实时画面 Image 加入 inkCanvas(若尚未存在),并尝试切换编辑工具为选择模式。若侧栏预览已有帧,则立即用该预览刷新画布上的实时画面图像源。
|
||||
/// </remarks>
|
||||
private void BtnToggleVideoPresenterLiveOnCanvas_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
int page = GetCurrentPageIndex();
|
||||
@@ -339,6 +408,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在当前页面禁用画布上的实时视频覆盖并移除其视觉元素。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 从记录已启用实时显示的集合中删除当前页面索引,并在存在对应的 Image 元素且已添加到 inkCanvas 时尝试将其移除。
|
||||
/// </remarks>
|
||||
private void BtnToggleVideoPresenterLiveOnCanvas_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
int page = GetCurrentPageIndex();
|
||||
@@ -357,7 +432,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 翻页前调用:保存当前页实时画面的位置/大小
|
||||
/// <summary>
|
||||
/// 在离开当前白板页之前保存该页实时视频画面在画布上的位置和宽度(按页索引进行存储)。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 若画面元素的 Left 或 Top 为 NaN,则按 0 处理;保存的数据格式为 (left, top, width) 到页面布局映射中供后续恢复使用。
|
||||
/// </remarks>
|
||||
private void VideoPresenter_BeforePageLeave()
|
||||
{
|
||||
try
|
||||
@@ -375,7 +455,12 @@ namespace Ink_Canvas
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
// 翻页后调用:根据该页状态恢复实时画面,并同步设备选择
|
||||
/// <summary>
|
||||
/// 在页面切换后恢复该页的实时画面状态并同步相关设备与 UI 控件状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 同步“上屏”切换按钮状态;若当前页启用了在画布上显示实时画面,则确保并布局对应的 Image 元素并用当前预览图像填充其 Source;同时恢复该页保存的摄像头索引并启动对应摄像头预览。
|
||||
/// </remarks>
|
||||
private void VideoPresenter_OnPageChanged()
|
||||
{
|
||||
try
|
||||
@@ -414,6 +499,15 @@ namespace Ink_Canvas
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理“拍照”按钮的点击:捕获当前视频帧并将照片加入捕获列表,随后刷新捕获照片的显示。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 在拍照前会检查并强制执行最小冷却时间,防止短时间内重复拍照。
|
||||
/// - 如果用户已启用照片纠正,会尝试检测纸张轮廓并对照片做透视校正再保存。
|
||||
/// - 照片处理在后台线程完成,最终的列表更新和 UI 刷新在 UI 线程上执行。
|
||||
/// - 发生异常时会记录错误日志,不会向上抛出异常。
|
||||
/// </remarks>
|
||||
private void BtnCapturePhoto_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -477,6 +571,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前相机预览的显示角度顺时针旋转 90°(在四个方向间切换)。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 更新内部 CameraService 的旋转状态以切换到下一个方向;在错误发生时会记录日志但不会抛出异常到调用者。
|
||||
/// </remarks>
|
||||
/// <param name="sender">触发该事件的控件(通常为旋转按钮)。</param>
|
||||
/// <param name="e">事件参数。</param>
|
||||
private void BtnRotateImage_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -490,6 +592,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在启用照片校正的切换按钮被选中时,将该偏好设置为开启并保存到设置文件。
|
||||
/// </summary>
|
||||
private void ToggleBtnPhotoCorrection_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Settings?.Automation == null) return;
|
||||
@@ -497,6 +602,9 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭“相片校正”设置并将变更持久化到设置文件。
|
||||
/// </summary>
|
||||
private void ToggleBtnPhotoCorrection_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Settings?.Automation == null) return;
|
||||
@@ -504,6 +612,12 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新并在 CapturedPhotosStackPanel 中显示最近捕获的照片缩略图,最多显示 30 张。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果 CapturedPhotosStackPanel 为 null 则不执行任何操作。该方法会清空面板现有内容,并为每张照片创建一个包含缩略图的按钮;点击按钮会将对应照片插入画布。
|
||||
/// </remarks>
|
||||
private void UpdateCapturedPhotosDisplay()
|
||||
{
|
||||
if (CapturedPhotosStackPanel == null) return;
|
||||
@@ -536,6 +650,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将选定的捕获图片作为图像元素插入到画布中央并切换到选择工具模式。
|
||||
/// </summary>
|
||||
/// <param name="photo">要插入的捕获图片;若为 null 或其 Image 为 null,则不进行任何操作。</param>
|
||||
/// <remarks>
|
||||
/// 在画布上创建并配置一个 Image 元素(设置 Source、Stretch、默认宽度及位置),初始化其变换与事件绑定,提交插入历史记录,添加到 inkCanvas,并将当前工具切换为“选择”同时隐藏相关子面板。方法内部捕获并记录异常,不会向外抛出。
|
||||
/// </remarks>
|
||||
private void InsertPhotoToCanvas(CapturedImage photo)
|
||||
{
|
||||
if (photo?.Image == null) return;
|
||||
@@ -572,6 +693,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在离开白板模式时关闭并清理视频呈现器相关的 UI 与运行状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 隐藏视频呈现侧栏、将“在画布上显示实时帧”开关取消选中、从画布中移除并隐藏所有每页的实时帧图像实例,并尝试停止相机预览。该方法在执行过程中会吞并内部异常以避免抛出至调用方。
|
||||
/// </remarks>
|
||||
private void VideoPresenter_OnExitWhiteboardMode()
|
||||
{
|
||||
try
|
||||
@@ -610,6 +737,11 @@ namespace Ink_Canvas
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个 System.Drawing.Bitmap 转换为可跨线程使用的 WPF BitmapImage。
|
||||
/// </summary>
|
||||
/// <param name="bitmap">要转换的源位图;若为 <see langword="null"/> 则直接返回 <see langword="null"/>。</param>
|
||||
/// <returns>转换得到的 <see cref="BitmapImage"/>;若输入为 <see langword="null"/> 或转换失败则返回 <see langword="null"/>。</returns>
|
||||
private static BitmapImage ConvertBitmapToBitmapImage(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
@@ -635,6 +767,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在给定帧中尝试检测纸张(四边形)角点,并返回按原始帧坐标排列的四个点。
|
||||
/// </summary>
|
||||
/// <param name="frame">要检测的输入位图帧。</param>
|
||||
/// <param name="cornersOut">检测到的四个角点(按顺序:左上、右上、左下、右下),坐标以输入帧的像素空间为准;检测失败时为 null。</param>
|
||||
/// <returns><see langword="true"/> 如果成功检测到四个角点并填充 <paramref name="cornersOut"/>,<see langword="false"/> 否则(包括输入为 null 或检测过程中发生错误)。</returns>
|
||||
private static bool TryDetectPaperCorners(Bitmap frame, out List<AForge.IntPoint> cornersOut)
|
||||
{
|
||||
cornersOut = null;
|
||||
@@ -715,6 +853,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将源图像中由四个角点定义的纸张区域进行透视矫正并裁切为目标尺寸的位图,目标高度为 CorrectedPaperHeight,宽度按纸张比例计算。
|
||||
/// </summary>
|
||||
/// <param name="frame">包含待矫正纸张的源位图。</param>
|
||||
/// <param name="corners">纸张在源图像中的四个角点,按顺序提供:左上 (top-left)、右上 (top-right)、左下 (bottom-left)、右下 (bottom-right)。坐标为图像像素坐标系。</param>
|
||||
/// <returns>透视矫正并裁切后的位图;在输入无效或矫正失败时返回 <see langword="null"/>。</returns>
|
||||
private static Bitmap ApplyPerspectiveCorrection(Bitmap frame, List<AForge.IntPoint> corners)
|
||||
{
|
||||
try
|
||||
@@ -748,6 +892,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算由给定顶点按顺序构成的多边形的有向面积(使用高斯面积/鞋带公式)。
|
||||
/// </summary>
|
||||
/// <param name="pts">按顶点顺序排列的多边形顶点列表(至少应包含三个点以形成多边形)。</param>
|
||||
/// <returns>多边形的有向面积;当顶点顺时针时为负值,逆时针为正值;点数少于三时返回 0。</returns>
|
||||
private static double PolygonArea(List<AForge.IntPoint> pts)
|
||||
{
|
||||
int n = pts.Count;
|
||||
@@ -763,4 +912,3 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ namespace Ink_Canvas.Models
|
||||
public string Timestamp { get; }
|
||||
public string FilePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的位图创建一个 CapturedImage 实例,并为其生成缩略图、空白笔划集合和时间戳。
|
||||
/// </summary>
|
||||
/// <param name="image">用于初始化的位图;不能为空。传入的图像将在内部确保为冻结状态以便安全跨线程使用。</param>
|
||||
/// <exception cref="System.ArgumentNullException">当 <paramref name="image"/> 为 null 时抛出。</exception>
|
||||
public CapturedImage(BitmapImage image)
|
||||
{
|
||||
if (image == null)
|
||||
@@ -25,6 +30,12 @@ namespace Ink_Canvas.Models
|
||||
FilePath = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 CapturedImage 实例:将指定图像冻结用于线程安全、生成缩略图并初始化空的笔迹集合,同时设置文件路径和时间戳(尝试从文件名提取时间戳,失败则使用当前时间)。
|
||||
/// </summary>
|
||||
/// <param name="image">源图像,不能为空。</param>
|
||||
/// <param name="filePath">关联文件的路径,可能为 null。</param>
|
||||
/// <exception cref="ArgumentNullException">当 <paramref name="image"/> 为 null 时抛出。</exception>
|
||||
public CapturedImage(BitmapImage image, string filePath)
|
||||
{
|
||||
if (image == null)
|
||||
@@ -38,6 +49,11 @@ namespace Ink_Canvas.Models
|
||||
Timestamp = TryExtractTimestampFromFilePath(filePath) ?? DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从给定文件路径的文件名中解析并返回规范化的时间戳。
|
||||
/// </summary>
|
||||
/// <param name="filePath">要从其文件名中解析时间戳的文件路径;可以为 null 或空字符串。</param>
|
||||
/// <returns>解析得到的时间戳,格式为 "yyyy-MM-dd HH:mm:ss.fff";无法解析时返回 null。</returns>
|
||||
private static string TryExtractTimestampFromFilePath(string filePath)
|
||||
{
|
||||
try
|
||||
@@ -74,6 +90,12 @@ namespace Ink_Canvas.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保并返回一个已冻结的 BitmapImage 副本,以便在跨线程场景中安全使用。
|
||||
/// </summary>
|
||||
/// <param name="image">要确保为冻结状态的源 BitmapImage。</param>
|
||||
/// <returns>与输入图像内容一致且已调用 Freeze 的 BitmapImage 实例。</returns>
|
||||
/// <exception cref="ArgumentNullException">在 <paramref name="image"/> 为 null 时抛出。</exception>
|
||||
private static BitmapImage EnsureFrozen(BitmapImage image)
|
||||
{
|
||||
if (image == null)
|
||||
@@ -99,6 +121,14 @@ namespace Ink_Canvas.Models
|
||||
return frozenCopy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成并返回一个在 290×180 约束内按比例缩放并已冻结的缩略图。
|
||||
/// </summary>
|
||||
/// <param name="original">用于生成缩略图的源 <see cref="BitmapImage"/>;不得为 <c>null</c>,且其像素宽度和高度必须大于 0。</param>
|
||||
/// <returns>已冻结的 <see cref="BitmapImage"/> 缩略图,尺寸不超过 290×180 且保持原图纵横比。</returns>
|
||||
/// <exception cref="ArgumentNullException">当 <paramref name="original"/> 为 <c>null</c> 时抛出。</exception>
|
||||
/// <exception cref="ArgumentException">当 <paramref name="original"/> 的像素宽度或高度小于等于 0 时抛出。</exception>
|
||||
/// <exception cref="InvalidOperationException">当无法计算出有效的缩放比例(例如结果为 NaN、Infinity 或非正数)时抛出。</exception>
|
||||
private static BitmapImage CreateThumbnail(BitmapImage original)
|
||||
{
|
||||
if (original == null)
|
||||
@@ -143,4 +173,3 @@ namespace Ink_Canvas.Models
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ namespace Ink_Canvas.Windows
|
||||
private int _currentStep = 0;
|
||||
private const int MaxStepIndex = 11;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 OobeWindow,并使用指定的 Settings 填充初始状态与界面。
|
||||
/// </summary>
|
||||
/// <param name="settings">用于读取和写入用户首选项的 Settings 实例。</param>
|
||||
/// <exception cref="System.ArgumentNullException">当 <paramref name="settings"/> 为 null 时抛出。</exception>
|
||||
public OobeWindow(Settings settings)
|
||||
{
|
||||
if (settings == null) throw new ArgumentNullException(nameof(settings));
|
||||
@@ -26,6 +31,12 @@ namespace Ink_Canvas.Windows
|
||||
UpdateStepUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从当前 Settings 对象将相关首选项映射并回显到各个 OOBE 界面控件中,以反映用户已保存的配置状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 对各个配置分组(外观、启动、托盘、PPT、画板、手势、墨迹纠正、快捷键、崩溃处理、自动化、随机点名、高级选项、截图等)分别进行读取并更新对应控件的选中/选项状态;在初始化每个分组时会捕获并忽略异常,避免单个分组的错误影响窗口启动流程。
|
||||
/// </remarks>
|
||||
private void InitializeFromSettings()
|
||||
{
|
||||
// 根据当前设置回显遥测选项
|
||||
@@ -214,6 +225,12 @@ namespace Ink_Canvas.Windows
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前界面上用户的选择写回到 Settings 对象并标记已接受遥测隐私说明。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 更新的设置包括:启动时的遥测级别、主题与启动外观、启动行为、托盘/快速面板、PowerPoint 联动、画板与墨迹选项、手势设置、墨迹纠正、鼠标模式快捷键、崩溃处理策略、自动化相关设置、随机点名设置以及高级日志选项;操作中对各子配置块采用防护性写回(局部异常被忽略)。方法结束时会将 HasAcceptedTelemetryPrivacy 置为 true。
|
||||
/// </remarks>
|
||||
private void ApplySelection()
|
||||
{
|
||||
// 将当前遥测选项写回到设置
|
||||
@@ -394,6 +411,11 @@ namespace Ink_Canvas.Windows
|
||||
_settings.Startup.HasAcceptedTelemetryPrivacy = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理“确认/下一步”按钮的点击:在未到最后一步时前进到下一步,若已是最后一步则应用当前选择并关闭窗口。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的源对象。</param>
|
||||
/// <param name="e">路由事件参数。</param>
|
||||
private void BtnConfirm_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 如果还没到最后一步,则进入下一步
|
||||
@@ -410,6 +432,11 @@ namespace Ink_Canvas.Windows
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导航到上一步骤;若已处于第一步(索引为 0)则不做任何操作。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发此事件的源对象。</param>
|
||||
/// <param name="e">事件的路由参数。</param>
|
||||
private void BtnPreviousStep_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_currentStep <= 0) return;
|
||||
@@ -418,8 +445,11 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口加载完成时触发淡入动画。
|
||||
/// 在窗口加载时对窗口不透明度执行淡入动画以显示窗口。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 使用约 220 毫秒的缓出三次方缓动实现淡入;如果动画失败,方法会立即将窗口不透明度设为 1 作为回退。
|
||||
/// </remarks>
|
||||
private void OobeWindow_OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -445,8 +475,11 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前步骤更新界面显示和按钮文案。
|
||||
/// 根据当前步骤索引更新向导界面:显示对应步骤面板,播放切换动画,并刷新步骤指示、标题、子标题和按钮文本/可见性。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 若更新过程中出现异常会被捕获并忽略以避免中断主流程。
|
||||
/// </remarks>
|
||||
private void UpdateStepUI()
|
||||
{
|
||||
try
|
||||
@@ -584,4 +617,3 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,20 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
public partial class SecurityPanel : SettingsPanelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化 SecurityPanel 实例并构建其界面组件。
|
||||
/// </summary>
|
||||
public SecurityPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 MainWindow.Settings 将安全相关设置加载并同步到面板的开关控件上。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 确保 MainWindow.Settings.Security 存在(若为 null 则创建),在加载期间暂时禁用变更处理以避免触发回调,设置各个开关的状态并更新与密码相关的 UI 状态;任何加载期间的异常会被捕获并静默忽略。
|
||||
/// </remarks>
|
||||
public override void LoadSettings()
|
||||
{
|
||||
if (MainWindow.Settings == null) return;
|
||||
@@ -37,6 +46,14 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
_isLoaded = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新与主密码相关的界面控件的可用性:根据当前设置启用或禁用“设置/更改密码”按钮及相关用途开关。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当全局安全设置的 PasswordEnabled 为 true 时,启用 BtnSetOrChangePassword 以及以下用途开关:
|
||||
/// ToggleSwitchRequirePasswordOnExit、ToggleSwitchRequirePasswordOnEnterSettings、ToggleSwitchRequirePasswordOnResetConfig;
|
||||
/// 否则禁用它们以阻止操作。
|
||||
/// </remarks>
|
||||
private void UpdatePasswordUiState()
|
||||
{
|
||||
var sec = MainWindow.Settings?.Security;
|
||||
@@ -54,6 +71,14 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
if (t3 != null) t3.IsEnabled = usageEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据切换项标识更新对应的安全设置并保存变更。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 对于启用或禁用主密码会在必要时弹出密码设置或验证对话框;对进程保护的修改会同时应用到 ProcessProtectionManager。方法会在成功变更后持久化设置并更新相关 UI 状态,若用户在交互中取消,则会恢复切换控件到原始状态。
|
||||
/// </remarks>
|
||||
/// <param name="tag">切换项的标识字符串,支持的值:`"PasswordEnabled"`、`"RequirePasswordOnExit"`、`"RequirePasswordOnEnterSettings"`、`"RequirePasswordOnResetConfig"`、`"EnableProcessProtection"`。</param>
|
||||
/// <param name="newState">切换的新布尔状态:`true` 表示启用,`false` 表示禁用。</param>
|
||||
protected override async void HandleToggleSwitchChange(string tag, bool newState)
|
||||
{
|
||||
if (MainWindow.Settings == null) return;
|
||||
@@ -127,16 +152,27 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理选项按钮组的选择更改。此面板不包含选项按钮组,因此不会执行任何操作。
|
||||
/// </summary>
|
||||
/// <param name="group">选项组的标识(未使用)。</param>
|
||||
/// <param name="value">被选中的值(未使用)。</param>
|
||||
protected override void HandleOptionChange(string group, string value)
|
||||
{
|
||||
// 本面板无选项按钮组
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理切换开关的点击事件。
|
||||
/// </summary>
|
||||
protected override void ToggleSwitch_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
base.ToggleSwitch_Click(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向用户弹出设置或更改密码的对话框;当用户输入非空新密码时,将该密码保存到设置中、启用密码功能、持久化设置并更新密码相关的 UI 状态。
|
||||
/// </summary>
|
||||
private async void BtnSetOrChangePassword_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (MainWindow.Settings == null) return;
|
||||
@@ -157,6 +193,11 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
/// <summary>
|
||||
/// 根据 ScrollViewer 的垂直滚动偏移触发顶部栏阴影显示或隐藏事件。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的 ScrollViewer 控件。</param>
|
||||
/// <param name="e">滚动更改的事件参数(未被方法使用)。</param>
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
@@ -164,6 +205,12 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
else IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前主题应用到此面板并重新加载面板设置。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在应用主题或重载设置时抛出的异常会被捕获并忽略,不会向上抛出。
|
||||
/// </remarks>
|
||||
public void ApplyTheme()
|
||||
{
|
||||
try
|
||||
@@ -175,4 +222,3 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
Loaded += SettingsPanelBase_Loaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理控件的 Loaded 事件:加载面板设置、启用触摸支持,尝试调用面板的 ApplyTheme 方法(若存在),
|
||||
/// 再次加载设置并将内部已加载标志设为 true。
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的对象(通常是当前面板实例)。</param>
|
||||
/// <param name="e">事件参数,包含路由事件的相关信息。</param>
|
||||
private void SettingsPanelBase_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LoadSettings();
|
||||
@@ -272,4 +278,3 @@ namespace Ink_Canvas.Windows.SettingsViews
|
||||
protected abstract void HandleOptionChange(string group, string value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
private MainWindow _mainWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化设置窗口的 UI、事件和面板并加载初始状态与主题。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 构造函数完成以下工作:获取主窗口引用,注册搜索与菜单事件,构建并绑定侧栏条目,挂接各设置面板的滚动/阴影事件,初始化面板数组、滚动容器、标题与名称映射,设置初始选中项与主题状态,为自定义滑块添加触摸支持,预加载所有面板设置,并多次应用主题以确保视觉元素正确呈现(包含延迟再应用以修正标题栏等)。
|
||||
/// </remarks>
|
||||
public SettingsWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -322,8 +328,11 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知所有面板应用主题
|
||||
/// 通知所有已注册的设置面板应用当前主题配置,使各面板更新其视觉样式以匹配窗口主题。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 对每个面板尝试通过反射调用其 `ApplyTheme` 方法;如果某面板不存在该方法则会跳过,调用过程中发生的异常会被捕获并写入调试输出,但不会中断对其它面板的处理。
|
||||
/// </remarks>
|
||||
private void ApplyThemeToAllPanels()
|
||||
{
|
||||
try
|
||||
@@ -753,8 +762,12 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载所有设置面板的设置
|
||||
/// 预加载并初始化所有设置面板:对每个面板尝试加载设置、启用触摸支持并应用当前主题。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该操作在 UI 线程上以 DispatcherPriority.Loaded 异步调度执行,并在完成后再次触发对所有面板的主题应用以确保视觉状态一致。
|
||||
/// 对单个面板的初始化错误会被捕获并处理,不会中断其它面板的预加载流程。
|
||||
/// </remarks>
|
||||
private void LoadAllPanelsSettings()
|
||||
{
|
||||
try
|
||||
@@ -938,6 +951,16 @@ namespace Ink_Canvas.Windows
|
||||
public string[] SettingsPaneTitles;
|
||||
public string[] SettingsPaneNames;
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前选中的侧边栏项更新侧边栏条目状态、面板可见性与主题并将视图滚动到顶部。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 将 SidebarItems 中的 Selected 与 SettingsWindowTitle.Text 与字段 _selectedSidebarItemName 同步;
|
||||
/// - 根据应用设置计算并同步每个 SidebarItem 的 IsDarkTheme;
|
||||
/// - 切换各个面板(Pane)的 Visibility,仅显示与 _selectedSidebarItemName 对应的面板;
|
||||
/// - 异步调用所选面板(若存在)的 ApplyTheme 方法以确保新显示面板使用正确主题;
|
||||
/// - 将所有已注册的 SettingsPaneScrollViewers 滚动到顶部以重置视图位置。
|
||||
/// </remarks>
|
||||
public void UpdateSidebarItemsSelection()
|
||||
{
|
||||
foreach (var si in SidebarItems)
|
||||
@@ -1815,4 +1838,4 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user