Merge pull request #137 from InkCanvasForClass/beta

ICC CE 1.7.7.7
This commit is contained in:
CJK_mkp
2025-08-24 11:58:06 +08:00
committed by GitHub
34 changed files with 4840 additions and 284 deletions
+2 -1
View File
@@ -86,7 +86,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/129855423?v=4",
"profile": "https://github.com/PrefacedCorg",
"contributions": [
"code"
"code",
"design"
]
}
]
+29 -3
View File
@@ -45,7 +45,7 @@ namespace Ink_Canvas
// 新增:进程ID
private static int currentProcessId = Process.GetCurrentProcess().Id;
// 新增:应用启动时间
private static DateTime appStartTime = DateTime.Now;
internal static DateTime appStartTime { get; private set; }
// 新增:最后一次错误信息
private static string lastErrorMessage = string.Empty;
// 新增:是否已初始化崩溃监听器
@@ -287,6 +287,7 @@ namespace Ink_Canvas
{
string reason = e.Reason == SessionEndReasons.Logoff ? "用户注销" : "系统关机";
WriteCrashLog($"系统会话即将结束: {reason}");
DeviceIdentifier.SaveUsageStatsOnShutdown();
}
// 新增:控制台取消事件处理
@@ -330,7 +331,8 @@ namespace Ink_Canvas
private void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
TimeSpan runDuration = DateTime.Now - appStartTime;
WriteCrashLog($"应用程序退出,运行时长: {runDuration}");
string durationText = FormatTimeSpan(runDuration);
WriteCrashLog($"应用程序退出,运行时长: {durationText}");
// 如果有最后错误消息,记录到日志
if (!string.IsNullOrEmpty(lastErrorMessage))
@@ -339,6 +341,27 @@ namespace Ink_Canvas
}
}
// 新增:格式化时间跨度
private static string FormatTimeSpan(TimeSpan timeSpan)
{
if (timeSpan.TotalDays >= 1)
{
return $"{timeSpan.Days}天 {timeSpan.Hours}小时 {timeSpan.Minutes}分钟";
}
else if (timeSpan.TotalHours >= 1)
{
return $"{timeSpan.Hours}小时 {timeSpan.Minutes}分钟";
}
else if (timeSpan.TotalMinutes >= 1)
{
return $"{timeSpan.Minutes}分钟 {timeSpan.Seconds}秒";
}
else
{
return $"{timeSpan.Seconds}秒";
}
}
// 新增:记录崩溃日志
private static void WriteCrashLog(string message)
{
@@ -355,7 +378,7 @@ namespace Ink_Canvas
// 收集系统状态信息
string memoryUsage = (Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024)) + " MB";
string cpuTime = Process.GetCurrentProcess().TotalProcessorTime.ToString();
string processUptime = (DateTime.Now - Process.GetCurrentProcess().StartTime).ToString();
string processUptime = FormatTimeSpan(DateTime.Now - Process.GetCurrentProcess().StartTime);
string statusInfo = $"[内存: {memoryUsage}, CPU时间: {cpuTime}, 运行时长: {processUptime}]";
@@ -435,6 +458,9 @@ namespace Ink_Canvas
void App_Startup(object sender, StartupEventArgs e)
{
// 初始化应用启动时间
appStartTime = DateTime.Now;
/*if (!StoreHelper.IsStoreApp) */
RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
+2 -2
View File
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.7.7.5")]
[assembly: AssemblyFileVersion("1.7.7.5")]
[assembly: AssemblyVersion("1.7.7.7")]
[assembly: AssemblyFileVersion("1.7.7.7")]
+9 -8
View File
@@ -45,9 +45,9 @@ namespace Ink_Canvas.Helpers
new UpdateLineGroup
{
GroupName = "GitHub主线",
VersionUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
DownloadUrlFormat = "https://github.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
LogUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md"
VersionUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
DownloadUrlFormat = "https://bgithub.xyz/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md"
},
new UpdateLineGroup
{
@@ -80,9 +80,9 @@ namespace Ink_Canvas.Helpers
new UpdateLineGroup
{
GroupName = "GitHub主线",
VersionUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
DownloadUrlFormat = "https://github.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
LogUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"
VersionUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt",
DownloadUrlFormat = "https://bgithub.xyz/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"
},
new UpdateLineGroup
{
@@ -1195,7 +1195,7 @@ namespace Ink_Canvas.Helpers
StringBuilder batchContent = new StringBuilder();
batchContent.AppendLine("@echo off");
batchContent.AppendLine("echo Set objShell = CreateObject(\"WScript.Shell\") > \"%temp%\\hideme.vbs\"");
batchContent.AppendLine("echo objShell.Run \"cmd /c \"\"\" ^& WScript.Arguments(0) ^& \"\"\"\", 0, True >> \"%temp%\\hideme.vbs\"");
batchContent.AppendLine("echo Wscript.Sleep 100 >> \"%temp%\\hideme.vbs\"");
@@ -1210,6 +1210,7 @@ namespace Ink_Canvas.Helpers
batchContent.AppendLine($"echo goto CHECK_PROCESS >> \"{updateBatPath}\"");
batchContent.AppendLine($"echo ) >> \"{updateBatPath}\"");
batchContent.AppendLine($"echo timeout /t 1 /nobreak > nul >> \"{updateBatPath}\"");
batchContent.AppendLine($"echo echo Application closed, starting update process... >> \"{updateBatPath}\"");
batchContent.AppendLine($"echo timeout /t 2 /nobreak ^> nul >> \"{updateBatPath}\"");
@@ -1514,7 +1515,7 @@ namespace Ink_Canvas.Helpers
LogHelper.WriteLogToFile("AutoUpdate | 开始测试Windows 7 TLS连接...");
// 测试GitHub连接
var testUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt";
var testUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt";
using (var handler = new HttpClientHandler())
{
+300 -1
View File
@@ -7,6 +7,7 @@ using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace Ink_Canvas.Helpers
{
@@ -2745,5 +2746,303 @@ namespace Ink_Canvas.Helpers
return errorMsg;
}
}
/// <summary>
/// 关机时保存使用时间数据
/// </summary>
public static void SaveUsageStatsOnShutdown()
{
// 使用超时锁防止死锁
if (!Monitor.TryEnter(fileLock, TimeSpan.FromSeconds(30)))
{
LogHelper.WriteLogToFile("DeviceIdentifier | 关机保存超时,使用备用保存策略", LogHelper.LogType.Warning);
SaveUsageStatsOnShutdownFallback();
return;
}
try
{
LogHelper.WriteLogToFile("DeviceIdentifier | 开始关机时保存使用时间数据", LogHelper.LogType.Info);
// 1. 加载现有使用统计数据(多重恢复策略)
UsageStats stats = LoadUsageStatsWithFallback();
if (stats == null)
{
stats = new UsageStats { DeviceId = DeviceId };
LogHelper.WriteLogToFile("DeviceIdentifier | 创建新的使用统计数据", LogHelper.LogType.Info);
}
// 2. 计算本次会话时长(防止异常值)
TimeSpan sessionDuration = DateTime.Now - App.appStartTime;
long sessionSeconds = Math.Max(0, (long)sessionDuration.TotalSeconds);
// 防止异常大的会话时长(超过24小时)
if (sessionSeconds > 86400)
{
sessionSeconds = 86400;
LogHelper.WriteLogToFile($"DeviceIdentifier | 会话时长异常,已限制为24小时: {sessionSeconds}秒", LogHelper.LogType.Warning);
}
// 3. 更新统计数据
stats.TotalUsageSeconds += sessionSeconds;
stats.LaunchCount++;
stats.AverageSessionSeconds = stats.TotalUsageSeconds / (double)Math.Max(1, stats.LaunchCount);
stats.LastLaunchTime = DateTime.Now;
// 更新数据哈希值
stats.UpdateDataHash();
// 4. 多重保存策略 - 确保数据不丢失
var saveResults = new List<string>();
// 4.1 保存到所有文件位置
saveResults.Add(SaveUsageStatsToAllFileLocations(stats));
// 4.2 保存到所有注册表位置
saveResults.Add(SaveUsageStatsToAllRegistryLocations(stats));
// 4.3 保存到内存缓存(作为最后防线)
SaveUsageStatsToMemoryCache(stats);
// 4.4 强制刷新文件系统缓存
ForceFlushFileSystem();
// 4.5 验证保存结果
var verificationResult = VerifyDataSaveResults(stats, saveResults);
LogHelper.WriteLogToFile($"DeviceIdentifier | 关机保存完成,验证结果: {verificationResult}", LogHelper.LogType.Info);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"DeviceIdentifier | 关机时保存使用时间数据失败: {ex.Message}", LogHelper.LogType.Error);
// 即使主保存失败,也要尝试备用保存
try
{
SaveUsageStatsOnShutdownFallback();
}
catch (Exception fallbackEx)
{
LogHelper.WriteLogToFile($"DeviceIdentifier | 备用保存也失败: {fallbackEx.Message}", LogHelper.LogType.Error);
}
}
finally
{
Monitor.Exit(fileLock);
}
}
/// <summary>
/// 关机保存的备用策略
/// </summary>
private static void SaveUsageStatsOnShutdownFallback()
{
try
{
LogHelper.WriteLogToFile("DeviceIdentifier | 执行关机保存备用策略", LogHelper.LogType.Warning);
// 使用最基本的保存方式
var stats = new UsageStats { DeviceId = DeviceId };
stats.TotalUsageSeconds = 1; // 最小记录
stats.LaunchCount = 1;
stats.LastLaunchTime = DateTime.Now;
// 只保存到最可靠的位置
SaveUsageStatsToFile(BackupUsageStatsPath, stats);
SaveUsageStatsToRegistry(stats);
LogHelper.WriteLogToFile("DeviceIdentifier | 备用策略执行完成", LogHelper.LogType.Info);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"DeviceIdentifier | 备用策略执行失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 加载使用统计数据(带多重恢复策略)
/// </summary>
private static UsageStats LoadUsageStatsWithFallback()
{
try
{
// 1. 尝试从主文件加载
var stats = LoadUsageStats();
if (stats != null) return stats;
// 2. 尝试从备份文件加载
stats = LoadUsageStatsFromFile(BackupUsageStatsPath);
if (stats != null) return stats;
// 3. 尝试从其他备份位置加载
var backupPaths = new[] { SecondaryUsageBackupPath, TertiaryUsageBackupPath, QuaternaryUsageBackupPath };
foreach (var path in backupPaths)
{
stats = LoadUsageStatsFromFile(path);
if (stats != null) return stats;
}
// 4. 尝试从注册表恢复
stats = LoadUsageStatsFromRegistry();
if (stats != null) return stats;
return null;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"DeviceIdentifier | 多重恢复加载失败: {ex.Message}", LogHelper.LogType.Error);
return null;
}
}
/// <summary>
/// 保存使用统计到所有文件位置
/// </summary>
private static string SaveUsageStatsToAllFileLocations(UsageStats stats)
{
var results = new List<string>();
var filePaths = new[]
{
UsageStatsFilePath,
BackupUsageStatsPath,
SecondaryUsageBackupPath,
TertiaryUsageBackupPath,
QuaternaryUsageBackupPath
};
foreach (var filePath in filePaths)
{
try
{
SaveUsageStatsToFile(filePath, stats);
results.Add($"✓ {Path.GetFileName(filePath)}");
}
catch (Exception ex)
{
results.Add($"✗ {Path.GetFileName(filePath)}: {ex.Message}");
}
}
return string.Join("\n", results);
}
/// <summary>
/// 保存使用统计到所有注册表位置
/// </summary>
private static string SaveUsageStatsToAllRegistryLocations(UsageStats stats)
{
var results = new List<string>();
try
{
// 主注册表位置
SaveUsageStatsToRegistry(stats);
results.Add("✓ 主注册表位置");
}
catch (Exception ex)
{
results.Add($"✗ 主注册表位置: {ex.Message}");
}
try
{
// 备用注册表位置
SaveUsageStatsToMultipleRegistryLocations(stats);
results.Add("✓ 备用注册表位置");
}
catch (Exception ex)
{
results.Add($"✗ 备用注册表位置: {ex.Message}");
}
return string.Join("\n", results);
}
/// <summary>
/// 保存使用统计到内存缓存
/// </summary>
private static void SaveUsageStatsToMemoryCache(UsageStats stats)
{
try
{
// 将数据保存到静态变量作为内存备份
_cachedUsageStats = stats;
LogHelper.WriteLogToFile("DeviceIdentifier | 数据已保存到内存缓存", LogHelper.LogType.Info);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"DeviceIdentifier | 保存到内存缓存失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 强制刷新文件系统缓存
/// </summary>
private static void ForceFlushFileSystem()
{
try
{
// 强制刷新所有相关目录
var directories = new[]
{
Path.GetDirectoryName(UsageStatsFilePath),
Path.GetDirectoryName(BackupUsageStatsPath),
Path.GetDirectoryName(SecondaryUsageBackupPath),
Path.GetDirectoryName(TertiaryUsageBackupPath),
Path.GetDirectoryName(QuaternaryUsageBackupPath)
};
foreach (var dir in directories.Where(d => !string.IsNullOrEmpty(d) && Directory.Exists(d)))
{
try
{
// 创建临时文件来强制刷新
var tempFile = Path.Combine(dir, ".flush.tmp");
File.WriteAllText(tempFile, DateTime.Now.ToString());
File.Delete(tempFile);
}
catch { /* 忽略刷新错误 */ }
}
LogHelper.WriteLogToFile("DeviceIdentifier | 文件系统缓存刷新完成", LogHelper.LogType.Info);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"DeviceIdentifier | 文件系统缓存刷新失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 验证数据保存结果
/// </summary>
private static string VerifyDataSaveResults(UsageStats stats, List<string> saveResults)
{
var verification = new StringBuilder();
verification.AppendLine("数据保存验证结果:");
verification.AppendLine(string.Join("\n", saveResults));
// 验证关键数据是否保存成功
try
{
var savedStats = LoadUsageStats();
if (savedStats != null && savedStats.DeviceId == stats.DeviceId)
{
verification.AppendLine("✓ 主数据文件验证成功");
}
else
{
verification.AppendLine("✗ 主数据文件验证失败");
}
}
catch (Exception ex)
{
verification.AppendLine($"✗ 主数据文件验证异常: {ex.Message}");
}
return verification.ToString();
}
// 内存缓存变量
private static UsageStats _cachedUsageStats;
}
}
}
+854
View File
@@ -0,0 +1,854 @@
using System;
using System.Collections.Generic;
using System.Windows.Input;
using System.IO;
using System.Reflection;
using Newtonsoft.Json;
using NHotkey.Wpf;
namespace Ink_Canvas.Helpers
{
/// <summary>
/// 全局快捷键管理器 - 使用NHotkey库实现全局快捷键功能
/// </summary>
public class GlobalHotkeyManager : IDisposable
{
#region Private Fields
private readonly Dictionary<string, HotkeyInfo> _registeredHotkeys;
private readonly MainWindow _mainWindow;
private bool _isDisposed = false;
private bool _hotkeysShouldBeRegistered = false; // 启动时不注册热键,等待需要时再注册
// 配置文件路径
private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "HotkeyConfig.json");
#endregion
#region Constructor
public GlobalHotkeyManager(MainWindow mainWindow)
{
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
_registeredHotkeys = new Dictionary<string, HotkeyInfo>();
_hotkeysShouldBeRegistered = false; // 启动时不注册热键,等待需要时再注册
}
#endregion
#region Public Methods
/// <summary>
/// 注册全局快捷键
/// </summary>
/// <param name="hotkeyName">快捷键名称</param>
/// <param name="key">按键</param>
/// <param name="modifiers">修饰键</param>
/// <param name="action">执行动作</param>
/// <returns>是否注册成功</returns>
public bool RegisterHotkey(string hotkeyName, Key key, ModifierKeys modifiers, Action action)
{
try
{
if (_isDisposed)
return false;
// 如果快捷键已存在,先注销
if (_registeredHotkeys.ContainsKey(hotkeyName))
{
UnregisterHotkey(hotkeyName);
}
// 创建快捷键信息
var hotkeyInfo = new HotkeyInfo
{
Name = hotkeyName,
Key = key,
Modifiers = modifiers,
Action = action
};
// 注册快捷键
HotkeyManager.Current.AddOrReplace(hotkeyName, key, modifiers, (sender, e) =>
{
try
{
// 确保在主线程中执行
_mainWindow.Dispatcher.Invoke(() =>
{
action?.Invoke();
});
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"执行快捷键 {hotkeyName} 时出错: {ex.Message}", LogHelper.LogType.Error);
}
});
_registeredHotkeys[hotkeyName] = hotkeyInfo;
// 成功注册全局快捷键
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"注册全局快捷键 {hotkeyName} 失败: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 注销指定快捷键
/// </summary>
/// <param name="hotkeyName">快捷键名称</param>
/// <returns>是否注销成功</returns>
public bool UnregisterHotkey(string hotkeyName)
{
try
{
if (_isDisposed || !_registeredHotkeys.ContainsKey(hotkeyName))
return false;
HotkeyManager.Current.Remove(hotkeyName);
_registeredHotkeys.Remove(hotkeyName);
// 成功注销全局快捷键
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"注销全局快捷键 {hotkeyName} 失败: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 注销所有快捷键
/// </summary>
public void UnregisterAllHotkeys()
{
try
{
if (_isDisposed)
return;
foreach (var hotkeyName in _registeredHotkeys.Keys)
{
try
{
HotkeyManager.Current.Remove(hotkeyName);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"注销快捷键 {hotkeyName} 时出错: {ex.Message}", LogHelper.LogType.Warning);
}
}
_registeredHotkeys.Clear();
// 已注销所有全局快捷键,集合已清空
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"注销所有快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 检查快捷键是否已注册
/// </summary>
/// <param name="hotkeyName">快捷键名称</param>
/// <returns>是否已注册</returns>
public bool IsHotkeyRegistered(string hotkeyName)
{
return _registeredHotkeys.ContainsKey(hotkeyName);
}
/// <summary>
/// 获取已注册的快捷键列表
/// </summary>
/// <returns>快捷键信息列表</returns>
public List<HotkeyInfo> GetRegisteredHotkeys()
{
return new List<HotkeyInfo>(_registeredHotkeys.Values);
}
/// <summary>
/// 获取配置文件中的快捷键信息(不注册,仅用于显示)
/// </summary>
/// <returns>配置文件中的快捷键列表</returns>
public List<HotkeyInfo> GetHotkeysFromConfigFile()
{
try
{
if (!File.Exists(HotkeyConfigFile))
{
LogHelper.WriteLogToFile("快捷键配置文件不存在", LogHelper.LogType.Info);
return new List<HotkeyInfo>();
}
// 读取配置文件内容
string jsonContent = File.ReadAllText(HotkeyConfigFile, System.Text.Encoding.UTF8);
if (string.IsNullOrEmpty(jsonContent))
{
LogHelper.WriteLogToFile("快捷键配置文件为空", LogHelper.LogType.Warning);
return new List<HotkeyInfo>();
}
// 反序列化配置
var config = JsonConvert.DeserializeObject<HotkeyConfig>(jsonContent);
if (config?.Hotkeys == null || config.Hotkeys.Count == 0)
{
LogHelper.WriteLogToFile("快捷键配置为空或格式错误", LogHelper.LogType.Warning);
return new List<HotkeyInfo>();
}
// 转换为HotkeyInfo列表(不注册,仅用于显示)
var hotkeyList = new List<HotkeyInfo>();
foreach (var hotkeyConfig in config.Hotkeys)
{
hotkeyList.Add(new HotkeyInfo
{
Name = hotkeyConfig.Name,
Key = hotkeyConfig.Key,
Modifiers = hotkeyConfig.Modifiers,
Action = null // 不设置动作,仅用于显示
});
}
LogHelper.WriteLogToFile($"从配置文件读取到 {hotkeyList.Count} 个快捷键信息", LogHelper.LogType.Info);
return hotkeyList;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"从配置文件读取快捷键信息时出错: {ex.Message}", LogHelper.LogType.Error);
return new List<HotkeyInfo>();
}
}
/// <summary>
/// 注册默认快捷键集合
/// </summary>
public void RegisterDefaultHotkeys()
{
try
{
// 开始注册默认快捷键集合
// 基本操作快捷键
RegisterHotkey("Undo", Key.Z, ModifierKeys.Control, () => _mainWindow.SymbolIconUndo_MouseUp(null, null));
RegisterHotkey("Redo", Key.Y, ModifierKeys.Control, () => _mainWindow.SymbolIconRedo_MouseUp(null, null));
RegisterHotkey("Clear", Key.E, ModifierKeys.Control, () => _mainWindow.SymbolIconDelete_MouseUp(null, null));
RegisterHotkey("Paste", Key.V, ModifierKeys.Control, () => _mainWindow.HandleGlobalPaste(null, null));
// 工具切换快捷键
RegisterHotkey("SelectTool", Key.S, ModifierKeys.Alt, () => _mainWindow.SymbolIconSelect_MouseUp(null, null));
RegisterHotkey("DrawTool", Key.D, ModifierKeys.Alt, () => _mainWindow.PenIcon_Click(null, null));
RegisterHotkey("EraserTool", Key.E, ModifierKeys.Alt, () => _mainWindow.EraserIcon_Click(null, null));
RegisterHotkey("BlackboardTool", Key.B, ModifierKeys.Alt, () => _mainWindow.ImageBlackboard_MouseUp(null, null));
RegisterHotkey("QuitDrawTool", Key.Q, ModifierKeys.Alt, () => _mainWindow.CursorIcon_Click(null, null));
// 画笔快捷键 - 使用反射访问penType字段
RegisterHotkey("Pen1", Key.D1, ModifierKeys.Alt, () => SwitchToPenType(0));
RegisterHotkey("Pen2", Key.D2, ModifierKeys.Alt, () => SwitchToPenType(1));
RegisterHotkey("Pen3", Key.D3, ModifierKeys.Alt, () => SwitchToPenType(2));
RegisterHotkey("Pen4", Key.D4, ModifierKeys.Alt, () => SwitchToPenType(3));
RegisterHotkey("Pen5", Key.D5, ModifierKeys.Alt, () => SwitchToPenType(4));
// 功能快捷键
RegisterHotkey("DrawLine", Key.L, ModifierKeys.Alt, () => _mainWindow.BtnDrawLine_Click(null, null));
RegisterHotkey("Screenshot", Key.C, ModifierKeys.Alt, () => _mainWindow.SaveScreenShotToDesktop());
RegisterHotkey("Hide", Key.V, ModifierKeys.Alt, () => _mainWindow.SymbolIconEmoji_MouseUp(null, null));
// 退出快捷键
RegisterHotkey("Exit", Key.Escape, ModifierKeys.None, () => _mainWindow.KeyExit(null, null));
// 已注册默认全局快捷键集合
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"注册默认快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 从配置文件加载快捷键
/// </summary>
public void LoadHotkeysFromSettings()
{
try
{
// 开始从配置文件加载快捷键设置
// 检查是否应该注册快捷键
if (!_hotkeysShouldBeRegistered)
{
// 当前状态不允许注册快捷键,跳过加载
return;
}
// 尝试从配置文件加载
if (LoadHotkeysFromConfigFile())
{
// 成功从配置文件加载快捷键设置
_hotkeysShouldBeRegistered = true;
LogHelper.WriteLogToFile("成功从配置文件加载快捷键设置", LogHelper.LogType.Info);
}
else
{
// 如果配置文件不存在,才使用默认快捷键
if (!File.Exists(HotkeyConfigFile))
{
LogHelper.WriteLogToFile("配置文件不存在,注册默认快捷键", LogHelper.LogType.Info);
RegisterDefaultHotkeys();
_hotkeysShouldBeRegistered = true;
}
else
{
LogHelper.WriteLogToFile("配置文件存在但加载失败,保持当前状态", LogHelper.LogType.Warning);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"从设置加载快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
// 出错时不自动使用默认快捷键,保持当前状态
}
}
/// <summary>
/// 保存快捷键配置到设置
/// </summary>
public void SaveHotkeysToSettings()
{
try
{
LogHelper.WriteLogToFile("开始保存快捷键配置到配置文件", LogHelper.LogType.Event);
if (SaveHotkeysToConfigFile())
{
LogHelper.WriteLogToFile("快捷键配置已成功保存到配置文件", LogHelper.LogType.Event);
}
else
{
LogHelper.WriteLogToFile("保存快捷键配置失败", LogHelper.LogType.Error);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"保存快捷键配置时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 启用快捷键注册功能
/// 调用此方法后,快捷键将被允许注册
/// </summary>
public void EnableHotkeyRegistration()
{
try
{
if (!_hotkeysShouldBeRegistered)
{
_hotkeysShouldBeRegistered = true;
LogHelper.WriteLogToFile("启用快捷键注册功能", LogHelper.LogType.Info);
// 立即加载快捷键设置
LoadHotkeysFromSettings();
}
else
{
LogHelper.WriteLogToFile("快捷键注册功能已经启用", LogHelper.LogType.Info);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"启用快捷键注册功能时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 更新快捷键配置
/// </summary>
/// <param name="hotkeyName">快捷键名称</param>
/// <param name="key">新按键</param>
/// <param name="modifiers">新修饰键</param>
/// <returns>是否更新成功</returns>
public bool UpdateHotkey(string hotkeyName, Key key, ModifierKeys modifiers)
{
try
{
if (!_registeredHotkeys.ContainsKey(hotkeyName))
{
LogHelper.WriteLogToFile($"快捷键 {hotkeyName} 不存在,无法更新", LogHelper.LogType.Warning);
return false;
}
// 获取原有的动作
var originalAction = _registeredHotkeys[hotkeyName].Action;
// 注销原有快捷键
UnregisterHotkey(hotkeyName);
// 注册新的快捷键
var success = RegisterHotkey(hotkeyName, key, modifiers, originalAction);
if (success)
{
LogHelper.WriteLogToFile($"成功更新快捷键 {hotkeyName}: {modifiers}+{key}", LogHelper.LogType.Event);
// 自动保存配置
SaveHotkeysToSettings();
}
return success;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新快捷键 {hotkeyName} 时出错: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
#endregion
#region Private Helper Methods
/// <summary>
/// 切换到指定笔类型
/// </summary>
/// <param name="penTypeIndex">笔类型索引</param>
private void SwitchToPenType(int penTypeIndex)
{
try
{
// 通过反射访问主窗口的penType字段
var penTypeField = _mainWindow.GetType().GetField("penType",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (penTypeField != null)
{
penTypeField.SetValue(_mainWindow, penTypeIndex);
// 调用CheckPenTypeUIState方法更新UI状态
var checkPenTypeMethod = _mainWindow.GetType().GetMethod("CheckPenTypeUIState",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (checkPenTypeMethod != null)
{
checkPenTypeMethod.Invoke(_mainWindow, null);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换到笔类型{penTypeIndex}时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 从配置文件加载快捷键设置
/// </summary>
/// <returns>是否加载成功</returns>
private bool LoadHotkeysFromConfigFile()
{
try
{
if (!File.Exists(HotkeyConfigFile))
{
LogHelper.WriteLogToFile($"快捷键配置文件不存在: {HotkeyConfigFile}", LogHelper.LogType.Warning);
return false;
}
// 读取配置文件内容
string jsonContent = File.ReadAllText(HotkeyConfigFile, System.Text.Encoding.UTF8);
if (string.IsNullOrEmpty(jsonContent))
{
LogHelper.WriteLogToFile("快捷键配置文件为空", LogHelper.LogType.Warning);
return false;
}
// 反序列化配置
var config = JsonConvert.DeserializeObject<HotkeyConfig>(jsonContent);
if (config?.Hotkeys == null || config.Hotkeys.Count == 0)
{
LogHelper.WriteLogToFile("快捷键配置为空或格式错误", LogHelper.LogType.Warning);
return false;
}
// 注册配置中的快捷键
int successCount = 0;
foreach (var hotkeyConfig in config.Hotkeys)
{
try
{
// 根据快捷键名称获取对应的动作
var action = GetActionByName(hotkeyConfig.Name);
if (action != null)
{
if (RegisterHotkey(hotkeyConfig.Name, hotkeyConfig.Key, hotkeyConfig.Modifiers, action))
{
successCount++;
}
}
else
{
LogHelper.WriteLogToFile($"未找到快捷键 {hotkeyConfig.Name} 对应的动作", LogHelper.LogType.Warning);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"注册快捷键 {hotkeyConfig.Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
LogHelper.WriteLogToFile($"成功加载 {successCount}/{config.Hotkeys.Count} 个快捷键配置", LogHelper.LogType.Event);
if (successCount > 0)
{
_hotkeysShouldBeRegistered = true;
}
return successCount > 0;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"从配置文件加载快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 保存快捷键配置到配置文件
/// </summary>
/// <returns>是否保存成功</returns>
private bool SaveHotkeysToConfigFile()
{
try
{
// 确保配置目录存在
string configDir = Path.GetDirectoryName(HotkeyConfigFile);
if (!Directory.Exists(configDir))
{
Directory.CreateDirectory(configDir);
}
// 创建配置对象
var config = new HotkeyConfig
{
Version = "1.0",
LastModified = DateTime.Now,
Hotkeys = new List<HotkeyConfigItem>()
};
// 添加所有已注册的快捷键
foreach (var hotkey in _registeredHotkeys.Values)
{
config.Hotkeys.Add(new HotkeyConfigItem
{
Name = hotkey.Name,
Key = hotkey.Key,
Modifiers = hotkey.Modifiers
});
}
// 序列化为JSON
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented
};
string jsonContent = JsonConvert.SerializeObject(config, settings);
// 直接写入原文件,覆盖原有内容
File.WriteAllText(HotkeyConfigFile, jsonContent, System.Text.Encoding.UTF8);
LogHelper.WriteLogToFile($"快捷键配置已保存到: {HotkeyConfigFile}", LogHelper.LogType.Event);
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"保存快捷键配置到配置文件时出错: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 根据快捷键名称获取对应的动作
/// </summary>
/// <param name="hotkeyName">快捷键名称</param>
/// <returns>对应的动作,如果不存在则返回null</returns>
private Action GetActionByName(string hotkeyName)
{
try
{
switch (hotkeyName)
{
case "Undo":
return () => _mainWindow.SymbolIconUndo_MouseUp(null, null);
case "Redo":
return () => _mainWindow.SymbolIconRedo_MouseUp(null, null);
case "Clear":
return () => _mainWindow.SymbolIconDelete_MouseUp(null, null);
case "Paste":
return () => _mainWindow.HandleGlobalPaste(null, null);
case "SelectTool":
return () => _mainWindow.SymbolIconSelect_MouseUp(null, null);
case "DrawTool":
return () => _mainWindow.PenIcon_Click(null, null);
case "EraserTool":
return () => _mainWindow.EraserIcon_Click(null, null);
case "BlackboardTool":
return () => _mainWindow.ImageBlackboard_MouseUp(null, null);
case "QuitDrawTool":
return () => _mainWindow.CursorIcon_Click(null, null);
case "Pen1":
return () => SwitchToPenType(0);
case "Pen2":
return () => SwitchToPenType(1);
case "Pen3":
return () => SwitchToPenType(2);
case "Pen4":
return () => SwitchToPenType(3);
case "Pen5":
return () => SwitchToPenType(4);
case "DrawLine":
return () => _mainWindow.BtnDrawLine_Click(null, null);
case "Screenshot":
return () => _mainWindow.SaveScreenShotToDesktop();
case "Hide":
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
case "Exit":
return () => _mainWindow.KeyExit(null, null);
default:
LogHelper.WriteLogToFile($"未知的快捷键名称: {hotkeyName}", LogHelper.LogType.Warning);
return null;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"获取快捷键 {hotkeyName} 对应动作时出错: {ex.Message}", LogHelper.LogType.Error);
return null;
}
}
/// <summary>
/// 检查当前是否处于鼠标模式(选择模式)
/// </summary>
/// <returns>如果处于鼠标模式则返回true(不应该注册快捷键),否则返回false(应该注册快捷键)</returns>
private bool IsInSelectMode()
{
try
{
// 通过反射访问主窗口的FloatingbarSelectionBG字段
var floatingbarSelectionBGField = _mainWindow.GetType().GetField("FloatingbarSelectionBG",
BindingFlags.NonPublic | BindingFlags.Instance);
if (floatingbarSelectionBGField != null)
{
var floatingbarSelectionBG = floatingbarSelectionBGField.GetValue(_mainWindow);
if (floatingbarSelectionBG != null)
{
// 检查高光是否可见
var visibilityProperty = floatingbarSelectionBG.GetType().GetProperty("Visibility");
if (visibilityProperty != null)
{
var visibility = visibilityProperty.GetValue(floatingbarSelectionBG);
if (visibility != null && visibility.ToString() == "Hidden")
{
// 高光隐藏,说明没有选中任何工具,此时应该注销快捷键以释放系统快捷键
return true; // 返回true表示应该注销快捷键
}
}
// 通过反射访问Canvas.GetLeft方法来获取高光位置
var canvasType = Type.GetType("System.Windows.Controls.Canvas, PresentationFramework");
if (canvasType != null)
{
var getLeftMethod = canvasType.GetMethod("GetLeft", BindingFlags.Public | BindingFlags.Static);
if (getLeftMethod != null)
{
var leftPosition = getLeftMethod.Invoke(null, new object[] { floatingbarSelectionBG });
if (leftPosition != null)
{
var position = Convert.ToDouble(leftPosition);
// 根据高光位置判断当前选中的工具
// 位置计算基于SetFloatingBarHighlightPosition方法中的逻辑
bool isMouseMode = false;
string currentTool = "unknown";
// 简化判断:如果位置接近0,说明是鼠标模式
// 如果位置接近28,说明是批注模式
// 如果位置更大,说明是其他工具
if (position < 5) // 鼠标模式:marginOffset + (cursorWidth - actualHighlightWidth) / 2 ≈ 0
{
isMouseMode = true;
currentTool = "鼠标";
}
else if (position < 35) // 批注模式:marginOffset + cursorWidth + (penWidth - actualHighlightWidth) / 2 ≈ 28
{
isMouseMode = false;
currentTool = "批注";
}
else // 其他工具(橡皮擦、选择等)
{
isMouseMode = false;
currentTool = "其他工具";
}
return isMouseMode;
}
}
}
}
}
// 如果无法获取高光状态,则回退到inkCanvas.EditingMode判断
// 通过反射访问主窗口的inkCanvas字段
var inkCanvasField = _mainWindow.GetType().GetField("inkCanvas",
BindingFlags.NonPublic | BindingFlags.Instance);
if (inkCanvasField != null)
{
var inkCanvas = inkCanvasField.GetValue(_mainWindow);
if (inkCanvas != null)
{
// 通过反射访问inkCanvas的EditingMode属性
var editingModeProperty = inkCanvas.GetType().GetProperty("EditingMode");
if (editingModeProperty != null)
{
var editingMode = editingModeProperty.GetValue(inkCanvas);
if (editingMode != null)
{
// 检查是否为批注模式
var isInkMode = editingMode.ToString().Contains("Ink");
var isSelectMode = editingMode.ToString().Contains("Select");
// 如果是批注模式或选择模式,则应该注册快捷键(返回false)
// 如果是橡皮擦模式或其他模式,则不应该注册快捷键(返回true)
var shouldNotRegisterHotkeys = !isInkMode && !isSelectMode;
return shouldNotRegisterHotkeys;
}
}
}
}
// 如果无法获取任何状态信息,则回退到原来的判断逻辑
// 通过反射访问主窗口的currentMode字段(作为最后的备用方案)
var currentModeField = _mainWindow.GetType().GetField("currentMode",
BindingFlags.NonPublic | BindingFlags.Instance);
if (currentModeField != null)
{
var currentMode = currentModeField.GetValue(_mainWindow);
if (currentMode != null)
{
var modeValue = currentMode.ToString();
// 注意:这里的逻辑需要修正
// currentMode == 0 表示屏幕模式(PPT放映),此时应该允许快捷键
// currentMode == 1 表示黑板/白板模式,此时也应该允许快捷键
var isSelectMode = false; // 修正:所有模式都应该允许快捷键
return isSelectMode;
}
}
return false; // 默认允许快捷键
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"检查鼠标模式状态时出错: {ex.Message}", LogHelper.LogType.Warning);
return false; // 出错时默认允许快捷键
}
}
/// <summary>
/// 动态管理快捷键注册状态
/// 根据当前工具选择状态自动注册或注销快捷键
/// </summary>
public void UpdateHotkeyRegistrationState()
{
try
{
bool isMouseMode = IsInSelectMode();
if (isMouseMode)
{
// 在鼠标模式下,注销所有快捷键以释放系统快捷键
if (_hotkeysShouldBeRegistered)
{
UnregisterAllHotkeys();
_hotkeysShouldBeRegistered = false;
}
else
{
// 快捷键已经处于注销状态,无需重复注销
}
}
else
{
// 在批注/选择/其他工具模式下,重新注册所有快捷键
if (!_hotkeysShouldBeRegistered)
{
// 第一次切换到批注/选择/其他工具模式,启用快捷键注册
EnableHotkeyRegistration();
}
else if (_registeredHotkeys.Count == 0)
{
// 快捷键已启用但数量为0,重新注册
LoadHotkeysFromSettings();
}
else
{
// 当前已有快捷键注册,无需重新注册
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新快捷键注册状态时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
#region IDisposable Implementation
public void Dispose()
{
if (!_isDisposed)
{
UnregisterAllHotkeys();
_isDisposed = true;
}
}
#endregion
#region Nested Classes
/// <summary>
/// 快捷键信息类
/// </summary>
public class HotkeyInfo
{
public string Name { get; set; }
public Key Key { get; set; }
public ModifierKeys Modifiers { get; set; }
public Action Action { get; set; }
public override string ToString()
{
var modifiersText = Modifiers == ModifierKeys.None ? "" : $"{Modifiers}+";
return $"{modifiersText}{Key}";
}
}
/// <summary>
/// 快捷键配置类
/// </summary>
private class HotkeyConfig
{
public string Version { get; set; }
public DateTime LastModified { get; set; }
public List<HotkeyConfigItem> Hotkeys { get; set; }
}
/// <summary>
/// 快捷键配置项类
/// </summary>
private class HotkeyConfigItem
{
public string Name { get; set; }
public Key Key { get; set; }
public ModifierKeys Modifiers { get; set; }
}
#endregion
}
}
+833
View File
@@ -0,0 +1,833 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.Windows.Ink;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Ink_Canvas.Helpers
{
/// <summary>
/// 墨迹渐隐管理器 - 管理墨迹的渐隐动画和状态
/// </summary>
public class InkFadeManager
{
#region Properties
/// <summary>
/// 是否启用墨迹渐隐功能
/// </summary>
public bool IsEnabled { get; set; } = false;
/// <summary>
/// 墨迹渐隐时间(毫秒)
/// </summary>
public int FadeTime { get; set; } = 3000;
/// <summary>
/// 渐隐动画持续时间(毫秒)
/// </summary>
public int AnimationDuration { get; set; } = 1000;
#endregion
#region Private Fields
private readonly MainWindow _mainWindow;
private readonly Dispatcher _dispatcher;
private readonly Dictionary<Stroke, DispatcherTimer> _fadeTimers;
private readonly Dictionary<Stroke, UIElement> _strokeVisuals;
private readonly Dictionary<Stroke, Point> _strokeStartPoints;
private readonly Dictionary<Stroke, Point> _strokeEndPoints;
#endregion
#region Constructor
public InkFadeManager(MainWindow mainWindow)
{
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
_dispatcher = _mainWindow.Dispatcher;
_fadeTimers = new Dictionary<Stroke, DispatcherTimer>();
_strokeVisuals = new Dictionary<Stroke, UIElement>();
_strokeStartPoints = new Dictionary<Stroke, Point>();
_strokeEndPoints = new Dictionary<Stroke, Point>();
}
#endregion
#region Public Methods
/// <summary>
/// 添加需要渐隐的墨迹
/// </summary>
/// <param name="stroke">墨迹对象</param>
/// <param name="startPoint">落笔点</param>
/// <param name="endPoint">抬笔点</param>
public void AddFadingStroke(Stroke stroke, Point startPoint, Point endPoint)
{
if (!IsEnabled || stroke == null)
{
return;
}
try
{
// 记录墨迹的起点和终点
_strokeStartPoints[stroke] = startPoint;
_strokeEndPoints[stroke] = endPoint;
// 创建墨迹的视觉元素(湿墨迹状态)
var strokeVisual = CreateStrokeVisual(stroke);
if (strokeVisual == null) return;
_strokeVisuals[stroke] = strokeVisual;
// 创建定时器,在指定时间后开始渐隐动画
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(FadeTime)
};
timer.Tick += (sender, e) =>
{
StartFadeAnimation(stroke);
timer.Stop();
_fadeTimers.Remove(stroke);
};
_fadeTimers[stroke] = timer;
timer.Start();
// 将视觉元素添加到画布上
_dispatcher.InvokeAsync(() =>
{
try
{
if (_mainWindow.inkCanvas != null)
{
// 将墨迹添加到 inkCanvas 的父容器中,而不是 inkCanvas.Children
// 这样可以避免坐标系统问题
var parent = _mainWindow.inkCanvas.Parent as System.Windows.Controls.Panel;
if (parent != null)
{
parent.Children.Add(strokeVisual);
}
else
{
// 如果无法获取父容器,则添加到 inkCanvas.Children
_mainWindow.inkCanvas.Children.Add(strokeVisual);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"添加墨迹视觉元素到画布失败: {ex}", LogHelper.LogType.Error);
}
});
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"添加渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 移除墨迹
/// </summary>
/// <param name="stroke">要移除的墨迹</param>
public void RemoveStroke(Stroke stroke)
{
if (stroke == null) return;
try
{
if (_fadeTimers.TryGetValue(stroke, out var timer))
{
timer.Stop();
_fadeTimers.Remove(stroke);
}
if (_strokeVisuals.TryGetValue(stroke, out var visual))
{
_dispatcher.InvokeAsync(() =>
{
try
{
// 从父容器中移除墨迹
var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.Panel;
if (parent != null && parent.Children.Contains(visual))
{
parent.Children.Remove(visual);
}
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual))
{
_mainWindow.inkCanvas.Children.Remove(visual);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"从画布移除墨迹视觉元素失败: {ex}", LogHelper.LogType.Error);
}
});
_strokeVisuals.Remove(stroke);
}
_strokeStartPoints.Remove(stroke);
_strokeEndPoints.Remove(stroke);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"移除渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 清除所有渐隐墨迹
/// </summary>
public void ClearAllFadingStrokes()
{
try
{
foreach (var timer in _fadeTimers.Values)
{
timer.Stop();
}
_fadeTimers.Clear();
_dispatcher.InvokeAsync(() =>
{
try
{
if (_mainWindow.inkCanvas != null)
{
var parent = _mainWindow.inkCanvas.Parent as System.Windows.Controls.Panel;
foreach (var visual in _strokeVisuals.Values)
{
if (parent != null && parent.Children.Contains(visual))
{
parent.Children.Remove(visual);
}
else if (_mainWindow.inkCanvas.Children.Contains(visual))
{
_mainWindow.inkCanvas.Children.Remove(visual);
}
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清除所有墨迹视觉元素失败: {ex}", LogHelper.LogType.Error);
}
});
_strokeVisuals.Clear();
_strokeStartPoints.Clear();
_strokeEndPoints.Clear();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清除所有渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 更新渐隐时间设置
/// </summary>
/// <param name="fadeTime">新的渐隐时间(毫秒)</param>
public void UpdateFadeTime(int fadeTime)
{
FadeTime = fadeTime;
foreach (var kvp in _fadeTimers)
{
var stroke = kvp.Key;
var timer = kvp.Value;
timer.Stop();
timer.Interval = TimeSpan.FromMilliseconds(FadeTime);
timer.Start();
}
}
/// <summary>
/// 启用墨迹渐隐功能
/// </summary>
public void Enable()
{
IsEnabled = true;
LogHelper.WriteLogToFile("墨迹渐隐功能已启用", LogHelper.LogType.Info);
}
/// <summary>
/// 禁用墨迹渐隐功能
/// </summary>
public void Disable()
{
IsEnabled = false;
LogHelper.WriteLogToFile("墨迹渐隐功能已禁用", LogHelper.LogType.Info);
}
#endregion
#region Private Methods
/// <summary>
/// 创建墨迹的视觉元素
/// </summary>
/// <param name="stroke">墨迹对象</param>
/// <returns>视觉元素</returns>
private UIElement CreateStrokeVisual(Stroke stroke)
{
try
{
// 创建路径几何,使用墨迹的实际位置
var geometry = stroke.GetGeometry();
if (geometry == null)
{
return null;
}
// 获取绘画属性
var drawingAttribs = stroke.DrawingAttributes;
// 创建路径元素,确保使用正确的绘画属性
var path = new Path
{
Data = geometry,
Stroke = new SolidColorBrush(drawingAttribs.Color),
StrokeThickness = drawingAttribs.Width, // 使用原始墨迹的粗细
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
StrokeLineJoin = PenLineJoin.Round,
Fill = drawingAttribs.IsHighlighter ? new SolidColorBrush(drawingAttribs.Color) : null, // 高亮笔需要填充
Opacity = 0.95, // 初始透明度更高,显得更自然
// 优化渲染质量
UseLayoutRounding = false,
SnapsToDevicePixels = false
};
// 如果是高亮笔,调整透明度和混合模式
if (drawingAttribs.IsHighlighter)
{
path.Opacity = 0.4; // 高亮笔初始透明度更低,更符合荧光笔特性
// 为高亮笔添加特殊的混合效果
// 使用更柔和的笔触样式
path.StrokeStartLineCap = PenLineCap.Flat;
path.StrokeEndLineCap = PenLineCap.Flat;
path.StrokeLineJoin = PenLineJoin.Miter;
// 高亮笔通常需要更宽的笔触来覆盖下面的内容
if (drawingAttribs.Width < 20)
{
path.StrokeThickness = Math.Max(drawingAttribs.Width * 1.5, 20);
}
}
// 不设置任何变换,保持墨迹原有粗细
var bounds = geometry.Bounds;
// 设置墨迹的初始位置
System.Windows.Controls.Canvas.SetLeft(path, bounds.Left);
System.Windows.Controls.Canvas.SetTop(path, bounds.Top);
return path;
}
catch (Exception ex)
{
return null;
}
}
/// <summary>
/// 开始渐隐动画
/// </summary>
/// <param name="stroke">要渐隐的墨迹</param>
private void StartFadeAnimation(Stroke stroke)
{
if (!_strokeVisuals.TryGetValue(stroke, out var visual)) return;
try
{
_dispatcher.InvokeAsync(() =>
{
// 获取当前透明度和判断是否为高亮笔
var currentOpacity = visual.Opacity;
var isHighlighter = stroke.DrawingAttributes.IsHighlighter;
// 根据墨迹类型选择不同的动画效果
if (isHighlighter)
{
StartHighlighterFadeAnimation(visual, stroke, currentOpacity);
}
else
{
StartNormalStrokeFadeAnimation(visual, stroke, currentOpacity);
}
});
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"开始渐隐动画失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 开始普通墨迹的渐隐动画
/// </summary>
private void StartNormalStrokeFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity)
{
try
{
StartProgressiveFadeAnimation(visual, stroke, currentOpacity, AnimationDuration);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"开始普通墨迹渐隐动画失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 开始高亮笔的渐隐动画
/// </summary>
private void StartHighlighterFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity)
{
try
{
StartProgressiveFadeAnimation(visual, stroke, currentOpacity, (int)(AnimationDuration * 1.5));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"开始高亮笔渐隐动画失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 渐进式渐隐动画 - 从起点到终点逐渐消失
/// </summary>
private void StartProgressiveFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration)
{
try
{
// 确保所有墨迹都能显示动画,包括短墨迹
if (stroke.StylusPoints.Count < 2)
{
// 只有1个点的墨迹也使用分段动画,确保视觉效果
CreateSegmentedStroke(visual, stroke, currentOpacity, duration);
return;
}
// 将墨迹分段并创建多个 Path
CreateSegmentedStroke(visual, stroke, currentOpacity, duration);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"渐进式渐隐动画失败: {ex}", LogHelper.LogType.Error);
// 失败时回退到简单动画
StartSimpleFadeAnimation(visual, stroke, currentOpacity, duration);
}
}
/// <summary>
/// 创建分段墨迹并开始渐进消失
/// </summary>
private void CreateSegmentedStroke(UIElement originalVisual, Stroke stroke, double opacity, int duration)
{
try
{
var stylusPoints = stroke.StylusPoints;
var totalPoints = stylusPoints.Count;
// 分段算法 - 确保所有墨迹都有足够的动画效果
var strokeLength = CalculateStrokeLength(stylusPoints);
var segmentCount = CalculateOptimalSegmentCount(totalPoints, strokeLength);
// 强制最小分段数量,确保短墨迹也有动画效果
segmentCount = Math.Max(segmentCount, 4);
var pointsPerSegment = Math.Max(1, totalPoints / segmentCount);
// 隐藏原始视觉元素
originalVisual.Visibility = Visibility.Hidden;
var segments = new List<UIElement>();
var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.Panel;
if (parent == null)
{
// 如果父容器不是Panel,直接使用InkCanvas
parent = null; // 稍后会检查并使用InkCanvas.Children
}
// 创建各个分段 - 确保短墨迹也能正确分段
for (int i = 0; i < segmentCount; i++)
{
var startIndex = i * pointsPerSegment;
var endIndex = (i == segmentCount - 1) ? totalPoints - 1 : (i + 1) * pointsPerSegment;
// 确保有足够的点来创建分段,对于短墨迹特殊处理
if (endIndex <= startIndex && totalPoints > 1)
{
// 短墨迹:每个点作为一个分段
startIndex = i;
endIndex = Math.Min(i + 1, totalPoints - 1);
}
// 为每个分段添加重叠,确保连接处平滑
var overlap = Math.Max(1, pointsPerSegment / 6); // 15%的重叠,平衡平滑与速度
var actualStartIndex = Math.Max(0, startIndex - overlap);
var actualEndIndex = Math.Min(totalPoints - 1, endIndex + overlap);
var segment = CreateStrokeSegment(stroke, actualStartIndex, actualEndIndex, opacity);
if (segment != null)
{
segments.Add(segment);
if (parent != null)
{
parent.Children.Add(segment);
}
else if (_mainWindow.inkCanvas != null)
{
_mainWindow.inkCanvas.Children.Add(segment);
}
}
}
// 开始分段渐隐动画
StartSegmentedFadeAnimation(segments, stroke, originalVisual, duration);
}
catch (Exception ex)
{
StartSimpleFadeAnimation(originalVisual, stroke, opacity, duration);
}
}
/// <summary>
/// 创建墨迹分段
/// </summary>
private UIElement CreateStrokeSegment(Stroke originalStroke, int startIndex, int endIndex, double opacity)
{
try
{
// 创建分段的 StylusPoint 集合
var segmentPoints = new StylusPointCollection();
for (int i = startIndex; i <= endIndex && i < originalStroke.StylusPoints.Count; i++)
{
segmentPoints.Add(originalStroke.StylusPoints[i]);
}
if (segmentPoints.Count < 2) return null;
// 创建分段墨迹
var segmentStroke = new Stroke(segmentPoints)
{
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
};
// 创建分段的视觉元素
var geometry = segmentStroke.GetGeometry();
if (geometry == null) return null;
var drawingAttribs = segmentStroke.DrawingAttributes;
var path = new Path
{
Data = geometry,
Stroke = new SolidColorBrush(drawingAttribs.Color),
StrokeThickness = drawingAttribs.Width,
StrokeStartLineCap = drawingAttribs.IsHighlighter ? PenLineCap.Flat : PenLineCap.Round,
StrokeEndLineCap = drawingAttribs.IsHighlighter ? PenLineCap.Flat : PenLineCap.Round,
StrokeLineJoin = drawingAttribs.IsHighlighter ? PenLineJoin.Miter : PenLineJoin.Round,
Fill = drawingAttribs.IsHighlighter ? new SolidColorBrush(drawingAttribs.Color) : null,
Opacity = opacity,
UseLayoutRounding = false,
SnapsToDevicePixels = false
};
// 设置位置
var bounds = geometry.Bounds;
System.Windows.Controls.Canvas.SetLeft(path, bounds.Left);
System.Windows.Controls.Canvas.SetTop(path, bounds.Top);
return path;
}
catch (Exception ex)
{
return null;
}
}
/// <summary>
/// 开始分段渐隐动画
/// </summary>
private void StartSegmentedFadeAnimation(List<UIElement> segments, Stroke originalStroke, UIElement originalVisual, int totalDuration)
{
try
{
// 动画时序算法
var segmentDuration = CalculateOptimalSegmentDuration(totalDuration, segments.Count);
var animationCurve = CreateAppleStyleAnimationCurve(segments.Count, totalDuration);
// 跟踪动画完成状态
var completedSegments = new HashSet<UIElement>();
var totalSegments = segments.Count;
// 渐隐效果 - 使用自然的动画曲线
for (int i = 0; i < segments.Count; i++)
{
var segment = segments[i];
// 使用预计算的动画曲线获取延迟时间
var delay = animationCurve[i];
// 使用定时器延迟启动每个分段的动画
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(delay)
};
int segmentIndex = i; // 捕获当前索引
timer.Tick += (sender, e) =>
{
StartSingleSegmentFadeAnimation(segment, segmentDuration, () =>
{
// 动画完成回调
lock (completedSegments)
{
completedSegments.Add(segment);
// 检查是否所有分段都完成了
if (completedSegments.Count >= totalSegments)
{
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
}
}
});
timer.Stop();
};
timer.Start();
}
// 设置一个安全超时定时器,防止无限等待
var safetyTimeout = totalDuration + (segments.Count * segmentDuration) + 1200; // 额外1.2秒缓冲,确保动画完整
var safetyTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(safetyTimeout)
};
safetyTimer.Tick += (sender, e) =>
{
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
safetyTimer.Stop();
};
safetyTimer.Start();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"分段渐隐动画失败: {ex}", LogHelper.LogType.Error);
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
}
}
/// <summary>
/// 单个分段的渐隐动画
/// </summary>
private void StartSingleSegmentFadeAnimation(UIElement segment, int duration, Action onCompleted = null)
{
try
{
// 只使用透明度动画,保持墨迹原有粗细
var fadeAnimation = new DoubleAnimation
{
From = segment.Opacity,
To = 0.0,
Duration = TimeSpan.FromMilliseconds(duration),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } // 更平滑的缓动
};
// 添加动画完成事件
if (onCompleted != null)
{
fadeAnimation.Completed += (sender, e) =>
{
onCompleted?.Invoke();
};
}
// 只应用透明度动画,不改变墨迹大小
segment.BeginAnimation(UIElement.OpacityProperty, fadeAnimation);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"单个分段渐隐动画失败: {ex}", LogHelper.LogType.Error);
// 即使失败也要调用完成回调
onCompleted?.Invoke();
}
}
/// <summary>
/// 清理分段动画
/// </summary>
private void CleanupSegmentedAnimation(List<UIElement> segments, Stroke originalStroke, UIElement originalVisual)
{
try
{
// 移除所有分段
var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.Panel;
foreach (var segment in segments)
{
if (parent != null && parent.Children.Contains(segment))
{
parent.Children.Remove(segment);
}
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(segment))
{
_mainWindow.inkCanvas.Children.Remove(segment);
}
}
// 清理原始墨迹
OnAnimationCompleted(originalVisual, originalStroke);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理分段动画失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 简单渐隐动画(备用方案)
/// </summary>
private void StartSimpleFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration)
{
try
{
var fadeAnimation = new DoubleAnimation
{
From = currentOpacity,
To = 0.0,
Duration = TimeSpan.FromMilliseconds(duration),
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }
};
fadeAnimation.Completed += (sender, e) => OnAnimationCompleted(visual, stroke);
visual.BeginAnimation(UIElement.OpacityProperty, fadeAnimation);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"简单渐隐动画失败: {ex}", LogHelper.LogType.Error);
OnAnimationCompleted(visual, stroke);
}
}
/// <summary>
/// 计算墨迹的实际长度
/// </summary>
private double CalculateStrokeLength(StylusPointCollection points)
{
if (points.Count < 2) return 0;
double totalLength = 0;
for (int i = 1; i < points.Count; i++)
{
var p1 = points[i - 1].ToPoint();
var p2 = points[i].ToPoint();
totalLength += Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
}
return totalLength;
}
/// <summary>
/// 根据墨迹特性计算最优分段数量 - 平衡速度与完整性
/// </summary>
private int CalculateOptimalSegmentCount(int pointCount, double strokeLength)
{
// 平衡速度与完整性,确保动画效果的同时提高速度
const double PIXELS_PER_SEGMENT = 12.0; // 每段适中长度,平衡效果与速度
const int MIN_SEGMENTS = 5; // 适当的最小分段数,确保动画效果
const int MAX_SEGMENTS = 100; // 适中的最大分段数,平衡性能与效果
// 根据长度计算基础分段数
var lengthBasedSegments = Math.Max(MIN_SEGMENTS, (int)(strokeLength / PIXELS_PER_SEGMENT));
// 根据点密度调整,平衡效果与速度
var density = pointCount > 0 ? strokeLength / pointCount : 1;
var densityFactor = Math.Max(0.4, Math.Min(2.5, density / 1.8));
var finalSegments = (int)(lengthBasedSegments * densityFactor);
// 对于短墨迹,确保至少有4个分段
if (pointCount <= 5)
{
finalSegments = Math.Max(finalSegments, 4);
}
// 限制在合理范围内
return Math.Min(MAX_SEGMENTS, Math.Max(MIN_SEGMENTS, finalSegments));
}
/// <summary>
/// 计算最优的单段动画持续时间 - 平衡速度与完整性
/// </summary>
private int CalculateOptimalSegmentDuration(int totalDuration, int segmentCount)
{
// 平衡速度与动画完整性
var baseDuration = totalDuration / Math.Max(segmentCount, 1);
var minDuration = 150; // 每段最少150ms,确保动画完整显示
var maxDuration = 500; // 每段最多500ms,平衡速度与完整性
return Math.Max(minDuration, Math.Min(maxDuration, baseDuration));
}
/// <summary>
/// 创建优化的动画时间曲线 - 平衡速度与完整性
/// </summary>
private int[] CreateAppleStyleAnimationCurve(int segmentCount, int totalDuration)
{
var curve = new int[segmentCount];
// 平衡速度与完整性,确保动画有足够时间播放
var availableTime = totalDuration * 0.6; // 使用60%的总时间,给动画留足够缓冲
var delayBetweenSegments = Math.Max(60, availableTime / Math.Max(segmentCount, 1));
for (int i = 0; i < segmentCount; i++)
{
// 线性延迟,确保每个分段都有足够时间
curve[i] = (int)(i * delayBetweenSegments);
}
return curve;
}
/// <summary>
/// 动画完成后的统一处理
/// </summary>
private void OnAnimationCompleted(UIElement visual, Stroke stroke)
{
try
{
// 从父容器中移除墨迹
var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.Panel;
if (parent != null && parent.Children.Contains(visual))
{
parent.Children.Remove(visual);
}
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual))
{
_mainWindow.inkCanvas.Children.Remove(visual);
}
RemoveStroke(stroke);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"渐隐动画完成后清理墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
#endregion
}
}
+21 -3
View File
@@ -55,6 +55,13 @@ namespace Ink_Canvas.Helpers
{
try
{
// 完全清理之前的墨迹状态
ClearAllStrokes();
// 重置墨迹锁定状态
_inkLockUntil = DateTime.MinValue;
_lockedSlideIndex = -1;
// 生成演示文稿唯一标识符
_currentPresentationId = GeneratePresentationId(presentation);
@@ -152,17 +159,28 @@ namespace Ink_Canvas.Helpers
{
try
{
// 如果有当前墨迹,先保存
// 如果有当前墨迹,先保存到正确的页面
if (currentStrokes != null && currentStrokes.Count > 0)
{
SaveCurrentSlideStrokes(_lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex, currentStrokes);
// 确定要保存的页面索引
int saveToSlideIndex = _lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex;
// 确保页面索引有效
if (saveToSlideIndex > 0 && saveToSlideIndex < _memoryStreams.Length)
{
SaveCurrentSlideStrokes(saveToSlideIndex, currentStrokes);
LogHelper.WriteLogToFile($"已保存第{saveToSlideIndex}页墨迹,墨迹数量: {currentStrokes.Count}", LogHelper.LogType.Trace);
}
}
// 设置墨迹锁定
LockInkForSlide(slideIndex);
// 加载新页面的墨迹
return LoadSlideStrokes(slideIndex);
var newStrokes = LoadSlideStrokes(slideIndex);
LogHelper.WriteLogToFile($"已切换到第{slideIndex}页,加载墨迹数量: {newStrokes.Count}", LogHelper.LogType.Trace);
return newStrokes;
}
catch (Exception ex)
{
+46
View File
@@ -25,6 +25,7 @@ namespace Ink_Canvas.Helpers
public event Action<Presentation> PresentationOpen;
public event Action<Presentation> PresentationClose;
public event Action<bool> PPTConnectionChanged;
public event Action<bool> SlideShowStateChanged;
#endregion
#region Properties
@@ -92,6 +93,7 @@ namespace Ink_Canvas.Helpers
#region Private Fields
private Timer _connectionCheckTimer;
private Timer _slideShowStateCheckTimer;
private Timer _wpsProcessCheckTimer;
private Process _wpsProcess;
private bool _hasWpsProcessId;
@@ -99,6 +101,7 @@ namespace Ink_Canvas.Helpers
private int _wpsProcessCheckCount;
private WpsWindowInfo _lastForegroundWpsWindow;
private DateTime _lastWindowCheckTime = DateTime.MinValue;
private bool _lastSlideShowState = false;
private readonly object _lockObject = new object();
private bool _disposed = false;
#endregion
@@ -114,6 +117,10 @@ namespace Ink_Canvas.Helpers
_connectionCheckTimer = new Timer(500);
_connectionCheckTimer.Elapsed += OnConnectionCheckTimerElapsed;
_connectionCheckTimer.AutoReset = true;
_slideShowStateCheckTimer = new Timer(1000);
_slideShowStateCheckTimer.Elapsed += OnSlideShowStateCheckTimerElapsed;
_slideShowStateCheckTimer.AutoReset = true;
}
public void StartMonitoring()
@@ -121,6 +128,7 @@ namespace Ink_Canvas.Helpers
if (!_disposed)
{
_connectionCheckTimer?.Start();
_slideShowStateCheckTimer?.Start();
LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Trace);
}
}
@@ -128,6 +136,7 @@ namespace Ink_Canvas.Helpers
public void StopMonitoring()
{
_connectionCheckTimer?.Stop();
_slideShowStateCheckTimer?.Stop();
DisconnectFromPPT();
LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Trace);
}
@@ -146,6 +155,18 @@ namespace Ink_Canvas.Helpers
}
}
private void OnSlideShowStateCheckTimerElapsed(object sender, ElapsedEventArgs e)
{
try
{
CheckSlideShowState();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"PPT放映状态检查失败: {ex}", LogHelper.LogType.Error);
}
}
private void CheckAndConnectToPPT()
{
lock (_lockObject)
@@ -187,6 +208,30 @@ namespace Ink_Canvas.Helpers
}
}
private void CheckSlideShowState()
{
try
{
if (!IsConnected) return;
var currentSlideShowState = IsInSlideShow;
if (currentSlideShowState != _lastSlideShowState)
{
_lastSlideShowState = currentSlideShowState;
SlideShowStateChanged?.Invoke(currentSlideShowState);
if (!currentSlideShowState)
{
LogHelper.WriteLogToFile("检测到PPT放映已结束", LogHelper.LogType.Trace);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"检查PPT放映状态异常: {ex}", LogHelper.LogType.Error);
}
}
private Microsoft.Office.Interop.PowerPoint.Application TryConnectToPowerPoint()
{
try
@@ -1627,6 +1672,7 @@ namespace Ink_Canvas.Helpers
StopWpsProcessCheckTimer();
_connectionCheckTimer?.Dispose();
_slideShowStateCheckTimer?.Dispose();
_wpsProcessCheckTimer?.Dispose();
_disposed = true;
+34 -1
View File
@@ -18,7 +18,10 @@ namespace Ink_Canvas.Helpers
public int PPTBButtonsOption { get; set; } = 121;
public int PPTLSButtonPosition { get; set; } = 0;
public int PPTRSButtonPosition { get; set; } = 0;
public int PPTLBButtonPosition { get; set; } = 0;
public int PPTRBButtonPosition { get; set; } = 0;
public bool EnablePPTButtonPageClickable { get; set; } = true;
public bool EnablePPTButtonLongPressPageTurn { get; set; } = true;
#endregion
#region Private Fields
@@ -120,6 +123,29 @@ namespace Ink_Canvas.Helpers
});
}
/// <summary>
/// 处理PPT放映状态变化
/// </summary>
public void OnSlideShowStateChanged(bool isInSlideShow)
{
_dispatcher.InvokeAsync(() =>
{
try
{
if (!isInSlideShow)
{
// 如果不在放映模式,隐藏所有导航面板
HideAllNavigationPanels();
LogHelper.WriteLogToFile("PPT放映状态变化:隐藏导航面板", LogHelper.LogType.Trace);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理PPT放映状态变化失败: {ex}", LogHelper.LogType.Error);
}
});
}
/// <summary>
/// 更新导航面板显示状态
/// </summary>
@@ -130,7 +156,10 @@ namespace Ink_Canvas.Helpers
try
{
// 检查是否应该显示PPT按钮
bool shouldShowButtons = ShowPPTButton && _mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible;
// 不仅要检查按钮设置,还要确保确实在PPT放映模式下
bool shouldShowButtons = ShowPPTButton &&
_mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
_mainWindow.PPTManager?.IsInSlideShow == true;
if (!shouldShowButtons)
{
@@ -142,6 +171,10 @@ namespace Ink_Canvas.Helpers
_mainWindow.LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTLSButtonPosition * 2);
_mainWindow.RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTRSButtonPosition * 2);
// 设置底部按钮水平位置
_mainWindow.LeftBottomPanelForPPTNavigation.Margin = new Thickness(6 + PPTLBButtonPosition, 0, 0, 6);
_mainWindow.RightBottomPanelForPPTNavigation.Margin = new Thickness(0, 0, 6 + PPTRBButtonPosition, 6);
// 根据显示选项设置面板可见性
var displayOption = PPTButtonsDisplayOption.ToString();
if (displayOption.Length >= 4)
+96
View File
@@ -0,0 +1,96 @@
{
"Version": "1.0",
"LastModified": "2025-01-28T15:30:00",
"Hotkeys": [
{
"Name": "Undo",
"Key": "Z",
"Modifiers": "Control"
},
{
"Name": "Redo",
"Key": "Y",
"Modifiers": "Control"
},
{
"Name": "Clear",
"Key": "E",
"Modifiers": "Control"
},
{
"Name": "Paste",
"Key": "V",
"Modifiers": "Control"
},
{
"Name": "SelectTool",
"Key": "S",
"Modifiers": "Alt"
},
{
"Name": "DrawTool",
"Key": "D",
"Modifiers": "Alt"
},
{
"Name": "EraserTool",
"Key": "E",
"Modifiers": "Alt"
},
{
"Name": "BlackboardTool",
"Key": "B",
"Modifiers": "Alt"
},
{
"Name": "QuitDrawTool",
"Key": "Q",
"Modifiers": "Alt"
},
{
"Name": "Pen1",
"Key": "D1",
"Modifiers": "Alt"
},
{
"Name": "Pen2",
"Key": "D2",
"Modifiers": "Alt"
},
{
"Name": "Pen3",
"Key": "D3",
"Modifiers": "Alt"
},
{
"Name": "Pen4",
"Key": "D4",
"Modifiers": "Alt"
},
{
"Name": "Pen5",
"Key": "D5",
"Modifiers": "Alt"
},
{
"Name": "DrawLine",
"Key": "L",
"Modifiers": "Alt"
},
{
"Name": "Screenshot",
"Key": "C",
"Modifiers": "Alt"
},
{
"Name": "Hide",
"Key": "V",
"Modifiers": "Alt"
},
{
"Name": "Exit",
"Key": "Escape",
"Modifiers": "None"
}
]
}
+350 -126
View File
@@ -832,6 +832,27 @@
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchAdvancedBezierSmoothing_Toggled" />
</ui:SimpleStackPanel>
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
StrokeThickness="1" Margin="0,4,0,4" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="启用墨迹渐隐功能" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchEnableInkFade"
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchEnableInkFade_Toggled" />
</ui:SimpleStackPanel>
<TextBlock Text="# 开启后墨迹不会绘制到画布上,而是保持湿墨迹状态,根据设置的渐隐时间自动消失" TextWrapping="Wrap" Foreground="#a1a1aa" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
Visibility="{Binding ElementName=ToggleSwitchEnableInkFade, Path=IsOn, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Foreground="#fafafa" Text="墨迹渐隐时间" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<Slider Name="InkFadeTimeSlider" Width="150" Minimum="1000" Maximum="10000"
Value="3000" TickFrequency="1000" IsSnapToTickEnabled="True"
ValueChanged="InkFadeTimeSlider_ValueChanged" />
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=InkFadeTimeSlider, Path=Value, StringFormat={}{0:0}ms}"
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
</GroupBox>
<!-- 新增:崩溃后操作设置 -->
@@ -1197,16 +1218,16 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示形状按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowShapeButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowShapeButton_Toggled" />
<CheckBox Name="CheckBoxShowShapeButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowShapeButton_Checked" Unchecked="CheckBoxShowShapeButton_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示撤销按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowUndoButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowUndoButton_Toggled" />
<CheckBox Name="CheckBoxShowUndoButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowUndoButton_Checked" Unchecked="CheckBoxShowUndoButton_Unchecked" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
@@ -1215,16 +1236,16 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示重做按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowRedoButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowRedoButton_Toggled" />
<CheckBox Name="CheckBoxShowRedoButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowRedoButton_Checked" Unchecked="CheckBoxShowRedoButton_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示清空按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowClearButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowClearButton_Toggled" />
<CheckBox Name="CheckBoxShowClearButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowClearButton_Checked" Unchecked="CheckBoxShowClearButton_Unchecked" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
@@ -1233,16 +1254,16 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示白板按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowWhiteboardButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowWhiteboardButton_Toggled" />
<CheckBox Name="CheckBoxShowWhiteboardButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowWhiteboardButton_Checked" Unchecked="CheckBoxShowWhiteboardButton_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示隐藏按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowHideButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowHideButton_Toggled" />
<CheckBox Name="CheckBoxShowHideButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowHideButton_Checked" Unchecked="CheckBoxShowHideButton_Unchecked" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
@@ -1251,16 +1272,16 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示套索选择按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowLassoSelectButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowLassoSelectButton_Toggled" />
<CheckBox Name="CheckBoxShowLassoSelectButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowLassoSelectButton_Checked" Unchecked="CheckBoxShowLassoSelectButton_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示清并鼠按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowClearAndMouseButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowClearAndMouseButton_Toggled" />
<CheckBox Name="CheckBoxShowClearAndMouseButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowClearAndMouseButton_Checked" Unchecked="CheckBoxShowClearAndMouseButton_Unchecked" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
@@ -1268,9 +1289,9 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,8">
<TextBlock Foreground="#fafafa" Text="显示快捷调色盘" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowQuickColorPalette"
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowQuickColorPalette_Toggled" />
<CheckBox Name="CheckBoxShowQuickColorPalette"
IsChecked="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowQuickColorPalette_Checked" Unchecked="CheckBoxShowQuickColorPalette_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
@@ -1430,9 +1451,21 @@
</Image.RenderTransform>
</Image>
<Image Name="PPTBtnPreviewLB" Source="Resources/PresentationExample/bottombar-white.png" Height="10"
VerticalAlignment="Bottom" HorizontalAlignment="Left" />
VerticalAlignment="Bottom" HorizontalAlignment="Left" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name="PPTBtnPreviewLBTransform"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
<Image Name="PPTBtnPreviewRB" Source="Resources/PresentationExample/bottombar-white.png" Height="12"
VerticalAlignment="Bottom" HorizontalAlignment="Right" />
VerticalAlignment="Bottom" HorizontalAlignment="Right" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name="PPTBtnPreviewRBTransform"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
</Grid>
</Border>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
@@ -1618,6 +1651,161 @@
</ui:SimpleStackPanel>
<TextBlock
Text="# 调大往上偏移,调小往下偏移,修改为0为不偏移,居中放置"
TextWrapping="Wrap" Foreground="#a1a1aa" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="左下偏移" VerticalAlignment="Center"
FontSize="14" Margin="0,0,12,0" />
<Slider x:Name="PPTButtonLBPositionValueSlider" Minimum="-500"
Maximum="500" Width="138" FontFamily="Microsoft YaHei UI"
FontSize="20" IsSnapToTickEnabled="True" Value="0"
TickFrequency="1"
TickPlacement="None" AutoToolTipPlacement="None"
ValueChanged="PPTButtonLBPositionValueSlider_ValueChanged" />
<Button Padding="5" Margin="8,0,0,0" Name="PPTBtnLBPlusBtn"
Click="PPTBtnLBPlusBtn_Clicked">
<Image Width="16" Height="16">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V16 H16 V0 H0 Z">
<GeometryDrawing Brush="#f4f4f5"
Geometry="F0 M16,16z M0,0z M9,1C9,0.447715 8.55229,0 8,0 7.44772,0 7,0.447715 7,1L7,7 1,7C0.447715,7 0,7.55272 0,8 0,8.44728 0.447715,9 1,9L7,9 7,15C7,15.5523 7.44772,16 8,16 8.55229,16 9,15.5523 9,15L9,9 15,9C15.5523,9 16,8.44728 16,8 16,7.55272 15.5523,7 15,7L9,7 9,1z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<Button Margin="6,0,0,0" Padding="5" Name="PPTBtnLBMinusBtn"
Click="PPTBtnLBMinusBtn_Clicked">
<Image Width="16" Height="16">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V2 H16 V0 H0 Z">
<GeometryDrawing Brush="#f4f4f5"
Geometry="F0 M16,2z M0,0z M0,1C0,0.447715,0.447715,0,1,0L15,0C15.5523,0 16,0.447715 16,1 16,1.55228 15.5523,2 15,2L1,2C0.447715,2,0,1.55228,0,1z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<Button Margin="6,0,0,0" Padding="5" Name="PPTBtnLBSyncBtn"
Click="PPTBtnLBSyncBtn_Clicked">
<Image Width="16" Height="16">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V6 H16 V0 H0 Z">
<DrawingGroup ClipGeometry="M0,0 V6 H16 V0 H0 Z">
<GeometryDrawing Brush="#f4f4f5"
Geometry="F0 M16,6z M0,0z M1,0C0.447715,0 0,0.447715 0,1 0,1.55228 0.447715,2 1,2L15,2C15.5523,2 16,1.55228 16,1 16,0.447715 15.5523,0 15,0L1,0z M1,4C0.447715,4 0,4.44772 0,5 0,5.55228 0.447715,6 1,6L15,6C15.5523,6 16,5.55228 16,5 16,4.44772 15.5523,4 15,4L1,4z" />
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<Button Margin="6,0,0,0" Padding="5" Name="PPTBtnLBResetBtn"
Click="PPTBtnLBResetBtn_Clicked">
<Image Width="16" Height="16">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
<GeometryDrawing Brush="#f4f4f5"
Geometry="F0 M24,24z M0,0z M12,2.01123C10.5428,2.01123 9.14528,2.5901 8.11488,3.6205 7.08449,4.65089 6.50562,6.04841 6.50562,7.50561L6.50562,16.4944C6.50562,17.9516 7.08449,19.3491 8.11488,20.3795 9.14528,21.4099 10.5428,21.9888 12,21.9888 13.4572,21.9888 14.8547,21.4099 15.8851,20.3795 16.9135,19.3511 17.4922,17.9569 17.4944,16.5027 17.4944,16.4999 17.4944,16.4972 17.4944,16.4944L17.4944,7.50563C17.4944,7.50285 17.4944,7.50007 17.4944,7.4973 17.4922,6.0431 16.9135,4.64893 15.8851,3.6205 14.8547,2.5901 13.4572,2.01123 12,2.01123z M9.5291,5.03471C10.1844,4.37939 11.0732,4.01123 12,4.01123 12.9268,4.01123 13.8156,4.37939 14.4709,5.03471 15.1262,5.69003 15.4944,6.57884 15.4944,7.50561L15.4944,16.4944C15.4944,17.4211 15.1262,18.31 14.4709,18.9653 13.8156,19.6206 12.9268,19.9888 12,19.9888 11.0732,19.9888 10.1844,19.6206 9.5291,18.9653 8.87377,18.31 8.50562,17.4211 8.50562,16.4944L8.50562,7.50561C8.50562,6.57884 8.87377,5.69003 9.5291,5.03471z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<TextBlock
Text="{Binding ElementName=PPTButtonLBPositionValueSlider, Path=Value}"
VerticalAlignment="Center" FontSize="13"
Margin="8,0,8,0" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="右下偏移" VerticalAlignment="Center"
FontSize="14" Margin="0,0,12,0" />
<Slider x:Name="PPTButtonRBPositionValueSlider" Minimum="-500"
Maximum="500" Width="138" FontFamily="Microsoft YaHei UI"
FontSize="20" IsSnapToTickEnabled="True" Value="0"
TickFrequency="1"
TickPlacement="None" AutoToolTipPlacement="None"
ValueChanged="PPTButtonRBPositionValueSlider_ValueChanged" />
<Button Padding="5" Margin="8,0,0,0" Name="PPTBtnRBPlusBtn"
Click="PPTBtnRBPlusBtn_Clicked">
<Image Width="16" Height="16">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V16 H16 V0 H0 Z">
<GeometryDrawing Brush="#f4f4f5"
Geometry="F0 M16,16z M0,0z M9,1C9,0.447715 8.55229,0 8,0 7.44772,0 7,0.447715 7,1L7,7 1,7C0.447715,7 0,7.55272 0,8 0,8.44728 0.447715,9 1,9L7,9 7,15C7,15.5523 7.44772,16 8,16 8.55229,16 9,15.5523 9,15L9,9 15,9C15.5523,9 16,8.44728 16,8 16,7.55272 15.5523,7 15,7L9,7 9,1z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<Button Margin="6,0,0,0" Padding="5" Name="PPTBtnRBMinusBtn"
Click="PPTBtnRBMinusBtn_Clicked">
<Image Width="16" Height="16">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V2 H16 V0 H0 Z">
<GeometryDrawing Brush="#f4f4f5"
Geometry="F0 M16,2z M0,0z M0,1C0,0.447715,0.447715,0,1,0L15,0C15.5523,0 16,0.447715 16,1 16,1.55228 15.5523,2 15,2L1,2C0.447715,2,0,1.55228,0,1z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<Button Margin="6,0,0,0" Padding="5" Name="PPTBtnRBSyncBtn"
Click="PPTBtnRBSyncBtn_Clicked">
<Image Width="16" Height="16">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V6 H16 V0 H0 Z">
<DrawingGroup ClipGeometry="M0,0 V6 H16 V0 H0 Z">
<GeometryDrawing Brush="#f4f4f5"
Geometry="F0 M16,6z M0,0z M1,0C0.447715,0 0,0.447715 0,1 0,1.55228 0.447715,2 1,2L15,2C15.5523,2 16,1.55228 16,1 16,0.447715 15.5523,0 15,0L1,0z M1,4C0.447715,4 0,4.44772 0,5 0,5.55228 0.447715,6 1,6L15,6C15.5523,6 16,5.55228 16,5 16,4.44772 15.5523,4 15,4L1,4z" />
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<Button Margin="6,0,0,0" Padding="5" Name="PPTBtnRBResetBtn"
Click="PPTBtnRBResetBtn_Clicked">
<Image Width="16" Height="16">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
<GeometryDrawing Brush="#f4f4f5"
Geometry="F0 M24,24z M0,0z M12,2.01123C10.5428,2.01123 9.14528,2.5901 8.11488,3.6205 7.08449,4.65089 6.50562,6.04841 6.50562,7.50561L6.50562,16.4944C6.50562,17.9516 7.08449,19.3491 8.11488,20.3795 9.14528,21.4099 10.5428,21.9888 12,21.9888 13.4572,21.9888 14.8547,21.4099 15.8851,20.3795 16.9135,19.3511 17.4922,17.9569 17.4944,16.5027 17.4944,16.4999 17.4944,16.4972 17.4944,16.4944L17.4944,7.50563C17.4944,7.50285 17.4944,7.50007 17.4944,7.4973 17.4922,6.0431 16.9135,4.64893 15.8851,3.6205 14.8547,2.5901 13.4572,2.01123 12,2.01123z M9.5291,5.03471C10.1844,4.37939 11.0732,4.01123 12,4.01123 12.9268,4.01123 13.8156,4.37939 14.4709,5.03471 15.1262,5.69003 15.4944,6.57884 15.4944,7.50561L15.4944,16.4944C15.4944,17.4211 15.1262,18.31 14.4709,18.9653 13.8156,19.6206 12.9268,19.9888 12,19.9888 11.0732,19.9888 10.1844,19.6206 9.5291,18.9653 8.87377,18.31 8.50562,17.4211 8.50562,16.4944L8.50562,7.50561C8.50562,6.57884,8.87377,5.69003,9.5291,5.03471z" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
<TextBlock
Text="{Binding ElementName=PPTButtonRBPositionValueSlider, Path=Value}"
VerticalAlignment="Center" FontSize="13"
Margin="8,0,16,0" />
</ui:SimpleStackPanel>
<TextBlock
Text="# 调大往右偏移,调小往左偏移,修改为0为不偏移,居中放置"
TextWrapping="Wrap" Foreground="#a1a1aa" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Margin="0,0,24,0" Text="两侧" FontWeight="Bold"
@@ -1676,6 +1864,19 @@
<TextBlock
Text="# 开启该选项后,点击页码按钮可以唤起PowerPoint自带的网格缩略图视图。WPS不支持该功能,开启也没用。"
TextWrapping="Wrap" Foreground="#a1a1aa" />
<ui:SimpleStackPanel Orientation="Horizontal"
HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="PPT 翻页按钮长按翻页"
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent=""
Name="ToggleSwitchEnablePPTButtonLongPressPageTurn"
IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchEnablePPTButtonLongPressPageTurn_OnToggled" />
</ui:SimpleStackPanel>
<TextBlock
Text="# 开启该选项后,长按PPT翻页按钮可以连续翻页,提高翻页效率。"
TextWrapping="Wrap" Foreground="#a1a1aa" />
</ui:SimpleStackPanel>
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
StrokeThickness="1" Margin="0,4,0,4" />
@@ -3860,7 +4061,7 @@
</Border>
<Border>
<Grid Margin="0,5,0,5" Width="0">
<Grid Name="BoardPenPaletteGrid" Margin="-160,-200,-33,50"
<Grid Name="BoardPenPaletteGrid" Margin="-160,-200,-53,50"
RenderTransformOrigin="0,1">
<Grid.RenderTransform>
<TransformGroup>
@@ -3874,7 +4075,7 @@
Background="#fafafa"
Opacity="1" BorderBrush="#2563eb"
BorderThickness="1" CornerRadius="8">
<ui:SimpleStackPanel>
<ui:SimpleStackPanel Margin="0,-20,0,0">
<Border BorderBrush="#1e3a8a" Height="32"
BorderThickness="0,0,0,1"
CornerRadius="8,8,0,0" Background="#2563eb"
@@ -3975,57 +4176,69 @@
Margin="12,8,12,0" HorizontalAlignment="Center"
VerticalAlignment="Center" Stretch="Fill">
<ui:SimpleStackPanel>
<Controls:UniformGrid Columns="2" Width="300"
<ui:SimpleStackPanel Orientation="Horizontal"
Height="35" Margin="0,0,0,8">
<Label Content="笔锋" Margin="0,0,6,0"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="16" FontWeight="Bold"
VerticalAlignment="Center" />
<ComboBox Height="30"
VerticalAlignment="Center"
Name="BoardComboBoxPenStyle"
FontFamily="Microsoft YaHei UI"
SelectedIndex="0"
SelectionChanged="ComboBoxPenStyle_SelectionChanged">
<ComboBoxItem Content="基于点集"
FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="基于速率"
FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="关闭笔锋"
FontFamily="Microsoft YaHei UI" />
</ComboBox>
</ui:SimpleStackPanel>
<Controls:UniformGrid Columns="3" Width="300"
Height="55">
<ui:SimpleStackPanel Orientation="Horizontal"
Height="35">
<Label Content="笔锋" Margin="0,0,6,0"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="16" FontWeight="Bold"
VerticalAlignment="Center" />
<ComboBox Height="30"
VerticalAlignment="Center"
Name="BoardComboBoxPenStyle"
<ui:SimpleStackPanel
x:Name="BoardNibModeSimpleStackPanel"
Orientation="Horizontal" Width="100">
<Label Content="笔尖模式" FontSize="12"
VerticalAlignment="Center" />
<ui:ToggleSwitch
x:Name="BoardToggleSwitchEnableNibMode"
MinWidth="0"
FontFamily="Microsoft YaHei UI"
SelectedIndex="0"
SelectionChanged="ComboBoxPenStyle_SelectionChanged">
<ComboBoxItem Content="基于点集"
FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="基于速率"
FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="关闭笔锋"
FontFamily="Microsoft YaHei UI" />
</ComboBox>
Width="80" Margin="10,0,0,0"
Toggled="ToggleSwitchEnableNibMode_Toggled"
IsOn="True" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Margin="0,-5,0,-5"
VerticalAlignment="Center">
<ui:SimpleStackPanel
x:Name="BoardNibModeSimpleStackPanel"
Orientation="Horizontal" Width="140">
<Label Content="笔尖模式" FontSize="15"
VerticalAlignment="Center" />
<ui:ToggleSwitch
x:Name="BoardToggleSwitchEnableNibMode"
MinWidth="0"
FontFamily="Microsoft YaHei UI"
Width="70" Margin="10,0,0,0"
Toggled="ToggleSwitchEnableNibMode_Toggled"
IsOn="True" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal"
Width="140">
<Label Margin="0,0,10,0" Content="墨迹纠正"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="15"
VerticalAlignment="Center" />
<ui:ToggleSwitch
Foreground="{DynamicResource FloatBarForeground}"
FontFamily="Microsoft YaHei UI"
Width="70"
Toggled="ToggleSwitchEnableInkToShape_Toggled"
MinWidth="0"
IsOn="{Binding ElementName=ToggleSwitchEnableInkToShape, Path=IsOn}" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal"
Width="100">
<Label Margin="0,0,10,0" Content="墨迹纠正"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="12"
VerticalAlignment="Center" />
<ui:ToggleSwitch
Foreground="{DynamicResource FloatBarForeground}"
FontFamily="Microsoft YaHei UI"
Width="80"
Toggled="ToggleSwitchEnableInkToShape_Toggled"
MinWidth="0"
IsOn="{Binding ElementName=ToggleSwitchEnableInkToShape, Path=IsOn}" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal"
Width="100">
<Label Margin="0,0,10,0" Content="墨迹渐隐"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="12"
VerticalAlignment="Center" />
<ui:ToggleSwitch
x:Name="ToggleSwitchInkFadeInPanel"
Foreground="{DynamicResource FloatBarForeground}"
FontFamily="Microsoft YaHei UI"
Width="90"
Toggled="ToggleSwitchInkFadeInPanel_Toggled"
MinWidth="0"
IsOn="False" />
</ui:SimpleStackPanel>
</Controls:UniformGrid>
@@ -6900,7 +7113,7 @@
<Border x:Name="PenPalette" Visibility="Visible" Background="#fafafa"
Opacity="1" BorderBrush="#2563eb"
BorderThickness="1" CornerRadius="8" Margin="-160,-200,-33,32">
<ui:SimpleStackPanel>
<ui:SimpleStackPanel Margin="0,-20,0,0">
<Border BorderBrush="#1e3a8a" Height="32" BorderThickness="0,0,0,1"
CornerRadius="8,8,0,0" Background="#2563eb"
Margin="-1,-1,-1,0"
@@ -6992,53 +7205,63 @@
Margin="12,8,12,0" HorizontalAlignment="Center"
VerticalAlignment="Center" Stretch="Fill">
<ui:SimpleStackPanel>
<Controls:UniformGrid Columns="2" Width="300" Height="55">
<ui:SimpleStackPanel Orientation="Horizontal"
Height="35">
<Label Content="笔锋" Margin="0,0,6,0"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="16" FontWeight="Bold"
<ui:SimpleStackPanel Orientation="Horizontal"
Height="35" Margin="0,0,0,8">
<Label Content="笔锋" Margin="0,0,6,0"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="16" FontWeight="Bold"
VerticalAlignment="Center" />
<ComboBox Height="30" VerticalAlignment="Center"
Name="ComboBoxPenStyle"
FontFamily="Microsoft YaHei UI"
SelectedIndex="0"
SelectionChanged="ComboBoxPenStyle_SelectionChanged">
<ComboBoxItem Content="基于点集"
FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="基于速率"
FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="关闭笔锋"
FontFamily="Microsoft YaHei UI" />
</ComboBox>
</ui:SimpleStackPanel>
<Controls:UniformGrid Columns="3" Width="300" Height="55">
<ui:SimpleStackPanel
x:Name="NibModeSimpleStackPanel"
Orientation="Horizontal" Width="100">
<Label Content="笔尖模式" FontSize="12"
VerticalAlignment="Center" />
<ComboBox Height="30" VerticalAlignment="Center"
Name="ComboBoxPenStyle"
FontFamily="Microsoft YaHei UI"
SelectedIndex="0"
SelectionChanged="ComboBoxPenStyle_SelectionChanged">
<ComboBoxItem Content="基于点集"
FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="基于速率"
FontFamily="Microsoft YaHei UI" />
<ComboBoxItem Content="关闭笔锋"
FontFamily="Microsoft YaHei UI" />
</ComboBox>
<ui:ToggleSwitch
x:Name="ToggleSwitchEnableNibMode"
MinWidth="0"
FontFamily="Microsoft YaHei UI"
Width="80" Margin="10,0,0,0"
Toggled="ToggleSwitchEnableNibMode_Toggled"
IsOn="True" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Margin="0,-5,0,-5"
VerticalAlignment="Center">
<ui:SimpleStackPanel
x:Name="NibModeSimpleStackPanel"
Orientation="Horizontal" Width="140">
<Label Content="笔尖模式" FontSize="15"
VerticalAlignment="Center" />
<ui:ToggleSwitch
x:Name="ToggleSwitchEnableNibMode"
MinWidth="0"
FontFamily="Microsoft YaHei UI"
Width="70" Margin="10,0,0,0"
Toggled="ToggleSwitchEnableNibMode_Toggled"
IsOn="True" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal"
Width="140">
<Label Margin="0,0,10,0" Content="墨迹纠正"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="15" VerticalAlignment="Center" />
<ui:ToggleSwitch
Foreground="{DynamicResource FloatBarForeground}"
FontFamily="Microsoft YaHei UI" Width="70"
Toggled="ToggleSwitchEnableInkToShape_Toggled"
MinWidth="0"
IsOn="{Binding ElementName=ToggleSwitchEnableInkToShape, Path=IsOn}" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal"
Width="100">
<Label Margin="0,0,10,0" Content="墨迹纠正"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="12" VerticalAlignment="Center" />
<ui:ToggleSwitch
Foreground="{DynamicResource FloatBarForeground}"
FontFamily="Microsoft YaHei UI" Width="80"
Toggled="ToggleSwitchEnableInkToShape_Toggled"
MinWidth="0"
IsOn="{Binding ElementName=ToggleSwitchEnableInkToShape, Path=IsOn}" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal"
Width="100">
<Label Margin="0,0,10,0" Content="墨迹渐隐"
Foreground="{DynamicResource FloatBarForeground}"
FontSize="12" VerticalAlignment="Center" />
<ui:ToggleSwitch
x:Name="ToggleSwitchInkFadeInPanel2"
Foreground="{DynamicResource FloatBarForeground}"
FontFamily="Microsoft YaHei UI" Width="90"
Toggled="ToggleSwitchInkFadeInPanel_Toggled"
MinWidth="0"
IsOn="{Binding ElementName=ToggleSwitchInkFadeInPanel, Path=IsOn}" />
</ui:SimpleStackPanel>
</Controls:UniformGrid>
@@ -7768,7 +7991,7 @@
<Border x:Name="BorderDrawShape" Background="#fafafa" Opacity="1"
Visibility="Visible" BorderBrush="#2563eb"
BorderThickness="1"
CornerRadius="5" Margin="-170,-122,-147,37">
CornerRadius="5" Margin="-170,-140,-147,37">
<ui:SimpleStackPanel Spacing="0" Orientation="Vertical">
<!--<TextBlock FontSize="16" Foreground="{DynamicResource FloatBarForeground}" HorizontalAlignment="Left" Margin="10,8">
<Run Text="图形"/>
@@ -7793,6 +8016,7 @@
</Canvas>
</ui:SimpleStackPanel>
</Border>
<ui:SimpleStackPanel Orientation="Horizontal" Height="38" Spacing="2"
Margin="4,6,4,0">
<ui:SimpleStackPanel Name="BoardImageDrawLine"
+231 -11
View File
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
@@ -41,6 +42,12 @@ namespace Ink_Canvas
private int currentPageIndex;
private System.Windows.Controls.Canvas currentCanvas;
private AutoUpdateHelper.UpdateLineGroup AvailableLatestLineGroup;
// 全局快捷键管理器
private GlobalHotkeyManager _globalHotkeyManager;
// 墨迹渐隐管理器
private InkFadeManager _inkFadeManager;
@@ -152,6 +159,9 @@ namespace Ink_Canvas
// 初始化墨迹平滑管理器
_inkSmoothingManager = new InkSmoothingManager(Dispatcher);
// 初始化墨迹渐隐管理器
_inkFadeManager = new InkFadeManager(this);
// 注册输入事件
inkCanvas.PreviewMouseDown += inkCanvas_PreviewMouseDown;
inkCanvas.StylusDown += inkCanvas_StylusDown;
@@ -283,9 +293,15 @@ namespace Ink_Canvas
if (StackPanelPPTControls.Visibility == Visibility.Visible)
{
if (gest.ApplicationGesture == ApplicationGesture.Left)
BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
{
// 直接发送翻页请求到PPT放映软件
SendKeyToPPTSlideShow(false); // 下一页
}
if (gest.ApplicationGesture == ApplicationGesture.Right)
BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
{
// 直接发送翻页请求到PPT放映软件
SendKeyToPPTSlideShow(true); // 上一页
}
}
}
catch { }
@@ -495,6 +511,12 @@ namespace Ink_Canvas
// 初始化剪贴板监控
InitializeClipboardMonitoring();
// 初始化全局快捷键管理器
InitializeGlobalHotkeyManager();
// 初始化墨迹渐隐管理器
InitializeInkFadeManager();
}
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e)
@@ -623,6 +645,20 @@ namespace Ink_Canvas
// 清理剪贴板监控
CleanupClipboardMonitoring();
ClipboardNotification.Stop();
// 清理全局快捷键管理器
if (_globalHotkeyManager != null)
{
_globalHotkeyManager.Dispose();
_globalHotkeyManager = null;
}
// 清理墨迹渐隐管理器
if (_inkFadeManager != null)
{
_inkFadeManager.ClearAllFadingStrokes();
_inkFadeManager = null;
}
// 停止置顶维护定时器
StopTopmostMaintenance();
@@ -1067,10 +1103,7 @@ namespace Ink_Canvas
// 新增:快捷键设置
private void NavShortcuts_Click(object sender, RoutedEventArgs e)
{
// 切换到快捷键设置页面
ShowSettingsSection("shortcuts");
// 如果设置部分尚未快捷键
MessageBox.Show("设置功能正在开发中", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
OpenHotkeySettingsWindow();
}
private void BtnCloseSettings_Click(object sender, RoutedEventArgs e)
@@ -1641,6 +1674,8 @@ namespace Ink_Canvas
private static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private const int GWL_EXSTYLE = -20;
@@ -1702,9 +1737,6 @@ namespace Ink_Canvas
// 停止置顶维护定时器
StopTopmostMaintenance();
}
// 添加调试日志
LogHelper.WriteLogToFile($"应用窗口置顶: 启用置顶 (无焦点模式: {Settings.Advanced.IsNoFocusMode})", LogHelper.LogType.Trace);
}
else
{
@@ -1801,7 +1833,6 @@ namespace Ink_Canvas
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST);
}
LogHelper.WriteLogToFile("置顶维护: 重新设置窗口置顶", LogHelper.LogType.Trace);
}
}
catch (Exception ex)
@@ -1959,5 +1990,194 @@ namespace Ink_Canvas
}
#endregion
#region
/// <summary>
/// 初始化墨迹渐隐管理器
/// </summary>
private void InitializeInkFadeManager()
{
try
{
// 确保墨迹渐隐管理器已初始化
if (_inkFadeManager == null)
{
_inkFadeManager = new InkFadeManager(this);
}
// 同步设置状态
_inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade;
_inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime);
LogHelper.WriteLogToFile("墨迹渐隐管理器已初始化", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化墨迹渐隐管理器时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 初始化全局快捷键管理器
/// </summary>
private void InitializeGlobalHotkeyManager()
{
try
{
_globalHotkeyManager = new GlobalHotkeyManager(this);
// 不在这里加载快捷键,等待需要时再加载
LogHelper.WriteLogToFile("全局快捷键管理器已初始化(未加载快捷键)", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化全局快捷键管理器时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 打开快捷键设置窗口
/// </summary>
private void OpenHotkeySettingsWindow()
{
try
{
if (_globalHotkeyManager == null)
{
MessageBox.Show("快捷键管理器尚未初始化,请稍后重试。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
var hotkeySettingsWindow = new HotkeySettingsWindow(this, _globalHotkeyManager);
hotkeySettingsWindow.ShowDialog();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"打开快捷键设置窗口时出错: {ex.Message}", LogHelper.LogType.Error);
MessageBox.Show($"打开快捷键设置窗口时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
#endregion
#region
/// <summary>
/// 墨迹渐隐开关切换事件处理
/// </summary>
private void ToggleSwitchEnableInkFade_Toggled(object sender, RoutedEventArgs e)
{
try
{
Settings.Canvas.EnableInkFade = ToggleSwitchEnableInkFade.IsOn;
_inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade;
// 同步批注子面板中的开关状态
if (ToggleSwitchInkFadeInPanel != null)
{
ToggleSwitchInkFadeInPanel.IsOn = Settings.Canvas.EnableInkFade;
}
// 同步普通画笔面板中的开关状态
if (ToggleSwitchInkFadeInPanel2 != null)
{
ToggleSwitchInkFadeInPanel2.IsOn = Settings.Canvas.EnableInkFade;
}
LogHelper.WriteLogToFile($"墨迹渐隐功能已{(Settings.Canvas.EnableInkFade ? "" : "")}", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换墨迹渐隐功能时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 墨迹渐隐时间滑块值改变事件处理
/// </summary>
private void InkFadeTimeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
try
{
Settings.Canvas.InkFadeTime = (int)e.NewValue;
_inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime);
LogHelper.WriteLogToFile($"墨迹渐隐时间已更新为 {Settings.Canvas.InkFadeTime}ms", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新墨迹渐隐时间时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 批注子面板中墨迹渐隐开关切换事件处理
/// </summary>
private void ToggleSwitchInkFadeInPanel_Toggled(object sender, RoutedEventArgs e)
{
try
{
Settings.Canvas.EnableInkFade = ToggleSwitchInkFadeInPanel.IsOn;
_inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade;
// 同步设置面板中的开关状态
if (ToggleSwitchEnableInkFade != null)
{
ToggleSwitchEnableInkFade.IsOn = Settings.Canvas.EnableInkFade;
}
// 同步普通画笔面板中的开关状态
if (ToggleSwitchInkFadeInPanel2 != null)
{
ToggleSwitchInkFadeInPanel2.IsOn = Settings.Canvas.EnableInkFade;
}
LogHelper.WriteLogToFile($"批注子面板中墨迹渐隐功能已{(Settings.Canvas.EnableInkFade ? "" : "")}", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"批注子面板中切换墨迹渐隐功能时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
#region PPT翻页直接传递
/// <summary>
/// 直接发送翻页请求到PPT放映软件,让PPT软件处理翻页
/// </summary>
/// <param name="isPrevious">是否为上一页</param>
private void SendKeyToPPTSlideShow(bool isPrevious)
{
try
{
// 查找PPT放映窗口并发送按键
var pptWindows = Process.GetProcessesByName("POWERPNT");
var wpsWindows = Process.GetProcessesByName("wpp");
foreach (var process in pptWindows.Concat(wpsWindows))
{
if (process.MainWindowHandle != IntPtr.Zero)
{
// 激活PPT窗口
SetForegroundWindow(process.MainWindowHandle);
// 发送翻页按键消息
int keyCode = isPrevious ? 0x21 : 0x22; // VK_PRIOR : VK_NEXT
// 发送按键按下和释放消息
PostMessage(process.MainWindowHandle, 0x0100, (IntPtr)keyCode, IntPtr.Zero); // WM_KEYDOWN
PostMessage(process.MainWindowHandle, 0x0101, (IntPtr)keyCode, IntPtr.Zero); // WM_KEYUP
break;
}
}
}
catch (Exception ex)
{
// 如果直接发送失败,回退到原来的方法
if (isPrevious)
BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
else
BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
}
}
#endregion
}
}
}
@@ -185,7 +185,7 @@ namespace Ink_Canvas
}
// 处理全局粘贴快捷键
private async void HandleGlobalPaste(object sender, ExecutedRoutedEventArgs e)
internal async void HandleGlobalPaste(object sender, ExecutedRoutedEventArgs e)
{
try
{
+8
View File
@@ -28,6 +28,11 @@ namespace Ink_Canvas
AnimationsHelper.HideWithSlideAndFade(BlackboardLeftSide);
AnimationsHelper.HideWithSlideAndFade(BlackboardCenterSide);
AnimationsHelper.HideWithSlideAndFade(BlackboardRightSide);
// 在PPT模式下隐藏手势面板和手势按钮
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
}
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
@@ -595,6 +600,9 @@ namespace Ink_Canvas
drawingAttributes.Height = Settings.Canvas.HighlighterWidth;
drawingAttributes.StylusTip = StylusTip.Rectangle;
drawingAttributes.IsHighlighter = true;
// 确保荧光笔模式切换后正确更新颜色和快捷调色板指示器
ColorSwitchCheck();
}
private void BtnColorBlack_Click(object sender, RoutedEventArgs e)
+232 -59
View File
@@ -27,7 +27,37 @@ namespace Ink_Canvas
{
public partial class MainWindow : Window
{
#region "按鈕
#region
/// <summary>
/// 统一的快捷键状态刷新方法
/// 在工具切换时调用,避免重复代码
/// </summary>
private void RefreshHotkeyState()
{
try
{
var hotkeyManagerField = this.GetType().GetField("_globalHotkeyManager",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (hotkeyManagerField != null)
{
var hotkeyManager = hotkeyManagerField.GetValue(this);
if (hotkeyManager != null)
{
var updateMethod = hotkeyManager.GetType().GetMethod("UpdateHotkeyRegistrationState");
updateMethod?.Invoke(hotkeyManager, null);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"刷新快捷键状态时出错: {ex.Message}", LogHelper.LogType.Warning);
}
}
#endregion
#region "手勢"
/// <summary>
/// 用於浮動工具欄的"手勢"按鈕和白板工具欄的"手勢"按鈕的點擊事件
@@ -124,6 +154,13 @@ namespace Ink_Canvas
/// </summary>
private void CheckEnableTwoFingerGestureBtnVisibility(bool isVisible)
{
// 在PPT模式下始终隐藏手势按钮
if (currentMode == 0 || BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
{
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
return;
}
if (StackPanelCanvasControls.Visibility != Visibility.Visible
|| BorderFloatingBarMainControls.Visibility != Visibility.Visible)
{
@@ -131,9 +168,7 @@ namespace Ink_Canvas
}
else if (isVisible)
{
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
else EnableTwoFingerGestureBorder.Visibility = Visibility.Visible;
EnableTwoFingerGestureBorder.Visibility = Visibility.Visible;
}
else
{
@@ -181,7 +216,7 @@ namespace Ink_Canvas
GridForFloatingBarDraging.Visibility = Visibility.Visible;
}
private void SymbolIconEmoji_MouseUp(object sender, MouseButtonEventArgs e)
internal void SymbolIconEmoji_MouseUp(object sender, MouseButtonEventArgs e)
{
isDragDropInEffect = false;
@@ -490,7 +525,8 @@ namespace Ink_Canvas
#endregion
#region
private void SymbolIconUndo_MouseUp(object sender, MouseButtonEventArgs e)
internal void SymbolIconUndo_MouseUp(object sender, MouseButtonEventArgs e)
{
//if (lastBorderMouseDownObject != sender) return;
@@ -503,7 +539,7 @@ namespace Ink_Canvas
HideSubPanels();
}
private void SymbolIconRedo_MouseUp(object sender, MouseButtonEventArgs e)
internal void SymbolIconRedo_MouseUp(object sender, MouseButtonEventArgs e)
{
//if (lastBorderMouseDownObject != sender) return;
@@ -523,7 +559,7 @@ namespace Ink_Canvas
//private bool Not_Enter_Blackboard_fir_Mouse_Click = true;
private bool isDisplayingOrHidingBlackboard;
private void ImageBlackboard_MouseUp(object sender, MouseButtonEventArgs e)
internal void ImageBlackboard_MouseUp(object sender, MouseButtonEventArgs e)
{
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
@@ -723,7 +759,7 @@ namespace Ink_Canvas
#region
private void SymbolIconDelete_MouseUp(object sender, MouseButtonEventArgs e)
internal void SymbolIconDelete_MouseUp(object sender, MouseButtonEventArgs e)
{
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
@@ -773,7 +809,7 @@ namespace Ink_Canvas
/// </summary>
/// <param name="sender">sender</param>
/// <param name="e">MouseButtonEventArgs</param>
private void SymbolIconSelect_MouseUp(object sender, MouseButtonEventArgs e)
internal void SymbolIconSelect_MouseUp(object sender, MouseButtonEventArgs e)
{
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
@@ -782,6 +818,9 @@ namespace Ink_Canvas
BtnSelect_Click(null, null);
HideSubPanels("select");
// 工具切换完成后,统一刷新快捷键状态
RefreshHotkeyState();
}
#endregion
@@ -836,7 +875,7 @@ namespace Ink_Canvas
break;
case "QuickColorOrange":
case "QuickColorOrangeSingle":
border.Background = new SolidColorBrush(Color.FromRgb(255, 165, 0));
border.Background = new SolidColorBrush(Color.FromRgb(251, 150, 80));
break;
case "QuickColorYellow":
case "QuickColorYellowSingle":
@@ -991,7 +1030,7 @@ namespace Ink_Canvas
{
Process.Start(new ProcessStartInfo
{
FileName = "classisland://plugins/IslandCaller/Run",
FileName = "classisland://plugins/IslandCaller/Simple/1",
UseShellExecute = true
});
}
@@ -1537,7 +1576,7 @@ namespace Ink_Canvas
});
}
private async void CursorIcon_Click(object sender, RoutedEventArgs e)
internal async void CursorIcon_Click(object sender, RoutedEventArgs e)
{
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
@@ -1641,9 +1680,17 @@ namespace Ink_Canvas
else
ViewboxFloatingBarMarginAnimation(100, true);
}
// 工具切换完成后,统一刷新快捷键状态
RefreshHotkeyState();
if (BtnSwitchTheme.Content.ToString() == "浅色")
BtnSwitch.Content = "黑板";
else
BtnSwitch.Content = "白板";
}
private void PenIcon_Click(object sender, RoutedEventArgs e)
internal void PenIcon_Click(object sender, RoutedEventArgs e)
{
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
@@ -1724,13 +1771,27 @@ namespace Ink_Canvas
}
}
// 修复:从线擦切换到批注时,重置为默认笔模式(非高光显示)
// 修复:从线擦切换到批注时,保持之前的笔类型状态
// 如果之前是荧光笔模式,则保持荧光笔状态;否则重置为默认笔模式
forceEraser = false;
forcePointEraser = false;
drawingShapeMode = 0;
penType = 0;
drawingAttributes.IsHighlighter = false;
drawingAttributes.StylusTip = StylusTip.Ellipse;
// 保持之前的笔类型状态,而不是强制重置
if (!wasHighlighter)
{
penType = 0;
drawingAttributes.IsHighlighter = false;
drawingAttributes.StylusTip = StylusTip.Ellipse;
}
// 如果之前是荧光笔模式,则保持荧光笔属性
else if (penType == 1)
{
drawingAttributes.IsHighlighter = true;
drawingAttributes.StylusTip = StylusTip.Rectangle;
drawingAttributes.Width = Settings.Canvas.HighlighterWidth / 2;
drawingAttributes.Height = Settings.Canvas.HighlighterWidth;
}
ColorSwitchCheck();
HideSubPanels("pen", true);
@@ -1742,13 +1803,26 @@ namespace Ink_Canvas
// 修复:从线擦切换到批注时,确保正确重置状态
if (forceEraser)
{
// 从橡皮擦模式切换过来,重置为默认笔模式
// 从橡皮擦模式切换过来,保持之前的笔类型状态
forceEraser = false;
forcePointEraser = false;
drawingShapeMode = 0;
penType = 0;
drawingAttributes.IsHighlighter = false;
drawingAttributes.StylusTip = StylusTip.Ellipse;
// 保持之前的笔类型状态,而不是强制重置
if (!wasHighlighter)
{
penType = 0;
drawingAttributes.IsHighlighter = false;
drawingAttributes.StylusTip = StylusTip.Ellipse;
}
// 如果之前是荧光笔模式,则保持荧光笔属性
else if (penType == 1)
{
drawingAttributes.IsHighlighter = true;
drawingAttributes.StylusTip = StylusTip.Rectangle;
drawingAttributes.Width = Settings.Canvas.HighlighterWidth / 2;
drawingAttributes.Height = Settings.Canvas.HighlighterWidth;
}
// 在非白板模式下,从线擦切换到批注时不直接弹出子面板
if (currentMode != 1)
@@ -1800,18 +1874,39 @@ namespace Ink_Canvas
}
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
// 修复:从线擦切换到批注时,重置为默认笔模式(非高光显示)
// 修复:从线擦切换到批注时,保持之前的笔类型状态
forceEraser = false;
forcePointEraser = false;
drawingShapeMode = 0;
penType = 0;
drawingAttributes.IsHighlighter = false;
drawingAttributes.StylusTip = StylusTip.Ellipse;
// 保持之前的笔类型状态,而不是强制重置
if (!wasHighlighter)
{
penType = 0;
drawingAttributes.IsHighlighter = false;
drawingAttributes.StylusTip = StylusTip.Ellipse;
}
// 如果之前是荧光笔模式,则保持荧光笔属性
else if (penType == 1)
{
drawingAttributes.IsHighlighter = true;
drawingAttributes.StylusTip = StylusTip.Rectangle;
drawingAttributes.Width = Settings.Canvas.HighlighterWidth / 2;
drawingAttributes.Height = Settings.Canvas.HighlighterWidth;
}
ColorSwitchCheck();
HideSubPanels("pen", true);
}
}
// 工具切换完成后,统一刷新快捷键状态
RefreshHotkeyState();
// 修复:从线擦切换到批注时,保持之前的笔类型状态
forceEraser = false;
forcePointEraser = false;
drawingShapeMode = 0;
}
private void ColorThemeSwitch_MouseUp(object sender, RoutedEventArgs e)
@@ -1821,7 +1916,7 @@ namespace Ink_Canvas
CheckColorTheme();
}
private void EraserIcon_Click(object sender, RoutedEventArgs e)
internal void EraserIcon_Click(object sender, RoutedEventArgs e)
{
EnterMultiTouchModeIfNeeded();
bool isAlreadyEraser = inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint;
@@ -1864,6 +1959,9 @@ namespace Ink_Canvas
AnimationsHelper.HideWithSlideAndFade(BoardEraserSizePanel);
}
}
// 工具切换完成后,统一刷新快捷键状态
RefreshHotkeyState();
}
private void BoardEraserIcon_Click(object sender, RoutedEventArgs e)
@@ -1898,6 +1996,9 @@ namespace Ink_Canvas
AnimationsHelper.HideWithSlideAndFade(EraserSizePanel);
}
}
// 工具切换完成后,统一刷新快捷键状态
RefreshHotkeyState();
}
private void EraserIconByStrokes_Click(object sender, RoutedEventArgs e)
@@ -1918,15 +2019,17 @@ namespace Ink_Canvas
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByStroke;
drawingShapeMode = 0;
// 修复:切换到线擦时,确保重置笔的状态
penType = 0;
drawingAttributes.IsHighlighter = false;
drawingAttributes.StylusTip = StylusTip.Ellipse;
// 修复:切换到线擦时,保存当前的笔类型状态,而不是强制重置
// 这样从线擦切换回批注时,可以恢复之前的荧光笔状态
// penType 和 drawingAttributes 的状态将在 PenIcon_Click 中根据 wasHighlighter 来恢复
inkCanvas_EditingModeChanged(inkCanvas, null);
CancelSingleFingerDragMode();
HideSubPanels("eraserByStrokes");
// 工具切换完成后,统一刷新快捷键状态
RefreshHotkeyState();
}
private void CursorWithDelIcon_Click(object sender, RoutedEventArgs e)
@@ -1948,7 +2051,7 @@ namespace Ink_Canvas
private void QuickColorOrange_Click(object sender, RoutedEventArgs e)
{
SetQuickColor(Color.FromRgb(255, 165, 0)); // 橙色
SetQuickColor(Color.FromRgb(251, 150, 80)); // 橙色
}
private void QuickColorYellow_Click(object sender, RoutedEventArgs e)
@@ -1993,12 +2096,70 @@ namespace Ink_Canvas
drawingAttributes.Color = color;
inkCanvas.DefaultDrawingAttributes.Color = color;
// 如果当前是荧光笔模式,同时更新荧光笔颜色和属性
if (penType == 1)
{
// 根据颜色设置对应的荧光笔颜色索引
if (color == Colors.White || IsColorSimilar(color, Color.FromRgb(250, 250, 250), 10))
{
highlighterColor = 101; // 白色荧光笔
}
else if (color == Colors.Black)
{
highlighterColor = 100; // 黑色荧光笔
}
else if (color == Colors.Yellow || IsColorSimilar(color, Color.FromRgb(234, 179, 8), 15) ||
IsColorSimilar(color, Color.FromRgb(250, 204, 21), 15) ||
IsColorSimilar(color, Color.FromRgb(253, 224, 71), 15))
{
highlighterColor = 103; // 黄色荧光笔
}
else if (color == Color.FromRgb(255, 165, 0) || color == Color.FromRgb(251, 150, 80) || IsColorSimilar(color, Color.FromRgb(249, 115, 22), 20) ||
IsColorSimilar(color, Color.FromRgb(234, 88, 12), 20) ||
IsColorSimilar(color, Color.FromRgb(251, 146, 60), 20) ||
IsColorSimilar(color, Color.FromRgb(253, 126, 20), 20))
{
highlighterColor = 109; // 橙色荧光笔
}
else if (color == Color.FromRgb(37, 99, 235))
{
highlighterColor = 106; // 蓝色荧光笔
}
else if (color == Colors.Red || IsColorSimilar(color, Color.FromRgb(220, 38, 38), 15) ||
IsColorSimilar(color, Color.FromRgb(239, 68, 68), 15))
{
highlighterColor = 102; // 红色荧光笔
}
else if (color == Colors.Green || IsColorSimilar(color, Color.FromRgb(22, 163, 74), 15))
{
highlighterColor = 104; // 绿色荧光笔
}
else if (color == Color.FromRgb(147, 51, 234))
{
highlighterColor = 107; // 紫色荧光笔
}
// 确保荧光笔属性正确设置
drawingAttributes.Width = Settings.Canvas.HighlighterWidth / 2;
drawingAttributes.Height = Settings.Canvas.HighlighterWidth;
drawingAttributes.StylusTip = StylusTip.Rectangle;
drawingAttributes.IsHighlighter = true;
inkCanvas.DefaultDrawingAttributes.Width = Settings.Canvas.HighlighterWidth / 2;
inkCanvas.DefaultDrawingAttributes.Height = Settings.Canvas.HighlighterWidth;
inkCanvas.DefaultDrawingAttributes.StylusTip = StylusTip.Rectangle;
inkCanvas.DefaultDrawingAttributes.IsHighlighter = true;
// 确保荧光笔颜色索引正确更新
inkCanvas.DefaultDrawingAttributes.Color = drawingAttributes.Color;
}
// 更新颜色状态
if (currentMode == 0)
{
// 桌面模式
if (color == Colors.White) lastDesktopInkColor = 5;
else if (color == Color.FromRgb(255, 165, 0)) lastDesktopInkColor = 8; // 橙色
else if (color == Color.FromRgb(251, 150, 80)) lastDesktopInkColor = 8; // 橙色
else if (color == Colors.Yellow) lastDesktopInkColor = 4;
else if (color == Colors.Black) lastDesktopInkColor = 0;
else if (color == Color.FromRgb(37, 99, 235)) lastDesktopInkColor = 3; // 蓝色
@@ -2010,7 +2171,7 @@ namespace Ink_Canvas
{
// 白板模式
if (color == Colors.White) lastBoardInkColor = 5;
else if (color == Color.FromRgb(255, 165, 0)) lastBoardInkColor = 8; // 橙色
else if (color == Color.FromRgb(251, 150, 80)) lastBoardInkColor = 8; // 橙色
else if (color == Colors.Yellow) lastBoardInkColor = 4;
else if (color == Colors.Black) lastBoardInkColor = 0;
else if (color == Color.FromRgb(37, 99, 235)) lastBoardInkColor = 3; // 蓝色
@@ -2024,6 +2185,12 @@ namespace Ink_Canvas
// 更新颜色显示
ColorSwitchCheck();
// 如果当前是荧光笔模式,调用ColorSwitchCheck确保颜色索引正确更新
if (penType == 1)
{
ColorSwitchCheck();
}
}
private void UpdateQuickColorPaletteIndicator(Color selectedColor)
@@ -2047,50 +2214,55 @@ namespace Ink_Canvas
QuickColorGreenCheckSingle.Visibility = Visibility.Collapsed;
// 显示当前选中颜色的check图标
if (IsColorSimilar(selectedColor, Colors.White, 10) || IsColorSimilar(selectedColor, Color.FromRgb(250, 250, 250), 10))
// 在荧光笔模式下,使用更宽松的颜色匹配
int tolerance = (penType == 1) ? 25 : 15; // 荧光笔模式使用更大的容差
if (IsColorSimilar(selectedColor, Colors.White, tolerance) || IsColorSimilar(selectedColor, Color.FromRgb(250, 250, 250), tolerance))
{
QuickColorWhiteCheck.Visibility = Visibility.Visible;
QuickColorWhiteCheckSingle.Visibility = Visibility.Visible;
}
else if (IsColorSimilar(selectedColor, Colors.Black, 10))
else if (IsColorSimilar(selectedColor, Colors.Black, tolerance))
{
QuickColorBlackCheck.Visibility = Visibility.Visible;
QuickColorBlackCheckSingle.Visibility = Visibility.Visible;
}
else if (IsColorSimilar(selectedColor, Colors.Yellow, 15) ||
IsColorSimilar(selectedColor, Color.FromRgb(234, 179, 8), 15) ||
IsColorSimilar(selectedColor, Color.FromRgb(250, 204, 21), 15) ||
IsColorSimilar(selectedColor, Color.FromRgb(253, 224, 71), 15))
else if (IsColorSimilar(selectedColor, Colors.Yellow, tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(234, 179, 8), tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(250, 204, 21), tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(253, 224, 71), tolerance))
{
QuickColorYellowCheck.Visibility = Visibility.Visible;
QuickColorYellowCheckSingle.Visibility = Visibility.Visible;
}
else if (IsColorSimilar(selectedColor, Color.FromRgb(255, 165, 0), 15) ||
IsColorSimilar(selectedColor, Color.FromRgb(249, 115, 22), 15) ||
IsColorSimilar(selectedColor, Color.FromRgb(234, 88, 12), 15))
else if (IsColorSimilar(selectedColor, Color.FromRgb(255, 165, 0), tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(251, 150, 80), tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(249, 115, 22), tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(234, 88, 12), tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(251, 146, 60), tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(253, 126, 20), tolerance))
{
QuickColorOrangeCheck.Visibility = Visibility.Visible;
QuickColorOrangeCheckSingle.Visibility = Visibility.Visible;
}
else if (IsColorSimilar(selectedColor, Color.FromRgb(37, 99, 235), 15))
else if (IsColorSimilar(selectedColor, Color.FromRgb(37, 99, 235), tolerance))
{
QuickColorBlueCheck.Visibility = Visibility.Visible;
// 单行显示模式没有蓝色,所以不设置单行的check
}
else if (IsColorSimilar(selectedColor, Colors.Red, 15) ||
IsColorSimilar(selectedColor, Color.FromRgb(220, 38, 38), 15) ||
IsColorSimilar(selectedColor, Color.FromRgb(239, 68, 68), 15))
else if (IsColorSimilar(selectedColor, Colors.Red, tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(220, 38, 38), tolerance) ||
IsColorSimilar(selectedColor, Color.FromRgb(239, 68, 68), tolerance))
{
QuickColorRedCheck.Visibility = Visibility.Visible;
QuickColorRedCheckSingle.Visibility = Visibility.Visible;
}
else if (IsColorSimilar(selectedColor, Colors.Green, 15) ||
IsColorSimilar(selectedColor, Color.FromRgb(22, 163, 74), 15))
else if (IsColorSimilar(selectedColor, Color.FromRgb(22, 163, 74), tolerance))
{
QuickColorGreenCheck.Visibility = Visibility.Visible;
QuickColorGreenCheckSingle.Visibility = Visibility.Visible;
}
else if (IsColorSimilar(selectedColor, Color.FromRgb(147, 51, 234), 15))
else if (IsColorSimilar(selectedColor, Color.FromRgb(147, 51, 234), tolerance))
{
QuickColorPurpleCheck.Visibility = Visibility.Visible;
// 单行显示模式没有紫色,所以不设置单行的check
@@ -2364,6 +2536,11 @@ namespace Ink_Canvas
DeselectUIElement();
// 在PPT模式下隐藏手势面板和手势按钮
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
SaveStrokes(true);
ClearStrokes(true);
RestoreStrokes(true);
@@ -2409,6 +2586,11 @@ namespace Ink_Canvas
// 取消任何UI元素的选择
DeselectUIElement();
// 在PPT模式下隐藏手势面板和手势按钮
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
SaveStrokes();
ClearStrokes(true);
RestoreStrokes(true);
@@ -2906,15 +3088,6 @@ namespace Ink_Canvas
// 设置高光位置
FloatingbarSelectionBG.Visibility = Visibility.Visible;
System.Windows.Controls.Canvas.SetLeft(FloatingbarSelectionBG, position);
// 详细的调试信息
string debugInfo = $"设置高光位置: {mode} -> {position:F2}, " +
$"高光宽度: {actualHighlightWidth:F2}, " +
$"快捷调色盘: {quickColorPaletteMode}, 宽度: {quickColorPaletteWidth:F2}, 总宽度: {quickColorPaletteTotalWidth:F2}, " +
$"按钮宽度: cursor={cursorWidth:F2}, pen={penWidth:F2}, delete={deleteWidth:F2}, " +
$"eraser={eraserWidth:F2}, eraserByStrokes={eraserByStrokesWidth:F2}, select={selectWidth:F2}";
LogHelper.WriteLogToFile(debugInfo, LogHelper.LogType.Trace);
}
catch (Exception ex)
{
+27 -6
View File
@@ -1,5 +1,9 @@
using System.Windows;
using System.Windows.Input;
using System.Diagnostics;
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace Ink_Canvas
{
@@ -8,19 +12,36 @@ namespace Ink_Canvas
private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (StackPanelPPTControls.Visibility != Visibility.Visible || currentMode != 0) return;
// 直接发送翻页请求到PPT放映软件,不通过软件处理
if (e.Delta >= 120)
BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
else if (e.Delta <= -120) BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
{
// 上一页 - 发送PageUp键到PPT放映窗口
SendKeyToPPTSlideShow(true);
}
else if (e.Delta <= -120)
{
// 下一页 - 发送PageDown键到PPT放映窗口
SendKeyToPPTSlideShow(false);
}
}
private void Main_Grid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (StackPanelPPTControls.Visibility != Visibility.Visible || currentMode != 0) return;
// 直接发送翻页请求到PPT放映软件,不通过软件处理
if (e.Key == Key.Down || e.Key == Key.PageDown || e.Key == Key.Right || e.Key == Key.N ||
e.Key == Key.Space) BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
if (e.Key == Key.Up || e.Key == Key.PageUp || e.Key == Key.Left || e.Key == Key.P)
BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
e.Key == Key.Space)
{
e.Handled = true; // 阻止事件继续传播
SendKeyToPPTSlideShow(false); // 下一页
}
else if (e.Key == Key.Up || e.Key == Key.PageUp || e.Key == Key.Left || e.Key == Key.P)
{
e.Handled = true; // 阻止事件继续传播
SendKeyToPPTSlideShow(true); // 上一页
}
}
private void Window_KeyDown(object sender, KeyEventArgs e)
@@ -57,7 +78,7 @@ namespace Ink_Canvas
}
private void KeyExit(object sender, ExecutedRoutedEventArgs e)
internal void KeyExit(object sender, ExecutedRoutedEventArgs e)
{
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null);
}
+164
View File
@@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using Application = System.Windows.Application;
using File = System.IO.File;
using MessageBox = System.Windows.MessageBox;
@@ -80,12 +81,24 @@ namespace Ink_Canvas
private bool isPresentationHaveBlackSpace;
private string pptName;
private bool _isPptClickingBtnTurned;
// 长按翻页相关字段
private DispatcherTimer _longPressTimer;
private bool _isLongPressActive = false;
private bool _isLongPressNext = true; // true为下一页,false为上一页
private const int LongPressDelay = 50; // 长按延迟时间(毫秒)
private const int LongPressInterval = 50; // 长按翻页间隔(毫秒)
#endregion
#region PPT Managers
private PPTManager _pptManager;
private PPTInkManager _pptInkManager;
private PPTUIManager _pptUIManager;
/// <summary>
/// 获取PPT管理器实例(供UI管理器使用)
/// </summary>
public PPTManager PPTManager => _pptManager;
#endregion
#region PPT Manager Initialization
@@ -93,6 +106,9 @@ namespace Ink_Canvas
{
try
{
// 初始化长按定时器
InitializeLongPressTimer();
// 初始化PPT管理器
_pptManager = new PPTManager();
_pptManager.IsSupportWPS = Settings.PowerPointSettings.IsSupportWPS;
@@ -104,6 +120,7 @@ namespace Ink_Canvas
_pptManager.SlideShowEnd += OnPPTSlideShowEnd;
_pptManager.PresentationOpen += OnPPTPresentationOpen;
_pptManager.PresentationClose += OnPPTPresentationClose;
_pptManager.SlideShowStateChanged += OnPPTSlideShowStateChanged;
// 初始化墨迹管理器
_pptInkManager = new PPTInkManager();
@@ -118,7 +135,10 @@ namespace Ink_Canvas
_pptUIManager.PPTBButtonsOption = Settings.PowerPointSettings.PPTBButtonsOption;
_pptUIManager.PPTLSButtonPosition = Settings.PowerPointSettings.PPTLSButtonPosition;
_pptUIManager.PPTRSButtonPosition = Settings.PowerPointSettings.PPTRSButtonPosition;
_pptUIManager.PPTLBButtonPosition = Settings.PowerPointSettings.PPTLBButtonPosition;
_pptUIManager.PPTRBButtonPosition = Settings.PowerPointSettings.PPTRBButtonPosition;
_pptUIManager.EnablePPTButtonPageClickable = Settings.PowerPointSettings.EnablePPTButtonPageClickable;
_pptUIManager.EnablePPTButtonLongPressPageTurn = Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn;
LogHelper.WriteLogToFile("PPT管理器初始化完成", LogHelper.LogType.Event);
}
@@ -149,6 +169,8 @@ namespace Ink_Canvas
{
_pptManager?.Dispose();
_pptInkManager?.Dispose();
_longPressTimer?.Stop();
_longPressTimer = null;
_pptManager = null;
_pptInkManager = null;
_pptUIManager = null;
@@ -159,6 +181,60 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile($"释放PPT管理器失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 初始化长按定时器
/// </summary>
private void InitializeLongPressTimer()
{
_longPressTimer = new DispatcherTimer();
_longPressTimer.Interval = TimeSpan.FromMilliseconds(LongPressDelay);
_longPressTimer.Tick += OnLongPressTimerTick;
}
/// <summary>
/// 启动长按检测
/// </summary>
/// <param name="sender">触发事件的控件</param>
/// <param name="isNext">是否为下一页按钮</param>
private void StartLongPressDetection(object sender, bool isNext)
{
if (!Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn) return;
_isLongPressNext = isNext;
_isLongPressActive = false;
_longPressTimer?.Start();
}
/// <summary>
/// 停止长按检测
/// </summary>
private void StopLongPressDetection()
{
_longPressTimer?.Stop();
_isLongPressActive = false;
}
/// <summary>
/// 长按定时器事件处理
/// </summary>
private void OnLongPressTimerTick(object sender, EventArgs e)
{
if (!Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn) return;
_isLongPressActive = true;
_longPressTimer.Interval = TimeSpan.FromMilliseconds(LongPressInterval);
// 执行翻页
if (_isLongPressNext)
{
BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
}
else
{
BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
}
}
#endregion
#region New PPT Event Handlers
@@ -194,6 +270,15 @@ namespace Ink_Canvas
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
// 在初始化墨迹管理器之前,先清理画布上的所有墨迹
ClearStrokes(true);
// 清理备份历史记录,防止旧演示文稿的墨迹影响新演示文稿
if (TimeMachineHistories != null && TimeMachineHistories.Length > 0)
{
TimeMachineHistories[0] = null;
}
// 初始化墨迹管理器
_pptInkManager?.InitializePresentation(pres);
@@ -213,6 +298,8 @@ namespace Ink_Canvas
}
_pptUIManager?.UpdateConnectionStatus(true);
LogHelper.WriteLogToFile($"已打开新演示文稿: {pres.Name},墨迹状态已清理", LogHelper.LogType.Event);
});
}
catch (Exception ex)
@@ -242,6 +329,27 @@ namespace Ink_Canvas
}
}
private void OnPPTSlideShowStateChanged(bool isInSlideShow)
{
try
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
// 通知UI管理器放映状态变化
_pptUIManager?.OnSlideShowStateChanged(isInSlideShow);
if (!isInSlideShow)
{
LogHelper.WriteLogToFile("PPT放映状态变化:退出放映模式", LogHelper.LogType.Trace);
}
});
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理PPT放映状态变化失败: {ex}", LogHelper.LogType.Error);
}
}
private async void OnPPTSlideShowBegin(SlideShowWindow wn)
{
try
@@ -292,6 +400,11 @@ namespace Ink_Canvas
BorderFloatingBarMainControls.Visibility = Visibility.Visible;
// 在PPT模式下隐藏手势面板和手势按钮
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow &&
!Settings.Automation.IsAutoFoldInPPTSlideShow)
BtnColorRed_Click(null, null);
@@ -394,6 +507,28 @@ namespace Ink_Canvas
// 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上)
TimeMachineHistories[0] = null;
// 退出PPT模式时恢复手势面板和手势按钮的显示状态
if (Settings.Gesture.IsEnableTwoFingerGesture && ToggleSwitchEnableMultiTouchMode.IsOn)
{
// 根据手势设置决定是否显示手势面板和手势按钮
CheckEnableTwoFingerGestureBtnVisibility(true);
}
else
{
// 如果手势功能未启用,确保手势按钮保持隐藏
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
}
// 退出PPT模式时隐藏快捷调色盘
if (QuickColorPalettePanel != null)
{
QuickColorPalettePanel.Visibility = Visibility.Collapsed;
}
if (QuickColorPaletteSingleRowPanel != null)
{
QuickColorPaletteSingleRowPanel.Visibility = Visibility.Collapsed;
}
if (GridTransparencyFakeBackground.Background != Brushes.Transparent)
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
}
@@ -406,6 +541,9 @@ namespace Ink_Canvas
await Task.Delay(150);
await Application.Current.Dispatcher.InvokeAsync(() =>
{
// 强制重新计算浮动栏位置,确保在退出PPT模式后正确复位
// 先调用桌面模式的复位方法,然后调用通用的位置计算方法
PureViewboxFloatingBarMarginAnimationInDesktopMode();
ViewboxFloatingBarMarginAnimation(100, true);
});
}
@@ -977,6 +1115,12 @@ namespace Ink_Canvas
{
PPTRBPreviousButtonFeedbackBorder.Opacity = 0.15;
}
// 启动长按检测
if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn)
{
StartLongPressDetection(sender, false);
}
}
private void GridPPTControlPrevious_MouseLeave(object sender, MouseEventArgs e)
{
@@ -997,6 +1141,9 @@ namespace Ink_Canvas
{
PPTRBPreviousButtonFeedbackBorder.Opacity = 0;
}
// 停止长按检测
StopLongPressDetection();
}
private void GridPPTControlPrevious_MouseUp(object sender, MouseButtonEventArgs e)
{
@@ -1017,6 +1164,10 @@ namespace Ink_Canvas
{
PPTRBPreviousButtonFeedbackBorder.Opacity = 0;
}
// 停止长按检测
StopLongPressDetection();
BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
}
@@ -1040,6 +1191,12 @@ namespace Ink_Canvas
{
PPTRBNextButtonFeedbackBorder.Opacity = 0.15;
}
// 启动长按检测
if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn)
{
StartLongPressDetection(sender, true);
}
}
private void GridPPTControlNext_MouseLeave(object sender, MouseEventArgs e)
{
@@ -1060,6 +1217,9 @@ namespace Ink_Canvas
{
PPTRBNextButtonFeedbackBorder.Opacity = 0;
}
// 停止长按检测
StopLongPressDetection();
}
private void GridPPTControlNext_MouseUp(object sender, MouseButtonEventArgs e)
{
@@ -1080,6 +1240,10 @@ namespace Ink_Canvas
{
PPTRBNextButtonFeedbackBorder.Opacity = 0;
}
// 停止长按检测
StopLongPressDetection();
BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
}
+1 -1
View File
@@ -42,7 +42,7 @@ namespace Ink_Canvas
SaveInkCanvasStrokes(false);
}
private void SaveScreenShotToDesktop()
internal void SaveScreenShotToDesktop()
{
var desktopPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
+254 -18
View File
@@ -511,6 +511,13 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
private void ToggleSwitchEnablePPTButtonLongPressPageTurn_OnToggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn = ToggleSwitchEnablePPTButtonLongPressPageTurn.IsOn;
SaveSettingsToFile();
}
private void CheckboxEnableLBPPTButton_IsCheckChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
@@ -728,6 +735,53 @@ namespace Ink_Canvas
PPTBtnRSMinusBtn.IsEnabled = true;
PPTBtnRSMinusBtn.Opacity = 1;
}
// 底部按钮滑块状态管理
if (PPTButtonLBPositionValueSlider.Value <= -500 || PPTButtonLBPositionValueSlider.Value >= 500)
{
if (PPTButtonLBPositionValueSlider.Value >= 500)
{
PPTBtnLBPlusBtn.IsEnabled = false;
PPTBtnLBPlusBtn.Opacity = 0.5;
PPTButtonLBPositionValueSlider.Value = 500;
}
else if (PPTButtonLBPositionValueSlider.Value <= -500)
{
PPTBtnLBMinusBtn.IsEnabled = false;
PPTBtnLBMinusBtn.Opacity = 0.5;
PPTButtonLBPositionValueSlider.Value = -500;
}
}
else
{
PPTBtnLBPlusBtn.IsEnabled = true;
PPTBtnLBPlusBtn.Opacity = 1;
PPTBtnLBMinusBtn.IsEnabled = true;
PPTBtnLBMinusBtn.Opacity = 1;
}
if (PPTButtonRBPositionValueSlider.Value <= -500 || PPTButtonRBPositionValueSlider.Value >= 500)
{
if (PPTButtonRBPositionValueSlider.Value >= 500)
{
PPTBtnRBPlusBtn.IsEnabled = false;
PPTBtnRBPlusBtn.Opacity = 0.5;
PPTButtonRBPositionValueSlider.Value = 500;
}
else if (PPTButtonRBPositionValueSlider.Value <= -500)
{
PPTBtnRBMinusBtn.IsEnabled = false;
PPTBtnRBMinusBtn.Opacity = 0.5;
PPTButtonRBPositionValueSlider.Value = -500;
}
}
else
{
PPTBtnRBPlusBtn.IsEnabled = true;
PPTBtnRBPlusBtn.Opacity = 1;
PPTBtnRBMinusBtn.IsEnabled = true;
PPTBtnRBMinusBtn.Opacity = 1;
}
}
private void PPTBtnLSPlusBtn_Clicked(object sender, RoutedEventArgs e)
@@ -834,6 +888,10 @@ namespace Ink_Canvas
_pptUIManager.PPTBButtonsOption = Settings.PowerPointSettings.PPTBButtonsOption;
_pptUIManager.PPTLSButtonPosition = Settings.PowerPointSettings.PPTLSButtonPosition;
_pptUIManager.PPTRSButtonPosition = Settings.PowerPointSettings.PPTRSButtonPosition;
_pptUIManager.PPTLBButtonPosition = Settings.PowerPointSettings.PPTLBButtonPosition;
_pptUIManager.PPTRBButtonPosition = Settings.PowerPointSettings.PPTRBButtonPosition;
_pptUIManager.EnablePPTButtonPageClickable = Settings.PowerPointSettings.EnablePPTButtonPageClickable;
_pptUIManager.EnablePPTButtonLongPressPageTurn = Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn;
_pptUIManager.UpdateNavigationPanelsVisibility();
_pptUIManager.UpdateNavigationButtonStyles();
}
@@ -922,6 +980,9 @@ namespace Ink_Canvas
PPTBtnPreviewRSTransform.Y = -(Settings.PowerPointSettings.PPTRSButtonPosition * 0.5);
PPTBtnPreviewLSTransform.Y = -(Settings.PowerPointSettings.PPTLSButtonPosition * 0.5);
PPTBtnPreviewLBTransform.X = -(Settings.PowerPointSettings.PPTLBButtonPosition * 0.5);
PPTBtnPreviewRBTransform.X = -(Settings.PowerPointSettings.PPTRBButtonPosition * 0.5);
}
private void ToggleSwitchShowCursor_Toggled(object sender, RoutedEventArgs e)
@@ -2344,74 +2405,146 @@ namespace Ink_Canvas
#region
private void ToggleSwitchShowShapeButton_Toggled(object sender, RoutedEventArgs e)
private void CheckBoxShowShapeButton_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowShapeButton = ToggleSwitchShowShapeButton.IsOn;
Settings.Appearance.IsShowShapeButton = CheckBoxShowShapeButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void ToggleSwitchShowUndoButton_Toggled(object sender, RoutedEventArgs e)
private void CheckBoxShowShapeButton_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowUndoButton = ToggleSwitchShowUndoButton.IsOn;
Settings.Appearance.IsShowShapeButton = CheckBoxShowShapeButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void ToggleSwitchShowRedoButton_Toggled(object sender, RoutedEventArgs e)
private void CheckBoxShowUndoButton_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowRedoButton = ToggleSwitchShowRedoButton.IsOn;
Settings.Appearance.IsShowUndoButton = CheckBoxShowUndoButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void ToggleSwitchShowClearButton_Toggled(object sender, RoutedEventArgs e)
private void CheckBoxShowUndoButton_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowClearButton = ToggleSwitchShowClearButton.IsOn;
Settings.Appearance.IsShowUndoButton = CheckBoxShowUndoButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void ToggleSwitchShowWhiteboardButton_Toggled(object sender, RoutedEventArgs e)
private void CheckBoxShowRedoButton_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowWhiteboardButton = ToggleSwitchShowWhiteboardButton.IsOn;
Settings.Appearance.IsShowRedoButton = CheckBoxShowRedoButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void CheckBoxShowRedoButton_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowRedoButton = CheckBoxShowRedoButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void CheckBoxShowClearButton_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowClearButton = CheckBoxShowClearButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void CheckBoxShowClearButton_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowClearButton = CheckBoxShowClearButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void CheckBoxShowWhiteboardButton_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowWhiteboardButton = CheckBoxShowWhiteboardButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void CheckBoxShowWhiteboardButton_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowWhiteboardButton = CheckBoxShowWhiteboardButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void ToggleSwitchShowLassoSelectButton_Toggled(object sender, RoutedEventArgs e)
private void CheckBoxShowLassoSelectButton_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowLassoSelectButton = ToggleSwitchShowLassoSelectButton.IsOn;
Settings.Appearance.IsShowLassoSelectButton = CheckBoxShowLassoSelectButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void ToggleSwitchShowClearAndMouseButton_Toggled(object sender, RoutedEventArgs e)
private void CheckBoxShowLassoSelectButton_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowClearAndMouseButton = ToggleSwitchShowClearAndMouseButton.IsOn;
Settings.Appearance.IsShowLassoSelectButton = CheckBoxShowLassoSelectButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void ToggleSwitchShowHideButton_Toggled(object sender, RoutedEventArgs e)
private void CheckBoxShowClearAndMouseButton_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowHideButton = ToggleSwitchShowHideButton.IsOn;
Settings.Appearance.IsShowClearAndMouseButton = CheckBoxShowClearAndMouseButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void ToggleSwitchShowQuickColorPalette_Toggled(object sender, RoutedEventArgs e)
private void CheckBoxShowClearAndMouseButton_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowQuickColorPalette = ToggleSwitchShowQuickColorPalette.IsOn;
Settings.Appearance.IsShowClearAndMouseButton = CheckBoxShowClearAndMouseButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void CheckBoxShowHideButton_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowHideButton = CheckBoxShowHideButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void CheckBoxShowHideButton_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowHideButton = CheckBoxShowHideButton.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void CheckBoxShowQuickColorPalette_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowQuickColorPalette = CheckBoxShowQuickColorPalette.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
private void CheckBoxShowQuickColorPalette_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowQuickColorPalette = CheckBoxShowQuickColorPalette.IsChecked ?? false;
UpdateFloatingBarButtonsVisibility();
SaveSettingsToFile();
}
@@ -2798,6 +2931,109 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
#region
private void PPTButtonLBPositionValueSlider_ValueChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.PowerPointSettings.PPTLBButtonPosition = (int)PPTButtonLBPositionValueSlider.Value;
UpdatePPTBtnSlidersStatus();
UpdatePPTUIManagerSettings();
SliderDelayAction.DebounceAction(2000, null, SaveSettingsToFile);
UpdatePPTBtnPreview();
}
private void PPTButtonRBPositionValueSlider_ValueChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.PowerPointSettings.PPTRBButtonPosition = (int)PPTButtonRBPositionValueSlider.Value;
UpdatePPTBtnSlidersStatus();
UpdatePPTUIManagerSettings();
SliderDelayAction.DebounceAction(2000, null, SaveSettingsToFile);
UpdatePPTBtnPreview();
}
private void PPTBtnLBPlusBtn_Clicked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
PPTButtonLBPositionValueSlider.Value++;
UpdatePPTBtnSlidersStatus();
Settings.PowerPointSettings.PPTLBButtonPosition = (int)PPTButtonLBPositionValueSlider.Value;
SaveSettingsToFile();
UpdatePPTBtnPreview();
}
private void PPTBtnLBMinusBtn_Clicked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
PPTButtonLBPositionValueSlider.Value--;
UpdatePPTBtnSlidersStatus();
Settings.PowerPointSettings.PPTLBButtonPosition = (int)PPTButtonLBPositionValueSlider.Value;
SaveSettingsToFile();
UpdatePPTBtnPreview();
}
private void PPTBtnLBSyncBtn_Clicked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
PPTButtonRBPositionValueSlider.Value = PPTButtonLBPositionValueSlider.Value;
UpdatePPTBtnSlidersStatus();
Settings.PowerPointSettings.PPTRBButtonPosition = (int)PPTButtonLBPositionValueSlider.Value;
SaveSettingsToFile();
UpdatePPTBtnPreview();
}
private void PPTBtnLBResetBtn_Clicked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
PPTButtonLBPositionValueSlider.Value = 0;
UpdatePPTBtnSlidersStatus();
Settings.PowerPointSettings.PPTLBButtonPosition = 0;
SaveSettingsToFile();
UpdatePPTBtnPreview();
}
private void PPTBtnRBPlusBtn_Clicked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
PPTButtonRBPositionValueSlider.Value++;
UpdatePPTBtnSlidersStatus();
Settings.PowerPointSettings.PPTRBButtonPosition = (int)PPTButtonRBPositionValueSlider.Value;
SaveSettingsToFile();
UpdatePPTBtnPreview();
}
private void PPTBtnRBMinusBtn_Clicked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
PPTButtonRBPositionValueSlider.Value--;
UpdatePPTBtnSlidersStatus();
Settings.PowerPointSettings.PPTRBButtonPosition = (int)PPTButtonRBPositionValueSlider.Value;
SaveSettingsToFile();
UpdatePPTBtnPreview();
}
private void PPTBtnRBSyncBtn_Clicked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
PPTButtonLBPositionValueSlider.Value = PPTButtonRBPositionValueSlider.Value;
UpdatePPTBtnSlidersStatus();
Settings.PowerPointSettings.PPTLBButtonPosition = (int)PPTButtonRBPositionValueSlider.Value;
SaveSettingsToFile();
UpdatePPTBtnPreview();
}
private void PPTBtnRBResetBtn_Clicked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
PPTButtonRBPositionValueSlider.Value = 0;
Settings.PowerPointSettings.PPTRBButtonPosition = 0;
SaveSettingsToFile();
UpdatePPTBtnPreview();
}
#endregion
}
}
+67 -9
View File
@@ -307,15 +307,15 @@ namespace Ink_Canvas
Settings.Appearance.EnableChickenSoupInWhiteboardMode;
// 浮动栏按钮显示控制开关初始化
ToggleSwitchShowShapeButton.IsOn = Settings.Appearance.IsShowShapeButton;
ToggleSwitchShowUndoButton.IsOn = Settings.Appearance.IsShowUndoButton;
ToggleSwitchShowRedoButton.IsOn = Settings.Appearance.IsShowRedoButton;
ToggleSwitchShowClearButton.IsOn = Settings.Appearance.IsShowClearButton;
ToggleSwitchShowWhiteboardButton.IsOn = Settings.Appearance.IsShowWhiteboardButton;
ToggleSwitchShowHideButton.IsOn = Settings.Appearance.IsShowHideButton;
ToggleSwitchShowQuickColorPalette.IsOn = Settings.Appearance.IsShowQuickColorPalette;
ToggleSwitchShowLassoSelectButton.IsOn = Settings.Appearance.IsShowLassoSelectButton;
ToggleSwitchShowClearAndMouseButton.IsOn = Settings.Appearance.IsShowClearAndMouseButton;
CheckBoxShowShapeButton.IsChecked = Settings.Appearance.IsShowShapeButton;
CheckBoxShowUndoButton.IsChecked = Settings.Appearance.IsShowUndoButton;
CheckBoxShowRedoButton.IsChecked = Settings.Appearance.IsShowRedoButton;
CheckBoxShowClearButton.IsChecked = Settings.Appearance.IsShowClearButton;
CheckBoxShowWhiteboardButton.IsChecked = Settings.Appearance.IsShowWhiteboardButton;
CheckBoxShowHideButton.IsChecked = Settings.Appearance.IsShowHideButton;
CheckBoxShowQuickColorPalette.IsChecked = Settings.Appearance.IsShowQuickColorPalette;
CheckBoxShowLassoSelectButton.IsChecked = Settings.Appearance.IsShowLassoSelectButton;
CheckBoxShowClearAndMouseButton.IsChecked = Settings.Appearance.IsShowClearAndMouseButton;
ComboBoxEraserDisplayOption.SelectedIndex = Settings.Appearance.EraserDisplayOption;
ComboBoxQuickColorPaletteDisplayMode.SelectedIndex = Settings.Appearance.QuickColorPaletteDisplayMode;
@@ -367,6 +367,9 @@ namespace Ink_Canvas
ToggleSwitchEnablePPTButtonPageClickable.IsOn =
Settings.PowerPointSettings.EnablePPTButtonPageClickable;
ToggleSwitchEnablePPTButtonLongPressPageTurn.IsOn =
Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn;
var dops = Settings.PowerPointSettings.PPTButtonsDisplayOption.ToString();
var dopsc = dops.ToCharArray();
if ((dopsc[0] == '1' || dopsc[0] == '2') && (dopsc[1] == '1' || dopsc[1] == '2') &&
@@ -427,6 +430,10 @@ namespace Ink_Canvas
PPTButtonRightPositionValueSlider.Value = Settings.PowerPointSettings.PPTRSButtonPosition;
PPTButtonLBPositionValueSlider.Value = Settings.PowerPointSettings.PPTLBButtonPosition;
PPTButtonRBPositionValueSlider.Value = Settings.PowerPointSettings.PPTRBButtonPosition;
UpdatePPTBtnSlidersStatus();
UpdatePPTBtnPreview();
@@ -876,6 +883,57 @@ namespace Ink_Canvas
{
ViewboxFloatingBarMarginAnimation(100, true);
}
// 加载墨迹渐隐设置
LoadInkFadeSettings();
}
/// <summary>
/// 加载墨迹渐隐设置
/// </summary>
private void LoadInkFadeSettings()
{
try
{
// 同步设置面板中的开关状态
if (ToggleSwitchEnableInkFade != null)
{
ToggleSwitchEnableInkFade.IsOn = Settings.Canvas.EnableInkFade;
}
// 同步批注子面板中的开关状态
if (ToggleSwitchInkFadeInPanel != null)
{
ToggleSwitchInkFadeInPanel.IsOn = Settings.Canvas.EnableInkFade;
}
// 同步普通画笔面板中的开关状态
if (ToggleSwitchInkFadeInPanel2 != null)
{
ToggleSwitchInkFadeInPanel2.IsOn = Settings.Canvas.EnableInkFade;
}
// 同步滑块值
if (InkFadeTimeSlider != null)
{
InkFadeTimeSlider.Value = Settings.Canvas.InkFadeTime;
}
// 同步墨迹渐隐管理器的状态
if (_inkFadeManager != null)
{
_inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade;
_inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime);
}
LogHelper.WriteLogToFile("墨迹渐隐设置已加载", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"加载墨迹渐隐设置时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
}
}
+2 -2
View File
@@ -172,8 +172,8 @@ namespace Ink_Canvas
return Task.FromResult(true);
}
private async void BtnDrawLine_Click(object sender, MouseButtonEventArgs e)
internal async void BtnDrawLine_Click(object sender, MouseButtonEventArgs e)
{
await CheckIsDrawingShapesInMultiTouchMode();
EnterShapeDrawingMode(1);
@@ -56,6 +56,33 @@ namespace Ink_Canvas
private void inkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
// 检查是否启用墨迹渐隐功能
if (Settings.Canvas.EnableInkFade)
{
// 获取墨迹的起点和终点
var startPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[0].ToPoint() : new Point();
var endPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[e.Stroke.StylusPoints.Count - 1].ToPoint() : new Point();
// 从InkCanvas中移除墨迹,因为我们要用渐隐管理器来管理它
if (inkCanvas.Strokes.Contains(e.Stroke))
{
inkCanvas.Strokes.Remove(e.Stroke);
}
// 添加到墨迹渐隐管理器
if (_inkFadeManager != null)
{
_inkFadeManager.AddFadingStroke(e.Stroke, startPoint, endPoint);
}
else
{
LogHelper.WriteLogToFile("StrokeCollected: 墨迹渐隐管理器为空,无法添加墨迹", LogHelper.LogType.Error);
}
// 墨迹渐隐模式下不参与墨迹纠正和其他处理,直接返回
return;
}
// 标记是否进行了直线拉直
bool wasStraightened = false;
+193 -16
View File
@@ -1,6 +1,7 @@
using Ink_Canvas.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
@@ -138,10 +139,13 @@ namespace Ink_Canvas
private void MainWindow_StylusDown(object sender, StylusDownEventArgs e)
{
LogHelper.WriteLogToFile($"MainWindow_StylusDown 被调用,笔尾状态: {e.StylusDevice.Inverted}, 当前 drawingShapeMode: {drawingShapeMode}, 当前 EditingMode: {inkCanvas.EditingMode}", LogHelper.LogType.Info);
// 新增:根据是否为笔尾自动切换橡皮擦/画笔模式
if (e.StylusDevice.Inverted)
{
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
LogHelper.WriteLogToFile("检测到笔尾,设置 EditingMode 为 EraseByPoint", LogHelper.LogType.Info);
}
else
{
@@ -150,12 +154,18 @@ namespace Ink_Canvas
{
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
inkCanvas.EditingMode = InkCanvasEditingMode.None;
LogHelper.WriteLogToFile("几何绘制模式,设置 EditingMode 为 None", LogHelper.LogType.Info);
return;
}
// 修复:保持当前的线擦模式,不要强制切换到Ink模式
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
{
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
LogHelper.WriteLogToFile("设置 EditingMode 为 Ink", LogHelper.LogType.Info);
}
else
{
LogHelper.WriteLogToFile("保持当前线擦模式", LogHelper.LogType.Info);
}
}
SetCursorBasedOnEditingMode(inkCanvas);
@@ -197,15 +207,25 @@ namespace Ink_Canvas
{
try
{
inkCanvas.Strokes.Add(GetStrokeVisual(e.StylusDevice.Id).Stroke);
LogHelper.WriteLogToFile($"MainWindow_StylusUp 被调用,EditingMode: {inkCanvas.EditingMode}, EnableInkFade: {Settings.Canvas.EnableInkFade}", LogHelper.LogType.Info);
var stroke = GetStrokeVisual(e.StylusDevice.Id).Stroke;
LogHelper.WriteLogToFile($"获取到墨迹,StylusPoints数量: {stroke.StylusPoints.Count}", LogHelper.LogType.Info);
// 正常模式:添加到画布并参与墨迹纠正
// 墨迹渐隐功能现在在 StrokeCollected 事件中统一处理所有输入方式
LogHelper.WriteLogToFile("StylusUp: 添加墨迹到画布", LogHelper.LogType.Info);
inkCanvas.Strokes.Add(stroke);
await Task.Delay(5); // 避免渲染墨迹完成前预览墨迹被删除导致墨迹闪烁
inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id));
inkCanvas_StrokeCollected(inkCanvas,
new InkCanvasStrokeCollectedEventArgs(GetStrokeVisual(e.StylusDevice.Id).Stroke));
new InkCanvasStrokeCollectedEventArgs(stroke));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"MainWindow_StylusUp 出错: {ex}", LogHelper.LogType.Error);
Label.Content = ex.ToString();
}
@@ -343,6 +363,10 @@ namespace Ink_Canvas
private InkCanvasEditingMode palmEraserLastEditingMode = InkCanvasEditingMode.Ink;
private bool palmEraserLastIsHighlighter;
private bool palmEraserWasEnabledBeforeMultiTouch;
private bool palmEraserTouchDownHandled = false; // 新增:标记手掌擦触摸按下是否已处理
private DateTime palmEraserActivationTime; // 新增:记录手掌擦激活时间
private const int PALM_ERASER_TIMEOUT_MS = 5000; // 新增:手掌擦超时时间(5秒)
private System.Windows.Threading.DispatcherTimer palmEraserRecoveryTimer; // 新增:手掌擦恢复定时器
private void inkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)
{
@@ -395,8 +419,8 @@ namespace Ink_Canvas
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
dec.Add(e.TouchDevice.Id);
// Palm Eraser 逻辑
if (Settings.Canvas.EnablePalmEraser && dec.Count >= 2 && !isPalmEraserActive)
// Palm Eraser 逻辑 - 修复:只在触摸按下时处理一次,避免重复触发
if (Settings.Canvas.EnablePalmEraser && dec.Count >= 2 && !isPalmEraserActive && !palmEraserTouchDownHandled)
{
var bounds = e.GetTouchPoint(inkCanvas).Bounds;
double palmThreshold = 40; // 触摸面积阈值,可根据实际调整
@@ -408,6 +432,11 @@ namespace Ink_Canvas
// 切换为橡皮擦
EraserIcon_Click(null, null);
isPalmEraserActive = true;
palmEraserActivationTime = DateTime.Now; // 记录激活时间
palmEraserTouchDownHandled = true; // 标记已处理
// 启动恢复定时器,防止卡死
StartPalmEraserRecoveryTimer();
}
}
@@ -474,23 +503,86 @@ namespace Ink_Canvas
{
// 恢复高光状态
drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter;
// 恢复编辑模式
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
// 恢复编辑模式 - 修复:确保正确恢复状态
try
{
if (palmEraserLastEditingMode == InkCanvasEditingMode.Ink)
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
{
PenIcon_Click(null, null);
}
else if (palmEraserLastEditingMode == InkCanvasEditingMode.Select)
{
SymbolIconSelect_MouseUp(null, null);
}
else
{
inkCanvas.EditingMode = palmEraserLastEditingMode;
if (palmEraserLastEditingMode == InkCanvasEditingMode.Ink)
{
PenIcon_Click(null, null);
}
else if (palmEraserLastEditingMode == InkCanvasEditingMode.Select)
{
SymbolIconSelect_MouseUp(null, null);
}
else
{
inkCanvas.EditingMode = palmEraserLastEditingMode;
}
}
}
catch (Exception ex)
{
// 如果恢复失败,强制切换到批注模式
Trace.WriteLine($"Palm eraser recovery failed: {ex.Message}, forcing to Ink mode");
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
// 重置手掌擦状态
isPalmEraserActive = false;
palmEraserTouchDownHandled = false;
// 停止恢复定时器
StopPalmEraserRecoveryTimer();
// 确保触摸事件能正常响应
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
}
// 新增:超时检测 - 如果手掌擦激活时间过长,强制重置状态
if (isPalmEraserActive && dec.Count == 0)
{
var timeSinceActivation = DateTime.Now - palmEraserActivationTime;
if (timeSinceActivation.TotalMilliseconds > PALM_ERASER_TIMEOUT_MS)
{
Trace.WriteLine($"Palm eraser timeout detected ({timeSinceActivation.TotalMilliseconds}ms), forcing recovery");
// 强制恢复状态
try
{
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
{
if (palmEraserLastEditingMode == InkCanvasEditingMode.Ink)
{
PenIcon_Click(null, null);
}
else if (palmEraserLastEditingMode == InkCanvasEditingMode.Select)
{
SymbolIconSelect_MouseUp(null, null);
}
else
{
inkCanvas.EditingMode = palmEraserLastEditingMode;
}
}
}
catch (Exception ex)
{
Trace.WriteLine($"Palm eraser timeout recovery failed: {ex.Message}, forcing to Ink mode");
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
// 重置所有手掌擦状态
isPalmEraserActive = false;
palmEraserTouchDownHandled = false;
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
// 停止恢复定时器
StopPalmEraserRecoveryTimer();
}
}
// 修复:几何绘制模式下,触摸抬手时应该正确处理,而不是简单模拟鼠标事件
if (drawingShapeMode != 0)
@@ -551,6 +643,16 @@ namespace Ink_Canvas
{
inkCanvas.EditingMode = lastInkCanvasEditingMode;
}
// 修复:确保手掌擦除后触摸事件能正常响应
if (isPalmEraserActive)
{
// 如果手掌擦还在激活状态但触摸点已清空,强制重置状态
isPalmEraserActive = false;
palmEraserTouchDownHandled = false;
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
}
}
}
inkCanvas.Opacity = 1;
@@ -753,5 +855,80 @@ namespace Ink_Canvas
ToggleSwitchEnablePalmEraser.IsOn = false;
}
}
/// <summary>
/// 启动手掌擦恢复定时器,防止卡死状态
/// </summary>
private void StartPalmEraserRecoveryTimer()
{
if (palmEraserRecoveryTimer == null)
{
palmEraserRecoveryTimer = new System.Windows.Threading.DispatcherTimer();
palmEraserRecoveryTimer.Interval = TimeSpan.FromMilliseconds(1000); // 每秒检查一次
palmEraserRecoveryTimer.Tick += PalmEraserRecoveryTimer_Tick;
}
palmEraserRecoveryTimer.Start();
}
/// <summary>
/// 停止手掌擦恢复定时器
/// </summary>
private void StopPalmEraserRecoveryTimer()
{
if (palmEraserRecoveryTimer != null)
{
palmEraserRecoveryTimer.Stop();
}
}
/// <summary>
/// 手掌擦恢复定时器事件处理
/// </summary>
private void PalmEraserRecoveryTimer_Tick(object sender, EventArgs e)
{
if (!isPalmEraserActive) return;
// 检查是否超时
var timeSinceActivation = DateTime.Now - palmEraserActivationTime;
if (timeSinceActivation.TotalMilliseconds > PALM_ERASER_TIMEOUT_MS)
{
Trace.WriteLine($"Palm eraser recovery timer triggered, forcing recovery after {timeSinceActivation.TotalMilliseconds}ms");
// 强制恢复状态
try
{
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
{
if (palmEraserLastEditingMode == InkCanvasEditingMode.Ink)
{
PenIcon_Click(null, null);
}
else if (palmEraserLastEditingMode == InkCanvasEditingMode.Select)
{
SymbolIconSelect_MouseUp(null, null);
}
else
{
inkCanvas.EditingMode = palmEraserLastEditingMode;
}
}
}
catch (Exception ex)
{
Trace.WriteLine($"Palm eraser recovery timer failed: {ex.Message}, forcing to Ink mode");
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
// 重置所有手掌擦状态
isPalmEraserActive = false;
palmEraserTouchDownHandled = false;
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
// 停止定时器
StopPalmEraserRecoveryTimer();
}
}
}
}
+1 -1
View File
@@ -84,7 +84,7 @@ namespace Ink_Canvas
startInfo.UseShellExecute = true;
// 启动进程但不等待
Process.Start(startInfo);
Process.Start(new ProcessStartInfo(exePath, "-delay 2000") { UseShellExecute = true });
}
catch (Exception ex)
{
+2 -2
View File
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.7.7.5")]
[assembly: AssemblyFileVersion("1.7.7.5")]
[assembly: AssemblyVersion("1.7.7.7")]
[assembly: AssemblyFileVersion("1.7.7.7")]
+18
View File
@@ -92,6 +92,13 @@ namespace Ink_Canvas
public bool ClearCanvasAlsoClearImages { get; set; } = true;
[JsonProperty("showCircleCenter")]
public bool ShowCircleCenter { get; set; } = false;
// 墨迹渐隐功能设置
[JsonProperty("enableInkFade")]
public bool EnableInkFade { get; set; } = false; // 是否启用墨迹渐隐功能
[JsonProperty("inkFadeTime")]
public int InkFadeTime { get; set; } = 3000; // 墨迹渐隐时间(毫秒)
}
public enum OptionalOperation
@@ -245,6 +252,14 @@ namespace Ink_Canvas
[JsonProperty("pptRSButtonPosition")]
public int PPTRSButtonPosition { get; set; }
// 0居中,+就是往右,-就是往左
[JsonProperty("pptLBButtonPosition")]
public int PPTLBButtonPosition { get; set; }
// 0居中,+就是往右,-就是往左
[JsonProperty("pptRBButtonPosition")]
public int PPTRBButtonPosition { get; set; }
[JsonProperty("pptSButtonsOption")]
public int PPTSButtonsOption { get; set; } = 221;
@@ -254,6 +269,9 @@ namespace Ink_Canvas
[JsonProperty("enablePPTButtonPageClickable")]
public bool EnablePPTButtonPageClickable { get; set; } = true;
[JsonProperty("enablePPTButtonLongPressPageTurn")]
public bool EnablePPTButtonLongPressPageTurn { get; set; } = true;
// -- new --
[JsonProperty("powerPointSupport")]
+65
View File
@@ -0,0 +1,65 @@
<UserControl x:Class="Ink_Canvas.Windows.HotkeyItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="600">
<Border Background="White"
BorderBrush="#E0E0E0"
BorderThickness="1"
CornerRadius="5"
Margin="0,2">
<Grid Margin="15,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- 左侧信息 -->
<ui:SimpleStackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock x:Name="TitleTextBlock"
Text="快捷键标题"
FontSize="14"
FontWeight="SemiBold"
Foreground="#333333"/>
<TextBlock x:Name="DescriptionTextBlock"
Text="快捷键描述"
FontSize="12"
Foreground="#666666"
Margin="0,2,0,0"/>
</ui:SimpleStackPanel>
<!-- 当前快捷键显示 -->
<Border Grid.Column="1"
Background="#F5F5F5"
BorderBrush="#D0D0D0"
BorderThickness="1"
CornerRadius="3"
Margin="10,0,0,0"
Padding="8,4">
<TextBlock x:Name="CurrentHotkeyTextBlock"
Text="未设置"
FontSize="12"
Foreground="#666666"
MinWidth="80"
TextAlignment="Center"/>
</Border>
<!-- 设置按钮 -->
<Button x:Name="BtnSetHotkey"
Grid.Column="2"
Content="设置"
Width="60"
Height="28"
Background="#0066BF"
Foreground="White"
FontSize="12"
Margin="10,0,0,0"
Click="BtnSetHotkey_Click"/>
</Grid>
</Border>
</UserControl>
+171
View File
@@ -0,0 +1,171 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Ink_Canvas.Windows
{
/// <summary>
/// 快捷键项控件
/// </summary>
public partial class HotkeyItem : UserControl
{
#region Events
/// <summary>
/// 快捷键变更事件
/// </summary>
public event EventHandler<HotkeyChangedEventArgs> HotkeyChanged;
#endregion
#region Properties
public string Title
{
get => TitleTextBlock.Text;
set => TitleTextBlock.Text = value;
}
public string Description
{
get => DescriptionTextBlock.Text;
set => DescriptionTextBlock.Text = value;
}
public string DefaultKey { get; set; }
public string DefaultModifiers { get; set; }
/// <summary>
/// 快捷键名称(用于标识,如"Undo"
/// </summary>
public string HotkeyName { get; set; }
private Key _currentKey = Key.None;
private ModifierKeys _currentModifiers = ModifierKeys.None;
#endregion
#region Constructor
public HotkeyItem()
{
InitializeComponent();
UpdateHotkeyDisplay();
}
#endregion
#region Public Methods
/// <summary>
/// 设置当前快捷键
/// </summary>
/// <param name="key">按键</param>
/// <param name="modifiers">修饰键</param>
public void SetCurrentHotkey(Key key, ModifierKeys modifiers)
{
_currentKey = key;
_currentModifiers = modifiers;
UpdateHotkeyDisplay();
}
/// <summary>
/// 获取当前快捷键
/// </summary>
/// <returns>快捷键信息</returns>
public (Key key, ModifierKeys modifiers) GetCurrentHotkey()
{
return (_currentKey, _currentModifiers);
}
#endregion
#region Private Methods
private void UpdateHotkeyDisplay()
{
if (_currentKey == Key.None)
{
CurrentHotkeyTextBlock.Text = "未设置";
CurrentHotkeyTextBlock.Foreground = System.Windows.Media.Brushes.Gray;
}
else
{
var modifiersText = _currentModifiers == ModifierKeys.None ? "" : $"{_currentModifiers}+";
CurrentHotkeyTextBlock.Text = $"{modifiersText}{_currentKey}";
CurrentHotkeyTextBlock.Foreground = System.Windows.Media.Brushes.Black;
}
}
private void StartHotkeyCapture()
{
BtnSetHotkey.Content = "请按键...";
BtnSetHotkey.Background = System.Windows.Media.Brushes.Orange;
// 设置焦点以捕获键盘事件
Focus();
// 添加键盘事件处理器
KeyDown += HotkeyItem_KeyDown;
KeyUp += HotkeyItem_KeyUp;
}
private void StopHotkeyCapture()
{
BtnSetHotkey.Content = "设置";
BtnSetHotkey.Background = System.Windows.Media.Brushes.DodgerBlue;
// 移除键盘事件处理器
KeyDown -= HotkeyItem_KeyDown;
KeyUp -= HotkeyItem_KeyUp;
}
private void HotkeyItem_KeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
// 忽略某些特殊键
if (e.Key == Key.LeftShift || e.Key == Key.RightShift ||
e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl ||
e.Key == Key.LeftAlt || e.Key == Key.RightAlt ||
e.Key == Key.LWin || e.Key == Key.RWin)
{
return;
}
// 获取修饰键
var modifiers = ModifierKeys.None;
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
modifiers |= ModifierKeys.Control;
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
modifiers |= ModifierKeys.Shift;
if (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt))
modifiers |= ModifierKeys.Alt;
if (Keyboard.IsKeyDown(Key.LWin) || Keyboard.IsKeyDown(Key.RWin))
modifiers |= ModifierKeys.Windows;
// 设置新的快捷键
var oldKey = _currentKey;
var oldModifiers = _currentModifiers;
_currentKey = e.Key;
_currentModifiers = modifiers;
UpdateHotkeyDisplay();
StopHotkeyCapture();
// 触发快捷键变更事件
HotkeyChanged?.Invoke(this, new HotkeyChangedEventArgs
{
HotkeyName = HotkeyName ?? Title, // 优先使用HotkeyName,如果没有则使用Title
Key = _currentKey,
Modifiers = _currentModifiers
});
}
private void HotkeyItem_KeyUp(object sender, KeyEventArgs e)
{
e.Handled = true;
}
#endregion
#region Event Handlers
private void BtnSetHotkey_Click(object sender, RoutedEventArgs e)
{
StartHotkeyCapture();
}
#endregion
}
}
@@ -0,0 +1,191 @@
<Window x:Class="Ink_Canvas.Windows.HotkeySettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Ink_Canvas.Windows"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
ui:ThemeManager.RequestedTheme="Light"
Topmost="True"
Background="Transparent"
AllowsTransparency="True"
mc:Ignorable="d"
WindowStyle="None"
WindowStartupLocation="CenterScreen"
Title="快捷键设置"
Height="600"
Width="800">
<Border Background="#F0F3F9" CornerRadius="10" BorderThickness="1" BorderBrush="#0066BF" Margin="10">
<Grid>
<!-- 标题栏 -->
<Border Height="50" Background="#0066BF" CornerRadius="10,10,0,0" VerticalAlignment="Top">
<Grid>
<TextBlock Text="快捷键设置"
Foreground="White"
FontSize="18"
FontWeight="Bold"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<Button x:Name="BtnClose"
Content="✕"
Width="30"
Height="30"
Background="Transparent"
Foreground="White"
FontSize="14"
HorizontalAlignment="Right"
Margin="0,0,30,0"
Click="BtnClose_Click"/>
</Grid>
</Border>
<!-- 内容区域 -->
<ScrollViewer Margin="0,50,0,0" VerticalScrollBarVisibility="Auto">
<ui:SimpleStackPanel Margin="20">
<!-- 说明文字 -->
<TextBlock Text="在这里可以自定义全局快捷键设置。全局快捷键在任何情况下都能生效,即使应用程序不在焦点状态。"
TextWrapping="Wrap"
Margin="0,0,0,20"
Foreground="#666666"/>
<!-- 快捷键列表 -->
<ui:SimpleStackPanel x:Name="HotkeyList" Margin="0,0,0,20">
<!-- 基本操作 -->
<GroupBox Header="基本操作" Margin="0,0,0,15">
<ui:SimpleStackPanel>
<local:HotkeyItem x:Name="UndoHotkey"
Title="撤销"
Description="撤销上一步操作"
DefaultKey="Z"
DefaultModifiers="Control"/>
<local:HotkeyItem x:Name="RedoHotkey"
Title="重做"
Description="重做上一步操作"
DefaultKey="Y"
DefaultModifiers="Control"/>
<local:HotkeyItem x:Name="ClearHotkey"
Title="清空"
Description="清空当前画板内容"
DefaultKey="E"
DefaultModifiers="Control"/>
<local:HotkeyItem x:Name="PasteHotkey"
Title="粘贴"
Description="粘贴剪贴板内容"
DefaultKey="V"
DefaultModifiers="Control"/>
</ui:SimpleStackPanel>
</GroupBox>
<!-- 工具切换 -->
<GroupBox Header="工具切换" Margin="0,0,0,15">
<ui:SimpleStackPanel>
<local:HotkeyItem x:Name="SelectToolHotkey"
Title="选择工具"
Description="切换到选择工具"
DefaultKey="S"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="DrawToolHotkey"
Title="绘图工具"
Description="切换到绘图工具"
DefaultKey="D"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="EraserToolHotkey"
Title="橡皮擦工具"
Description="切换到橡皮擦工具"
DefaultKey="E"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="BlackboardToolHotkey"
Title="黑板工具"
Description="切换到黑板工具"
DefaultKey="B"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="QuitDrawToolHotkey"
Title="退出绘图"
Description="退出绘图模式"
DefaultKey="Q"
DefaultModifiers="Alt"/>
</ui:SimpleStackPanel>
</GroupBox>
<!-- 画笔设置 -->
<GroupBox Header="画笔设置" Margin="0,0,0,15">
<ui:SimpleStackPanel>
<local:HotkeyItem x:Name="Pen1Hotkey"
Title="画笔1"
Description="选择画笔1"
DefaultKey="D1"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="Pen2Hotkey"
Title="画笔2"
Description="选择画笔2"
DefaultKey="D2"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="Pen3Hotkey"
Title="画笔3"
Description="选择画笔3"
DefaultKey="D3"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="Pen4Hotkey"
Title="画笔4"
Description="选择画笔4"
DefaultKey="D4"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="Pen5Hotkey"
Title="画笔5"
Description="选择画笔5"
DefaultKey="D5"
DefaultModifiers="Alt"/>
</ui:SimpleStackPanel>
</GroupBox>
<!-- 功能快捷键 -->
<GroupBox Header="功能快捷键" Margin="0,0,0,15">
<ui:SimpleStackPanel>
<local:HotkeyItem x:Name="DrawLineHotkey"
Title="绘制直线"
Description="绘制直线工具"
DefaultKey="L"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="ScreenshotHotkey"
Title="截图"
Description="保存屏幕截图到桌面"
DefaultKey="C"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="HideHotkey"
Title="隐藏"
Description="隐藏应用程序"
DefaultKey="V"
DefaultModifiers="Alt"/>
<local:HotkeyItem x:Name="ExitHotkey"
Title="退出"
Description="退出当前模式或应用程序"
DefaultKey="Escape"
DefaultModifiers="None"/>
</ui:SimpleStackPanel>
</GroupBox>
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
</ScrollViewer>
<!-- 底部按钮 -->
<Border Height="60" Background="#F8F9FA" CornerRadius="0,0,10,10" VerticalAlignment="Bottom">
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,20,0">
<Button x:Name="BtnResetToDefault"
Content="重置为默认"
Width="100"
Height="35"
Margin="0,0,10,0"
Click="BtnResetToDefault_Click"/>
<Button x:Name="BtnSave"
Content="保存设置"
Width="100"
Height="35"
Background="#0066BF"
Foreground="White"
Click="BtnSave_Click"/>
</ui:SimpleStackPanel>
</Border>
</Grid>
</Border>
</Window>
@@ -0,0 +1,570 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using Ink_Canvas.Helpers;
namespace Ink_Canvas.Windows
{
/// <summary>
/// 快捷键设置窗口
/// </summary>
public partial class HotkeySettingsWindow : Window
{
#region Private Fields
private readonly MainWindow _mainWindow;
private readonly GlobalHotkeyManager _hotkeyManager;
private readonly Dictionary<string, HotkeyItem> _hotkeyItems;
#endregion
#region Constructor
public HotkeySettingsWindow(MainWindow mainWindow, GlobalHotkeyManager hotkeyManager)
{
InitializeComponent();
_mainWindow = mainWindow;
_hotkeyManager = hotkeyManager;
_hotkeyItems = new Dictionary<string, HotkeyItem>();
// 隐藏主窗口的设置页面
HideMainWindowSettings();
InitializeHotkeyItems();
// 延迟加载快捷键,确保快捷键管理器已完全初始化
this.Loaded += (s, e) =>
{
try
{
// 不启用快捷键注册功能,只读取配置文件中的快捷键信息用于显示
// 这样用户可以看到配置文件中保存的快捷键,但不会自动注册
// 加载当前快捷键(包括配置文件中的)
LoadCurrentHotkeys();
SetupEventHandlers();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"快捷键设置窗口初始化时出错: {ex.Message}", LogHelper.LogType.Error);
}
};
// 注册窗口关闭事件
this.Closed += HotkeySettingsWindow_Closed;
}
#endregion
#region Private Methods
private void InitializeHotkeyItems()
{
try
{
LogHelper.WriteLogToFile("开始初始化快捷键项", LogHelper.LogType.Info);
// 初始化快捷键项并设置HotkeyName
_hotkeyItems["Undo"] = UndoHotkey;
UndoHotkey.HotkeyName = "Undo";
_hotkeyItems["Redo"] = RedoHotkey;
RedoHotkey.HotkeyName = "Redo";
_hotkeyItems["Clear"] = ClearHotkey;
ClearHotkey.HotkeyName = "Clear";
_hotkeyItems["Paste"] = PasteHotkey;
PasteHotkey.HotkeyName = "Paste";
_hotkeyItems["SelectTool"] = SelectToolHotkey;
SelectToolHotkey.HotkeyName = "SelectTool";
_hotkeyItems["DrawTool"] = DrawToolHotkey;
DrawToolHotkey.HotkeyName = "DrawTool";
_hotkeyItems["EraserTool"] = EraserToolHotkey;
EraserToolHotkey.HotkeyName = "EraserTool";
_hotkeyItems["BlackboardTool"] = BlackboardToolHotkey;
BlackboardToolHotkey.HotkeyName = "BlackboardTool";
_hotkeyItems["QuitDrawTool"] = QuitDrawToolHotkey;
QuitDrawToolHotkey.HotkeyName = "QuitDrawTool";
_hotkeyItems["Pen1"] = Pen1Hotkey;
Pen1Hotkey.HotkeyName = "Pen1";
_hotkeyItems["Pen2"] = Pen2Hotkey;
Pen2Hotkey.HotkeyName = "Pen2";
_hotkeyItems["Pen3"] = Pen3Hotkey;
Pen3Hotkey.HotkeyName = "Pen3";
_hotkeyItems["Pen4"] = Pen4Hotkey;
Pen4Hotkey.HotkeyName = "Pen4";
_hotkeyItems["Pen5"] = Pen5Hotkey;
Pen5Hotkey.HotkeyName = "Pen5";
_hotkeyItems["DrawLine"] = DrawLineHotkey;
DrawLineHotkey.HotkeyName = "DrawLine";
_hotkeyItems["Screenshot"] = ScreenshotHotkey;
ScreenshotHotkey.HotkeyName = "Screenshot";
_hotkeyItems["Hide"] = HideHotkey;
HideHotkey.HotkeyName = "Hide";
_hotkeyItems["Exit"] = ExitHotkey;
ExitHotkey.HotkeyName = "Exit";
LogHelper.WriteLogToFile($"成功初始化 {_hotkeyItems.Count} 个快捷键项", LogHelper.LogType.Info);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化快捷键项时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
private void LoadCurrentHotkeys()
{
try
{
// 首先尝试从配置文件获取快捷键信息
var configHotkeys = _hotkeyManager.GetHotkeysFromConfigFile();
LogHelper.WriteLogToFile($"配置文件中的快捷键数量: {configHotkeys.Count}", LogHelper.LogType.Info);
// 显示配置文件中的快捷键
foreach (var hotkey in configHotkeys)
{
if (_hotkeyItems.TryGetValue(hotkey.Name, out var hotkeyItem))
{
hotkeyItem.SetCurrentHotkey(hotkey.Key, hotkey.Modifiers);
LogHelper.WriteLogToFile($"从配置文件设置快捷键项: {hotkey.Name} -> {hotkey.Modifiers}+{hotkey.Key}", LogHelper.LogType.Info);
}
}
// 为没有快捷键的项目设置默认显示值(仅用于UI显示,不实际注册)
foreach (var kvp in _hotkeyItems)
{
var hotkeyItem = kvp.Value;
if (hotkeyItem.GetCurrentHotkey().key == Key.None)
{
// 根据DefaultKey和DefaultModifiers设置默认显示值
SetDefaultHotkeyForItem(hotkeyItem);
LogHelper.WriteLogToFile($"设置默认显示值: {hotkeyItem.HotkeyName}", LogHelper.LogType.Info);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"加载当前快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 为快捷键项设置默认值
/// </summary>
private void SetDefaultHotkeyForItem(HotkeyItem hotkeyItem)
{
try
{
// 根据HotkeyName设置默认快捷键
switch (hotkeyItem.HotkeyName)
{
case "Undo":
hotkeyItem.SetCurrentHotkey(Key.Z, ModifierKeys.Control);
break;
case "Redo":
hotkeyItem.SetCurrentHotkey(Key.Y, ModifierKeys.Control);
break;
case "Clear":
hotkeyItem.SetCurrentHotkey(Key.E, ModifierKeys.Control);
break;
case "Paste":
hotkeyItem.SetCurrentHotkey(Key.V, ModifierKeys.Control);
break;
case "SelectTool":
hotkeyItem.SetCurrentHotkey(Key.S, ModifierKeys.Alt);
break;
case "DrawTool":
hotkeyItem.SetCurrentHotkey(Key.D, ModifierKeys.Alt);
break;
case "EraserTool":
hotkeyItem.SetCurrentHotkey(Key.E, ModifierKeys.Alt);
break;
case "BlackboardTool":
hotkeyItem.SetCurrentHotkey(Key.B, ModifierKeys.Alt);
break;
case "QuitDrawTool":
hotkeyItem.SetCurrentHotkey(Key.Q, ModifierKeys.Alt);
break;
case "Pen1":
hotkeyItem.SetCurrentHotkey(Key.D1, ModifierKeys.Alt);
break;
case "Pen2":
hotkeyItem.SetCurrentHotkey(Key.D2, ModifierKeys.Alt);
break;
case "Pen3":
hotkeyItem.SetCurrentHotkey(Key.D3, ModifierKeys.Alt);
break;
case "Pen4":
hotkeyItem.SetCurrentHotkey(Key.D4, ModifierKeys.Alt);
break;
case "Pen5":
hotkeyItem.SetCurrentHotkey(Key.D5, ModifierKeys.Alt);
break;
case "DrawLine":
hotkeyItem.SetCurrentHotkey(Key.L, ModifierKeys.Alt);
break;
case "Screenshot":
hotkeyItem.SetCurrentHotkey(Key.C, ModifierKeys.Alt);
break;
case "Hide":
hotkeyItem.SetCurrentHotkey(Key.V, ModifierKeys.Alt);
break;
case "Exit":
hotkeyItem.SetCurrentHotkey(Key.Escape, ModifierKeys.None);
break;
}
}
catch (Exception ex)
{
// 设置默认快捷键时出错,忽略
}
}
private void SetupEventHandlers()
{
// 为每个快捷键项设置事件处理器
foreach (var hotkeyItem in _hotkeyItems.Values)
{
hotkeyItem.HotkeyChanged += OnHotkeyChanged;
}
}
private void OnHotkeyChanged(object sender, HotkeyChangedEventArgs e)
{
try
{
LogHelper.WriteLogToFile($"收到快捷键变更事件: {e.HotkeyName} -> {e.Modifiers}+{e.Key}", LogHelper.LogType.Info);
// 检查快捷键冲突
if (IsHotkeyConflict(e.Key, e.Modifiers, e.HotkeyName))
{
MessageBox.Show($"快捷键 {e.Modifiers}+{e.Key} 已被其他功能使用,请选择其他组合。",
"快捷键冲突", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 更新快捷键管理器
UpdateHotkeyInManager(e.HotkeyName, e.Key, e.Modifiers);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理快捷键变更时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
private bool IsHotkeyConflict(Key key, ModifierKeys modifiers, string excludeHotkeyName)
{
// 检查是否与已注册的快捷键冲突
var registeredHotkeys = _hotkeyManager.GetRegisteredHotkeys();
foreach (var hotkey in registeredHotkeys)
{
if (hotkey.Name != excludeHotkeyName &&
hotkey.Key == key &&
hotkey.Modifiers == modifiers)
{
return true;
}
}
// 检查是否与默认快捷键冲突(如果当前快捷键项还没有注册)
if (excludeHotkeyName != null && _hotkeyItems.TryGetValue(excludeHotkeyName, out var currentItem))
{
var currentHotkey = currentItem.GetCurrentHotkey();
if (currentHotkey.key == Key.None)
{
// 如果当前项还没有快捷键,检查是否与其他默认快捷键冲突
foreach (var kvp in _hotkeyItems)
{
if (kvp.Key != excludeHotkeyName)
{
var item = kvp.Value;
var itemHotkey = item.GetCurrentHotkey();
if (itemHotkey.key == key && itemHotkey.modifiers == modifiers)
{
return true;
}
}
}
}
}
return false;
}
private void UpdateHotkeyInManager(string hotkeyName, Key key, ModifierKeys modifiers)
{
try
{
LogHelper.WriteLogToFile($"开始更新快捷键: {hotkeyName} -> {modifiers}+{key}", LogHelper.LogType.Info);
// 先注销原有的快捷键(如果存在)
_hotkeyManager.UnregisterHotkey(hotkeyName);
LogHelper.WriteLogToFile($"已注销原有快捷键: {hotkeyName}", LogHelper.LogType.Info);
// 根据快捷键名称获取对应的动作
var action = GetActionForHotkey(hotkeyName);
if (action != null)
{
LogHelper.WriteLogToFile($"找到快捷键动作: {hotkeyName}", LogHelper.LogType.Info);
// 直接注册新的快捷键
if (_hotkeyManager.RegisterHotkey(hotkeyName, key, modifiers, action))
{
LogHelper.WriteLogToFile($"成功注册新快捷键: {hotkeyName} -> {modifiers}+{key}", LogHelper.LogType.Info);
// 立即保存到配置文件
_hotkeyManager.SaveHotkeysToSettings();
LogHelper.WriteLogToFile($"已保存快捷键配置", LogHelper.LogType.Info);
// 更新UI显示
LoadCurrentHotkeys();
LogHelper.WriteLogToFile($"已更新UI显示", LogHelper.LogType.Info);
LogHelper.WriteLogToFile($"快捷键 {hotkeyName} 已更新为 {modifiers}+{key} 并保存", LogHelper.LogType.Event);
}
else
{
LogHelper.WriteLogToFile($"更新快捷键 {hotkeyName} 失败", LogHelper.LogType.Error);
}
}
else
{
LogHelper.WriteLogToFile($"未找到快捷键 {hotkeyName} 对应的动作", LogHelper.LogType.Warning);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新快捷键管理器时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
private Action GetActionForHotkey(string hotkeyName)
{
switch (hotkeyName)
{
case "Undo":
return () => _mainWindow.SymbolIconUndo_MouseUp(null, null);
case "Redo":
return () => _mainWindow.SymbolIconRedo_MouseUp(null, null);
case "Clear":
return () => _mainWindow.SymbolIconDelete_MouseUp(null, null);
case "Paste":
return () => _mainWindow.HandleGlobalPaste(null, null);
case "SelectTool":
return () => _mainWindow.SymbolIconSelect_MouseUp(null, null);
case "DrawTool":
return () => _mainWindow.PenIcon_Click(null, null);
case "EraserTool":
return () => _mainWindow.EraserIcon_Click(null, null);
case "BlackboardTool":
return () => _mainWindow.ImageBlackboard_MouseUp(null, null);
case "QuitDrawTool":
return () => _mainWindow.CursorIcon_Click(null, null);
case "Pen1":
return () => SwitchToPenType(0);
case "Pen2":
return () => SwitchToPenType(1);
case "Pen3":
return () => SwitchToPenType(2);
case "Pen4":
return () => SwitchToPenType(3);
case "Pen5":
return () => SwitchToPenType(4);
case "DrawLine":
return () => _mainWindow.BtnDrawLine_Click(null, null);
case "Screenshot":
return () => _mainWindow.SaveScreenShotToDesktop();
case "Hide":
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
case "Exit":
return () => _mainWindow.KeyExit(null, null);
default:
return null;
}
}
/// <summary>
/// 切换到指定笔类型
/// </summary>
/// <param name="penTypeIndex">笔类型索引</param>
private void SwitchToPenType(int penTypeIndex)
{
try
{
// 通过反射访问主窗口的penType字段
var penTypeField = _mainWindow.GetType().GetField("penType",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (penTypeField != null)
{
penTypeField.SetValue(_mainWindow, penTypeIndex);
// 调用CheckPenTypeUIState方法更新UI状态
var checkPenTypeMethod = _mainWindow.GetType().GetMethod("CheckPenTypeUIState",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (checkPenTypeMethod != null)
{
checkPenTypeMethod.Invoke(_mainWindow, null);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换到笔类型{penTypeIndex}时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
#region MainWindow Settings Management
/// <summary>
/// 隐藏主窗口的设置页面
/// </summary>
private void HideMainWindowSettings()
{
try
{
// 通过反射访问主窗口的设置面板
var settingsBorder = _mainWindow.GetType().GetField("BorderSettings",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(_mainWindow) as System.Windows.Controls.Border;
if (settingsBorder != null)
{
settingsBorder.Visibility = System.Windows.Visibility.Collapsed;
}
// 隐藏设置蒙版
var settingsMask = _mainWindow.GetType().GetField("BorderSettingsMask",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(_mainWindow) as System.Windows.Controls.Border;
if (settingsMask != null)
{
settingsMask.Visibility = System.Windows.Visibility.Collapsed;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"隐藏主窗口设置页面时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 显示主窗口的设置页面
/// </summary>
private void ShowMainWindowSettings()
{
try
{
// 通过反射访问主窗口的设置面板
var settingsBorder = _mainWindow.GetType().GetField("BorderSettings",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(_mainWindow) as System.Windows.Controls.Border;
if (settingsBorder != null)
{
settingsBorder.Visibility = System.Windows.Visibility.Visible;
}
// 显示设置蒙版
var settingsMask = _mainWindow.GetType().GetField("BorderSettingsMask",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(_mainWindow) as System.Windows.Controls.Border;
if (settingsMask != null)
{
settingsMask.Visibility = System.Windows.Visibility.Visible;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"显示主窗口设置页面时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
#region Window Event Handlers
/// <summary>
/// 窗口关闭事件处理
/// </summary>
private void HotkeySettingsWindow_Closed(object sender, EventArgs e)
{
// 恢复主窗口设置页面的显示
ShowMainWindowSettings();
}
#endregion
#region Event Handlers
private void BtnClose_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void BtnResetToDefault_Click(object sender, RoutedEventArgs e)
{
try
{
var result = MessageBox.Show("确定要重置所有快捷键为默认设置吗?",
"确认重置", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
// 先注销所有现有快捷键
_hotkeyManager.UnregisterAllHotkeys();
// 重置为默认快捷键
_hotkeyManager.RegisterDefaultHotkeys();
// 立即保存到配置文件
_hotkeyManager.SaveHotkeysToSettings();
// 更新UI显示
LoadCurrentHotkeys();
MessageBox.Show("快捷键已重置为默认设置。", "重置完成", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"重置快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
MessageBox.Show($"重置快捷键时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void BtnSave_Click(object sender, RoutedEventArgs e)
{
try
{
// 保存快捷键配置
_hotkeyManager.SaveHotkeysToSettings();
MessageBox.Show("快捷键设置已保存。", "保存成功", MessageBoxButton.OK, MessageBoxImage.Information);
Close();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"保存快捷键设置时出错: {ex.Message}", LogHelper.LogType.Error);
MessageBox.Show($"保存快捷键设置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
#endregion
}
#region Hotkey Changed Event Args
/// <summary>
/// 快捷键变更事件参数
/// </summary>
public class HotkeyChangedEventArgs : EventArgs
{
public string HotkeyName { get; set; }
public Key Key { get; set; }
public ModifierKeys Modifiers { get; set; }
}
#endregion
}
+16 -8
View File
@@ -103,14 +103,22 @@
<TextBlock Text="开抽" Foreground="White" FontSize="32" Margin="-1,-1,4,0" VerticalAlignment="Center"/>
</ui:SimpleStackPanel>
</Border>
<Border x:Name="BorderBtnIslandCaller" MouseUp="BorderBtnIslandCaller_MouseUp" Background="#00B894" Height="50" Width="200" CornerRadius="25" Margin="0,16,0,0">
<ui:SimpleStackPanel Margin="3,0" Spacing="12" Orientation="Horizontal" HorizontalAlignment="Center">
<Viewbox Margin="0,10">
<ui:SymbolIcon Symbol="Globe" Foreground="White"/>
</Viewbox>
<TextBlock Text="ClassIsland点名" Foreground="White" FontSize="18" Margin="-1,-1,4,0" VerticalAlignment="Center"/>
</ui:SimpleStackPanel>
</Border>
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="8" Margin="0,16,0,0" HorizontalAlignment="Center">
<ComboBox Name="ComboBoxCallerType" Width="120" Height="50" VerticalAlignment="Center"
IsEditable="False" IsReadOnly="True" SelectedIndex="0">
<ComboBoxItem>ClassIsland点名</ComboBoxItem>
<ComboBoxItem>SecRandom点名</ComboBoxItem>
<ComboBoxItem>NamePicker点名</ComboBoxItem>
</ComboBox>
<Border x:Name="BorderBtnExternalCaller" MouseUp="BorderBtnExternalCaller_MouseUp" Background="#00B894" Height="50" Width="120" CornerRadius="25">
<ui:SimpleStackPanel Margin="3,0" Spacing="8" Orientation="Horizontal" HorizontalAlignment="Center">
<Viewbox Margin="0,10">
<ui:SymbolIcon Symbol="Globe" Foreground="White"/>
</Viewbox>
<TextBlock Text="外部点名" Foreground="White" FontSize="16" Margin="-1,-1,4,0" VerticalAlignment="Center"/>
</ui:SimpleStackPanel>
</Border>
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
</Grid>
<Border UseLayoutRounding="True" Canvas.Bottom="8" Canvas.Right="8" x:Name="BorderBtnHelp" MouseUp="BorderBtnHelp_MouseUp" Background="#FBFBFD" Grid.Column="1" Margin="10,10,60,10" Height="40" VerticalAlignment="Bottom" HorizontalAlignment="Right" CornerRadius="20">
+21 -4
View File
@@ -295,13 +295,13 @@ namespace Ink_Canvas
// 将 isIslandCallerFirstClick 设为静态字段,实现全局记录
private static bool isIslandCallerFirstClick = true;
private void BorderBtnIslandCaller_MouseUp(object sender, MouseButtonEventArgs e)
private void BorderBtnExternalCaller_MouseUp(object sender, MouseButtonEventArgs e)
{
if (isIslandCallerFirstClick)
{
MessageBox.Show(
"首次使用ClassIsland点名功能,请确保已安装ClassIsland和Island caller插件。\n" +
"如未安装,请前往官网下载并安装后再使用。如果安装请再次点击此按钮。",
"首次使用外部点名功能,请确保已安装相应的点名软件。\n" +
"如未安装,请前往官网下载并安装后再使用。如果安装请再次点击此按钮。",
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
isIslandCallerFirstClick = false;
return;
@@ -309,9 +309,26 @@ namespace Ink_Canvas
try
{
string protocol = "";
switch (ComboBoxCallerType.SelectedIndex)
{
case 0: // ClassIsland点名
protocol = "classisland://plugins/IslandCaller/Simple/1";
break;
case 1: // SecRandom点名
protocol = "secrandom://pumping?action=start";
break;
case 2: // NamePicker点名
protocol = "namepicker://";
break;
default:
protocol = "classisland://plugins/IslandCaller/Simple/1";
break;
}
Process.Start(new ProcessStartInfo
{
FileName = "classisland://plugins/IslandCaller/Run",
FileName = protocol,
UseShellExecute = true
});
}
+2 -1
View File
@@ -85,6 +85,7 @@
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CJKmkp"><img src="https://avatars.githubusercontent.com/u/113243675?v=4?s=100" width="100px;" alt="CJK_mkp"/><br /><sub><b>CJK_mkp</b></sub></a><br /><a href="#maintenance-CJKmkp" title="Maintenance">🚧</a> <a href="#doc-CJKmkp" title="Documentation">📖</a> <a href="#code-CJKmkp" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hydro11451.qzz.io"><img src="https://avatars.githubusercontent.com/u/214308559?v=4?s=100" width="100px;" alt="Hydrogen"/><br /><sub><b>Hydrogen</b></sub></a><br /><a href="#code-Hydro11451" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CreeperAWA"><img src="https://avatars.githubusercontent.com/u/134939494?v=4?s=100" width="100px;" alt="CreeperAWA"/><br /><sub><b>CreeperAWA</b></sub></a><br /><a href="#code-CreeperAWA" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/2-2-3-trimethylpentane"><img src="https://avatars.githubusercontent.com/u/141403762?v=4?s=100" width="100px;" alt="2,2,3-三甲基戊烷"/><br /><sub><b>2,2,3-三甲基戊烷</b></sub></a><br /><a href="#blog-2-2-3-trimethylpentane" title="Blogposts">📝</a> <a href="#doc-2-2-3-trimethylpentane" title="Documentation">📖</a> <a href="#design-2-2-3-trimethylpentane" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alan-CRL"><img src="https://avatars.githubusercontent.com/u/92425617?v=4?s=100" width="100px;" alt="Alan-CRL"/><br /><sub><b>Alan-CRL</b></sub></a><br /><a href="#code-Alan-CRL" title="Code">💻</a> <a href="#infra-Alan-CRL" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#doc-Alan-CRL" title="Documentation">📖</a> <a href="#financial-Alan-CRL" title="Financial">💵</a></td>
@@ -92,7 +93,7 @@
<td align="center" valign="top" width="14.28%"><a href="https://github.com/awesome-iwb"><img src="https://avatars.githubusercontent.com/u/184760810?v=4?s=100" width="100px;" alt="Awesome Iwb"/><br /><sub><b>Awesome Iwb</b></sub></a><br /><a href="#doc-awesome-iwb" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PrefacedCorg"><img src="https://avatars.githubusercontent.com/u/129855423?v=4?s=100" width="100px;" alt="PrefacedCorg"/><br /><sub><b>PrefacedCorg</b></sub></a><br /><a href="#code-PrefacedCorg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PrefacedCorg"><img src="https://avatars.githubusercontent.com/u/129855423?v=4?s=100" width="100px;" alt="PrefacedCorg"/><br /><sub><b>PrefacedCorg</b></sub></a><br /><a href="#code-PrefacedCorg" title="Code">💻</a> <a href="#design-PrefacedCorg" title="Design">🎨</a></td>
</tr>
</tbody>
</table>