From 9b5ee56a091b9a19f9314b3924573c3fb9b4de98 Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Sun, 28 Dec 2025 14:30:02 +0800
Subject: [PATCH] improve:issue #175
---
Ink Canvas/Helpers/WindowOverviewModel.cs | 479 ++++++++++++++++++++++
Ink Canvas/MainWindow.xaml.cs | 20 +
Ink Canvas/MainWindow_cs/MW_Timer.cs | 245 ++++++++++-
3 files changed, 736 insertions(+), 8 deletions(-)
create mode 100644 Ink Canvas/Helpers/WindowOverviewModel.cs
diff --git a/Ink Canvas/Helpers/WindowOverviewModel.cs b/Ink Canvas/Helpers/WindowOverviewModel.cs
new file mode 100644
index 00000000..c9af2f2b
--- /dev/null
+++ b/Ink Canvas/Helpers/WindowOverviewModel.cs
@@ -0,0 +1,479 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Ink_Canvas.Helpers
+{
+ ///
+ /// 矩形结构体(用于窗口位置和大小)
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct WindowRect
+ {
+ public int Left;
+ public int Top;
+ public int Right;
+ public int Bottom;
+
+ public int Width => Right - Left;
+ public int Height => Bottom - Top;
+ }
+
+ ///
+ /// 窗口信息结构
+ ///
+ public class WindowInfo
+ {
+ public IntPtr Handle { get; set; }
+ public string Title { get; set; }
+ public string ClassName { get; set; }
+ public string ProcessName { get; set; }
+ public string ProcessPath { get; set; }
+ public WindowRect Rect { get; set; }
+ public bool IsVisible { get; set; }
+ public bool IsMinimized { get; set; }
+ public bool IsMaximized { get; set; }
+ public int ZOrder { get; set; }
+ public uint ProcessId { get; set; }
+ public bool IsFullScreen { get; set; }
+
+ ///
+ /// 计算窗口是否覆盖指定区域
+ ///
+ public bool CoversArea(WindowRect area)
+ {
+ if (!IsVisible || IsMinimized) return false;
+
+ // 计算交集
+ int left = Math.Max(Rect.Left, area.Left);
+ int top = Math.Max(Rect.Top, area.Top);
+ int right = Math.Min(Rect.Right, area.Right);
+ int bottom = Math.Min(Rect.Bottom, area.Bottom);
+
+ // 如果有交集,说明窗口覆盖了该区域
+ return left < right && top < bottom;
+ }
+
+ ///
+ /// 计算窗口覆盖指定区域的比例
+ ///
+ public double GetCoverageRatio(WindowRect area)
+ {
+ if (!IsVisible || IsMinimized) return 0.0;
+
+ // 计算交集
+ int left = Math.Max(Rect.Left, area.Left);
+ int top = Math.Max(Rect.Top, area.Top);
+ int right = Math.Min(Rect.Right, area.Right);
+ int bottom = Math.Min(Rect.Bottom, area.Bottom);
+
+ if (left >= right || top >= bottom) return 0.0;
+
+ // 计算交集面积
+ double intersectionArea = (right - left) * (bottom - top);
+ // 计算目标区域面积
+ double targetArea = area.Width * area.Height;
+
+ if (targetArea == 0) return 0.0;
+
+ return intersectionArea / targetArea;
+ }
+ }
+
+ ///
+ /// 窗口概览模型 - 实时监控桌面所有可见窗口并计算遮挡情况
+ ///
+ public class WindowOverviewModel : IDisposable
+ {
+ #region Win32 API Declarations
+ [DllImport("user32.dll")]
+ private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
+
+ [DllImport("user32.dll")]
+ private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool GetWindowRect(IntPtr hWnd, out WindowRect lpRect);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool IsWindowVisible(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool IsIconic(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool IsZoomed(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr GetForegroundWindow();
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
+
+ private const uint GW_HWNDNEXT = 2;
+ private const uint GW_HWNDPREV = 3;
+
+ private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
+ #endregion
+
+ private readonly object _lockObject = new object();
+ private List _windows = new List();
+ private Timer _updateTimer;
+ private bool _isDisposed = false;
+ private readonly int _updateInterval = 200; // 更新间隔(毫秒)
+
+ ///
+ /// 窗口列表更新事件
+ ///
+ public event EventHandler> WindowsUpdated;
+
+ ///
+ /// 当前窗口列表(按Z顺序排序,最上层在前)
+ ///
+ public List Windows
+ {
+ get
+ {
+ lock (_lockObject)
+ {
+ return new List(_windows);
+ }
+ }
+ }
+
+ ///
+ /// 构造函数
+ ///
+ public WindowOverviewModel()
+ {
+ // 立即执行一次更新
+ UpdateWindows();
+
+ // 启动定时器,定期更新窗口列表
+ _updateTimer = new Timer(OnUpdateTimer, null, _updateInterval, _updateInterval);
+ }
+
+ ///
+ /// 定时器回调
+ ///
+ private void OnUpdateTimer(object state)
+ {
+ if (_isDisposed) return;
+
+ try
+ {
+ UpdateWindows();
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"窗口概览模型更新失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ ///
+ /// 更新窗口列表
+ ///
+ public void UpdateWindows()
+ {
+ var windows = new List();
+ var zOrder = 0;
+
+ EnumWindows((hWnd, lParam) =>
+ {
+ try
+ {
+ // 检查窗口是否可见
+ if (!IsWindowVisible(hWnd)) return true;
+
+ // 检查窗口是否最小化
+ bool isMinimized = IsIconic(hWnd);
+ if (isMinimized) return true;
+
+ // 获取窗口矩形
+ if (!GetWindowRect(hWnd, out WindowRect rect)) return true;
+
+ // 过滤掉无效的窗口(太小或位置异常的窗口)
+ if (rect.Width <= 0 || rect.Height <= 0) return true;
+ if (rect.Right < rect.Left || rect.Bottom < rect.Top) return true;
+
+ // 获取窗口标题
+ const int nChars = 256;
+ StringBuilder windowTitle = new StringBuilder(nChars);
+ GetWindowText(hWnd, windowTitle, nChars);
+ string title = windowTitle.ToString();
+
+ // 获取窗口类名
+ StringBuilder className = new StringBuilder(nChars);
+ GetClassName(hWnd, className, nChars);
+ string classNameStr = className.ToString();
+
+ // 获取进程信息
+ GetWindowThreadProcessId(hWnd, out uint processId);
+ string processName = "Unknown";
+ string processPath = "Unknown";
+
+ try
+ {
+ Process process = Process.GetProcessById((int)processId);
+ processName = process.ProcessName;
+ try
+ {
+ processPath = process.MainModule?.FileName ?? "Unknown";
+ }
+ catch
+ {
+ processPath = "Unknown";
+ }
+ }
+ catch
+ {
+ // 进程可能已退出
+ }
+
+ // 检查是否最大化
+ bool isMaximized = IsZoomed(hWnd);
+
+ // 检查是否全屏(窗口大小接近屏幕大小)
+ bool isFullScreen = false;
+ try
+ {
+ var screen = System.Windows.Forms.Screen.FromHandle(hWnd);
+ var screenBounds = screen.Bounds;
+ // 如果窗口大小接近屏幕大小(允许10像素误差),认为是全屏
+ isFullScreen = rect.Width >= screenBounds.Width - 10 &&
+ rect.Height >= screenBounds.Height - 10 &&
+ Math.Abs(rect.Left - screenBounds.Left) <= 10 &&
+ Math.Abs(rect.Top - screenBounds.Top) <= 10;
+ }
+ catch
+ {
+ // 无法获取屏幕信息,使用默认值
+ }
+
+ // 跳过当前应用程序的窗口(避免检测到自己)
+ if (processName == "InkCanvasForClass" || processName == "Ink Canvas")
+ {
+ return true;
+ }
+
+ var windowInfo = new WindowInfo
+ {
+ Handle = hWnd,
+ Title = title,
+ ClassName = classNameStr,
+ ProcessName = processName,
+ ProcessPath = processPath,
+ Rect = rect,
+ IsVisible = true,
+ IsMinimized = false,
+ IsMaximized = isMaximized,
+ ZOrder = zOrder++,
+ ProcessId = processId,
+ IsFullScreen = isFullScreen
+ };
+
+ windows.Add(windowInfo);
+ }
+ catch
+ {
+ // 忽略单个窗口的错误,继续枚举其他窗口
+ }
+
+ return true; // 继续枚举
+ }, IntPtr.Zero);
+
+ // 按Z顺序排序(从最上层到最下层)
+ // 注意:EnumWindows返回的顺序可能不是严格的Z顺序,但我们可以通过GetWindow来获取更准确的顺序
+ windows = windows.OrderByDescending(w => w.ZOrder).ToList();
+
+ lock (_lockObject)
+ {
+ _windows = windows;
+ }
+
+ // 触发更新事件
+ WindowsUpdated?.Invoke(this, windows);
+ }
+
+ ///
+ /// 检查指定区域是否被其他窗口覆盖
+ ///
+ /// 要检查的区域
+ /// 要排除的进程名列表(例如当前应用程序)
+ /// 覆盖阈值(0.0-1.0),超过此阈值认为被覆盖
+ /// 如果被覆盖返回true
+ public bool IsAreaCovered(WindowRect area, List excludeProcessNames = null, double coverageThreshold = 0.5)
+ {
+ lock (_lockObject)
+ {
+ excludeProcessNames = excludeProcessNames ?? new List();
+
+ // 从最上层窗口开始检查
+ foreach (var window in _windows)
+ {
+ // 跳过排除的进程
+ if (excludeProcessNames.Contains(window.ProcessName)) continue;
+
+ // 计算覆盖比例
+ double coverage = window.GetCoverageRatio(area);
+ if (coverage >= coverageThreshold)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ /// 检查指定区域是否被全屏窗口覆盖
+ ///
+ /// 要检查的区域
+ /// 要排除的进程名列表
+ /// 如果被全屏窗口覆盖返回true
+ public bool IsAreaCoveredByFullScreenWindow(WindowRect area, List excludeProcessNames = null)
+ {
+ lock (_lockObject)
+ {
+ excludeProcessNames = excludeProcessNames ?? new List();
+
+ // 查找全屏窗口
+ foreach (var window in _windows)
+ {
+ // 跳过排除的进程
+ if (excludeProcessNames.Contains(window.ProcessName)) continue;
+
+ // 只检查全屏窗口
+ if (window.IsFullScreen && window.CoversArea(area))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ /// 获取覆盖指定区域的所有窗口
+ ///
+ /// 要检查的区域
+ /// 要排除的进程名列表
+ /// 覆盖阈值
+ /// 覆盖该区域的窗口列表(按Z顺序,最上层在前)
+ public List GetCoveringWindows(WindowRect area, List excludeProcessNames = null, double coverageThreshold = 0.1)
+ {
+ lock (_lockObject)
+ {
+ excludeProcessNames = excludeProcessNames ?? new List();
+ var coveringWindows = new List();
+
+ foreach (var window in _windows)
+ {
+ // 跳过排除的进程
+ if (excludeProcessNames.Contains(window.ProcessName)) continue;
+
+ // 计算覆盖比例
+ double coverage = window.GetCoverageRatio(area);
+ if (coverage >= coverageThreshold)
+ {
+ coveringWindows.Add(window);
+ }
+ }
+
+ return coveringWindows;
+ }
+ }
+
+ ///
+ /// 检查是否有全屏窗口
+ ///
+ /// 要排除的进程名列表
+ /// 如果有全屏窗口返回true
+ public bool HasFullScreenWindow(List excludeProcessNames = null)
+ {
+ lock (_lockObject)
+ {
+ excludeProcessNames = excludeProcessNames ?? new List();
+
+ return _windows.Any(w => !excludeProcessNames.Contains(w.ProcessName) && w.IsFullScreen);
+ }
+ }
+
+ ///
+ /// 获取所有全屏窗口
+ ///
+ /// 要排除的进程名列表
+ /// 全屏窗口列表
+ public List GetFullScreenWindows(List excludeProcessNames = null)
+ {
+ lock (_lockObject)
+ {
+ excludeProcessNames = excludeProcessNames ?? new List();
+
+ return _windows.Where(w => !excludeProcessNames.Contains(w.ProcessName) && w.IsFullScreen).ToList();
+ }
+ }
+
+ ///
+ /// 根据进程名查找窗口
+ ///
+ /// 进程名
+ /// 匹配的窗口列表
+ public List FindWindowsByProcessName(string processName)
+ {
+ lock (_lockObject)
+ {
+ return _windows.Where(w => w.ProcessName.Equals(processName, StringComparison.OrdinalIgnoreCase)).ToList();
+ }
+ }
+
+ ///
+ /// 根据窗口标题查找窗口
+ ///
+ /// 窗口标题(支持部分匹配)
+ /// 匹配的窗口列表
+ public List FindWindowsByTitle(string title)
+ {
+ lock (_lockObject)
+ {
+ return _windows.Where(w => w.Title.IndexOf(title, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
+ }
+ }
+
+ ///
+ /// 释放资源
+ ///
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+
+ _isDisposed = true;
+ _updateTimer?.Dispose();
+ _updateTimer = null;
+
+ lock (_lockObject)
+ {
+ _windows.Clear();
+ }
+ }
+ }
+}
+
diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs
index c477b173..0f0e9182 100644
--- a/Ink Canvas/MainWindow.xaml.cs
+++ b/Ink Canvas/MainWindow.xaml.cs
@@ -62,6 +62,8 @@ namespace Ink_Canvas
// 悬浮窗拦截管理器
private FloatingWindowInterceptorManager _floatingWindowInterceptorManager;
+ // 窗口概览模型
+ private WindowOverviewModel _windowOverviewModel;
// 设置面板相关状态
private bool userChangedNoFocusModeInSettings;
@@ -591,6 +593,17 @@ namespace Ink_Canvas
StartPPTMonitoring();
}
+ // 初始化窗口概览模型
+ try
+ {
+ _windowOverviewModel = new WindowOverviewModel();
+ LogHelper.WriteLogToFile("窗口概览模型已初始化", LogHelper.LogType.Event);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"初始化窗口概览模型失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+
// 如果启用PowerPoint联动增强功能,启动进程守护
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
{
@@ -995,6 +1008,13 @@ namespace Ink_Canvas
_floatingWindowInterceptorManager = null;
}
+ // 清理窗口概览模型
+ if (_windowOverviewModel != null)
+ {
+ _windowOverviewModel.Dispose();
+ _windowOverviewModel = null;
+ }
+
// 停止置顶维护定时器
StopTopmostMaintenance();
diff --git a/Ink Canvas/MainWindow_cs/MW_Timer.cs b/Ink Canvas/MainWindow_cs/MW_Timer.cs
index 3de74f33..305378aa 100644
--- a/Ink Canvas/MainWindow_cs/MW_Timer.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Timer.cs
@@ -1,5 +1,6 @@
using Ink_Canvas.Helpers;
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
@@ -13,6 +14,7 @@ using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
+using WinForms = System.Windows.Forms;
namespace Ink_Canvas
{
@@ -57,18 +59,18 @@ namespace Ink_Canvas
public partial class MainWindow : Window
{
- private Timer timerCheckPPT = new Timer();
- private Timer timerKillProcess = new Timer();
- private Timer timerCheckAutoFold = new Timer();
+ private System.Timers.Timer timerCheckPPT = new System.Timers.Timer();
+ private System.Timers.Timer timerKillProcess = new System.Timers.Timer();
+ private System.Timers.Timer timerCheckAutoFold = new System.Timers.Timer();
private string AvailableLatestVersion;
- private Timer timerCheckAutoUpdateWithSilence = new Timer();
- private Timer timerCheckAutoUpdateRetry = new Timer();
+ private System.Timers.Timer timerCheckAutoUpdateWithSilence = new System.Timers.Timer();
+ private System.Timers.Timer timerCheckAutoUpdateRetry = new System.Timers.Timer();
private bool isHidingSubPanelsWhenInking; // 避免书写时触发二次关闭二级菜单导致动画不连续
private int updateCheckRetryCount = 0;
private const int MAX_UPDATE_CHECK_RETRIES = 6;
- private Timer timerDisplayTime = new Timer();
- private Timer timerDisplayDate = new Timer();
- private Timer timerNtpSync = new Timer();
+ private System.Timers.Timer timerDisplayTime = new System.Timers.Timer();
+ private System.Timers.Timer timerDisplayDate = new System.Timers.Timer();
+ private System.Timers.Timer timerNtpSync = new System.Timers.Timer();
private TimeViewModel nowTimeVM = new TimeViewModel();
private DateTime cachedNetworkTime = DateTime.Now;
@@ -512,6 +514,14 @@ namespace Ink_Canvas
if (isFloatingBarChangingHideMode) return;
try
{
+ // 优先使用窗口概览模型进行检测
+ if (_windowOverviewModel != null)
+ {
+ CheckAutoFoldWithWindowOverviewModel();
+ return;
+ }
+
+ // 如果窗口概览模型未初始化,回退到传统的进程检测方式
var windowProcessName = ForegroundWindowInfo.ProcessName();
var windowTitle = ForegroundWindowInfo.WindowTitle();
//LogHelper.WriteLogToFile("windowTitle | " + windowTitle + " | windowProcessName | " + windowProcessName);
@@ -732,6 +742,225 @@ namespace Ink_Canvas
catch { }
}
+ ///
+ /// 检查进程是否在用户的自动收纳设置中启用
+ ///
+ private bool IsProcessInAutoFoldSettings(string processName, WindowInfo windowInfo = null)
+ {
+ // 根据进程名和窗口信息检查是否在自动收纳设置中
+ switch (processName)
+ {
+ case "EasiNote":
+ // EasiNote需要检查版本
+ if (windowInfo != null && !string.IsNullOrEmpty(windowInfo.ProcessPath) && windowInfo.ProcessPath != "Unknown")
+ {
+ try
+ {
+ var versionInfo = FileVersionInfo.GetVersionInfo(windowInfo.ProcessPath);
+ string version = versionInfo.FileVersion;
+ string prodName = versionInfo.ProductName;
+
+ if (version != null && version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote)
+ return true;
+ if (version != null && version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3)
+ return true;
+ if (prodName != null && prodName.Contains("3C") && Settings.Automation.IsAutoFoldInEasiNote3C)
+ return true;
+ }
+ catch { }
+ }
+ return false;
+
+ case "EasiCamera":
+ return Settings.Automation.IsAutoFoldInEasiCamera;
+
+ case "EasiNote5C":
+ return Settings.Automation.IsAutoFoldInEasiNote5C;
+
+ case "BoardService":
+ case "seewoPincoTeacher":
+ return Settings.Automation.IsAutoFoldInSeewoPincoTeacher;
+
+ case "HiteCamera":
+ return Settings.Automation.IsAutoFoldInHiteCamera;
+
+ case "HiteTouchPro":
+ return Settings.Automation.IsAutoFoldInHiteTouchPro;
+
+ case "HiteLightBoard":
+ return Settings.Automation.IsAutoFoldInHiteLightBoard;
+
+ case "WxBoardMain":
+ return Settings.Automation.IsAutoFoldInWxBoardMain;
+
+ case "MicrosoftWhiteboard":
+ case "msedgewebview2":
+ return Settings.Automation.IsAutoFoldInMSWhiteboard;
+
+ case "Amdox.WhiteBoard":
+ return Settings.Automation.IsAutoFoldInAdmoxWhiteboard;
+
+ case "Amdox.Booth":
+ return Settings.Automation.IsAutoFoldInAdmoxBooth;
+
+ case "QPoint":
+ return Settings.Automation.IsAutoFoldInQPoint;
+
+ case "YiYunVisualPresenter":
+ return Settings.Automation.IsAutoFoldInYiYunVisualPresenter;
+
+ case "WhiteBoard":
+ // MaxHub需要检查窗口标题
+ if (windowInfo != null && !string.IsNullOrEmpty(windowInfo.Title) &&
+ windowInfo.Title.Contains("白板书写") && Settings.Automation.IsAutoFoldInMaxHubWhiteboard)
+ {
+ if (!string.IsNullOrEmpty(windowInfo.ProcessPath) && windowInfo.ProcessPath != "Unknown")
+ {
+ try
+ {
+ var versionInfo = FileVersionInfo.GetVersionInfo(windowInfo.ProcessPath);
+ if (versionInfo.FileVersion != null && versionInfo.FileVersion.StartsWith("6.") &&
+ versionInfo.ProductName == "WhiteBoard")
+ return true;
+ }
+ catch { }
+ }
+ }
+ return false;
+
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// 使用窗口概览模型检测是否需要自动收纳
+ ///
+ private void CheckAutoFoldWithWindowOverviewModel()
+ {
+ try
+ {
+ if (_windowOverviewModel == null) return;
+
+ // 获取浮动栏的位置和大小
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ try
+ {
+ var floatingBarMargin = ViewboxFloatingBar.Margin;
+ var floatingBarWidth = ViewboxFloatingBar.ActualWidth;
+ var floatingBarHeight = ViewboxFloatingBar.ActualHeight;
+
+ // 如果浮动栏未显示或大小为0,跳过检测
+ if (floatingBarWidth <= 0 || floatingBarHeight <= 0) return;
+
+ // 计算浮动栏在屏幕上的位置(考虑DPI缩放)
+ var screen = WinForms.Screen.PrimaryScreen;
+ var dpiScaleX = PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1.0;
+ var dpiScaleY = PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M22 ?? 1.0;
+
+ // 将WPF坐标转换为屏幕坐标
+ var point = ViewboxFloatingBar.PointToScreen(new System.Windows.Point(0, 0));
+ int left = (int)(point.X);
+ int top = (int)(point.Y);
+ int right = left + (int)(floatingBarWidth * dpiScaleX);
+ int bottom = top + (int)(floatingBarHeight * dpiScaleY);
+
+ // 创建检测区域(稍微扩大一点,确保检测到覆盖)
+ var detectionArea = new WindowRect
+ {
+ Left = left - 5,
+ Top = top - 5,
+ Right = right + 5,
+ Bottom = bottom + 5
+ };
+
+ // 排除当前应用程序的进程
+ var excludeProcesses = new List { "InkCanvasForClass", "Ink Canvas" };
+
+ // 检查 OldZyBoard(通过窗口标题检测)
+ bool isOldZyBoardWindowExisted = Settings.Automation.IsAutoFoldInOldZyBoard &&
+ (WinTabWindowsChecker.IsWindowExisted("WhiteBoard - DrawingWindow") ||
+ WinTabWindowsChecker.IsWindowExisted("InstantAnnotationWindow"));
+
+ if (isOldZyBoardWindowExisted)
+ {
+ if (!isFloatingBarFolded)
+ {
+ FoldFloatingBar_MouseUp(new object(), null);
+ }
+ }
+ else if (!isOldZyBoardWindowExisted && isFloatingBarFolded && !foldFloatingBarByUser)
+ {
+ // OldZyBoard窗口退出时,如果未开启保持收纳模式,则展开
+ if (!Settings.Automation.KeepFoldAfterSoftwareExit)
+ {
+ UnFoldFloatingBar_MouseUp(new object(), null);
+ }
+ return; // OldZyBoard 使用特殊检测方式,处理完后直接返回
+ }
+
+ if (isOldZyBoardWindowExisted)
+ {
+ return; // OldZyBoard 窗口存在时,直接返回,不继续检测其他窗口
+ }
+
+ // 获取覆盖浮动栏的所有窗口
+ var coveringWindows = _windowOverviewModel.GetCoveringWindows(detectionArea, excludeProcesses, 0.1);
+
+ // 检查是否有覆盖窗口在用户的自动收纳设置中
+ bool shouldFold = false;
+
+ foreach (var window in coveringWindows)
+ {
+ // 检查窗口是否全屏(全屏窗口优先)
+ bool isFullScreen = window.IsFullScreen;
+
+ // 检查窗口大小是否接近全屏(用于检测二级菜单等)
+ bool isNearFullScreen = false;
+ try
+ {
+ var screenBounds = WinForms.Screen.FromHandle(window.Handle).Bounds;
+ isNearFullScreen = window.Rect.Width >= screenBounds.Width - 16 &&
+ window.Rect.Height >= screenBounds.Height - 16;
+ }
+ catch { }
+
+ // 如果窗口是全屏或接近全屏,且进程在自动收纳设置中,则应该收纳
+ if ((isFullScreen || isNearFullScreen) && IsProcessInAutoFoldSettings(window.ProcessName, window))
+ {
+ shouldFold = true;
+ break; // 找到匹配的窗口就退出
+ }
+ }
+
+ // 如果检测到应该收纳的窗口,且当前未收纳,则收纳
+ if (shouldFold && !isFloatingBarFolded)
+ {
+ FoldFloatingBar_MouseUp(new object(), null);
+ }
+ // 如果未检测到应该收纳的窗口,且当前已收纳(且不是用户手动收纳),则展开
+ else if (!shouldFold && isFloatingBarFolded && !foldFloatingBarByUser)
+ {
+ // 检查是否启用了软件退出后保持收纳模式
+ if (!Settings.Automation.KeepFoldAfterSoftwareExit)
+ {
+ UnFoldFloatingBar_MouseUp(new object(), null);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"窗口概览模型检测失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ });
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"窗口概览模型自动收纳检测异常: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
private void timerCheckAutoUpdateWithSilence_Elapsed(object sender, ElapsedEventArgs e)
{
// 停止计时器,避免重复触发