2025-09-18 17:50:47 +08:00
|
|
|
|
using Ink_Canvas.Helpers;
|
2025-08-31 11:43:52 +08:00
|
|
|
|
using System;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
|
using System.Diagnostics;
|
2025-06-19 11:47:16 +08:00
|
|
|
|
using System.IO;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Net;
|
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
|
using System.Runtime.CompilerServices;
|
2025-06-19 11:47:16 +08:00
|
|
|
|
using System.Threading.Tasks;
|
2025-07-28 14:40:44 +08:00
|
|
|
|
using System.Timers;
|
|
|
|
|
|
using System.Windows;
|
2025-06-29 11:56:38 +08:00
|
|
|
|
using System.Windows.Controls;
|
2025-11-01 20:42:18 +08:00
|
|
|
|
using System.Windows.Threading;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
namespace Ink_Canvas
|
|
|
|
|
|
{
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 时间视图模型类,用于绑定显示时间和日期
|
|
|
|
|
|
/// </summary>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
public class TimeViewModel : INotifyPropertyChanged
|
|
|
|
|
|
{
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前时间字符串
|
|
|
|
|
|
/// </summary>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
private string _nowTime;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前日期字符串
|
|
|
|
|
|
/// </summary>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
private string _nowDate;
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前时间属性
|
|
|
|
|
|
/// </summary>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
public string nowTime
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
get => _nowTime;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_nowTime != value)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
_nowTime = value;
|
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前日期属性
|
|
|
|
|
|
/// </summary>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
public string nowDate
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
get => _nowDate;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_nowDate != value)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
_nowDate = value;
|
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 属性变化事件
|
|
|
|
|
|
/// </summary>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 触发属性变化事件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="propertyName">属性名称</param>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
public partial class MainWindow : Window
|
|
|
|
|
|
{
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 进程终止定时器
|
|
|
|
|
|
/// </summary>
|
2026-01-02 09:28:00 +08:00
|
|
|
|
private Timer timerKillProcess = new Timer();
|
2026-04-26 09:12:26 +08:00
|
|
|
|
|
|
|
|
|
|
public void UpdateAutoKillProcessTimer(bool shouldRun)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (shouldRun) timerKillProcess.Start();
|
|
|
|
|
|
else timerKillProcess.Stop();
|
|
|
|
|
|
}
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 统一的主窗口定时器
|
|
|
|
|
|
/// </summary>
|
2026-02-13 09:29:24 +08:00
|
|
|
|
private Timer _unifiedMainWindowTimer;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 可用的最新版本号
|
|
|
|
|
|
/// </summary>
|
2025-07-28 14:40:44 +08:00
|
|
|
|
private string AvailableLatestVersion;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 静默更新检查定时器
|
|
|
|
|
|
/// </summary>
|
2026-01-02 09:28:00 +08:00
|
|
|
|
private Timer timerCheckAutoUpdateWithSilence = new Timer();
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新检查重试定时器
|
|
|
|
|
|
/// </summary>
|
2026-01-02 09:28:00 +08:00
|
|
|
|
private Timer timerCheckAutoUpdateRetry = new Timer();
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 避免书写时触发二次关闭二级菜单导致动画不连续
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private bool isHidingSubPanelsWhenInking;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新检查重试计数
|
|
|
|
|
|
/// </summary>
|
2025-10-06 18:29:12 +08:00
|
|
|
|
private int updateCheckRetryCount = 0;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 最大更新检查重试次数
|
|
|
|
|
|
/// </summary>
|
2025-10-06 18:29:12 +08:00
|
|
|
|
private const int MAX_UPDATE_CHECK_RETRIES = 6;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 时间显示定时器
|
|
|
|
|
|
/// </summary>
|
2026-01-02 09:28:00 +08:00
|
|
|
|
private Timer timerDisplayTime = new Timer();
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 日期显示定时器
|
|
|
|
|
|
/// </summary>
|
2026-01-02 09:28:00 +08:00
|
|
|
|
private Timer timerDisplayDate = new Timer();
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// NTP时间同步定时器
|
|
|
|
|
|
/// </summary>
|
2026-01-02 09:28:00 +08:00
|
|
|
|
private Timer timerNtpSync = new Timer();
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 时间视图模型实例
|
|
|
|
|
|
/// </summary>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
private TimeViewModel nowTimeVM = new TimeViewModel();
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 缓存的网络时间
|
|
|
|
|
|
/// </summary>
|
2025-09-13 10:58:43 +08:00
|
|
|
|
private DateTime cachedNetworkTime = DateTime.Now;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 上次NTP同步时间
|
|
|
|
|
|
/// </summary>
|
2025-09-13 10:58:43 +08:00
|
|
|
|
private DateTime lastNtpSyncTime = DateTime.MinValue;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 上次显示的时间字符串
|
|
|
|
|
|
/// </summary>
|
2025-10-03 17:08:46 +08:00
|
|
|
|
private string lastDisplayedTime = "";
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否使用网络时间
|
|
|
|
|
|
/// </summary>
|
2025-10-03 17:08:46 +08:00
|
|
|
|
private bool useNetworkTime = false;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 网络时间与本地时间的偏移量
|
|
|
|
|
|
/// </summary>
|
2025-09-20 19:36:00 +08:00
|
|
|
|
private TimeSpan networkTimeOffset = TimeSpan.Zero;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 记录上次的本地时间,用于检测时间跳跃
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private DateTime lastLocalTime = DateTime.Now;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 防止重复NTP同步的标志
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private bool isNtpSyncing = false;
|
2026-02-22 11:25:30 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 橡皮擦自动切换回批注模式的计时器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private DispatcherTimer _eraserAutoSwitchBackTimer;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 异步获取网络时间
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>返回网络时间,如果获取失败则返回本地时间</returns>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 使用NTP协议从国家授时中心服务器获取网络时间
|
|
|
|
|
|
/// </remarks>
|
2025-07-22 16:12:42 +08:00
|
|
|
|
private async Task<DateTime> GetNetworkTimeAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
const string ntpServer = "ntp.ntsc.ac.cn";
|
|
|
|
|
|
var ntpData = new byte[48];
|
|
|
|
|
|
ntpData[0] = 0x1B;
|
|
|
|
|
|
var addresses = await Dns.GetHostAddressesAsync(ntpServer);
|
|
|
|
|
|
var ipEndPoint = new IPEndPoint(addresses[0], 123);
|
|
|
|
|
|
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
|
|
|
|
|
|
{
|
2025-10-03 17:08:46 +08:00
|
|
|
|
socket.ReceiveTimeout = 5000;
|
2025-07-22 16:12:42 +08:00
|
|
|
|
socket.Connect(ipEndPoint);
|
|
|
|
|
|
await Task.Factory.FromAsync(socket.BeginSend(ntpData, 0, ntpData.Length, SocketFlags.None, null, socket), socket.EndSend);
|
|
|
|
|
|
await Task.Factory.FromAsync(socket.BeginReceive(ntpData, 0, ntpData.Length, SocketFlags.None, null, socket), socket.EndReceive);
|
|
|
|
|
|
}
|
|
|
|
|
|
const byte serverReplyTime = 40;
|
|
|
|
|
|
ulong intPart = BitConverter.ToUInt32(ntpData.Skip(serverReplyTime).Take(4).Reverse().ToArray(), 0);
|
|
|
|
|
|
ulong fractPart = BitConverter.ToUInt32(ntpData.Skip(serverReplyTime + 4).Take(4).Reverse().ToArray(), 0);
|
|
|
|
|
|
var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
|
|
|
|
|
|
var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds);
|
|
|
|
|
|
return networkDateTime.ToLocalTime();
|
|
|
|
|
|
}
|
2025-09-20 21:25:30 +08:00
|
|
|
|
catch (Exception)
|
2025-07-22 16:12:42 +08:00
|
|
|
|
{
|
|
|
|
|
|
return DateTime.Now;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化所有定时器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 初始化以下定时器:
|
|
|
|
|
|
/// 1. timerKillProcess: 进程终止定时器,每2秒执行一次
|
|
|
|
|
|
/// 2. _unifiedMainWindowTimer: 统一的主窗口定时器,每500毫秒执行一次
|
|
|
|
|
|
/// 3. timerCheckAutoUpdateWithSilence: 静默更新检查定时器,每10分钟执行一次
|
|
|
|
|
|
/// 4. timerCheckAutoUpdateRetry: 更新检查重试定时器,每10分钟执行一次
|
|
|
|
|
|
/// 5. timerDisplayTime: 时间显示定时器,每秒执行一次
|
|
|
|
|
|
/// 6. timerDisplayDate: 日期显示定时器,每小时执行一次
|
|
|
|
|
|
/// 7. timerNtpSync: NTP时间同步定时器,每2小时执行一次
|
|
|
|
|
|
/// 同时初始化定时保存墨迹定时器
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void InitTimers()
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
timerKillProcess.Elapsed += TimerKillProcess_Elapsed;
|
|
|
|
|
|
timerKillProcess.Interval = 2000;
|
2026-02-13 09:29:24 +08:00
|
|
|
|
_unifiedMainWindowTimer = new Timer(500);
|
|
|
|
|
|
_unifiedMainWindowTimer.Elapsed += OnUnifiedMainWindowTimerElapsed;
|
|
|
|
|
|
_unifiedMainWindowTimer.AutoReset = true;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
timerCheckAutoUpdateWithSilence.Elapsed += timerCheckAutoUpdateWithSilence_Elapsed;
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Interval = 1000 * 60 * 10;
|
2025-10-04 14:47:53 +08:00
|
|
|
|
timerCheckAutoUpdateRetry.Elapsed += timerCheckAutoUpdateRetry_Elapsed;
|
2025-10-06 18:29:12 +08:00
|
|
|
|
timerCheckAutoUpdateRetry.Interval = 1000 * 60 * 10;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
WaterMarkTime.DataContext = nowTimeVM;
|
|
|
|
|
|
WaterMarkDate.DataContext = nowTimeVM;
|
2025-09-20 19:36:00 +08:00
|
|
|
|
timerDisplayTime.Elapsed += TimerDisplayTime_Elapsed;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
timerDisplayTime.Interval = 1000;
|
|
|
|
|
|
timerDisplayTime.Start();
|
|
|
|
|
|
timerDisplayDate.Elapsed += TimerDisplayDate_Elapsed;
|
|
|
|
|
|
timerDisplayDate.Interval = 1000 * 60 * 60 * 1;
|
|
|
|
|
|
timerDisplayDate.Start();
|
2025-09-13 10:58:43 +08:00
|
|
|
|
timerNtpSync.Elapsed += async (s, e) => await TimerNtpSync_ElapsedAsync();
|
|
|
|
|
|
timerNtpSync.Interval = 1000 * 60 * 60 * 2; // 每2小时同步一次
|
|
|
|
|
|
timerNtpSync.Start();
|
2025-05-25 09:29:48 +08:00
|
|
|
|
timerKillProcess.Start();
|
2025-08-01 02:56:24 +08:00
|
|
|
|
nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd");
|
|
|
|
|
|
nowTimeVM.nowTime = DateTime.Now.ToString("tt hh'时'mm'分'ss'秒'");
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 程序启动时立即进行一次NTP同步
|
|
|
|
|
|
Task.Run(async () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await TimerNtpSync_ElapsedAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"程序启动时NTP同步失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-11-01 20:42:18 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化定时保存墨迹定时器
|
|
|
|
|
|
InitAutoSaveStrokesTimer();
|
2026-02-22 11:25:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化橡皮擦自动切换回批注模式计时器
|
|
|
|
|
|
InitEraserAutoSwitchBackTimer();
|
2025-11-01 20:42:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 统一主窗口定时器事件处理方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件发送者</param>
|
|
|
|
|
|
/// <param name="e">事件参数</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 调用timerCheckAutoFold_Elapsed方法处理自动收纳逻辑
|
|
|
|
|
|
/// </remarks>
|
2026-02-13 09:29:24 +08:00
|
|
|
|
private void OnUnifiedMainWindowTimerElapsed(object sender, ElapsedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
timerCheckAutoFold_Elapsed(sender, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化定时保存墨迹定时器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 初始化DispatcherTimer实例并绑定AutoSaveStrokesTimer_Tick事件处理方法
|
|
|
|
|
|
/// 然后调用UpdateAutoSaveStrokesTimer方法根据设置更新定时器状态
|
|
|
|
|
|
/// </remarks>
|
2025-11-01 20:42:18 +08:00
|
|
|
|
private void InitAutoSaveStrokesTimer()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (autoSaveStrokesTimer == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
autoSaveStrokesTimer = new DispatcherTimer();
|
|
|
|
|
|
autoSaveStrokesTimer.Tick += AutoSaveStrokesTimer_Tick;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据设置更新定时器间隔和启动状态
|
|
|
|
|
|
UpdateAutoSaveStrokesTimer();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新定时保存墨迹定时器状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 根据Settings.Automation.IsEnableAutoSaveStrokes设置决定是否启用定时器
|
|
|
|
|
|
/// 如果启用,则根据Settings.Automation.AutoSaveStrokesIntervalMinutes设置定时器间隔
|
|
|
|
|
|
/// 最小间隔为1分钟
|
|
|
|
|
|
/// </remarks>
|
2026-04-26 09:12:26 +08:00
|
|
|
|
public void UpdateAutoSaveStrokesTimer()
|
2025-11-01 20:42:18 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (autoSaveStrokesTimer == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
autoSaveStrokesTimer.Stop();
|
|
|
|
|
|
|
|
|
|
|
|
if (Settings.Automation.IsEnableAutoSaveStrokes)
|
|
|
|
|
|
{
|
|
|
|
|
|
int intervalMinutes = Settings.Automation.AutoSaveStrokesIntervalMinutes;
|
|
|
|
|
|
if (intervalMinutes < 1) intervalMinutes = 1; // 最小间隔1分钟
|
|
|
|
|
|
autoSaveStrokesTimer.Interval = TimeSpan.FromMinutes(intervalMinutes);
|
|
|
|
|
|
autoSaveStrokesTimer.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 定时保存墨迹定时器事件处理方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件发送者</param>
|
|
|
|
|
|
/// <param name="e">事件参数</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 当定时器触发时,检查画布是否可见且有墨迹
|
|
|
|
|
|
/// 如果满足条件,则调用SaveInkCanvasStrokes方法进行静默保存
|
|
|
|
|
|
/// </remarks>
|
2025-11-01 20:42:18 +08:00
|
|
|
|
private void AutoSaveStrokesTimer_Tick(object sender, EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 只有在画布可见且有墨迹时才保存
|
|
|
|
|
|
if (inkCanvas.Visibility == Visibility.Visible && inkCanvas.Strokes.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 静默保存
|
|
|
|
|
|
SaveInkCanvasStrokes(false, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// NTP同步定时器事件处理方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>异步任务</returns>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 异步执行NTP时间同步,包括以下步骤:
|
|
|
|
|
|
/// 1. 防止重复同步(使用isNtpSyncing标志)
|
|
|
|
|
|
/// 2. 添加10秒超时机制
|
|
|
|
|
|
/// 3. 调用GetNetworkTimeAsync获取网络时间
|
|
|
|
|
|
/// 4. 计算网络时间与本地时间的偏移量
|
|
|
|
|
|
/// 5. 如果时间差超过3分钟,则使用网络时间
|
|
|
|
|
|
/// 6. 处理异常情况,确保即使同步失败也能恢复到使用本地时间
|
|
|
|
|
|
/// </remarks>
|
2025-09-13 10:58:43 +08:00
|
|
|
|
private async Task TimerNtpSync_ElapsedAsync()
|
2025-07-22 16:12:42 +08:00
|
|
|
|
{
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 防止重复同步
|
|
|
|
|
|
if (isNtpSyncing) return;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
isNtpSyncing = true;
|
2025-09-06 15:09:34 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 添加超时机制,最多等待10秒
|
|
|
|
|
|
var timeoutTask = Task.Delay(10000);
|
|
|
|
|
|
var ntpTask = GetNetworkTimeAsync();
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
var completedTask = await Task.WhenAny(ntpTask, timeoutTask);
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
if (completedTask == timeoutTask)
|
|
|
|
|
|
{
|
|
|
|
|
|
cachedNetworkTime = DateTime.Now;
|
|
|
|
|
|
lastNtpSyncTime = DateTime.Now;
|
|
|
|
|
|
useNetworkTime = false;
|
|
|
|
|
|
networkTimeOffset = TimeSpan.Zero;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
DateTime networkTime = await ntpTask;
|
|
|
|
|
|
DateTime localTime = DateTime.Now;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-13 10:58:43 +08:00
|
|
|
|
cachedNetworkTime = networkTime;
|
2025-09-20 19:36:00 +08:00
|
|
|
|
lastNtpSyncTime = localTime;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 计算网络时间与本地时间的偏移量
|
|
|
|
|
|
networkTimeOffset = networkTime - localTime;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 如果时间差超过3分钟,则使用网络时间
|
|
|
|
|
|
useNetworkTime = Math.Abs(networkTimeOffset.TotalMinutes) > 3.0;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
|
|
|
|
|
}
|
2025-09-20 19:36:00 +08:00
|
|
|
|
catch (Exception ex)
|
2025-09-06 15:09:34 +08:00
|
|
|
|
{
|
2025-09-13 10:58:43 +08:00
|
|
|
|
// NTP同步失败时,保持使用本地时间
|
|
|
|
|
|
cachedNetworkTime = DateTime.Now;
|
2025-09-18 17:50:47 +08:00
|
|
|
|
lastNtpSyncTime = DateTime.Now;
|
2025-09-20 19:36:00 +08:00
|
|
|
|
useNetworkTime = false;
|
|
|
|
|
|
networkTimeOffset = TimeSpan.Zero;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"NTP同步失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
isNtpSyncing = false;
|
2025-09-06 15:09:34 +08:00
|
|
|
|
}
|
2025-09-13 10:58:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 优化后的时间显示方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件发送者</param>
|
|
|
|
|
|
/// <param name="e">事件参数</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 处理时间显示逻辑,包括以下步骤:
|
|
|
|
|
|
/// 1. 获取当前本地时间
|
|
|
|
|
|
/// 2. 检测系统时间是否发生重大跳跃(超过3分钟),如果是则触发NTP同步
|
|
|
|
|
|
/// 3. 如果启用网络时间且偏移量已计算,则应用偏移量
|
|
|
|
|
|
/// 4. 格式化时间字符串
|
|
|
|
|
|
/// 5. 只有当时间字符串发生变化时才更新UI,避免不必要的UI刷新
|
|
|
|
|
|
/// 6. 使用BeginInvoke异步更新UI,避免阻塞
|
|
|
|
|
|
/// </remarks>
|
2025-09-20 19:36:00 +08:00
|
|
|
|
private void TimerDisplayTime_Elapsed(object sender, ElapsedEventArgs e)
|
2025-09-13 10:58:43 +08:00
|
|
|
|
{
|
|
|
|
|
|
DateTime localTime = DateTime.Now;
|
|
|
|
|
|
DateTime displayTime = localTime; // 默认使用本地时间
|
|
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 检测系统时间是否发生重大跳跃(超过2分钟)
|
|
|
|
|
|
TimeSpan timeJump = localTime - lastLocalTime;
|
|
|
|
|
|
double timeJumpMinutes = Math.Abs(timeJump.TotalMinutes);
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
if (timeJumpMinutes > 3 && !isNtpSyncing)
|
2025-09-13 10:58:43 +08:00
|
|
|
|
{
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 系统时间发生重大变化(超过3分钟),立即触发NTP同步
|
|
|
|
|
|
// 使用异步方式触发NTP同步,避免阻塞主线程
|
|
|
|
|
|
Task.Run(async () =>
|
2025-09-13 10:58:43 +08:00
|
|
|
|
{
|
2025-09-20 19:36:00 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await TimerNtpSync_ElapsedAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"时间跳跃触发的NTP同步失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-09-13 10:58:43 +08:00
|
|
|
|
}
|
2025-09-20 19:36:00 +08:00
|
|
|
|
lastLocalTime = localTime;
|
2025-09-13 10:58:43 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 如果启用网络时间且偏移量已计算,则应用偏移量
|
|
|
|
|
|
if (useNetworkTime && networkTimeOffset != TimeSpan.Zero)
|
|
|
|
|
|
{
|
|
|
|
|
|
displayTime = localTime + networkTimeOffset;
|
|
|
|
|
|
}
|
2025-09-13 10:58:43 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 格式化时间字符串
|
|
|
|
|
|
string timeString = displayTime.ToString("tt hh'时'mm'分'ss'秒'");
|
2025-09-07 13:30:46 +08:00
|
|
|
|
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 只有当时间字符串发生变化时才更新UI,避免不必要的UI刷新
|
|
|
|
|
|
if (timeString != lastDisplayedTime)
|
2025-07-22 16:12:42 +08:00
|
|
|
|
{
|
2025-09-20 19:36:00 +08:00
|
|
|
|
lastDisplayedTime = timeString;
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 19:36:00 +08:00
|
|
|
|
// 使用BeginInvoke异步更新UI,避免阻塞
|
|
|
|
|
|
Dispatcher.BeginInvoke(new Action(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
nowTimeVM.nowTime = timeString;
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 日期显示定时器事件处理方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件发送者</param>
|
|
|
|
|
|
/// <param name="e">事件参数</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 更新nowTimeVM的nowDate属性,设置为当前日期的格式化字符串
|
|
|
|
|
|
/// 格式为:yyyy年MM月dd日 星期几
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void TimerDisplayDate_Elapsed(object sender, ElapsedEventArgs e)
|
|
|
|
|
|
{
|
2026-02-22 10:14:12 +08:00
|
|
|
|
// 使用BeginInvoke异步更新UI,避免阻塞
|
|
|
|
|
|
Dispatcher.BeginInvoke(new Action(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd");
|
|
|
|
|
|
}));
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 进程终止定时器事件处理方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件发送者</param>
|
|
|
|
|
|
/// <param name="e">事件参数</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 根据设置终止指定的进程,包括PPTService、EasiNote、HiteAnnotation等
|
|
|
|
|
|
/// 对于每个终止的进程,会显示相应的通知
|
|
|
|
|
|
/// 对于HiteAnnotation进程,还会根据设置决定是否自动进入批注状态
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void TimerKillProcess_Elapsed(object sender, ElapsedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
// 希沃相关: easinote swenserver RemoteProcess EasiNote.MediaHttpService smartnote.cloud EasiUpdate smartnote EasiUpdate3 EasiUpdate3Protect SeewoP2P CefSharp.BrowserSubprocess SeewoUploadService
|
|
|
|
|
|
var arg = "/F";
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (Settings.Automation.IsAutoKillPptService)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
var processes = Process.GetProcessesByName("PPTService");
|
|
|
|
|
|
if (processes.Length > 0) arg += " /IM PPTService.exe";
|
|
|
|
|
|
processes = Process.GetProcessesByName("SeewoIwbAssistant");
|
|
|
|
|
|
if (processes.Length > 0) arg += " /IM SeewoIwbAssistant.exe" + " /IM Sia.Guard.exe";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (Settings.Automation.IsAutoKillEasiNote)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
var processes = Process.GetProcessesByName("EasiNote");
|
|
|
|
|
|
if (processes.Length > 0) arg += " /IM EasiNote.exe";
|
2025-10-01 18:55:03 +08:00
|
|
|
|
var seewoStartProcesses = Process.GetProcessesByName("SeewoStart");
|
|
|
|
|
|
if (seewoStartProcesses.Length > 0) arg += " /IM SeewoStart.exe";
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (Settings.Automation.IsAutoKillHiteAnnotation)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
var processes = Process.GetProcessesByName("HiteAnnotation");
|
|
|
|
|
|
if (processes.Length > 0) arg += " /IM HiteAnnotation.exe";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Settings.Automation.IsAutoKillVComYouJiao)
|
|
|
|
|
|
{
|
|
|
|
|
|
var processes = Process.GetProcessesByName("VcomTeach");
|
|
|
|
|
|
if (processes.Length > 0) arg += " /IM VcomTeach.exe" + " /IM VcomDaemon.exe" + " /IM VcomRender.exe";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (Settings.Automation.IsAutoKillICA)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
var processesAnnotation = Process.GetProcessesByName("Ink Canvas Annotation");
|
|
|
|
|
|
var processesArtistry = Process.GetProcessesByName("Ink Canvas Artistry");
|
|
|
|
|
|
if (processesAnnotation.Length > 0) arg += " /IM \"Ink Canvas Annotation.exe\"";
|
|
|
|
|
|
if (processesArtistry.Length > 0) arg += " /IM \"Ink Canvas Artistry.exe\"";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (Settings.Automation.IsAutoKillInkCanvas)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
var processes = Process.GetProcessesByName("Ink Canvas");
|
|
|
|
|
|
if (processes.Length > 0) arg += " /IM \"Ink Canvas.exe\"";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (Settings.Automation.IsAutoKillIDT)
|
|
|
|
|
|
{
|
2025-06-08 10:12:35 +08:00
|
|
|
|
var processes = Process.GetProcessesByName("Inkeys");
|
|
|
|
|
|
if (processes.Length > 0) arg += " /IM \"Inkeys.exe\"";
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (Settings.Automation.IsAutoKillSeewoLauncher2DesktopAnnotation)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
//由于希沃桌面2.0提供的桌面批注是64位应用程序,32位程序无法访问,目前暂不做精准匹配,只匹配进程名称,后面会考虑封装一套基于P/Invoke和WMI的综合进程识别方案。
|
|
|
|
|
|
var processes = Process.GetProcessesByName("DesktopAnnotation");
|
|
|
|
|
|
if (processes.Length > 0) arg += " /IM DesktopAnnotation.exe";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (arg != "/F")
|
|
|
|
|
|
{
|
2026-02-22 13:57:56 +08:00
|
|
|
|
using (var p = new Process())
|
|
|
|
|
|
{
|
|
|
|
|
|
p.StartInfo = new ProcessStartInfo("taskkill", arg);
|
|
|
|
|
|
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
|
|
|
|
|
p.Start();
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (arg.Contains("EasiNote"))
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
ShowNotification("“希沃白板 5”已自动关闭");
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (arg.Contains("HiteAnnotation"))
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
ShowNotification("“鸿合屏幕书写”已自动关闭");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (Settings.Automation.IsAutoKillHiteAnnotation && Settings.Automation.IsAutoEnterAnnotationAfterKillHite)
|
|
|
|
|
|
{
|
2025-09-06 22:22:41 +08:00
|
|
|
|
// 检查是否处于收纳状态,如果是则先展开浮动栏
|
|
|
|
|
|
if (isFloatingBarFolded)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 先展开浮动栏,然后进入批注状态
|
|
|
|
|
|
// UnFoldFloatingBar 方法内部会根据设置自动进入批注模式
|
|
|
|
|
|
UnFoldFloatingBar(null);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果已经展开,直接进入批注状态
|
|
|
|
|
|
PenIcon_Click(null, null);
|
|
|
|
|
|
}
|
2025-07-21 17:28:52 +08:00
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (arg.Contains("Ink Canvas Annotation") || arg.Contains("Ink Canvas Artistry"))
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
ShowNewMessage("“ICA”已自动关闭");
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (arg.Contains("\"Ink Canvas.exe\""))
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
ShowNotification("“Ink Canvas”已自动关闭");
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (arg.Contains("Inkeys"))
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-06-08 10:12:35 +08:00
|
|
|
|
ShowNotification("“智绘教Inkeys”已自动关闭");
|
2025-05-25 09:29:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (arg.Contains("VcomTeach"))
|
|
|
|
|
|
{
|
2025-08-03 16:46:33 +08:00
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
ShowNotification("“优教授课端”已自动关闭");
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (arg.Contains("DesktopAnnotation"))
|
|
|
|
|
|
{
|
2025-08-03 16:46:33 +08:00
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-06-08 10:12:35 +08:00
|
|
|
|
ShowNotification("“希沃桌面2.0 桌面批注”已自动关闭");
|
2025-05-25 09:29:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-28 14:40:44 +08:00
|
|
|
|
private bool foldFloatingBarByUser, // 保持收纳操作不受自动收纳的控制
|
|
|
|
|
|
unfoldFloatingBarByUser; // 允许用户在希沃软件内进行展开操作
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2025-09-06 15:16:03 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检测是否为批注窗口(窗口标题为空且高度小于500像素)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>如果是批注窗口返回true,否则返回false</returns>
|
|
|
|
|
|
private bool IsAnnotationWindow()
|
|
|
|
|
|
{
|
|
|
|
|
|
var windowTitle = ForegroundWindowInfo.WindowTitle();
|
|
|
|
|
|
var windowRect = ForegroundWindowInfo.WindowRect();
|
2025-09-07 01:50:44 +08:00
|
|
|
|
var windowProcessName = ForegroundWindowInfo.ProcessName();
|
2025-09-07 13:30:46 +08:00
|
|
|
|
|
2025-09-07 01:50:44 +08:00
|
|
|
|
// 检测希沃白板五的批注面板
|
|
|
|
|
|
// 希沃白板五的批注面板通常具有以下特征:
|
|
|
|
|
|
// 1. 窗口标题为空或包含特定关键词
|
|
|
|
|
|
// 2. 窗口高度较小(批注工具栏)
|
|
|
|
|
|
// 3. 窗口宽度适中(工具栏宽度)
|
|
|
|
|
|
if (windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher")
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检测希沃白板五的批注工具栏
|
|
|
|
|
|
// 批注工具栏通常高度在50-200像素之间,宽度在200-800像素之间
|
2025-09-07 13:30:46 +08:00
|
|
|
|
if (windowRect.Height >= 50 && windowRect.Height <= 200 &&
|
2025-09-07 01:50:44 +08:00
|
|
|
|
windowRect.Width >= 200 && windowRect.Width <= 800)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-09-07 13:30:46 +08:00
|
|
|
|
|
2025-09-07 01:50:44 +08:00
|
|
|
|
// 检测希沃白板五的二级菜单面板
|
|
|
|
|
|
// 二级菜单面板通常高度在100-400像素之间,宽度在150-400像素之间
|
2025-09-07 13:30:46 +08:00
|
|
|
|
if (windowRect.Height >= 100 && windowRect.Height <= 400 &&
|
2025-09-07 01:50:44 +08:00
|
|
|
|
windowRect.Width >= 150 && windowRect.Width <= 400)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-07 13:30:46 +08:00
|
|
|
|
|
2025-09-07 01:50:44 +08:00
|
|
|
|
// 检测鸿合软件的批注面板
|
|
|
|
|
|
if (windowProcessName == "HiteCamera" || windowProcessName == "HiteTouchPro" || windowProcessName == "HiteLightBoard")
|
|
|
|
|
|
{
|
|
|
|
|
|
// 鸿合软件的批注面板特征
|
2025-09-07 13:30:46 +08:00
|
|
|
|
if (windowRect.Height >= 50 && windowRect.Height <= 300 &&
|
2025-09-07 01:50:44 +08:00
|
|
|
|
windowRect.Width >= 200 && windowRect.Width <= 600)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-07 13:30:46 +08:00
|
|
|
|
|
2025-09-07 01:50:44 +08:00
|
|
|
|
// 原有的检测逻辑(保持向后兼容)
|
2025-09-06 15:16:03 +08:00
|
|
|
|
return windowTitle.Length == 0 && windowRect.Height < 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 16:46:37 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查是否存在应当被收纳应用的全屏窗口
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>如果存在应当被收纳应用的全屏窗口返回true,否则返回false</returns>
|
|
|
|
|
|
private bool HasFullScreenWindowOfAutoFoldApps()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_windowOverviewModel == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var fullScreenWindows = _windowOverviewModel.GetFullScreenWindows();
|
|
|
|
|
|
if (fullScreenWindows == null || fullScreenWindows.Count == 0) return false;
|
|
|
|
|
|
|
2026-04-26 16:38:54 +08:00
|
|
|
|
var foregroundHandle = ForegroundWindowInfo.GetForegroundWindowHandle();
|
|
|
|
|
|
|
2026-01-17 16:46:37 +08:00
|
|
|
|
foreach (var window in fullScreenWindows)
|
|
|
|
|
|
{
|
|
|
|
|
|
var windowProcessName = window.ProcessName;
|
|
|
|
|
|
|
|
|
|
|
|
if (windowProcessName == "EasiNote")
|
|
|
|
|
|
{
|
|
|
|
|
|
if (window.ProcessPath != "Unknown")
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var versionInfo = FileVersionInfo.GetVersionInfo(window.ProcessPath);
|
|
|
|
|
|
string version = versionInfo.FileVersion;
|
|
|
|
|
|
string prodName = versionInfo.ProductName;
|
|
|
|
|
|
|
2026-04-26 16:38:54 +08:00
|
|
|
|
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote &&
|
|
|
|
|
|
window.Handle == foregroundHandle)
|
2026-01-17 16:46:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-04-26 16:38:54 +08:00
|
|
|
|
else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3 &&
|
|
|
|
|
|
window.Handle == foregroundHandle)
|
2026-01-17 16:46:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-04-26 16:38:54 +08:00
|
|
|
|
else if (prodName.Contains("3C") && Settings.Automation.IsAutoFoldInEasiNote3C &&
|
|
|
|
|
|
window.Handle == foregroundHandle)
|
2026-01-17 16:46:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2026-01-17 16:46:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInEasiCamera && windowProcessName == "EasiCamera")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInEasiNote5C && windowProcessName == "EasiNote5C")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-03-03 16:04:20 +08:00
|
|
|
|
else if (Settings.Automation.IsAutoFoldInSeewoPincoTeacher &&
|
2026-01-17 16:46:37 +08:00
|
|
|
|
(windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher"))
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInHiteCamera && windowProcessName == "HiteCamera")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInHiteTouchPro && windowProcessName == "HiteTouchPro")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInWxBoardMain && windowProcessName == "WxBoardMain")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-03-03 16:04:20 +08:00
|
|
|
|
else if (Settings.Automation.IsAutoFoldInMSWhiteboard &&
|
2026-01-17 16:46:37 +08:00
|
|
|
|
(windowProcessName == "MicrosoftWhiteboard" || windowProcessName == "msedgewebview2"))
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInHiteLightBoard && windowProcessName == "HiteLightBoard")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInAdmoxWhiteboard && windowProcessName == "Amdox.WhiteBoard")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInAdmoxBooth && windowProcessName == "Amdox.Booth")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInQPoint && windowProcessName == "QPoint")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInYiYunVisualPresenter && windowProcessName == "YiYunVisualPresenter")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInMaxHubWhiteboard && windowProcessName == "WhiteBoard")
|
|
|
|
|
|
{
|
|
|
|
|
|
if (window.ProcessPath != "Unknown")
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var versionInfo = FileVersionInfo.GetVersionInfo(window.ProcessPath);
|
|
|
|
|
|
var version = versionInfo.FileVersion;
|
|
|
|
|
|
var prodName = versionInfo.ProductName;
|
|
|
|
|
|
if (version.StartsWith("6.") && prodName == "WhiteBoard")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2026-01-17 16:46:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Settings.Automation.IsAutoFoldInOldZyBoard &&
|
|
|
|
|
|
(WinTabWindowsChecker.IsWindowExisted("WhiteBoard - DrawingWindow") ||
|
|
|
|
|
|
WinTabWindowsChecker.IsWindowExisted("InstantAnnotationWindow")))
|
|
|
|
|
|
{
|
|
|
|
|
|
var oldZyWindows = _windowOverviewModel.Windows.Where(w =>
|
|
|
|
|
|
(w.Title.Contains("WhiteBoard - DrawingWindow") || w.Title.Contains("InstantAnnotationWindow")) &&
|
|
|
|
|
|
w.IsFullScreen).ToList();
|
|
|
|
|
|
if (oldZyWindows.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"检查全屏窗口失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 09:28:00 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 使用窗口预览模型检测前台窗口是否符合自动收纳要求(仅用于检测,不执行任何操作)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>如果符合自动收纳要求返回true,否则返回false</returns>
|
|
|
|
|
|
private bool CheckShouldAutoFoldByWindowPreview()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_windowOverviewModel == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var windows = _windowOverviewModel.Windows;
|
|
|
|
|
|
if (windows == null || windows.Count == 0) return false;
|
|
|
|
|
|
|
2026-04-26 16:38:54 +08:00
|
|
|
|
var foregroundHandle = ForegroundWindowInfo.GetForegroundWindowHandle();
|
|
|
|
|
|
var foregroundWindow = windows.FirstOrDefault(w => w.Handle == foregroundHandle);
|
2026-01-02 09:28:00 +08:00
|
|
|
|
if (foregroundWindow == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
var windowProcessName = foregroundWindow.ProcessName;
|
|
|
|
|
|
|
|
|
|
|
|
if (windowProcessName == "EasiNote")
|
|
|
|
|
|
{
|
|
|
|
|
|
if (foregroundWindow.ProcessPath != "Unknown")
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var versionInfo = FileVersionInfo.GetVersionInfo(foregroundWindow.ProcessPath);
|
|
|
|
|
|
string version = versionInfo.FileVersion;
|
|
|
|
|
|
string prodName = versionInfo.ProductName;
|
|
|
|
|
|
|
2026-04-26 16:38:54 +08:00
|
|
|
|
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote &&
|
|
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
2026-04-26 16:38:54 +08:00
|
|
|
|
return true;
|
2026-01-02 09:28:00 +08:00
|
|
|
|
}
|
2026-04-26 16:38:54 +08:00
|
|
|
|
else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3 &&
|
|
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (prodName.Contains("3C") && Settings.Automation.IsAutoFoldInEasiNote3C &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2026-01-02 09:28:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInEasiCamera && windowProcessName == "EasiCamera" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInEasiNote5C && windowProcessName == "EasiNote5C" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-03-03 16:04:20 +08:00
|
|
|
|
else if (Settings.Automation.IsAutoFoldInSeewoPincoTeacher &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
(windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher") &&
|
|
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInHiteCamera && windowProcessName == "HiteCamera" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInHiteTouchPro && windowProcessName == "HiteTouchPro" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInWxBoardMain && windowProcessName == "WxBoardMain" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-03-03 16:04:20 +08:00
|
|
|
|
else if (Settings.Automation.IsAutoFoldInMSWhiteboard &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
(windowProcessName == "MicrosoftWhiteboard" || windowProcessName == "msedgewebview2") &&
|
|
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInOldZyBoard &&
|
|
|
|
|
|
(WinTabWindowsChecker.IsWindowExisted("WhiteBoard - DrawingWindow") ||
|
2026-04-26 16:38:54 +08:00
|
|
|
|
WinTabWindowsChecker.IsWindowExisted("InstantAnnotationWindow")) &&
|
|
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInHiteLightBoard && windowProcessName == "HiteLightBoard" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInAdmoxWhiteboard && windowProcessName == "Amdox.WhiteBoard" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInAdmoxBooth && windowProcessName == "Amdox.Booth" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInQPoint && windowProcessName == "QPoint" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInYiYunVisualPresenter && windowProcessName == "YiYunVisualPresenter" &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.Automation.IsAutoFoldInMaxHubWhiteboard && windowProcessName == "WhiteBoard" &&
|
|
|
|
|
|
WinTabWindowsChecker.IsWindowExisted("白板书写") &&
|
2026-04-26 16:38:54 +08:00
|
|
|
|
foregroundWindow.IsFullScreen)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (foregroundWindow.ProcessPath != "Unknown")
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var versionInfo = FileVersionInfo.GetVersionInfo(foregroundWindow.ProcessPath);
|
|
|
|
|
|
var version = versionInfo.FileVersion;
|
|
|
|
|
|
var prodName = versionInfo.ProductName;
|
|
|
|
|
|
if (version.StartsWith("6.") && prodName == "WhiteBoard")
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-21 16:51:34 +08:00
|
|
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
2026-01-02 09:28:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"窗口预览模型检测失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 自动收纳定时器事件处理方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件发送者</param>
|
|
|
|
|
|
/// <param name="e">事件参数</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 检查是否需要自动收纳浮动栏,包括以下逻辑:
|
|
|
|
|
|
/// 1. 检查是否有全屏窗口需要收纳
|
|
|
|
|
|
/// 2. 检查是否有应用程序需要自动收纳
|
|
|
|
|
|
/// 3. 对于EasiNote应用,根据版本和窗口类型决定是否收纳
|
|
|
|
|
|
/// 4. 对于其他应用程序,根据设置决定是否收纳
|
|
|
|
|
|
/// 5. 当没有需要收纳的应用程序时,根据设置决定是否展开浮动栏
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void timerCheckAutoFold_Elapsed(object sender, ElapsedEventArgs e)
|
|
|
|
|
|
{
|
2026-04-26 16:38:54 +08:00
|
|
|
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
if (isFloatingBarChangingHideMode) return;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-02-03 22:23:45 +08:00
|
|
|
|
bool hasFullScreen = HasFullScreenWindowOfAutoFoldApps();
|
2026-01-02 09:28:00 +08:00
|
|
|
|
bool shouldAutoFold = CheckShouldAutoFoldByWindowPreview();
|
2026-02-03 22:23:45 +08:00
|
|
|
|
|
2026-02-03 22:34:17 +08:00
|
|
|
|
Thickness currentMargin = new Thickness();
|
2026-04-26 16:38:54 +08:00
|
|
|
|
try
|
2026-03-03 16:04:20 +08:00
|
|
|
|
{
|
2026-04-26 16:38:54 +08:00
|
|
|
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
|
|
|
|
|
Dispatcher.Invoke(() => { currentMargin = ViewboxFloatingBar.Margin; });
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-03 16:04:20 +08:00
|
|
|
|
|
2026-02-03 22:34:17 +08:00
|
|
|
|
if (hasFullScreen)
|
|
|
|
|
|
{
|
2026-03-03 16:04:20 +08:00
|
|
|
|
if (!isFloatingBarFolded)
|
2026-02-03 22:23:45 +08:00
|
|
|
|
{
|
2026-02-03 22:34:17 +08:00
|
|
|
|
FoldFloatingBar_MouseUp(null, null);
|
2026-02-03 22:23:45 +08:00
|
|
|
|
}
|
2026-02-03 22:34:17 +08:00
|
|
|
|
else if (currentMargin.Left > -50 && !isFloatingBarChangingHideMode)
|
|
|
|
|
|
{
|
2026-03-03 16:04:20 +08:00
|
|
|
|
FoldFloatingBar_MouseUp(null, null);
|
2026-02-03 22:34:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-02-03 22:23:45 +08:00
|
|
|
|
|
2026-01-02 09:28:00 +08:00
|
|
|
|
if (shouldAutoFold)
|
2025-12-28 14:30:02 +08:00
|
|
|
|
{
|
2026-04-26 16:38:54 +08:00
|
|
|
|
if (!unfoldFloatingBarByUser && !isFloatingBarFolded)
|
2026-01-02 09:28:00 +08:00
|
|
|
|
{
|
2026-02-03 22:23:45 +08:00
|
|
|
|
FoldFloatingBar_MouseUp(null, null);
|
2026-01-02 09:28:00 +08:00
|
|
|
|
}
|
2025-12-28 14:30:02 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 22:23:45 +08:00
|
|
|
|
if (!WinTabWindowsChecker.IsWindowExisted("幻灯片放映", false))
|
2025-08-03 16:46:33 +08:00
|
|
|
|
{
|
2026-02-03 22:23:45 +08:00
|
|
|
|
if (isFloatingBarFolded && !foldFloatingBarByUser)
|
2025-08-03 16:46:33 +08:00
|
|
|
|
{
|
2026-02-03 22:23:45 +08:00
|
|
|
|
// 检查是否启用了软件退出后保持收纳模式
|
|
|
|
|
|
if (Settings.Automation.KeepFoldAfterSoftwareExit)
|
|
|
|
|
|
{
|
|
|
|
|
|
unfoldFloatingBarByUser = false;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
2026-04-30 14:29:06 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// schedule unfold if dispatcher still running
|
|
|
|
|
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
|
|
|
|
|
UnFoldFloatingBar_MouseUp(new object(), null);
|
|
|
|
|
|
unfoldFloatingBarByUser = false;
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-12 23:19:55 +08:00
|
|
|
|
catch (Exception)
|
2026-02-03 22:23:45 +08:00
|
|
|
|
{
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 静默更新检查定时器事件处理方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件发送者</param>
|
|
|
|
|
|
/// <param name="e">事件参数</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 处理静默更新的检查和安装逻辑,包括以下步骤:
|
|
|
|
|
|
/// 1. 停止计时器,避免重复触发
|
|
|
|
|
|
/// 2. 检查是否有可用的更新版本
|
|
|
|
|
|
/// 3. 检查是否启用了静默更新
|
|
|
|
|
|
/// 4. 检查更新文件是否已下载
|
|
|
|
|
|
/// 5. 如果未下载,尝试使用多线路组下载更新文件
|
|
|
|
|
|
/// 6. 检查是否在静默更新时间段内
|
|
|
|
|
|
/// 7. 检查应用程序状态,确保可以安全更新
|
|
|
|
|
|
/// 8. 如果可以安全更新,执行更新安装并关闭应用程序
|
|
|
|
|
|
/// 9. 如果不能安全更新,重新启动计时器,稍后再检查
|
|
|
|
|
|
/// 10. 处理异常情况,确保计时器能够重新启动
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void timerCheckAutoUpdateWithSilence_Elapsed(object sender, ElapsedEventArgs e)
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
// 停止计时器,避免重复触发
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Stop();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
// 检查是否有可用的更新
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (string.IsNullOrEmpty(AvailableLatestVersion))
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | No available update version found");
|
|
|
|
|
|
return;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-19 11:47:16 +08:00
|
|
|
|
// 检查是否启用了静默更新
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (!Settings.Startup.IsAutoUpdateWithSilence)
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Silent update is disabled");
|
|
|
|
|
|
return;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-19 11:47:16 +08:00
|
|
|
|
// 检查更新文件是否已下载
|
2026-03-28 20:20:44 +08:00
|
|
|
|
string statusFilePath = AutoUpdateHelper.GetUpdateDownloadStatusFilePath(AvailableLatestVersion);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
|
|
|
|
|
if (!File.Exists(statusFilePath) || File.ReadAllText(statusFilePath).Trim().ToLower() != "true")
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Update file not downloaded yet");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
// 尝试下载更新文件,使用多线路组下载功能
|
2025-08-03 16:46:33 +08:00
|
|
|
|
Task.Run(async () =>
|
|
|
|
|
|
{
|
2025-07-19 15:36:05 +08:00
|
|
|
|
bool isDownloadSuccessful = false;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
try
|
2025-07-19 15:36:05 +08:00
|
|
|
|
{
|
2025-07-19 16:03:45 +08:00
|
|
|
|
// 如果主要线路组可用,直接使用
|
|
|
|
|
|
if (AvailableLatestLineGroup != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 使用主要线路组下载: {AvailableLatestLineGroup.GroupName}");
|
|
|
|
|
|
isDownloadSuccessful = await AutoUpdateHelper.DownloadSetupFile(AvailableLatestVersion, AvailableLatestLineGroup);
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-19 16:03:45 +08:00
|
|
|
|
// 如果主要线路组不可用或下载失败,获取所有可用线路组
|
|
|
|
|
|
if (!isDownloadSuccessful)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | 主要线路组不可用或下载失败,获取所有可用线路组");
|
|
|
|
|
|
var availableGroups = await AutoUpdateHelper.GetAvailableLineGroupsOrdered(Settings.Startup.UpdateChannel);
|
|
|
|
|
|
if (availableGroups.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 使用 {availableGroups.Count} 个可用线路组进行下载");
|
|
|
|
|
|
isDownloadSuccessful = await AutoUpdateHelper.DownloadSetupFileWithFallback(AvailableLatestVersion, availableGroups);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-19 15:36:05 +08:00
|
|
|
|
}
|
2025-07-19 16:03:45 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | 下载更新时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
|
|
|
|
|
if (isDownloadSuccessful)
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Update downloaded successfully, will check again for installation");
|
|
|
|
|
|
// 重新启动计时器,下次检查时安装
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Start();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Failed to download update", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-19 11:47:16 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-19 11:47:16 +08:00
|
|
|
|
// 检查是否在静默更新时间段内
|
|
|
|
|
|
bool isInSilencePeriod = AutoUpdateWithSilenceTimeComboBox.CheckIsInSilencePeriod(
|
|
|
|
|
|
Settings.Startup.AutoUpdateWithSilenceStartTime,
|
|
|
|
|
|
Settings.Startup.AutoUpdateWithSilenceEndTime);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
|
|
|
|
|
if (!isInSilencePeriod)
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Not in silence update time period");
|
|
|
|
|
|
// 重新启动计时器,稍后再检查
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Start();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-29 11:56:38 +08:00
|
|
|
|
// 检查应用程序状态,确保可以安全更新
|
|
|
|
|
|
// 空闲状态的判定为不处于批注模式和画板模式
|
2025-06-19 11:47:16 +08:00
|
|
|
|
bool canSafelyUpdate = false;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2026-04-26 16:38:54 +08:00
|
|
|
|
try
|
2025-08-03 16:46:33 +08:00
|
|
|
|
{
|
2026-04-26 16:38:54 +08:00
|
|
|
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
|
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2026-04-30 14:29:06 +08:00
|
|
|
|
try
|
2025-08-03 16:46:33 +08:00
|
|
|
|
{
|
2026-04-30 14:29:06 +08:00
|
|
|
|
// 判断是否处于批注模式(inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
|
|
|
|
|
// 判断是否处于画板模式(!Topmost)
|
|
|
|
|
|
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink && Topmost)
|
2025-08-03 16:46:33 +08:00
|
|
|
|
{
|
2026-04-30 14:29:06 +08:00
|
|
|
|
// 检查是否有未保存的内容或正在进行的操作
|
|
|
|
|
|
if (!isHidingSubPanelsWhenInking)
|
|
|
|
|
|
{
|
|
|
|
|
|
canSafelyUpdate = true;
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Application is in a safe state for update - not in ink or board mode");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Application is currently performing operations");
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-04-30 14:29:06 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Application is in ink or board mode, cannot update now");
|
2025-06-19 11:47:16 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
2026-04-26 16:38:54 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Error checking application state: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Dispatcher not available
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
|
|
|
|
|
if (canSafelyUpdate)
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Installing update now");
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-19 11:47:16 +08:00
|
|
|
|
// 设置为用户主动退出,避免被看门狗判定为崩溃
|
|
|
|
|
|
App.IsAppExitByUser = true;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-19 11:47:16 +08:00
|
|
|
|
// 执行更新安装
|
2025-05-25 09:29:48 +08:00
|
|
|
|
AutoUpdateHelper.InstallNewVersionApp(AvailableLatestVersion, true);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-06-19 11:47:16 +08:00
|
|
|
|
// 关闭应用程序
|
2026-04-26 16:38:54 +08:00
|
|
|
|
try
|
2025-08-03 16:46:33 +08:00
|
|
|
|
{
|
2026-04-26 16:38:54 +08:00
|
|
|
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
|
|
|
|
|
Dispatcher.Invoke(() => { Application.Current.Shutdown(); });
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception) { }
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Cannot safely update now, will try again later");
|
|
|
|
|
|
// 重新启动计时器,稍后再检查
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Start();
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-06-19 11:47:16 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Error in silent update check: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
// 出错时重新启动计时器,稍后再检查
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Start();
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-04 14:47:53 +08:00
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查更新失败重试定时器事件处理方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件发送者</param>
|
|
|
|
|
|
/// <param name="e">事件参数</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 异步处理更新检查失败后的重试逻辑,包括以下步骤:
|
|
|
|
|
|
/// 1. 停止计时器,避免重复触发
|
|
|
|
|
|
/// 2. 检查是否启用了自动更新
|
|
|
|
|
|
/// 3. 增加重试计数
|
|
|
|
|
|
/// 4. 检查是否超过最大重试次数
|
|
|
|
|
|
/// 5. 清除之前的更新状态
|
|
|
|
|
|
/// 6. 使用当前选择的更新通道检查更新
|
|
|
|
|
|
/// 7. 如果检查成功,重置重试计数并停止重试定时器
|
|
|
|
|
|
/// 8. 如果检查失败,重新启动定时器,10分钟后再次尝试
|
|
|
|
|
|
/// 9. 处理异常情况,确保定时器能够重新启动
|
|
|
|
|
|
/// </remarks>
|
2025-10-04 14:47:53 +08:00
|
|
|
|
private async void timerCheckAutoUpdateRetry_Elapsed(object sender, ElapsedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 停止定时器,避免重复触发
|
|
|
|
|
|
timerCheckAutoUpdateRetry.Stop();
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查是否启用了自动更新
|
|
|
|
|
|
if (!Settings.Startup.IsAutoUpdate)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Auto update is disabled, stopping retry timer");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 增加重试计数
|
|
|
|
|
|
updateCheckRetryCount++;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Retry check attempt {updateCheckRetryCount}/{MAX_UPDATE_CHECK_RETRIES}");
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否超过最大重试次数
|
|
|
|
|
|
if (updateCheckRetryCount > MAX_UPDATE_CHECK_RETRIES)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Maximum retry attempts reached, stopping retry timer", LogHelper.LogType.Warning);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行更新检查
|
|
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Retrying update check after failure");
|
2025-10-06 18:29:12 +08:00
|
|
|
|
|
2025-10-04 14:47:53 +08:00
|
|
|
|
// 清除之前的更新状态
|
|
|
|
|
|
AvailableLatestVersion = null;
|
|
|
|
|
|
AvailableLatestLineGroup = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用当前选择的更新通道检查更新
|
|
|
|
|
|
var (remoteVersion, lineGroup, apiReleaseNotes) = await AutoUpdateHelper.CheckForUpdates(Settings.Startup.UpdateChannel);
|
|
|
|
|
|
AvailableLatestVersion = remoteVersion;
|
|
|
|
|
|
AvailableLatestLineGroup = lineGroup;
|
|
|
|
|
|
|
|
|
|
|
|
if (AvailableLatestVersion != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查更新成功,重置重试计数
|
|
|
|
|
|
updateCheckRetryCount = 0;
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Retry successful, found new version: {AvailableLatestVersion}");
|
2025-10-06 18:29:12 +08:00
|
|
|
|
|
2025-10-04 14:47:53 +08:00
|
|
|
|
// 停止重试定时器,因为已经找到了更新
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查更新仍然失败,继续重试
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Retry {updateCheckRetryCount} failed, will retry in 10 minutes");
|
2025-10-06 18:29:12 +08:00
|
|
|
|
|
2025-10-04 14:47:53 +08:00
|
|
|
|
// 重新启动定时器,10分钟后再次尝试
|
|
|
|
|
|
timerCheckAutoUpdateRetry.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Error in retry check: {ex.Message}", LogHelper.LogType.Error);
|
2025-10-06 18:29:12 +08:00
|
|
|
|
|
2025-10-04 14:47:53 +08:00
|
|
|
|
// 出错时也重新启动定时器,稍后再检查
|
|
|
|
|
|
if (updateCheckRetryCount <= MAX_UPDATE_CHECK_RETRIES)
|
|
|
|
|
|
{
|
|
|
|
|
|
timerCheckAutoUpdateRetry.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 重置更新检查重试状态方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 重置更新检查的重试状态,包括以下步骤:
|
|
|
|
|
|
/// 1. 停止重试定时器
|
|
|
|
|
|
/// 2. 重置重试计数为0
|
|
|
|
|
|
/// 3. 记录日志
|
|
|
|
|
|
/// 4. 处理异常情况
|
|
|
|
|
|
/// </remarks>
|
2025-10-04 14:47:53 +08:00
|
|
|
|
public void ResetUpdateCheckRetry()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 停止重试定时器
|
|
|
|
|
|
timerCheckAutoUpdateRetry.Stop();
|
2025-10-06 18:29:12 +08:00
|
|
|
|
|
2025-10-04 14:47:53 +08:00
|
|
|
|
// 重置重试计数
|
|
|
|
|
|
updateCheckRetryCount = 0;
|
2025-10-06 18:29:12 +08:00
|
|
|
|
|
2025-10-04 14:47:53 +08:00
|
|
|
|
LogHelper.WriteLogToFile("AutoUpdate | Update check retry state reset");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"AutoUpdate | Error resetting retry state: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-22 11:25:30 +08:00
|
|
|
|
|
2026-04-19 14:42:11 +08:00
|
|
|
|
public void StartSilentUpdateTimer()
|
|
|
|
|
|
{
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 11:25:30 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化橡皮擦自动切换回批注模式计时器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void InitEraserAutoSwitchBackTimer()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_eraserAutoSwitchBackTimer == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_eraserAutoSwitchBackTimer = new DispatcherTimer();
|
|
|
|
|
|
_eraserAutoSwitchBackTimer.Tick += EraserAutoSwitchBackTimer_Tick;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-26 16:38:54 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 在窗口关闭时停止并释放所有定时器与事件,防止在 Dispatcher 关闭期间还有后台线程调用 UI
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void StopAllTimersAndHandlers()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// Stop and detach System.Timers.Timer
|
|
|
|
|
|
if (_unifiedMainWindowTimer != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_unifiedMainWindowTimer.Stop();
|
|
|
|
|
|
_unifiedMainWindowTimer.Elapsed -= OnUnifiedMainWindowTimerElapsed;
|
|
|
|
|
|
_unifiedMainWindowTimer.Dispose();
|
|
|
|
|
|
_unifiedMainWindowTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timerKillProcess != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
timerKillProcess.Stop();
|
|
|
|
|
|
timerKillProcess.Elapsed -= TimerKillProcess_Elapsed;
|
|
|
|
|
|
timerKillProcess.Dispose();
|
|
|
|
|
|
timerKillProcess = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timerCheckAutoUpdateWithSilence != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Stop();
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Elapsed -= timerCheckAutoUpdateWithSilence_Elapsed;
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence.Dispose();
|
|
|
|
|
|
timerCheckAutoUpdateWithSilence = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timerCheckAutoUpdateRetry != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
timerCheckAutoUpdateRetry.Stop();
|
|
|
|
|
|
timerCheckAutoUpdateRetry.Elapsed -= timerCheckAutoUpdateRetry_Elapsed;
|
|
|
|
|
|
timerCheckAutoUpdateRetry.Dispose();
|
|
|
|
|
|
timerCheckAutoUpdateRetry = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timerDisplayTime != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
timerDisplayTime.Stop();
|
|
|
|
|
|
timerDisplayTime.Elapsed -= TimerDisplayTime_Elapsed;
|
|
|
|
|
|
timerDisplayTime.Dispose();
|
|
|
|
|
|
timerDisplayTime = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timerDisplayDate != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
timerDisplayDate.Stop();
|
|
|
|
|
|
timerDisplayDate.Elapsed -= TimerDisplayDate_Elapsed;
|
|
|
|
|
|
timerDisplayDate.Dispose();
|
|
|
|
|
|
timerDisplayDate = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (timerNtpSync != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
timerNtpSync.Stop();
|
|
|
|
|
|
timerNtpSync.Elapsed -= async (s, e) => await TimerNtpSync_ElapsedAsync();
|
|
|
|
|
|
timerNtpSync.Dispose();
|
|
|
|
|
|
timerNtpSync = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DispatcherTimers run on UI thread
|
|
|
|
|
|
if (autoSaveStrokesTimer != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
autoSaveStrokesTimer.Stop();
|
|
|
|
|
|
autoSaveStrokesTimer.Tick -= AutoSaveStrokesTimer_Tick;
|
|
|
|
|
|
autoSaveStrokesTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_eraserAutoSwitchBackTimer != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_eraserAutoSwitchBackTimer.Stop();
|
|
|
|
|
|
_eraserAutoSwitchBackTimer.Tick -= EraserAutoSwitchBackTimer_Tick;
|
|
|
|
|
|
_eraserAutoSwitchBackTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"StopAllTimers failed: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void OnClosing(CancelEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Stop timers and handlers to avoid background callbacks invoking Dispatcher after shutdown
|
|
|
|
|
|
StopAllTimersAndHandlers();
|
|
|
|
|
|
base.OnClosing(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 11:25:30 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 启动橡皮擦自动切换回批注模式计时器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void StartEraserAutoSwitchBackTimer()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Settings.Canvas.EnableEraserAutoSwitchBack) return;
|
|
|
|
|
|
if (_eraserAutoSwitchBackTimer == null) InitEraserAutoSwitchBackTimer();
|
|
|
|
|
|
|
|
|
|
|
|
// 停止之前的计时器
|
|
|
|
|
|
_eraserAutoSwitchBackTimer.Stop();
|
|
|
|
|
|
|
|
|
|
|
|
// 设置计时器间隔
|
|
|
|
|
|
int delaySeconds = Settings.Canvas.EraserAutoSwitchBackDelaySeconds;
|
|
|
|
|
|
if (delaySeconds < 1) delaySeconds = 10; // 最小1秒
|
|
|
|
|
|
_eraserAutoSwitchBackTimer.Interval = TimeSpan.FromSeconds(delaySeconds);
|
|
|
|
|
|
|
|
|
|
|
|
// 启动计时器
|
|
|
|
|
|
_eraserAutoSwitchBackTimer.Start();
|
|
|
|
|
|
|
|
|
|
|
|
LogHelper.WriteLogToFile($"橡皮擦自动切换计时器已启动,延迟 {delaySeconds} 秒", LogHelper.LogType.Trace);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"启动橡皮擦自动切换计时器失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 停止橡皮擦自动切换回批注模式计时器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void StopEraserAutoSwitchBackTimer()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_eraserAutoSwitchBackTimer != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_eraserAutoSwitchBackTimer.Stop();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"停止橡皮擦自动切换计时器失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 橡皮擦自动切换回批注模式计时器事件处理
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void EraserAutoSwitchBackTimer_Tick(object sender, EventArgs e)
|
|
|
|
|
|
{
|
2026-04-26 16:38:54 +08:00
|
|
|
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
2026-02-22 11:25:30 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查是否仍然在橡皮擦模式
|
2026-03-03 16:04:20 +08:00
|
|
|
|
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint &&
|
2026-02-22 11:25:30 +08:00
|
|
|
|
inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopEraserAutoSwitchBackTimer();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查设置是否仍然启用
|
|
|
|
|
|
if (!Settings.Canvas.EnableEraserAutoSwitchBack)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopEraserAutoSwitchBackTimer();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换到批注模式
|
2026-04-26 16:38:54 +08:00
|
|
|
|
try
|
2026-02-22 11:25:30 +08:00
|
|
|
|
{
|
2026-04-26 16:38:54 +08:00
|
|
|
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
|
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
PenIcon_Click(null, null);
|
|
|
|
|
|
StopEraserAutoSwitchBackTimer();
|
|
|
|
|
|
LogHelper.WriteLogToFile("橡皮擦自动切换回批注模式", LogHelper.LogType.Event);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception) { }
|
2026-02-22 11:25:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"橡皮擦自动切换计时器事件处理失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2026-03-21 16:13:42 +08:00
|
|
|
|
}
|