diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt
index a3caf1e9..784b0d37 100644
--- a/AutomaticUpdateVersionControl.txt
+++ b/AutomaticUpdateVersionControl.txt
@@ -1 +1 @@
-1.7.11.0
+1.7.12.0
diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs
index 2e4f8e12..396a012e 100644
--- a/Ink Canvas/App.xaml.cs
+++ b/Ink Canvas/App.xaml.cs
@@ -1,5 +1,6 @@
using Hardcodet.Wpf.TaskbarNotification;
using Ink_Canvas.Helpers;
+using Ink_Canvas.Windows;
using iNKORE.UI.WPF.Modern.Controls;
using Microsoft.Win32;
using Newtonsoft.Json;
@@ -14,12 +15,14 @@ using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Threading;
using Application = System.Windows.Application;
using MessageBox = System.Windows.MessageBox;
+using SplashScreen = Ink_Canvas.Windows.SplashScreen;
using Timer = System.Threading.Timer;
namespace Ink_Canvas
@@ -36,6 +39,8 @@ namespace Ink_Canvas
// 新增:标记是否通过--board参数启动
public static bool StartWithBoardMode = false;
+ // 新增:标记是否通过--show参数启动
+ public static bool StartWithShowMode = false;
// 新增:保存看门狗进程对象
private static Process watchdogProcess;
// 新增:标记是否为软件内主动退出
@@ -52,6 +57,9 @@ namespace Ink_Canvas
private static string lastErrorMessage = string.Empty;
// 新增:是否已初始化崩溃监听器
private static bool crashListenersInitialized;
+ // 新增:启动画面相关
+ private static SplashScreen _splashScreen;
+ private static bool _isSplashScreenShown = false;
public App()
{
@@ -74,7 +82,7 @@ namespace Ink_Canvas
DispatcherUnhandledException += App_DispatcherUnhandledException;
StartHeartbeatMonitor();
- // 新增:初始化全局异常和进程结束处理
+ // 初始化全局异常和进程结束处理
InitializeCrashListeners();
// 仅在崩溃后操作为静默重启时才启动看门狗
@@ -90,7 +98,7 @@ namespace Ink_Canvas
Exit += App_Exit; // 注册退出事件
}
- // 新增:配置TLS协议以支持Windows 7
+ // 配置TLS协议以支持Windows 7
private void ConfigureTlsForWindows7()
{
try
@@ -125,7 +133,7 @@ namespace Ink_Canvas
}
}
- // 新增:初始化崩溃监听器
+ // 初始化崩溃监听器
private void InitializeCrashListeners()
{
if (crashListenersInitialized) return;
@@ -173,7 +181,7 @@ namespace Ink_Canvas
}
}
- // 新增:动态加载WMI监控(避免直接引用System.Management)
+ // 动态加载WMI监控
private void TrySetupWmiMonitoring()
{
try
@@ -226,7 +234,7 @@ namespace Ink_Canvas
}
}
- // WMI事件处理方法(通过反射调用)
+ // WMI事件处理方法
private void WmiEventHandler(object sender, EventArgs e)
{
try
@@ -250,7 +258,7 @@ namespace Ink_Canvas
}
}
- // 新增:Windows控制台控制处理程序
+ // Windows控制台控制处理程序
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);
@@ -289,43 +297,52 @@ namespace Ink_Canvas
return false;
}
- // 新增:系统会话结束事件处理
+ // 系统会话结束事件处理
private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
{
string reason = e.Reason == SessionEndReasons.Logoff ? "用户注销" : "系统关机";
WriteCrashLog($"系统会话即将结束: {reason}");
- // 清理PowerPoint进程守护
+ // 清理PowerPoint进程守护和悬浮窗拦截器
try
{
- // 获取主窗口实例并清理PowerPoint进程守护
+ // 获取主窗口实例
var mainWindow = Current.MainWindow as MainWindow;
if (mainWindow != null)
{
- // 通过反射调用StopPowerPointProcessMonitoring方法
+ // 清理PowerPoint进程守护
var method = mainWindow.GetType().GetMethod("StopPowerPointProcessMonitoring",
BindingFlags.NonPublic | BindingFlags.Instance);
method?.Invoke(mainWindow, null);
-
WriteCrashLog("PowerPoint进程守护已在系统关机时清理");
+
+ // 清理悬浮窗拦截器
+ var interceptorField = mainWindow.GetType().GetField("_floatingWindowInterceptorManager",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ var interceptorManager = interceptorField?.GetValue(mainWindow);
+ if (interceptorManager != null)
+ {
+ var disposeMethod = interceptorManager.GetType().GetMethod("Dispose");
+ disposeMethod?.Invoke(interceptorManager, null);
+ }
}
}
catch (Exception ex)
{
- WriteCrashLog($"清理PowerPoint进程守护失败: {ex.Message}");
+ WriteCrashLog($"清理资源失败: {ex.Message}");
}
DeviceIdentifier.SaveUsageStatsOnShutdown();
}
- // 新增:控制台取消事件处理
+ // 控制台取消事件处理
private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
WriteCrashLog($"接收到控制台中断信号: {e.SpecialKey}");
e.Cancel = true; // 取消默认处理
}
- // 新增:处理非UI线程的未处理异常
+ // 处理非UI线程的未处理异常
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
try
@@ -355,7 +372,7 @@ namespace Ink_Canvas
}
}
- // 新增:处理进程退出事件
+ // 处理进程退出事件
private void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
TimeSpan runDuration = DateTime.Now - appStartTime;
@@ -369,7 +386,7 @@ namespace Ink_Canvas
}
}
- // 新增:格式化时间跨度
+ // 格式化时间跨度
private static string FormatTimeSpan(TimeSpan timeSpan)
{
if (timeSpan.TotalDays >= 1)
@@ -390,7 +407,92 @@ namespace Ink_Canvas
return $"{timeSpan.Seconds}秒";
}
- // 新增:记录崩溃日志
+ public static void ShowSplashScreen()
+ {
+ if (_isSplashScreenShown)
+ {
+ LogHelper.WriteLogToFile("启动画面已经显示,跳过重复显示");
+ return;
+ }
+
+ try
+ {
+ LogHelper.WriteLogToFile("开始创建启动画面...");
+ _splashScreen = new SplashScreen();
+ LogHelper.WriteLogToFile("启动画面对象创建成功,准备显示...");
+ _splashScreen.Show();
+ _isSplashScreenShown = true;
+ LogHelper.WriteLogToFile("启动画面已显示");
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"显示启动画面失败: {ex.Message}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"异常堆栈: {ex.StackTrace}", LogHelper.LogType.Error);
+ }
+ }
+
+ // 关闭启动画面
+ public static void CloseSplashScreen()
+ {
+ if (!_isSplashScreenShown || _splashScreen == null) return;
+
+ try
+ {
+ _splashScreen.CloseSplashScreen();
+ _isSplashScreenShown = false;
+ LogHelper.WriteLogToFile("启动画面已关闭");
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"关闭启动画面失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ // 设置启动画面进度
+ public static void SetSplashProgress(int progress)
+ {
+ if (_splashScreen != null)
+ {
+ _splashScreen.SetProgress(progress);
+ }
+ }
+
+ // 设置启动画面消息
+ public static void SetSplashMessage(string message)
+ {
+ if (_splashScreen != null)
+ {
+ _splashScreen.SetLoadingMessage(message);
+ }
+ }
+
+ private static bool ShouldShowSplashScreen()
+ {
+ try
+ {
+ // 检查设置文件中的启动动画开关
+ var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
+ if (File.Exists(settingsPath))
+ {
+ var json = File.ReadAllText(settingsPath);
+ dynamic obj = JsonConvert.DeserializeObject(json);
+ if (obj?["appearance"]?["enableSplashScreen"] != null)
+ {
+ return (bool)obj["appearance"]["enableSplashScreen"];
+ }
+ }
+
+ // 如果设置文件不存在或没有该设置,返回默认值false
+ return false;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"检查启动动画设置失败: {ex.Message}", LogHelper.LogType.Warning);
+ return false;
+ }
+ }
+
+ // 记录崩溃日志
private static void WriteCrashLog(string message)
{
try
@@ -425,7 +527,6 @@ namespace Ink_Canvas
// 增加字段保存崩溃后操作设置
public static CrashActionType CrashAction = CrashActionType.SilentRestart;
- // 修正:允许静态调用
public static void SyncCrashActionFromSettings()
{
try
@@ -440,7 +541,7 @@ namespace Ink_Canvas
try { crashAction = (int)(obj["startup"]["crashAction"] ?? 0); } catch { }
CrashAction = (CrashActionType)crashAction;
}
- // 兜底:从主窗口同步
+ // 从主窗口同步
else if (Ink_Canvas.MainWindow.Settings != null && Ink_Canvas.MainWindow.Settings.Startup != null)
{
CrashAction = (CrashActionType)Ink_Canvas.MainWindow.Settings.Startup.CrashAction;
@@ -454,13 +555,13 @@ namespace Ink_Canvas
Ink_Canvas.MainWindow.ShowNewMessage("抱歉,出现未预期的异常,可能导致 InkCanvasForClass 运行不稳定。\n建议保存墨迹后重启应用。");
LogHelper.NewLog(e.Exception.ToString());
- // 新增:记录到崩溃日志
+ // 记录到崩溃日志
lastErrorMessage = e.Exception.ToString();
WriteCrashLog($"UI线程未处理异常: {e.Exception}");
e.Handled = true;
- SyncCrashActionFromSettings(); // 新增:崩溃时同步最新设置
+ SyncCrashActionFromSettings(); // 崩溃时同步最新设置
if (CrashAction == CrashActionType.SilentRestart && !IsAppExitByUser)
{
@@ -484,12 +585,24 @@ namespace Ink_Canvas
private TaskbarIcon _taskbar;
- void App_Startup(object sender, StartupEventArgs e)
+ async void App_Startup(object sender, StartupEventArgs e)
{
// 初始化应用启动时间
appStartTime = DateTime.Now;
- /*if (!StoreHelper.IsStoreApp) */
+ // 根据设置决定是否显示启动画面
+ if (ShouldShowSplashScreen())
+ {
+ ShowSplashScreen();
+ SetSplashMessage("正在启动 Ink Canvas...");
+ SetSplashProgress(20);
+ await Task.Delay(500);
+
+ // 强制刷新UI,确保启动画面显示
+ Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.Render);
+ }
+
+ System.Threading.Thread.Sleep(500);
RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version));
@@ -506,6 +619,14 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile("App | 检测到--board参数,将直接进入白板模式");
}
+ // 检查是否通过--show参数启动
+ bool hasShowArg = e.Args.Contains("--show");
+ if (hasShowArg)
+ {
+ StartWithShowMode = true;
+ LogHelper.WriteLogToFile("App | 检测到--show参数,将退出收纳模式并恢复浮动栏");
+ }
+
// 记录最终应用启动状态
if (isFinalApp)
{
@@ -513,6 +634,12 @@ namespace Ink_Canvas
}
// 在应用启动时自动释放IACore相关DLL
+ if (_isSplashScreenShown)
+ {
+ SetSplashMessage("正在初始化组件...");
+ SetSplashProgress(40);
+ await Task.Delay(500);
+ }
try
{
IACoreDllExtractor.ExtractIACoreDlls();
@@ -523,6 +650,12 @@ namespace Ink_Canvas
}
// 记录应用启动(设备标识符)
+ if (_isSplashScreenShown)
+ {
+ SetSplashMessage("正在加载配置...");
+ SetSplashProgress(60);
+ await Task.Delay(500);
+ }
DeviceIdentifier.RecordAppLaunch();
LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}");
LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}");
@@ -696,6 +829,21 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile("通过IPC发送白板模式命令失败", LogHelper.LogType.Warning);
}
}
+ // 检查是否有--show参数
+ else if (hasShowArg)
+ {
+ LogHelper.WriteLogToFile("检测到已运行实例且有--show参数,尝试通过IPC发送展开浮动栏命令", LogHelper.LogType.Event);
+
+ // 尝试通过IPC发送展开浮动栏命令给已运行实例
+ if (FileAssociationManager.TrySendShowModeCommandToExistingInstance())
+ {
+ LogHelper.WriteLogToFile("展开浮动栏命令已通过IPC发送给已运行实例", LogHelper.LogType.Event);
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("通过IPC发送展开浮动栏命令失败", LogHelper.LogType.Warning);
+ }
+ }
else
{
LogHelper.WriteLogToFile("检测到已运行实例,但无文件参数", LogHelper.LogType.Event);
@@ -750,11 +898,33 @@ namespace Ink_Canvas
StartArgs = e.Args;
// 在非更新模式下创建主窗口
+ if (_isSplashScreenShown)
+ {
+ SetSplashMessage("正在初始化主界面...");
+ SetSplashProgress(80);
+ await Task.Delay(500);
+ }
var mainWindow = new MainWindow();
MainWindow = mainWindow;
+
+ // 主窗口加载完成后关闭启动画面
+ mainWindow.Loaded += (s, args) =>
+ {
+ if (_isSplashScreenShown)
+ {
+ SetSplashMessage("启动完成!");
+ SetSplashProgress(100);
+ // 延迟关闭启动画面,让用户看到完成消息
+ Task.Delay(500).ContinueWith(_ =>
+ {
+ Dispatcher.Invoke(() => CloseSplashScreen());
+ });
+ }
+ };
+
mainWindow.Show();
- // 新增:注册.icstk文件关联
+ // 注册.icstk文件关联
try
{
LogHelper.WriteLogToFile("开始注册.icstk文件关联");
@@ -766,7 +936,7 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
}
- // 新增:启动IPC监听器
+ // 启动IPC监听器
try
{
LogHelper.WriteLogToFile("启动IPC监听器");
@@ -777,125 +947,6 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile($"启动IPC监听器时出错: {ex.Message}", LogHelper.LogType.Error);
}
- // 新增:Office注册表检测
- try
- {
- LogHelper.WriteLogToFile("开始Office注册表检测");
-
- // 检查Office安装
- if (!IsOfficeInstalled())
- {
- LogHelper.WriteLogToFile("未检测到Office安装", LogHelper.LogType.Warning);
- return;
- }
-
- // 尝试获取所有可能的Office版本路径
- var officeVersions = GetOfficeVersions();
- if (officeVersions.Count == 0)
- {
- LogHelper.WriteLogToFile("未找到任何Office版本", LogHelper.LogType.Warning);
- return;
- }
-
- foreach (var version in officeVersions)
- {
- string regPath = $"Software\\Microsoft\\Office\\{version}\\Common\\Security";
- LogHelper.WriteLogToFile($"正在处理Office版本 {version}, 注册表路径: {regPath}");
-
- try
- {
- using (RegistryKey baseKey = Registry.CurrentUser.OpenSubKey(regPath))
- {
- if (baseKey == null)
- {
- LogHelper.WriteLogToFile($"注册表路径不存在: {regPath}", LogHelper.LogType.Warning);
- // 尝试创建路径
- try
- {
- using (RegistryKey createKey = Registry.CurrentUser.CreateSubKey(regPath, true))
- {
- if (createKey != null)
- {
- createKey.SetValue("DisableProtectedView", 1, RegistryValueKind.DWord);
- LogHelper.WriteLogToFile($"创建并设置注册表路径: {regPath}");
- }
- }
- }
- catch (Exception createEx)
- {
- LogHelper.WriteLogToFile($"创建注册表路径失败: {createEx.Message}", LogHelper.LogType.Error);
- }
- continue;
- }
-
- // 备份路径更改为软件根目录下的saves/RegistryBackups文件夹
- string backupPath = Path.Combine(RootPath, "Saves", "RegistryBackups");
- LogHelper.WriteLogToFile($"备份路径: {backupPath}");
-
- if (!Directory.Exists(backupPath))
- {
- Directory.CreateDirectory(backupPath);
- LogHelper.WriteLogToFile($"创建备份目录: {backupPath}");
- }
-
- string backupFile = Path.Combine(backupPath, $"SecurityBackup_{version}_{DateTime.Now:yyyyMMddHHmmss}.reg");
- LogHelper.WriteLogToFile($"创建备份文件: {backupFile}");
-
- // 使用UTF8编码写入注册表文件
- using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8))
- {
- sw.WriteLine("Windows Registry Editor Version 5.00\n");
- sw.WriteLine();
- sw.WriteLine($"[{Registry.CurrentUser.Name}\\{regPath}]");
-
- foreach (string valueName in baseKey.GetValueNames())
- {
- object value = baseKey.GetValue(valueName);
- sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}");
- LogHelper.WriteLogToFile($"备份注册表值: {valueName} = {value}");
- }
- }
-
- using (RegistryKey key = Registry.CurrentUser.CreateSubKey(regPath, true))
- {
- // 仅在值不存在或不等于1时更新
- object currentValue = key.GetValue("DisableProtectedView");
- if (currentValue == null || (int)currentValue != 1)
- {
- key.SetValue("DisableProtectedView", 1, RegistryValueKind.DWord);
- LogHelper.WriteLogToFile($"Office {version} 注册表值已设置: DisableProtectedView = 1");
- }
- else
- {
- LogHelper.WriteLogToFile($"Office {version} 注册表值已存在且无需更改: DisableProtectedView = 1");
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"处理Office版本 {version} 时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
-
- // 处理Office 365的特殊路径
- TryModifyOffice365Registry();
- }
- catch (SecurityException secEx)
- {
- LogHelper.WriteLogToFile($"安全异常: {secEx.Message}", LogHelper.LogType.Error);
- ShowPermissionError();
- }
- catch (UnauthorizedAccessException authEx)
- {
- LogHelper.WriteLogToFile($"访问被拒绝: {authEx.Message}", LogHelper.LogType.Error);
- ShowPermissionError();
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"未知错误: {ex.GetType().FullName} - {ex.Message}", LogHelper.LogType.Error);
- LogHelper.WriteLogToFile(ex.StackTrace);
- }
}
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
@@ -916,7 +967,7 @@ namespace Ink_Canvas
catch { }
}
- // 新增:用于设置崩溃后操作类型
+ // 用于设置崩溃后操作类型
public enum CrashActionType
{
SilentRestart,
@@ -978,7 +1029,7 @@ namespace Ink_Canvas
watchdogProcess = Process.Start(psi);
}
- // 看门狗主逻辑(在 Main 函数或 App_Startup 入口前加判断)
+ // 看门狗主逻辑
public static void RunWatchdogIfNeeded()
{
var args = Environment.GetCommandLineArgs();
@@ -1025,7 +1076,7 @@ namespace Ink_Canvas
// 仅在软件内主动退出时关闭看门狗,并写入退出信号
try
{
- // 新增:记录应用退出状态
+ // 记录应用退出状态
string exitType = IsAppExitByUser ? "用户主动退出" : "应用程序退出";
WriteCrashLog($"{exitType},退出代码: {e.ApplicationExitCode}");
@@ -1061,450 +1112,5 @@ namespace Ink_Canvas
catch { }
}
}
-
- ///
- /// 检查Office是否安装
- ///
- private bool IsOfficeInstalled()
- {
- try
- {
- // 检查多个可能的注册表路径
- // 1. 检查传统的Office版本
- using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office"))
- {
- if (key != null && key.GetSubKeyNames().Any(name => name.Contains(".0")))
- {
- LogHelper.WriteLogToFile("检测到传统Office安装");
- return true;
- }
- }
-
- // 2. 检查64位注册表中的Office
- using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\Microsoft\\Office"))
- {
- if (key != null && key.GetSubKeyNames().Any(name => name.Contains(".0")))
- {
- LogHelper.WriteLogToFile("检测到64位注册表中的Office安装");
- return true;
- }
- }
-
- // 3. 检查Office 365/Click-to-Run安装
- using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun"))
- {
- if (key != null)
- {
- LogHelper.WriteLogToFile("检测到Office 365 Click-to-Run");
- return true;
- }
- }
-
- // 4. 检查Office 365部署配置
- using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\15.0\\ClickToRun"))
- {
- if (key != null)
- {
- LogHelper.WriteLogToFile("检测到Office 365 (15.0)");
- return true;
- }
- }
-
- using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\16.0\\ClickToRun"))
- {
- if (key != null)
- {
- LogHelper.WriteLogToFile("检测到Office 365 (16.0)");
- return true;
- }
- }
-
- // 5. 检查Office 365零售订阅信息
- using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun\\Configuration"))
- {
- if (key != null)
- {
- LogHelper.WriteLogToFile("检测到Office 365配置");
- return true;
- }
- }
-
- LogHelper.WriteLogToFile("未检测到任何Office安装", LogHelper.LogType.Warning);
- return false;
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"检查Office安装时出错: {ex.Message}", LogHelper.LogType.Error);
- return false;
- }
- }
-
- ///
- /// 显示权限不足的错误提示
- ///
- private void ShowPermissionError()
- {
- const string message = "需要管理员权限才能完成此操作\n请以管理员身份重新启动应用程序";
- LogHelper.WriteLogToFile(message, LogHelper.LogType.Error);
- MessageBox.Show(message, "权限错误", MessageBoxButton.OK, MessageBoxImage.Error);
- }
-
- ///
- /// 获取所有已安装的Office版本
- ///
- private List GetOfficeVersions()
- {
- var versions = new List();
- try
- {
- // 检查HKLM
- using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office"))
- {
- if (key != null)
- {
- foreach (var subKeyName in key.GetSubKeyNames())
- {
- if (subKeyName.Contains(".0"))
- {
- versions.Add(subKeyName);
- LogHelper.WriteLogToFile($"在HKLM中找到Office版本: {subKeyName}");
- }
- }
- }
- }
-
- // 检查HKCU
- using (var key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Office"))
- {
- if (key != null)
- {
- foreach (var subKeyName in key.GetSubKeyNames())
- {
- if (subKeyName.Contains(".0") && !versions.Contains(subKeyName))
- {
- versions.Add(subKeyName);
- LogHelper.WriteLogToFile($"在HKCU中找到Office版本: {subKeyName}");
- }
- }
- }
- }
-
- // 检查64位注册表
- using (var key = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\Microsoft\\Office"))
- {
- if (key != null)
- {
- foreach (var subKeyName in key.GetSubKeyNames())
- {
- if (subKeyName.Contains(".0") && !versions.Contains(subKeyName))
- {
- versions.Add(subKeyName);
- LogHelper.WriteLogToFile($"在64位注册表中找到Office版本: {subKeyName}");
- }
- }
- }
- }
-
- // 检查Office 365的特殊路径
- CheckOffice365Versions(versions);
-
- // 如果没有找到任何版本,添加默认的Office 365版本号
- if (versions.Count == 0 && IsOffice365Installed())
- {
- versions.Add("16.0");
- LogHelper.WriteLogToFile("未找到具体版本,添加默认Office 365版本: 16.0");
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"获取Office版本时出错: {ex.Message}", LogHelper.LogType.Error);
- }
-
- // 按版本号排序
- versions.Sort((a, b) =>
- {
- try
- {
- double va = double.Parse(a.Replace(".0", ""));
- double vb = double.Parse(b.Replace(".0", ""));
- return vb.CompareTo(va); // 降序排列,最新版本在前
- }
- catch
- {
- return 0;
- }
- });
-
- return versions;
- }
-
- ///
- /// 检测Office 365是否已安装
- ///
- private bool IsOffice365Installed()
- {
- try
- {
- // 检查多个Office 365特定路径
- using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun"))
- {
- if (key != null)
- return true;
- }
-
- using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\15.0\\ClickToRun"))
- {
- if (key != null)
- return true;
- }
-
- using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\16.0\\ClickToRun"))
- {
- if (key != null)
- return true;
- }
-
- using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun\\Configuration"))
- {
- if (key != null)
- return true;
- }
-
- return false;
- }
- catch
- {
- return false;
- }
- }
-
- ///
- /// 检查Office 365特有的版本信息
- ///
- private void CheckOffice365Versions(List versions)
- {
- try
- {
- // 检查Click-to-Run版本路径
- using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun\\Configuration"))
- {
- if (key != null)
- {
- var platformVersion = key.GetValue("Platform") as string;
- var clickToRunVersion = key.GetValue("VersionToReport") as string;
-
- if (!string.IsNullOrEmpty(platformVersion))
- {
- var majorVersion = platformVersion.Split('.').FirstOrDefault();
- if (!string.IsNullOrEmpty(majorVersion) && !versions.Contains($"{majorVersion}.0"))
- {
- versions.Add($"{majorVersion}.0");
- LogHelper.WriteLogToFile($"在Office 365配置中找到平台版本: {majorVersion}.0");
- }
- }
-
- if (!string.IsNullOrEmpty(clickToRunVersion))
- {
- var majorVersion = clickToRunVersion.Split('.').FirstOrDefault();
- if (!string.IsNullOrEmpty(majorVersion) && !versions.Contains($"{majorVersion}.0"))
- {
- versions.Add($"{majorVersion}.0");
- LogHelper.WriteLogToFile($"在Office 365配置中找到报告版本: {majorVersion}.0");
- }
- }
- }
- }
-
- // 检查安装路径来确认版本
- var possibleVersions = new[] { "15.0", "16.0" }; // Office 2013 (15.0) 和 Office 2016/2019/365 (16.0)
- foreach (var version in possibleVersions)
- {
- using (var key = Registry.LocalMachine.OpenSubKey($"Software\\Microsoft\\Office\\{version}\\Common\\InstallRoot"))
- {
- if (key != null && key.GetValue("Path") != null && !versions.Contains(version))
- {
- versions.Add(version);
- LogHelper.WriteLogToFile($"在InstallRoot中找到Office版本: {version}");
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"检查Office 365版本时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
-
- ///
- /// 尝试修改Office 365的特殊注册表路径
- ///
- private void TryModifyOffice365Registry()
- {
- try
- {
- // 准备备份目录
- string backupPath = Path.Combine(RootPath, "Saves", "RegistryBackups");
- if (!Directory.Exists(backupPath))
- {
- Directory.CreateDirectory(backupPath);
- LogHelper.WriteLogToFile($"创建Office 365备份目录: {backupPath}");
- }
-
- // 检查Office 365 Outlook和PowerPoint的特定路径
- string[] apps = { "outlook", "powerpoint" };
-
- foreach (var app in apps)
- {
- // 检查用户级别的注册表
- string regPath = $"Software\\Microsoft\\Office\\16.0\\{app}\\Security";
- LogHelper.WriteLogToFile($"检查Office 365特定应用注册表: {regPath}");
-
- try
- {
- // 先检查是否存在该路径
- using (var baseKey = Registry.CurrentUser.OpenSubKey(regPath))
- {
- // 如果路径存在,先备份
- if (baseKey != null)
- {
- string backupFile = Path.Combine(backupPath, $"SecurityBackup_365_{app}_{DateTime.Now:yyyyMMddHHmmss}.reg");
- LogHelper.WriteLogToFile($"创建Office 365 {app}备份文件: {backupFile}");
-
- // 使用UTF8编码写入注册表文件
- using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8))
- {
- sw.WriteLine("Windows Registry Editor Version 5.00\n");
- sw.WriteLine();
- sw.WriteLine($"[{Registry.CurrentUser.Name}\\{regPath}]");
-
- foreach (string valueName in baseKey.GetValueNames())
- {
- object value = baseKey.GetValue(valueName);
- sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}");
- LogHelper.WriteLogToFile($"备份Office 365 {app}注册表值: {valueName} = {value}");
- }
- }
- }
- }
-
- // 修改或创建注册表项
- using (var key = Registry.CurrentUser.CreateSubKey(regPath, true))
- {
- if (key != null)
- {
- object currentValue = key.GetValue("DisableProtectedView");
- if (currentValue == null || (int)currentValue != 1)
- {
- key.SetValue("DisableProtectedView", 1, RegistryValueKind.DWord);
- LogHelper.WriteLogToFile($"Office 365 {app} 注册表值已设置: DisableProtectedView = 1");
- }
- else
- {
- LogHelper.WriteLogToFile($"Office 365 {app} 注册表值已存在且无需更改");
- }
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"修改 {app} 注册表时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
-
- // 尝试通过Office信任中心路径修改
- string trustCenterPath = "Software\\Microsoft\\Office\\16.0\\Common\\Security\\FileValidation";
- LogHelper.WriteLogToFile($"检查信任中心路径: {trustCenterPath}");
-
- try
- {
- // 先检查是否存在该路径
- using (var baseKey = Registry.CurrentUser.OpenSubKey(trustCenterPath))
- {
- // 如果路径存在,先备份
- if (baseKey != null)
- {
- string backupFile = Path.Combine(backupPath, $"SecurityBackup_365_TrustCenter_{DateTime.Now:yyyyMMddHHmmss}.reg");
- LogHelper.WriteLogToFile($"创建信任中心备份文件: {backupFile}");
-
- // 使用UTF8编码写入注册表文件
- using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8))
- {
- sw.WriteLine("Windows Registry Editor Version 5.00\n");
- sw.WriteLine();
- sw.WriteLine($"[{Registry.CurrentUser.Name}\\{trustCenterPath}]");
-
- foreach (string valueName in baseKey.GetValueNames())
- {
- object value = baseKey.GetValue(valueName);
- sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}");
- LogHelper.WriteLogToFile($"备份信任中心注册表值: {valueName} = {value}");
- }
- }
- }
- }
-
- using (var key = Registry.CurrentUser.CreateSubKey(trustCenterPath, true))
- {
- if (key != null)
- {
- key.SetValue("DisableEditFromPV", 1, RegistryValueKind.DWord);
- LogHelper.WriteLogToFile("已禁用受保护视图中的编辑");
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"修改信任中心路径时出错: {ex.Message}", LogHelper.LogType.Error);
- }
-
- // 尝试修改EnableEditWhileViewingPolicy
- string policyPath = "Software\\Policies\\Microsoft\\Office\\16.0\\Common\\Security";
- try
- {
- // 先检查是否存在该路径
- using (var baseKey = Registry.CurrentUser.OpenSubKey(policyPath))
- {
- // 如果路径存在,先备份
- if (baseKey != null)
- {
- string backupFile = Path.Combine(backupPath, $"SecurityBackup_365_Policy_{DateTime.Now:yyyyMMddHHmmss}.reg");
- LogHelper.WriteLogToFile($"创建策略备份文件: {backupFile}");
-
- // 使用UTF8编码写入注册表文件
- using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8))
- {
- sw.WriteLine("Windows Registry Editor Version 5.00\n");
- sw.WriteLine();
- sw.WriteLine($"[{Registry.CurrentUser.Name}\\{policyPath}]");
-
- foreach (string valueName in baseKey.GetValueNames())
- {
- object value = baseKey.GetValue(valueName);
- sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}");
- LogHelper.WriteLogToFile($"备份策略注册表值: {valueName} = {value}");
- }
- }
- }
- }
-
- using (var key = Registry.CurrentUser.CreateSubKey(policyPath, true))
- {
- if (key != null)
- {
- key.SetValue("EnableEditWhileViewingPolicy", 1, RegistryValueKind.DWord);
- LogHelper.WriteLogToFile("已启用查看时编辑策略");
- }
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"修改策略路径时出错: {ex.Message}", LogHelper.LogType.Error);
- }
- }
- catch (Exception ex)
- {
- LogHelper.WriteLogToFile($"修改Office 365注册表时发生未知错误: {ex.Message}", LogHelper.LogType.Error);
- }
- }
}
}
diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs
index 1f1d5c72..16ecbd01 100644
--- a/Ink Canvas/AssemblyInfo.cs
+++ b/Ink Canvas/AssemblyInfo.cs
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.7.11.4")]
-[assembly: AssemblyFileVersion("1.7.11.4")]
+[assembly: AssemblyVersion("1.7.12.0")]
+[assembly: AssemblyFileVersion("1.7.12.0")]
diff --git a/Ink Canvas/Helpers/CameraService.cs b/Ink Canvas/Helpers/CameraService.cs
index 449e31fe..e0c4c68c 100644
--- a/Ink Canvas/Helpers/CameraService.cs
+++ b/Ink Canvas/Helpers/CameraService.cs
@@ -18,6 +18,11 @@ namespace Ink_Canvas.Helpers
private readonly object _frameLock = new object();
private Dispatcher _dispatcher;
+ // 新增属性
+ private int _rotationAngle = 0; // 0=0度,1=90度,2=180度,3=270度
+ private int _resolutionWidth = 640;
+ private int _resolutionHeight = 480;
+
public event EventHandler FrameReceived;
public event EventHandler ErrorOccurred;
@@ -25,6 +30,25 @@ namespace Ink_Canvas.Helpers
public List AvailableCameras { get; private set; }
public FilterInfo CurrentCamera { get; private set; }
+ // 新增属性
+ public int RotationAngle
+ {
+ get => _rotationAngle;
+ set => _rotationAngle = Math.Max(0, Math.Min(3, value));
+ }
+
+ public int ResolutionWidth
+ {
+ get => _resolutionWidth;
+ set => _resolutionWidth = Math.Max(320, Math.Min(1920, value));
+ }
+
+ public int ResolutionHeight
+ {
+ get => _resolutionHeight;
+ set => _resolutionHeight = Math.Max(240, Math.Min(1080, value));
+ }
+
public CameraService()
{
_dispatcher = Dispatcher.CurrentDispatcher;
@@ -32,6 +56,16 @@ namespace Ink_Canvas.Helpers
RefreshCameraList();
}
+ public CameraService(int rotationAngle, int resolutionWidth, int resolutionHeight)
+ {
+ _dispatcher = Dispatcher.CurrentDispatcher;
+ AvailableCameras = new List();
+ _rotationAngle = rotationAngle;
+ _resolutionWidth = resolutionWidth;
+ _resolutionHeight = resolutionHeight;
+ RefreshCameraList();
+ }
+
///
/// 刷新可用摄像头列表
///
@@ -242,14 +276,16 @@ namespace Ink_Canvas.Helpers
var width = sourceFrame.Width;
var height = sourceFrame.Height;
- if (width > 0 && height > 0)
- {
- _currentFrame = new Bitmap(width, height, PixelFormat.Format24bppRgb);
- using (var graphics = Graphics.FromImage(_currentFrame))
- {
- graphics.DrawImage(sourceFrame, 0, 0);
- }
- }
+ if (width > 0 && height > 0)
+ {
+ // 应用旋转
+ Bitmap rotatedFrame = ApplyRotation(sourceFrame);
+
+ // 应用分辨率调整
+ _currentFrame = ResizeImage(rotatedFrame, _resolutionWidth, _resolutionHeight);
+
+ rotatedFrame?.Dispose();
+ }
else
{
_currentFrame = null;
@@ -300,6 +336,46 @@ namespace Ink_Canvas.Helpers
return AvailableCameras.Count > 0;
}
+ ///
+ /// 应用旋转到图像
+ ///
+ private Bitmap ApplyRotation(Bitmap source)
+ {
+ if (_rotationAngle == 0)
+ return new Bitmap(source);
+
+ var rotationType = RotateFlipType.RotateNoneFlipNone;
+ switch (_rotationAngle)
+ {
+ case 1: rotationType = RotateFlipType.Rotate90FlipNone; break;
+ case 2: rotationType = RotateFlipType.Rotate180FlipNone; break;
+ case 3: rotationType = RotateFlipType.Rotate270FlipNone; break;
+ }
+
+ var rotated = new Bitmap(source);
+ rotated.RotateFlip(rotationType);
+ return rotated;
+ }
+
+ ///
+ /// 调整图像大小
+ ///
+ private Bitmap ResizeImage(Bitmap source, int width, int height)
+ {
+ if (source.Width == width && source.Height == height)
+ return new Bitmap(source);
+
+ var resized = new Bitmap(width, height, PixelFormat.Format24bppRgb);
+ using (var graphics = Graphics.FromImage(resized))
+ {
+ graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
+ graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
+ graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
+ graphics.DrawImage(source, 0, 0, width, height);
+ }
+ return resized;
+ }
+
public void Dispose()
{
StopPreview();
diff --git a/Ink Canvas/Helpers/FileAssociationManager.cs b/Ink Canvas/Helpers/FileAssociationManager.cs
index cc5460ad..89525e93 100644
--- a/Ink Canvas/Helpers/FileAssociationManager.cs
+++ b/Ink Canvas/Helpers/FileAssociationManager.cs
@@ -25,6 +25,7 @@ namespace Ink_Canvas.Helpers
private const string IpcEventName = "InkCanvasFileAssociationEvent";
private const string IpcFilePrefix = "InkCanvasFileAssociation_";
private const string IpcBoardModePrefix = "InkCanvasBoardMode_";
+ private const string IpcShowModePrefix = "InkCanvasShowMode_";
private const int IpcTimeout = 5000; // 5秒超时
///
@@ -310,6 +311,56 @@ namespace Ink_Canvas.Helpers
}
}
+ ///
+ /// 尝试通过IPC将展开浮动栏命令发送给已运行的实例
+ ///
+ /// 是否成功发送
+ public static bool TrySendShowModeCommandToExistingInstance()
+ {
+ try
+ {
+ LogHelper.WriteLogToFile("尝试通过IPC发送展开浮动栏命令给已运行实例", LogHelper.LogType.Event);
+
+ // 创建IPC文件
+ string tempDir = Path.GetTempPath();
+ string ipcFileName = IpcShowModePrefix + Guid.NewGuid().ToString("N") + ".tmp";
+ string ipcFilePath = Path.Combine(tempDir, ipcFileName);
+
+ // 写入展开浮动栏命令到IPC文件
+ File.WriteAllText(ipcFilePath, "SHOW_MODE", Encoding.UTF8);
+
+ // 创建事件通知已运行实例
+ using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
+ {
+ ipcEvent.Set();
+ }
+
+ // 等待一段时间让已运行实例处理命令
+ Thread.Sleep(1000);
+
+ // 清理IPC文件
+ try
+ {
+ if (File.Exists(ipcFilePath))
+ {
+ File.Delete(ipcFilePath);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+
+ LogHelper.WriteLogToFile("IPC展开浮动栏命令发送完成", LogHelper.LogType.Event);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"通过IPC发送展开浮动栏命令失败: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
///
/// 启动IPC监听器,等待其他实例发送文件路径
///
@@ -470,6 +521,61 @@ namespace Ink_Canvas.Helpers
catch { }
}
}
+
+ // 处理展开浮动栏命令IPC文件
+ string[] showModeFiles = Directory.GetFiles(tempDir, IpcShowModePrefix + "*.tmp");
+ foreach (string ipcFile in showModeFiles)
+ {
+ try
+ {
+ // 读取命令内容
+ string command = File.ReadAllText(ipcFile, Encoding.UTF8);
+
+ if (command == "SHOW_MODE")
+ {
+ LogHelper.WriteLogToFile("IPC接收到展开浮动栏命令", LogHelper.LogType.Event);
+
+ // 在UI线程中处理展开浮动栏
+ Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
+ {
+ try
+ {
+ // 获取主窗口并展开浮动栏
+ if (Application.Current.MainWindow is MainWindow mainWindow)
+ {
+ // 如果当前处于收纳模式,则展开浮动栏
+ if (mainWindow.isFloatingBarFolded)
+ {
+ await mainWindow.UnFoldFloatingBar(new object());
+ }
+ mainWindow.ShowNotification("已退出收纳模式并恢复浮动栏");
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"IPC处理展开浮动栏失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }));
+ }
+
+ // 删除IPC文件
+ File.Delete(ipcFile);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"处理展开浮动栏IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
+
+ // 尝试删除损坏的IPC文件
+ try
+ {
+ if (File.Exists(ipcFile))
+ {
+ File.Delete(ipcFile);
+ }
+ }
+ catch { }
+ }
+ }
}
catch (Exception ex)
{
diff --git a/Ink Canvas/Helpers/FloatingWindowInterceptor.cs b/Ink Canvas/Helpers/FloatingWindowInterceptor.cs
index 665099e2..1e0e9d10 100644
--- a/Ink Canvas/Helpers/FloatingWindowInterceptor.cs
+++ b/Ink Canvas/Helpers/FloatingWindowInterceptor.cs
@@ -55,6 +55,27 @@ namespace Ink_Canvas.Helpers
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out ForegroundWindowInfo.RECT lpRect);
+ [DllImport("user32.dll")]
+ private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
+
+ [DllImport("dwmapi.dll")]
+ private static extern int DwmGetWindowAttribute(IntPtr hWnd, int dwAttribute, out ForegroundWindowInfo.RECT pvAttribute, int cbAttribute);
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr GetDC(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
+
+ [DllImport("gdi32.dll")]
+ private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
+
+ [DllImport("user32.dll")]
+ private static extern bool SetForegroundWindow(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern bool BringWindowToTop(IntPtr hWnd);
+
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
@@ -68,9 +89,11 @@ namespace Ink_Canvas.Helpers
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
+ private const int SW_SHOWNORMAL = 1;
private const int SW_MINIMIZE = 6;
private const int SW_RESTORE = 9;
+ private const int GWL_STYLE = -16;
private const int GWL_EXSTYLE = -20;
private const uint WS_EX_TOOLWINDOW = 0x00000080;
private const uint WS_EX_APPWINDOW = 0x00040000;
@@ -79,10 +102,17 @@ namespace Ink_Canvas.Helpers
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOZORDER = 0x0004;
private const uint SWP_HIDEWINDOW = 0x0080;
+ private const uint SWP_SHOWWINDOW = 0x0040;
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
private const uint PROCESS_VM_READ = 0x0010;
+ private const uint WM_CLOSE = 0x0010;
+ private const int DWMWA_EXTENDED_FRAME_BOUNDS = 9;
+ private const int LOGPIXELSX = 88;
+ private const int LOGPIXELSY = 90;
+
+
#endregion
#region 拦截规则定义
@@ -212,6 +242,19 @@ namespace Ink_Canvas.Helpers
public string Description { get; set; }
public InterceptType? ParentType { get; set; }
public List ChildTypes { get; set; } = new List();
+
+ // 新增的精确匹配字段
+ public bool HasWindowStyle { get; set; }
+ public uint WindowStyle { get; set; }
+ public bool HasWindowSize { get; set; }
+ public int WindowWidth { get; set; }
+ public int WindowHeight { get; set; }
+ public bool ExactTitleMatch { get; set; } = false;
+ public bool ExactClassNameMatch { get; set; } = false;
+
+ // 运行时状态字段
+ public bool foundHwnd { get; set; } = false;
+ public IntPtr outHwnd { get; set; } = IntPtr.Zero;
}
#endregion
@@ -225,13 +268,9 @@ namespace Ink_Canvas.Helpers
private bool _isRunning;
private bool _disposed;
- // 性能优化字段
- private readonly Dictionary _lastScanTime = new Dictionary();
- private readonly HashSet _knownWindows = new HashSet();
- private readonly Dictionary _processLastScanTime = new Dictionary();
+ // 简化的性能统计
private int _consecutiveEmptyScans = 0;
private DateTime _lastSuccessfulScan = DateTime.Now;
- private readonly object _scanLock = new object();
#endregion
@@ -275,7 +314,14 @@ namespace Ink_Canvas.Helpers
ClassNamePattern = "HwndWrapper[EasiNote.exe;;",
IsEnabled = true,
RequiresAdmin = false,
- Description = "希沃白板3 桌面悬浮窗"
+ Description = "希沃白板3 桌面悬浮窗",
+ HasWindowStyle = true,
+ WindowStyle = 370081792,
+ HasWindowSize = true,
+ WindowWidth = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
+ WindowHeight = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height,
+ ExactTitleMatch = true,
+ ExactClassNameMatch = false
};
// 希沃白板5 桌面悬浮窗
@@ -287,7 +333,14 @@ namespace Ink_Canvas.Helpers
ClassNamePattern = "HwndWrapper[EasiNote;;",
IsEnabled = true,
RequiresAdmin = false,
- Description = "希沃白板5 桌面悬浮窗"
+ Description = "希沃白板5 桌面悬浮窗",
+ HasWindowStyle = true,
+ WindowStyle = 369623040,
+ HasWindowSize = true,
+ WindowWidth = 550,
+ WindowHeight = 200,
+ ExactTitleMatch = false,
+ ExactClassNameMatch = false
};
// 希沃白板5C 桌面悬浮窗
@@ -299,7 +352,14 @@ namespace Ink_Canvas.Helpers
ClassNamePattern = "HwndWrapper[EasiNote5C;;",
IsEnabled = true,
RequiresAdmin = false,
- Description = "希沃白板5C 桌面悬浮窗"
+ Description = "希沃白板5C 桌面悬浮窗",
+ HasWindowStyle = true,
+ WindowStyle = 369623040,
+ HasWindowSize = true,
+ WindowWidth = 550,
+ WindowHeight = 200,
+ ExactTitleMatch = false,
+ ExactClassNameMatch = false
};
// 希沃品课教师端 桌面悬浮窗(父规则)
@@ -313,7 +373,11 @@ namespace Ink_Canvas.Helpers
RequiresAdmin = false,
Description = "希沃品课教师端 桌面悬浮窗",
ParentType = null,
- ChildTypes = new List { InterceptType.SeewoPincoDrawingFloating, InterceptType.SeewoPincoBoardService }
+ ChildTypes = new List { InterceptType.SeewoPincoDrawingFloating, InterceptType.SeewoPincoBoardService },
+ HasWindowStyle = true,
+ WindowStyle = 0x16CF0000,
+ ExactTitleMatch = true,
+ ExactClassNameMatch = true
};
// 希沃品课教师端 画笔悬浮窗(子规则)
@@ -327,7 +391,11 @@ namespace Ink_Canvas.Helpers
RequiresAdmin = false,
Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)",
ParentType = InterceptType.SeewoPincoSideBarFloating,
- ChildTypes = new List()
+ ChildTypes = new List(),
+ HasWindowStyle = true,
+ WindowStyle = 335675392,
+ ExactTitleMatch = true,
+ ExactClassNameMatch = true
};
// 希沃品课教师端 桌面画板(子规则)
@@ -341,7 +409,14 @@ namespace Ink_Canvas.Helpers
RequiresAdmin = false,
Description = "希沃品课教师端 桌面画板",
ParentType = InterceptType.SeewoPincoSideBarFloating,
- ChildTypes = new List()
+ ChildTypes = new List(),
+ HasWindowStyle = true,
+ WindowStyle = 369623040,
+ HasWindowSize = true,
+ WindowWidth = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
+ WindowHeight = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height,
+ ExactTitleMatch = false,
+ ExactClassNameMatch = false
};
// 希沃PPT小工具
@@ -665,6 +740,12 @@ namespace Ink_Canvas.Helpers
var rule = _interceptRules[type];
rule.IsEnabled = enabled;
+ // 如果规则被禁用,恢复相关的被拦截窗口
+ if (!enabled)
+ {
+ RestoreWindowsByType(type);
+ }
+
// 如果是父规则被禁用,则禁用所有子规则
if (!enabled && rule.ChildTypes.Count > 0)
{
@@ -673,6 +754,7 @@ namespace Ink_Canvas.Helpers
if (_interceptRules.ContainsKey(childType))
{
_interceptRules[childType].IsEnabled = false;
+ RestoreWindowsByType(childType);
}
}
}
@@ -726,6 +808,14 @@ namespace Ink_Canvas.Helpers
return new Dictionary(_interceptRules);
}
+ ///
+ /// 获取当前被拦截的窗口数量
+ ///
+ public int GetInterceptedWindowsCount()
+ {
+ return _interceptedWindows.Count;
+ }
+
///
/// 手动扫描一次
///
@@ -740,10 +830,41 @@ namespace Ink_Canvas.Helpers
public void RestoreAllWindows()
{
var windowsToRestore = new List(_interceptedWindows.Keys);
+ var restoredCount = 0;
+
foreach (var hWnd in windowsToRestore)
{
- RestoreWindow(hWnd);
+ if (RestoreWindow(hWnd))
+ {
+ restoredCount++;
+ }
}
+
+ }
+
+ ///
+ /// 恢复指定类型的被拦截窗口
+ ///
+ public void RestoreWindowsByType(InterceptType type)
+ {
+ var windowsToRestore = new List();
+ foreach (var kvp in _interceptedWindows)
+ {
+ if (kvp.Value == type)
+ {
+ windowsToRestore.Add(kvp.Key);
+ }
+ }
+
+ var restoredCount = 0;
+ foreach (var hWnd in windowsToRestore)
+ {
+ if (RestoreWindow(hWnd))
+ {
+ restoredCount++;
+ }
+ }
+
}
///
@@ -753,15 +874,29 @@ namespace Ink_Canvas.Helpers
{
if (!_interceptedWindows.ContainsKey(hWnd)) return false;
+ var interceptType = _interceptedWindows[hWnd];
+
if (IsWindow(hWnd))
{
+ // 使用多种方法确保窗口恢复显示
ShowWindow(hWnd, SW_RESTORE);
+ ShowWindow(hWnd, SW_SHOW);
+ ShowWindow(hWnd, SW_SHOWNORMAL);
+
+ // 将窗口置于前台并显示
+ SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
+
+ // 强制将窗口带到前台
+ BringWindowToTop(hWnd);
+ SetForegroundWindow(hWnd);
+
_interceptedWindows.Remove(hWnd);
WindowRestored?.Invoke(this, new WindowRestoredEventArgs
{
WindowHandle = hWnd,
- InterceptType = _interceptedWindows[hWnd]
+ InterceptType = interceptType
});
return true;
@@ -775,187 +910,103 @@ namespace Ink_Canvas.Helpers
#region 私有方法
+ ///
+ /// 清理无效的窗口句柄
+ ///
+ private void CleanupInvalidWindows()
+ {
+ var invalidWindows = new List();
+
+ foreach (var kvp in _interceptedWindows)
+ {
+ var hWnd = kvp.Key;
+ if (!IsWindow(hWnd))
+ {
+ invalidWindows.Add(hWnd);
+ }
+ }
+ foreach (var hWnd in invalidWindows)
+ {
+ _interceptedWindows.Remove(hWnd);
+ }
+ }
+
private void ScanForWindows(object state)
{
if (!_isRunning) return;
- lock (_scanLock)
+ try
{
- try
+ // 简化的扫描逻辑
+ var interceptedCount = 0;
+ CleanupInvalidWindows();
+
+ // 重置所有规则的发现状态
+ foreach (var rule in _interceptRules.Values)
{
- var scanStartTime = DateTime.Now;
- var windowsFound = 0;
- var windowsIntercepted = 0;
-
- // 清理过期的缓存
- CleanupExpiredCache();
-
- // 使用优化的扫描策略
- if (_consecutiveEmptyScans > 3)
+ if (rule.IsEnabled)
{
- // 如果连续多次扫描没有发现新窗口,使用快速扫描模式
- PerformQuickScan(ref windowsFound, ref windowsIntercepted);
+ rule.foundHwnd = false;
}
- else
+ }
+
+ // 枚举所有窗口
+ EnumWindows(EnumWindowsCallback, IntPtr.Zero);
+
+ // 处理找到的窗口
+ foreach (var rule in _interceptRules.Values)
+ {
+ if (rule.IsEnabled && rule.foundHwnd && rule.outHwnd != IntPtr.Zero)
{
- // 正常扫描模式
- PerformFullScan(ref windowsFound, ref windowsIntercepted);
- }
-
- // 更新扫描统计
- UpdateScanStatistics(windowsFound, windowsIntercepted, scanStartTime);
-
- // 动态调整扫描间隔
- AdjustScanInterval();
- }
- catch (Exception ex)
- {
- // 记录错误但不中断扫描
- LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
- _consecutiveEmptyScans++;
- }
- }
- }
-
- ///
- /// 执行快速扫描 - 只检查已知进程
- ///
- private void PerformQuickScan(ref int windowsFound, ref int windowsIntercepted)
- {
- var targetProcesses = new HashSet();
- var scanData = new ScanData { WindowsFound = 0, WindowsIntercepted = 0 };
-
- // 收集所有启用的规则对应的进程名
- foreach (var rule in _interceptRules.Values)
- {
- if (rule.IsEnabled && !string.IsNullOrEmpty(rule.ProcessName))
- {
- targetProcesses.Add(rule.ProcessName.ToLower());
- }
- }
-
- // 只扫描目标进程的窗口
- foreach (var processName in targetProcesses)
- {
- try
- {
- var processes = Process.GetProcessesByName(processName);
- foreach (var process in processes)
- {
- if (process.MainWindowHandle != IntPtr.Zero)
+ bool shouldIntercept = !_interceptedWindows.ContainsKey(rule.outHwnd) ||
+ (_interceptedWindows.ContainsKey(rule.outHwnd) && IsWindowVisible(rule.outHwnd));
+
+ if (shouldIntercept)
{
- ProcessWindow(process.MainWindowHandle, scanData);
+ InterceptWindow(rule.outHwnd, rule);
+ interceptedCount++;
}
}
}
- catch
+
+ // 更新统计
+ if (interceptedCount == 0)
{
- // 忽略进程访问错误
+ _consecutiveEmptyScans++;
+ }
+ else
+ {
+ _consecutiveEmptyScans = 0;
+ _lastSuccessfulScan = DateTime.Now;
}
}
-
- windowsFound = scanData.WindowsFound;
- windowsIntercepted = scanData.WindowsIntercepted;
- }
-
- ///
- /// 执行完整扫描
- ///
- private void PerformFullScan(ref int windowsFound, ref int windowsIntercepted)
- {
- var scanData = new ScanData { WindowsFound = 0, WindowsIntercepted = 0 };
-
- EnumWindows((hWnd, lParam) =>
+ catch (Exception ex)
{
- ProcessWindow(hWnd, scanData);
- return true;
- }, IntPtr.Zero);
-
- windowsFound = scanData.WindowsFound;
- windowsIntercepted = scanData.WindowsIntercepted;
+ LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
+ _consecutiveEmptyScans++;
+ }
}
- ///
- /// 处理单个窗口
- ///
- private bool ProcessWindow(IntPtr hWnd, ScanData scanData)
+
+ private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
{
try
{
+ // 递归枚举子窗口
+ EnumChildWindows(hWnd, EnumWindowsCallback, lParam);
+
// 基本检查
if (!IsWindow(hWnd) || !IsWindowVisible(hWnd)) return true;
-
- // 检查是否已经被拦截
- if (_interceptedWindows.ContainsKey(hWnd)) return true;
- // 检查缓存,避免重复处理
- if (_knownWindows.Contains(hWnd))
- {
- var lastScan = _lastScanTime.ContainsKey(hWnd) ? _lastScanTime[hWnd] : DateTime.MinValue;
- if (DateTime.Now - lastScan < TimeSpan.FromSeconds(30)) // 30秒内不重复处理
- {
- return true;
- }
- }
-
- scanData.WindowsFound++;
- _knownWindows.Add(hWnd);
- _lastScanTime[hWnd] = DateTime.Now;
-
- // 获取窗口信息
- var windowInfo = GetWindowInfo(hWnd);
- if (windowInfo == null) return true;
-
- // 检查进程缓存
- if (_processLastScanTime.ContainsKey(windowInfo.ProcessName))
- {
- var lastProcessScan = _processLastScanTime[windowInfo.ProcessName];
- if (DateTime.Now - lastProcessScan < TimeSpan.FromSeconds(10)) // 10秒内不重复扫描同一进程
- {
- return true;
- }
- }
- _processLastScanTime[windowInfo.ProcessName] = DateTime.Now;
-
- // 检查窗口样式,过滤掉系统窗口和主窗口
- var exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
- var style = GetWindowLong(hWnd, -16); // GWL_STYLE
-
- // 跳过工具窗口
- if ((exStyle & WS_EX_TOOLWINDOW) != 0)
- {
- return true;
- }
-
- // 跳过主窗口(有标题栏和系统菜单的窗口)
- const uint WS_CAPTION = 0x00C00000;
- const uint WS_SYSMENU = 0x00080000;
- if ((style & WS_CAPTION) != 0 && (style & WS_SYSMENU) != 0)
- {
- return true;
- }
-
- // 检查窗口大小,跳过过大的窗口
- var rect = new ForegroundWindowInfo.RECT();
- GetWindowRect(hWnd, out rect);
- var width = rect.Right - rect.Left;
- var height = rect.Bottom - rect.Top;
-
- if (width > 600 || height > 400)
- {
- return true;
- }
-
- // 检查是否匹配拦截规则
+ // 检查每个启用的规则
foreach (var rule in _interceptRules.Values)
{
- if (!rule.IsEnabled) continue;
+ if (!rule.IsEnabled || rule.foundHwnd) continue;
- if (MatchesRule(windowInfo, rule))
+ if (MatchesRulePrecise(hWnd, rule))
{
- InterceptWindow(hWnd, rule);
- scanData.WindowsIntercepted++;
- break;
+ rule.outHwnd = hWnd;
+ rule.foundHwnd = true;
}
}
@@ -963,107 +1014,11 @@ namespace Ink_Canvas.Helpers
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"处理窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"枚举窗口回调错误: {ex.Message}", LogHelper.LogType.Error);
return true;
}
}
- ///
- /// 清理过期缓存
- ///
- private void CleanupExpiredCache()
- {
- var now = DateTime.Now;
- var expiredWindows = new List();
- var expiredProcesses = new List();
-
- // 清理窗口缓存
- foreach (var kvp in _lastScanTime)
- {
- if (now - kvp.Value > TimeSpan.FromMinutes(5))
- {
- expiredWindows.Add(kvp.Key);
- }
- }
-
- foreach (var hWnd in expiredWindows)
- {
- _lastScanTime.Remove(hWnd);
- _knownWindows.Remove(hWnd);
- }
-
- // 清理进程缓存
- foreach (var kvp in _processLastScanTime)
- {
- if (now - kvp.Value > TimeSpan.FromMinutes(2))
- {
- expiredProcesses.Add(kvp.Key);
- }
- }
-
- foreach (var processName in expiredProcesses)
- {
- _processLastScanTime.Remove(processName);
- }
- }
-
- ///
- /// 更新扫描统计
- ///
- private void UpdateScanStatistics(int windowsFound, int windowsIntercepted, DateTime scanStartTime)
- {
- var scanDuration = DateTime.Now - scanStartTime;
-
- if (windowsFound == 0)
- {
- _consecutiveEmptyScans++;
- }
- else
- {
- _consecutiveEmptyScans = 0;
- _lastSuccessfulScan = DateTime.Now;
- }
- }
-
- ///
- /// 动态调整扫描间隔
- ///
- private void AdjustScanInterval()
- {
- if (!_isRunning) return;
-
- int newInterval;
- if (_consecutiveEmptyScans > 5)
- {
- // 连续多次空扫描,增加间隔到30秒
- newInterval = 30000;
- }
- else if (_consecutiveEmptyScans > 3)
- {
- // 连续多次空扫描,增加间隔到15秒
- newInterval = 15000;
- }
- else if (_consecutiveEmptyScans > 1)
- {
- // 连续空扫描,增加间隔到10秒
- newInterval = 10000;
- }
- else
- {
- // 正常扫描,使用5秒间隔
- newInterval = 5000;
- }
-
- // 更新定时器间隔
- _scanTimer.Change(newInterval, newInterval);
- }
-
- private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
- {
- // 这个方法现在由ProcessWindow替代,保留用于兼容性
- return true;
- }
-
private WindowInfo GetWindowInfo(IntPtr hWnd)
{
try
@@ -1100,62 +1055,119 @@ namespace Ink_Canvas.Helpers
}
}
- private bool MatchesRule(WindowInfo windowInfo, InterceptRule rule)
+ ///
+ /// 精确匹配规则
+ ///
+ private bool MatchesRulePrecise(IntPtr hWnd, InterceptRule rule)
{
try
{
- // 检查进程名(如果指定了进程名)
- if (!string.IsNullOrEmpty(rule.ProcessName))
- {
- if (!windowInfo.ProcessName.ToLower().Contains(rule.ProcessName.ToLower()))
- {
- return false;
- }
- }
-
- // 检查窗口标题(如果指定了模式)
- if (!string.IsNullOrEmpty(rule.WindowTitlePattern))
- {
- if (!windowInfo.WindowTitle.ToLower().Contains(rule.WindowTitlePattern.ToLower()))
- {
- return false;
- }
- }
-
- // 检查类名(如果指定了模式)
+ // 检查类名
if (!string.IsNullOrEmpty(rule.ClassNamePattern))
{
- if (!windowInfo.ClassName.ToLower().Contains(rule.ClassNamePattern.ToLower()))
+ var className = new StringBuilder(256);
+ GetClassName(hWnd, className, className.Capacity);
+ var classNameStr = className.ToString();
+
+ if (rule.ExactClassNameMatch)
{
+ if (!classNameStr.Equals(rule.ClassNamePattern, StringComparison.OrdinalIgnoreCase))
+ return false;
+ }
+ else
+ {
+ if (!classNameStr.Contains(rule.ClassNamePattern))
+ return false;
+ }
+ }
+
+ // 检查窗口标题
+ if (!string.IsNullOrEmpty(rule.WindowTitlePattern))
+ {
+ var windowTitle = new StringBuilder(256);
+ GetWindowText(hWnd, windowTitle, windowTitle.Capacity);
+ var titleStr = windowTitle.ToString();
+
+ if (rule.ExactTitleMatch)
+ {
+ if (!titleStr.Equals(rule.WindowTitlePattern, StringComparison.OrdinalIgnoreCase))
+ return false;
+ }
+ else
+ {
+ if (!titleStr.Contains(rule.WindowTitlePattern))
+ return false;
+ }
+ }
+
+ // 检查窗口样式
+ if (rule.HasWindowStyle)
+ {
+ var style = GetWindowLong(hWnd, GWL_STYLE);
+ if (style != rule.WindowStyle)
+ return false;
+ }
+
+ // 检查窗口尺寸
+ if (rule.HasWindowSize)
+ {
+ var rect = new ForegroundWindowInfo.RECT();
+ if (DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, out rect, Marshal.SizeOf(rect)) == 0)
+ {
+ var width = rect.Right - rect.Left;
+ var height = rect.Bottom - rect.Top;
+
+ // 检查精确匹配
+ if (rule.WindowWidth == width && rule.WindowHeight == height)
+ return true;
+
+ // 检查缩放匹配
+ var hdc = GetDC(IntPtr.Zero);
+ var horizontalDPI = GetDeviceCaps(hdc, LOGPIXELSX);
+ var verticalDPI = GetDeviceCaps(hdc, LOGPIXELSY);
+ ReleaseDC(IntPtr.Zero, hdc);
+
+ var scale = (horizontalDPI + verticalDPI) / 2.0f / 96.0f;
+ var scaledWidth = (int)(rule.WindowWidth * scale);
+ var scaledHeight = (int)(rule.WindowHeight * scale);
+
+ if (Math.Abs(scaledWidth - width) <= 1 && Math.Abs(scaledHeight - height) <= 1)
+ return true;
+
return false;
}
}
- // 如果所有检查都通过,就认为是目标窗口
return true;
}
catch (Exception ex)
{
- LogHelper.WriteLogToFile($"匹配规则时发生错误: {ex.Message}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"精确匹配规则时发生错误: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
+ private bool MatchesRule(WindowInfo windowInfo, InterceptRule rule)
+ {
+ return MatchesRulePrecise(windowInfo.Handle, rule);
+ }
+
private void InterceptWindow(IntPtr hWnd, InterceptRule rule)
{
try
{
- // 使用多种方法隐藏窗口
- // 方法1:移动到屏幕外
- SetWindowPos(hWnd, IntPtr.Zero, -2000, -2000, 0, 0,
- SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_HIDEWINDOW);
-
- // 方法2:最小化窗口
- ShowWindow(hWnd, SW_MINIMIZE);
-
- // 方法3:隐藏窗口
+ if (!IsWindow(hWnd) || !IsWindowVisible(hWnd))
+ {
+ if (_interceptedWindows.ContainsKey(hWnd))
+ {
+ _interceptedWindows.Remove(hWnd);
+ }
+ return;
+ }
+
+ // 直接隐藏窗口,不发送关闭消息
ShowWindow(hWnd, SW_HIDE);
-
+
// 记录拦截的窗口
_interceptedWindows[hWnd] = rule.Type;
@@ -1167,6 +1179,7 @@ namespace Ink_Canvas.Helpers
Rule = rule,
WindowTitle = GetWindowTitle(hWnd)
});
+
}
catch (Exception ex)
{
@@ -1243,12 +1256,6 @@ namespace Ink_Canvas.Helpers
public Process Process { get; set; }
}
- private class ScanData
- {
- public int WindowsFound { get; set; }
- public int WindowsIntercepted { get; set; }
- }
-
#endregion
#region 事件参数类
diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj
index 5ff02806..c2576703 100644
--- a/Ink Canvas/InkCanvasForClass.csproj
+++ b/Ink Canvas/InkCanvasForClass.csproj
@@ -575,6 +575,11 @@
+
+
+
+
+
diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml
index e9495e55..1669a49e 100644
--- a/Ink Canvas/MainWindow.xaml
+++ b/Ink Canvas/MainWindow.xaml
@@ -1040,6 +1040,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3666,9 +3688,6 @@
-
diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs
index a18bbb96..7857e8bb 100644
--- a/Ink Canvas/MainWindow.xaml.cs
+++ b/Ink Canvas/MainWindow.xaml.cs
@@ -511,8 +511,8 @@ namespace Ink_Canvas
}
SystemEvents.DisplaySettingsChanged += SystemEventsOnDisplaySettingsChanged;
- // 自动收纳到侧边栏(若通过 --board 进入白板模式则跳过收纳)
- if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode)
+ // 自动收纳到侧边栏(若通过 --board 进入白板模式或 --show 参数则跳过收纳)
+ if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode && !App.StartWithShowMode)
{
FoldFloatingBar_MouseUp(new object(), null);
}
@@ -523,8 +523,6 @@ namespace Ink_Canvas
else
RadioCrashNoAction.IsChecked = true;
-
-
// 如果当前不是黑板模式,则切换到黑板模式
if (currentMode == 0)
{
@@ -584,6 +582,21 @@ namespace Ink_Canvas
SwitchToBoardMode();
}), DispatcherPriority.Loaded);
}
+
+ // 检查是否通过--show参数启动,如果是则确保退出收纳模式并恢复浮动栏
+ if (App.StartWithShowMode)
+ {
+ LogHelper.WriteLogToFile("检测到--show参数,退出收纳模式并恢复浮动栏", LogHelper.LogType.Event);
+ // 延迟执行,确保UI已完全加载
+ Dispatcher.BeginInvoke(new Action(async () =>
+ {
+ // 如果当前处于收纳模式,则展开浮动栏
+ if (isFloatingBarFolded)
+ {
+ await UnFoldFloatingBar(new object());
+ }
+ }), DispatcherPriority.Loaded);
+ }
}
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e)
@@ -727,6 +740,13 @@ namespace Ink_Canvas
_inkFadeManager = null;
}
+ // 清理悬浮窗拦截管理器
+ if (_floatingWindowInterceptorManager != null)
+ {
+ _floatingWindowInterceptorManager.Dispose();
+ _floatingWindowInterceptorManager = null;
+ }
+
// 停止置顶维护定时器
StopTopmostMaintenance();
diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
index 289e9039..5fe56780 100644
--- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
+++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
@@ -29,7 +29,6 @@ namespace Ink_Canvas
{
public partial class MainWindow : Window
{
- // 添加当前模式的缓存,避免依赖可能过时的inkCanvas.EditingMode状态
private string _currentToolMode = "cursor";
#region "手勢"按鈕
@@ -1537,7 +1536,6 @@ namespace Ink_Canvas
// 仅计算Windows任务栏高度,不考虑其他程序对工作区的影响
var toolbarHeight = ForegroundWindowInfo.GetTaskbarHeight(screen, dpiScaleY);
- // 计算浮动栏位置,考虑快捷调色盘的显示状态
// 使用更可靠的方法获取浮动栏宽度
double baseWidth = ViewboxFloatingBar.ActualWidth;
@@ -1577,7 +1575,7 @@ namespace Ink_Canvas
else
{
// 双行显示模式,宽度较大
- floatingBarWidth = Math.Max(floatingBarWidth, 820 * ViewboxFloatingBarScaleTransform.ScaleX);
+ floatingBarWidth = Math.Max(floatingBarWidth, 68 * ViewboxFloatingBarScaleTransform.ScaleX);
}
}
@@ -1657,7 +1655,7 @@ namespace Ink_Canvas
public async void PureViewboxFloatingBarMarginAnimationInDesktopMode()
{
- // 新增:在白板模式下不执行浮动栏动画
+ // 在白板模式下不执行浮动栏动画
if (currentMode == 1)
{
return;
@@ -1682,8 +1680,6 @@ namespace Ink_Canvas
// 仅计算Windows任务栏高度,不考虑其他程序对工作区的影响
var toolbarHeight = ForegroundWindowInfo.GetTaskbarHeight(screen, dpiScaleY);
- // 计算浮动栏位置,考虑快捷调色盘的显示状态
- // 使用更可靠的方法获取浮动栏宽度
double baseWidth = ViewboxFloatingBar.ActualWidth;
// 如果ActualWidth为0,尝试使用DesiredSize
@@ -1722,13 +1718,13 @@ namespace Ink_Canvas
else
{
// 双行显示模式,宽度较大
- floatingBarWidth = Math.Max(floatingBarWidth, 850 * ViewboxFloatingBarScaleTransform.ScaleX);
+ floatingBarWidth = Math.Max(floatingBarWidth, 68 * ViewboxFloatingBarScaleTransform.ScaleX);
}
}
pos.X = (screenWidth - floatingBarWidth) / 2;
- // 如果任务栏高度为0(隐藏状态),则使用固定边距
+ // 如果任务栏高度为0,则使用固定边距
if (toolbarHeight == 0)
{
pos.Y = screenHeight - ViewboxFloatingBar.ActualHeight * ViewboxFloatingBarScaleTransform.ScaleY -
@@ -1828,7 +1824,7 @@ namespace Ink_Canvas
else
{
// 双行显示模式,宽度较大
- floatingBarWidth = Math.Max(floatingBarWidth, 820 * ViewboxFloatingBarScaleTransform.ScaleX);
+ floatingBarWidth = Math.Max(floatingBarWidth, 68 * ViewboxFloatingBarScaleTransform.ScaleX);
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs
index 48b22800..dd5391e5 100644
--- a/Ink Canvas/MainWindow_cs/MW_Settings.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs
@@ -186,6 +186,20 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
+ private void ToggleSwitchEnableSplashScreen_Toggled(object sender, RoutedEventArgs e)
+ {
+ if (!isLoaded) return;
+ Settings.Appearance.EnableSplashScreen = ToggleSwitchEnableSplashScreen.IsOn;
+ SaveSettingsToFile();
+ }
+
+ private void ComboBoxSplashScreenStyle_SelectionChanged(object sender, RoutedEventArgs e)
+ {
+ if (!isLoaded) return;
+ Settings.Appearance.SplashScreenStyle = ComboBoxSplashScreenStyle.SelectedIndex;
+ SaveSettingsToFile();
+ }
+
private void ViewboxFloatingBarScaleTransformValueSlider_ValueChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
index f5d4f5cb..0653fd3a 100644
--- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
@@ -332,6 +332,10 @@ namespace Ink_Canvas
ToggleSwitchEnableQuickPanel.IsOn = Settings.Appearance.IsShowQuickPanel;
+ ToggleSwitchEnableSplashScreen.IsOn = Settings.Appearance.EnableSplashScreen;
+
+ ComboBoxSplashScreenStyle.SelectedIndex = Settings.Appearance.SplashScreenStyle;
+
ToggleSwitchEnableTrayIcon.IsOn = Settings.Appearance.EnableTrayIcon;
ICCTrayIconExampleImage.Visibility =
Settings.Appearance.EnableTrayIcon ? Visibility.Visible : Visibility.Collapsed;
diff --git a/Ink Canvas/MainWindow_cs/MW_Timer.cs b/Ink Canvas/MainWindow_cs/MW_Timer.cs
index aaa6b3f4..309f239b 100644
--- a/Ink Canvas/MainWindow_cs/MW_Timer.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Timer.cs
@@ -275,6 +275,8 @@ namespace Ink_Canvas
{
var processes = Process.GetProcessesByName("EasiNote");
if (processes.Length > 0) arg += " /IM EasiNote.exe";
+ var seewoStartProcesses = Process.GetProcessesByName("SeewoStart");
+ if (seewoStartProcesses.Length > 0) arg += " /IM SeewoStart.exe";
}
if (Settings.Automation.IsAutoKillHiteAnnotation)
@@ -471,10 +473,22 @@ namespace Ink_Canvas
Trace.WriteLine(ForegroundWindowInfo.ProcessPath());
Trace.WriteLine(version);
Trace.WriteLine(prodName);
- if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote && (!(windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500) ||
- !Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno))
+ if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote)
{ // EasiNote5
- if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
+ // 检查是否是桌面批注窗口
+ bool isAnnotationWindow = windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500;
+
+ // 如果启用了忽略桌面批注窗口功能,且当前是批注窗口
+ if (Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno && isAnnotationWindow)
+ {
+ // 强制保持收纳状态
+ if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
+ }
+ else if (!isAnnotationWindow)
+ {
+ // 非批注窗口时正常收纳
+ if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
+ }
}
else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3)
{ // EasiNote3
diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs
index bd5b753d..b34b60b4 100644
--- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs
+++ b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs
@@ -696,6 +696,11 @@ namespace Ink_Canvas
touchPoint = e.GetTouchPoint(inkCanvas);
EraserOverlay_PointerDown(sender);
EraserOverlay_PointerMove(sender, touchPoint.Position);
+ if (Settings.Canvas.IsShowCursor)
+ {
+ inkCanvas.ForceCursor = false;
+ inkCanvas.UseCustomCursor = false;
+ }
}
}
}
@@ -840,6 +845,11 @@ namespace Ink_Canvas
// 禁用橡皮擦覆盖层
DisableEraserOverlay();
+ if (Settings.Canvas.IsShowCursor)
+ {
+ inkCanvas.ForceCursor = true;
+ inkCanvas.UseCustomCursor = true;
+ }
LogHelper.WriteLogToFile("Palm eraser state reset completed");
}
@@ -948,6 +958,12 @@ namespace Ink_Canvas
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
+ DisableEraserOverlay();
+ if (Settings.Canvas.IsShowCursor)
+ {
+ inkCanvas.ForceCursor = true;
+ inkCanvas.UseCustomCursor = true;
+ }
LogHelper.WriteLogToFile("Palm eraser force recovery completed");
}
diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs
index 1f1d5c72..16ecbd01 100644
--- a/Ink Canvas/Properties/AssemblyInfo.cs
+++ b/Ink Canvas/Properties/AssemblyInfo.cs
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.7.11.4")]
-[assembly: AssemblyFileVersion("1.7.11.4")]
+[assembly: AssemblyVersion("1.7.12.0")]
+[assembly: AssemblyFileVersion("1.7.12.0")]
diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs
index 77f0103b..73cd49ed 100644
--- a/Ink Canvas/Resources/Settings.cs
+++ b/Ink Canvas/Resources/Settings.cs
@@ -27,6 +27,8 @@ namespace Ink_Canvas
public RandSettings RandSettings { get; set; } = new RandSettings();
[JsonProperty("modeSettings")]
public ModeSettings ModeSettings { get; set; } = new ModeSettings();
+ [JsonProperty("camera")]
+ public CameraSettings Camera { get; set; } = new CameraSettings();
}
public class Canvas
@@ -199,6 +201,10 @@ namespace Ink_Canvas
public int UnFoldButtonImageType { get; set; }
[JsonProperty("isShowLRSwitchButton")]
public bool IsShowLRSwitchButton { get; set; }
+ [JsonProperty("enableSplashScreen")]
+ public bool EnableSplashScreen { get; set; } = false;
+ [JsonProperty("splashScreenStyle")]
+ public int SplashScreenStyle { get; set; } = 1; // 0-随机, 1-跟随四季, 2-春季, 3-夏季, 4-秋季, 5-冬季, 6-马年限定
[JsonProperty("isShowQuickPanel")]
public bool IsShowQuickPanel { get; set; } = true;
[JsonProperty("chickenSoupSource")]
@@ -678,4 +684,19 @@ namespace Ink_Canvas
[JsonProperty("isPPTOnlyMode")]
public bool IsPPTOnlyMode { get; set; } = false; // 是否为仅PPT模式,默认为false(正常模式)
}
+
+ public class CameraSettings
+ {
+ [JsonProperty("rotationAngle")]
+ public int RotationAngle { get; set; } = 0;
+
+ [JsonProperty("resolutionWidth")]
+ public int ResolutionWidth { get; set; } = 1920;
+
+ [JsonProperty("resolutionHeight")]
+ public int ResolutionHeight { get; set; } = 1080;
+
+ [JsonProperty("selectedCameraIndex")]
+ public int SelectedCameraIndex { get; set; } = 0;
+ }
}
diff --git a/Ink Canvas/Resources/Startup-animation/ICC Autumn.png b/Ink Canvas/Resources/Startup-animation/ICC Autumn.png
new file mode 100644
index 00000000..2fdaee0d
Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Autumn.png differ
diff --git a/Ink Canvas/Resources/Startup-animation/ICC Horse.png b/Ink Canvas/Resources/Startup-animation/ICC Horse.png
new file mode 100644
index 00000000..0a4d14f8
Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Horse.png differ
diff --git a/Ink Canvas/Resources/Startup-animation/ICC Spring.png b/Ink Canvas/Resources/Startup-animation/ICC Spring.png
new file mode 100644
index 00000000..15be2f3e
Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Spring.png differ
diff --git a/Ink Canvas/Resources/Startup-animation/ICC Summer.png b/Ink Canvas/Resources/Startup-animation/ICC Summer.png
new file mode 100644
index 00000000..b18708a3
Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Summer.png differ
diff --git a/Ink Canvas/Resources/Startup-animation/ICC Winter.png b/Ink Canvas/Resources/Startup-animation/ICC Winter.png
new file mode 100644
index 00000000..f11542f4
Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Winter.png differ
diff --git a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml
index b4832ff9..97c4804f 100644
--- a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml
+++ b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml
@@ -181,29 +181,65 @@
VerticalAlignment="Center" />
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml.cs b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml.cs
index a03480dc..551c8bca 100644
--- a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml.cs
+++ b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml.cs
@@ -104,12 +104,20 @@ namespace Ink_Canvas
{
try
{
- _cameraService = new CameraService();
+ // 从设置中加载摄像头配置
+ var cameraSettings = MainWindow.Settings.Camera;
+ _cameraService = new CameraService(
+ cameraSettings.RotationAngle,
+ cameraSettings.ResolutionWidth,
+ cameraSettings.ResolutionHeight);
_cameraService.FrameReceived += CameraService_FrameReceived;
_cameraService.ErrorOccurred += CameraService_ErrorOccurred;
// 初始化摄像头选择下拉框
RefreshCameraComboBox();
+
+ // 初始化旋转和分辨率显示
+ InitializeCameraControls();
}
catch (Exception ex)
{
@@ -117,6 +125,26 @@ namespace Ink_Canvas
}
}
+ private void InitializeCameraControls()
+ {
+ if (_cameraService != null)
+ {
+ // 更新旋转角度显示
+ UpdateRotationDisplay();
+
+ // 设置分辨率下拉框
+ var currentResolution = $"{_cameraService.ResolutionWidth}x{_cameraService.ResolutionHeight}";
+ foreach (ComboBoxItem item in ResolutionComboBox.Items)
+ {
+ if (item.Tag?.ToString() == $"{_cameraService.ResolutionWidth},{_cameraService.ResolutionHeight}")
+ {
+ ResolutionComboBox.SelectedItem = item;
+ break;
+ }
+ }
+ }
+ }
+
private void RefreshCameraComboBox()
{
try
@@ -1213,6 +1241,70 @@ namespace Ink_Canvas
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
}
+ #region 摄像头旋转和分辨率控制
+
+ private void RotateLeftButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (_cameraService != null)
+ {
+ _cameraService.RotationAngle = (_cameraService.RotationAngle - 1 + 4) % 4;
+ UpdateRotationDisplay();
+ SaveCameraSettings();
+ }
+ }
+
+ private void RotateRightButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (_cameraService != null)
+ {
+ _cameraService.RotationAngle = (_cameraService.RotationAngle + 1) % 4;
+ UpdateRotationDisplay();
+ SaveCameraSettings();
+ }
+ }
+
+ private void ResolutionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (_cameraService != null && ResolutionComboBox.SelectedItem is ComboBoxItem selectedItem)
+ {
+ var resolution = selectedItem.Tag?.ToString();
+ if (!string.IsNullOrEmpty(resolution))
+ {
+ var parts = resolution.Split(',');
+ if (parts.Length == 2 &&
+ int.TryParse(parts[0], out int width) &&
+ int.TryParse(parts[1], out int height))
+ {
+ _cameraService.ResolutionWidth = width;
+ _cameraService.ResolutionHeight = height;
+ SaveCameraSettings();
+ }
+ }
+ }
+ }
+
+ private void UpdateRotationDisplay()
+ {
+ if (_cameraService != null)
+ {
+ var angle = _cameraService.RotationAngle * 90;
+ RotationAngleText.Text = $"{angle}°";
+ }
+ }
+
+ private void SaveCameraSettings()
+ {
+ if (_cameraService != null)
+ {
+ MainWindow.Settings.Camera.RotationAngle = _cameraService.RotationAngle;
+ MainWindow.Settings.Camera.ResolutionWidth = _cameraService.ResolutionWidth;
+ MainWindow.Settings.Camera.ResolutionHeight = _cameraService.ResolutionHeight;
+ MainWindow.SaveSettingsToFile();
+ }
+ }
+
+ #endregion
+
protected override void OnClosed(EventArgs e)
{
try
diff --git a/Ink Canvas/Windows/SplashScreen.xaml b/Ink Canvas/Windows/SplashScreen.xaml
new file mode 100644
index 00000000..8ad2c252
--- /dev/null
+++ b/Ink Canvas/Windows/SplashScreen.xaml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Ink Canvas/Windows/SplashScreen.xaml.cs b/Ink Canvas/Windows/SplashScreen.xaml.cs
new file mode 100644
index 00000000..e4e91da7
--- /dev/null
+++ b/Ink Canvas/Windows/SplashScreen.xaml.cs
@@ -0,0 +1,530 @@
+using System;
+using System.Reflection;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Threading;
+using System.IO;
+using Newtonsoft.Json;
+
+namespace Ink_Canvas.Windows
+{
+ ///
+ /// SplashScreen.xaml 的交互逻辑
+ ///
+ public partial class SplashScreen : Window
+ {
+ private DispatcherTimer _timer;
+ private int _loadingStep = 0;
+ private int _actualSplashStyle = 1;
+ private readonly string[] _loadingMessages = {
+ "正在启动 Ink Canvas...",
+ "正在初始化组件...",
+ "正在加载配置...",
+ "正在准备界面...",
+ "启动完成!"
+ };
+
+ public SplashScreen()
+ {
+ InitializeComponent();
+ InitializeSplashScreen();
+ }
+
+ private void InitializeSplashScreen()
+ {
+ // 设置窗口居中
+ WindowStartupLocation = WindowStartupLocation.CenterScreen;
+
+ // 设置版本号
+ SetVersionText();
+
+ // 加载启动图片并获取实际样式
+ _actualSplashStyle = LoadSplashImageWithStyle();
+
+ // 启动加载动画
+ StartLoadingAnimation();
+ }
+
+ private void StartLoadingAnimation()
+ {
+ _timer = new DispatcherTimer
+ {
+ Interval = TimeSpan.FromMilliseconds(1200)
+ };
+ _timer.Tick += Timer_Tick;
+ _timer.Start();
+ }
+
+ private void Timer_Tick(object sender, EventArgs e)
+ {
+ if (_loadingStep < _loadingMessages.Length)
+ {
+ LoadingText.Text = _loadingMessages[_loadingStep];
+ _loadingStep++;
+ }
+ else
+ {
+ _timer.Stop();
+ // 不要自动关闭启动画面,等待外部调用CloseSplashScreen
+ }
+ }
+
+ public void CloseSplashScreen()
+ {
+ // 添加淡出动画
+ var fadeOutAnimation = new DoubleAnimation
+ {
+ From = 1,
+ To = 0,
+ Duration = TimeSpan.FromMilliseconds(300),
+ EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
+ };
+
+ fadeOutAnimation.Completed += (s, e) =>
+ {
+ this.Close();
+ };
+
+ this.BeginAnimation(OpacityProperty, fadeOutAnimation);
+ }
+
+ ///
+ /// 设置加载进度(0-100)
+ ///
+ /// 进度百分比
+ public void SetProgress(int progress)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ // 设置进度条颜色
+ SetProgressBarColor();
+
+ // 获取进度条容器的实际宽度
+ double containerWidth = ProgressBarBackground.ActualWidth;
+ if (containerWidth <= 0)
+ {
+ // 如果ActualWidth为0,使用设计时宽度
+ containerWidth = 530;
+ }
+
+ // 计算目标宽度
+ double targetWidth = containerWidth * (progress / 100.0);
+
+ // 创建Storyboard动画
+ var storyboard = new Storyboard();
+
+ // 创建宽度动画
+ var widthAnimation = new DoubleAnimation
+ {
+ From = ProgressBarFill.Width,
+ To = targetWidth,
+ Duration = TimeSpan.FromMilliseconds(300),
+ EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
+ };
+
+ // 设置动画目标
+ Storyboard.SetTarget(widthAnimation, ProgressBarFill);
+ Storyboard.SetTargetProperty(widthAnimation, new PropertyPath(Border.WidthProperty));
+
+ // 添加动画到Storyboard
+ storyboard.Children.Add(widthAnimation);
+
+ // 添加动画完成事件
+ storyboard.Completed += (s, e) =>
+ {
+ // 确保最终值正确设置
+ ProgressBarFill.Width = targetWidth;
+
+ // 根据进度调整圆角
+ if (progress >= 100)
+ {
+ // 进度100%时,底部角都是圆角
+ ProgressBarFill.CornerRadius = new CornerRadius(0, 0, 7, 7);
+ }
+ else
+ {
+ // 进度未满时,只有左侧是圆角
+ ProgressBarFill.CornerRadius = new CornerRadius(0, 0, 0, 7);
+ }
+ };
+
+ // 开始动画
+ storyboard.Begin();
+ });
+ }
+
+ ///
+ /// 设置加载消息
+ ///
+ /// 加载消息
+ public void SetLoadingMessage(string message)
+ {
+ // 使用实际选择的样式
+ SetLoadingMessage(message, _actualSplashStyle);
+ }
+
+ public void SetLoadingMessage(string message, int actualSplashStyle)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ LoadingText.Text = message;
+
+ // 根据实际启动动画样式调整加载文本样式
+ if (actualSplashStyle == 6) // 马年限定
+ {
+ // 马年限定样式
+ LoadingText.FontSize = 12;
+ LoadingText.FontWeight = FontWeights.SemiBold;
+ LoadingText.Foreground = Brushes.White;
+ LoadingText.HorizontalAlignment = HorizontalAlignment.Center;
+ LoadingText.Margin = new Thickness(0,200,140,4);
+ }
+ else
+ {
+ // 默认样式
+ LoadingText.FontSize = 18;
+ LoadingText.FontWeight = FontWeights.SemiBold;
+ LoadingText.Foreground = Brushes.White;
+ LoadingText.HorizontalAlignment = HorizontalAlignment.Center;
+ LoadingText.Margin = new Thickness(0,200,0,0);
+ }
+ });
+ }
+
+ ///
+ /// 获取当前启动动画样式
+ ///
+ /// 启动动画样式索引
+ private int GetCurrentSplashStyle()
+ {
+ try
+ {
+ var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
+ if (File.Exists(settingsPath))
+ {
+ var json = File.ReadAllText(settingsPath);
+ dynamic obj = JsonConvert.DeserializeObject(json);
+ if (obj?["appearance"]?["splashScreenStyle"] != null)
+ {
+ return (int)obj["appearance"]["splashScreenStyle"];
+ }
+ }
+ return 1; // 默认跟随四季
+ }
+ catch
+ {
+ return 1; // 默认跟随四季
+ }
+ }
+
+ ///
+ /// 设置版本号文本
+ ///
+ private void SetVersionText()
+ {
+ try
+ {
+ var version = Assembly.GetExecutingAssembly().GetName().Version;
+ if (version != null)
+ {
+ VersionTextBlock.Text = $"v{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
+ }
+ else
+ {
+ VersionTextBlock.Text = "v5.0.4.0";
+ }
+ }
+ catch
+ {
+ VersionTextBlock.Text = "v5.0.4.0";
+ }
+ }
+
+ ///
+ /// 加载启动图片
+ ///
+ private void LoadSplashImage()
+ {
+ try
+ {
+ string imagePath = GetSplashImagePath();
+ if (!string.IsNullOrEmpty(imagePath))
+ {
+ StartupImage.Source = new System.Windows.Media.Imaging.BitmapImage(new Uri(imagePath));
+ }
+ }
+ catch (Exception ex)
+ {
+ // 如果加载失败,使用默认图片
+ System.Diagnostics.Debug.WriteLine($"加载启动图片失败: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 加载启动图片并返回实际样式
+ ///
+ /// 实际选择的样式
+ public int LoadSplashImageWithStyle()
+ {
+ try
+ {
+ int actualStyle;
+ string imagePath = GetSplashImagePath(out actualStyle);
+ if (!string.IsNullOrEmpty(imagePath))
+ {
+ StartupImage.Source = new System.Windows.Media.Imaging.BitmapImage(new Uri(imagePath));
+ }
+ return actualStyle;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"加载启动图片失败: {ex.Message}");
+ return GetActualStyle(1);
+ }
+ }
+
+ ///
+ /// 根据设置获取启动图片路径
+ ///
+ private string GetSplashImagePath()
+ {
+ try
+ {
+ // 读取设置
+ var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
+ int splashStyle = 1; // 默认跟随四季
+
+ if (File.Exists(settingsPath))
+ {
+ var json = File.ReadAllText(settingsPath);
+ dynamic obj = JsonConvert.DeserializeObject(json);
+ if (obj?["appearance"]?["splashScreenStyle"] != null)
+ {
+ splashStyle = (int)obj["appearance"]["splashScreenStyle"];
+ }
+ }
+
+ // 根据样式选择图片
+ string imageName = GetImageNameByStyle(splashStyle);
+ return $"pack://application:,,,/Resources/Startup-animation/{imageName}";
+ }
+ catch
+ {
+ string imageName = GetImageNameByStyle(1);
+ return $"pack://application:,,,/Resources/Startup-animation/{imageName}";
+ }
+ }
+
+ ///
+ /// 根据设置获取启动图片路径和实际样式
+ ///
+ /// 返回实际选择的样式
+ /// 图片路径
+ private string GetSplashImagePath(out int actualStyle)
+ {
+ try
+ {
+ // 读取设置
+ var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
+ int splashStyle = 1; // 默认跟随四季
+
+ if (File.Exists(settingsPath))
+ {
+ var json = File.ReadAllText(settingsPath);
+ dynamic obj = JsonConvert.DeserializeObject(json);
+ if (obj?["appearance"]?["splashScreenStyle"] != null)
+ {
+ splashStyle = (int)obj["appearance"]["splashScreenStyle"];
+ }
+ }
+
+ // 根据样式选择图片,并获取实际样式
+ actualStyle = GetActualStyle(splashStyle);
+ string imageName = GetImageNameByStyle(splashStyle);
+ return $"pack://application:,,,/Resources/Startup-animation/{imageName}";
+ }
+ catch
+ {
+ actualStyle = GetActualStyle(1);
+ string imageName = GetImageNameByStyle(1);
+ return $"pack://application:,,,/Resources/Startup-animation/{imageName}";
+ }
+ }
+
+ ///
+ /// 获取实际样式
+ ///
+ /// 设置中的样式
+ /// 实际选择的样式
+ private int GetActualStyle(int style)
+ {
+ switch (style)
+ {
+ case 0: // 随机
+ var random = new Random();
+ var randomStyles = new[] { 2, 3, 4, 5, 6 }; // 春季、夏季、秋季、冬季、马年限定
+ return randomStyles[random.Next(randomStyles.Length)];
+
+ case 1: // 跟随四季
+ var month = DateTime.Now.Month;
+ if (month >= 3 && month <= 5) return 2; // 春季
+ if (month >= 6 && month <= 8) return 3; // 夏季
+ if (month >= 9 && month <= 11) return 4; // 秋季
+ return 5; // 冬季
+
+ default:
+ return style;
+ }
+ }
+
+ ///
+ /// 根据样式获取图片名称
+ ///
+ private string GetImageNameByStyle(int style)
+ {
+ switch (style)
+ {
+ case 0: // 随机
+ var random = new Random();
+ var randomStyles = new[] { 2, 3, 4, 5, 6 }; // 春季、夏季、秋季、冬季、马年限定
+ return GetImageNameByStyle(randomStyles[random.Next(randomStyles.Length)]);
+
+ case 1: // 跟随四季
+ var month = DateTime.Now.Month;
+ if (month >= 3 && month <= 5) return GetImageNameByStyle(2); // 春季
+ if (month >= 6 && month <= 8) return GetImageNameByStyle(3); // 夏季
+ if (month >= 9 && month <= 11) return GetImageNameByStyle(4); // 秋季
+ return GetImageNameByStyle(5); // 冬季
+
+ case 2: // 春季
+ return "ICC Spring.png";
+ case 3: // 夏季
+ return "ICC Summer.png";
+ case 4: // 秋季
+ return "ICC Autumn.png";
+ case 5: // 冬季
+ return "ICC Winter.png";
+ case 6: // 马年限定
+ return "ICC Horse.png";
+ default:// 默认返回
+ return "ICC Horse.png";
+ }
+ }
+
+ ///
+ /// 根据实际样式设置进度条颜色
+ ///
+ private void SetProgressBarColor()
+ {
+ Color progressColor;
+
+ switch (_actualSplashStyle)
+ {
+ case 2: // 春季 - H=136, S=15, L=22
+ progressColor = HslToRgb(136, 15, 22);
+ break;
+ case 3: // 夏季 - H=6, S=15, L=22
+ progressColor = HslToRgb(6, 15, 22);
+ break;
+ case 4: // 秋季 - H=39, S=15, L=22
+ progressColor = HslToRgb(39, 15, 22);
+ break;
+ case 5: // 冬季 - H=204, S=15, L=22
+ progressColor = HslToRgb(204, 15, 22);
+ break;
+ case 6: // 马年限定 - 白色
+ progressColor = Colors.White;
+ break;
+ default: // 默认使用
+ progressColor = Colors.White;
+ break;
+ }
+
+ // 创建渐变画刷
+ var gradientBrush = new LinearGradientBrush
+ {
+ StartPoint = new System.Windows.Point(0, 0),
+ EndPoint = new System.Windows.Point(1, 0)
+ };
+
+ // 根据颜色类型设置渐变
+ if (_actualSplashStyle == 6) // 马年限定使用白色渐变
+ {
+ gradientBrush.GradientStops.Add(new GradientStop(Colors.White, 0));
+ gradientBrush.GradientStops.Add(new GradientStop(Color.FromArgb(200, 255, 255, 255), 0.5));
+ gradientBrush.GradientStops.Add(new GradientStop(Color.FromArgb(150, 255, 255, 255), 1));
+ }
+ else // 其他样式使用HSL颜色的渐变
+ {
+ var lighterColor = Color.FromArgb(255,
+ (byte)Math.Min(255, progressColor.R + 30),
+ (byte)Math.Min(255, progressColor.G + 30),
+ (byte)Math.Min(255, progressColor.B + 30));
+ var darkerColor = Color.FromArgb(255,
+ (byte)Math.Max(0, progressColor.R - 30),
+ (byte)Math.Max(0, progressColor.G - 30),
+ (byte)Math.Max(0, progressColor.B - 30));
+
+ gradientBrush.GradientStops.Add(new GradientStop(lighterColor, 0));
+ gradientBrush.GradientStops.Add(new GradientStop(progressColor, 0.5));
+ gradientBrush.GradientStops.Add(new GradientStop(darkerColor, 1));
+ }
+
+ ProgressBarFill.Background = gradientBrush;
+ }
+
+ ///
+ /// 将HSL颜色转换为RGB颜色
+ ///
+ /// 色相 (0-360)
+ /// 饱和度 (0-100)
+ /// 亮度 (0-100)
+ /// RGB颜色
+ private Color HslToRgb(double h, double s, double l)
+ {
+ // 将HSL值转换为0-1范围
+ h = h / 360.0;
+ s = s / 100.0;
+ l = l / 100.0;
+
+ double r, g, b;
+
+ if (s == 0)
+ {
+ // 无饱和度,为灰度
+ r = g = b = l;
+ }
+ else
+ {
+ double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ double p = 2 * l - q;
+
+ r = HueToRgb(p, q, h + 1.0 / 3.0);
+ g = HueToRgb(p, q, h);
+ b = HueToRgb(p, q, h - 1.0 / 3.0);
+ }
+
+ return Color.FromRgb(
+ (byte)Math.Round(r * 255),
+ (byte)Math.Round(g * 255),
+ (byte)Math.Round(b * 255)
+ );
+ }
+
+ ///
+ /// HSL颜色转换辅助方法
+ ///
+ private double HueToRgb(double p, double q, double t)
+ {
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1.0 / 6.0) return p + (q - p) * 6 * t;
+ if (t < 1.0 / 2.0) return q;
+ if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6;
+ return p;
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index cdce865c..84467aa3 100644
--- a/README.md
+++ b/README.md
@@ -110,3 +110,6 @@
## License
GPLv3
+
+## 项目引用
+[Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker)
diff --git a/UpdateLog.md b/UpdateLog.md
index 850af10d..cf2fa362 100644
--- a/UpdateLog.md
+++ b/UpdateLog.md
@@ -81,4 +81,21 @@ ICC CE 1.7.X.X更新日志
80. 修复切换模式后无法持续生效
81. 优化UI
82. 修复插入图片后使用多指书写导致图片消失
-83. 优化PowerPoint联动增强
\ No newline at end of file
+83. 优化PowerPoint联动增强
+84. 修复墨迹克隆
+85. 修复白板手势
+86. 新增退出白板自动收纳
+87. 修复进入PPT自动收纳
+88. 更新计时器铃声及按钮样式
+89. 更新更新弹窗样式
+90. 完善悬浮窗拦截功能
+91. 新增主题切换
+92. 更新历史版本回滚页面样式
+93. 修复悬浮窗拦截
+94. 改进计时器
+95. 新增配置文件损坏自动恢复
+96. 修复手掌擦
+97. 改进橡皮系统
+98. 新增启动动画
+99. 修复仅调色盘状态下浮动栏不居中
+100. 修复希沃白板查杀与思锐希沃启动器导致的重复启动
\ No newline at end of file