diff --git a/Ink Canvas/App.xaml b/Ink Canvas/App.xaml index 92169636..1ba8b030 100644 --- a/Ink Canvas/App.xaml +++ b/Ink Canvas/App.xaml @@ -10,7 +10,7 @@ - + diff --git a/Ink Canvas/Helpers/InkFadeManager.cs b/Ink Canvas/Helpers/InkFadeManager.cs index 4490b721..3477b100 100644 --- a/Ink Canvas/Helpers/InkFadeManager.cs +++ b/Ink Canvas/Helpers/InkFadeManager.cs @@ -381,7 +381,8 @@ namespace Ink_Canvas.Helpers { try { - StartProgressiveFadeAnimation(visual, stroke, currentOpacity, AnimationDuration); + // 对于普通墨迹也使用连续渐隐动画 + StartContinuousFadeAnimation(visual, stroke, currentOpacity, AnimationDuration); } catch (Exception ex) { @@ -396,7 +397,8 @@ namespace Ink_Canvas.Helpers { try { - StartProgressiveFadeAnimation(visual, stroke, currentOpacity, (int)(AnimationDuration * 1.5)); + // 使用连续渐隐动画而不是分段动画 + StartContinuousFadeAnimation(visual, stroke, currentOpacity, (int)(AnimationDuration * 1.5)); } catch (Exception ex) { @@ -802,6 +804,50 @@ namespace Ink_Canvas.Helpers return curve; } + /// + /// 连续渐隐动画 - 模拟橡皮擦擦除效果 + /// + private void StartContinuousFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration) + { + try + { + // 获取墨迹的边界 + var geometry = stroke.GetGeometry(); + if (geometry == null) return; + + var bounds = geometry.Bounds; + var strokeStart = stroke.StylusPoints[0].ToPoint(); + var strokeEnd = stroke.StylusPoints[stroke.StylusPoints.Count - 1].ToPoint(); + + // 计算墨迹的方向向量 + var direction = new Vector(strokeEnd.X - strokeStart.X, strokeEnd.Y - strokeStart.Y); + var length = direction.Length; + + if (length == 0) + { + // 如果墨迹没有方向(单点),使用简单渐隐 + StartSimpleFadeAnimation(visual, stroke, currentOpacity, duration); + return; + } + + // 归一化方向向量 + direction.Normalize(); + + // 使用OpacityMask创建更自然的擦除效果 + var maskBrush = CreateEraseMaskBrush(stroke, strokeStart, strokeEnd, 1.0); + visual.OpacityMask = maskBrush; + + // 创建擦除动画 - 使用定时器来更新遮罩 + StartEraseMaskAnimation(visual, stroke, strokeStart, strokeEnd, duration); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"连续渐隐动画失败: {ex}", LogHelper.LogType.Error); + // 失败时回退到简单动画 + StartSimpleFadeAnimation(visual, stroke, currentOpacity, duration); + } + } + /// /// 动画完成后的统一处理 /// diff --git a/Ink Canvas/Helpers/WindowZOrderManager.cs b/Ink Canvas/Helpers/WindowZOrderManager.cs new file mode 100644 index 00000000..f1d19f91 --- /dev/null +++ b/Ink Canvas/Helpers/WindowZOrderManager.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; + +namespace Ink_Canvas.Helpers +{ + /// + /// 窗口Z-Order管理器,用于管理窗口的层级顺序 + /// 在无焦点模式下,确保后打开的窗口能够置顶于先打开的窗口 + /// + public static class WindowZOrderManager + { + #region Win32 API 声明 + private const int GWL_EXSTYLE = -20; + private const int WS_EX_TOPMOST = 0x00000008; + private const int WS_EX_NOACTIVATE = 0x08000000; + private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); + private const uint SWP_NOMOVE = 0x0002; + private const uint SWP_NOSIZE = 0x0001; + private const uint SWP_NOACTIVATE = 0x0010; + private const uint SWP_SHOWWINDOW = 0x0040; + private const uint SWP_NOOWNERZORDER = 0x0200; + + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + private static extern int GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); + + [DllImport("user32.dll")] + private static extern bool IsWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsIconic(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("kernel32.dll")] + private static extern uint GetCurrentProcessId(); + #endregion + + // 窗口层级管理 + private static readonly List _windowStack = new List(); + private static readonly object _lockObject = new object(); + + /// + /// 窗口信息类 + /// + private class WindowInfo + { + public IntPtr Handle { get; set; } + public Window Window { get; set; } + public DateTime CreatedTime { get; set; } + public bool IsTopmost { get; set; } + public bool IsNoFocusMode { get; set; } + } + + /// + /// 注册窗口到Z-Order管理器 + /// + /// 要注册的窗口 + /// 是否置顶 + /// 是否无焦点模式 + public static void RegisterWindow(Window window, bool isTopmost = false, bool isNoFocusMode = false) + { + if (window == null) return; + + lock (_lockObject) + { + var hwnd = new WindowInteropHelper(window).Handle; + if (hwnd == IntPtr.Zero) return; + + // 移除已存在的记录 + _windowStack.RemoveAll(w => w.Handle == hwnd); + + // 添加新记录 + var windowInfo = new WindowInfo + { + Handle = hwnd, + Window = window, + CreatedTime = DateTime.Now, + IsTopmost = isTopmost, + IsNoFocusMode = isNoFocusMode + }; + + _windowStack.Add(windowInfo); + + // 应用Z-Order + ApplyZOrder(); + } + } + + /// + /// 从Z-Order管理器中移除窗口 + /// + /// 要移除的窗口 + public static void UnregisterWindow(Window window) + { + if (window == null) return; + + lock (_lockObject) + { + var hwnd = new WindowInteropHelper(window).Handle; + _windowStack.RemoveAll(w => w.Handle == hwnd); + ApplyZOrder(); + } + } + + /// + /// 设置窗口为置顶状态 + /// + /// 要置顶的窗口 + /// 是否置顶 + public static void SetWindowTopmost(Window window, bool isTopmost) + { + if (window == null) return; + + lock (_lockObject) + { + var windowInfo = _windowStack.FirstOrDefault(w => w.Window == window); + if (windowInfo != null) + { + windowInfo.IsTopmost = isTopmost; + ApplyZOrder(); + } + } + } + + /// + /// 将窗口移到最顶层 + /// + /// 要移到最顶层的窗口 + public static void BringToTop(Window window) + { + if (window == null) return; + + lock (_lockObject) + { + var windowInfo = _windowStack.FirstOrDefault(w => w.Window == window); + if (windowInfo != null) + { + // 更新创建时间,使其成为最新的窗口 + windowInfo.CreatedTime = DateTime.Now; + ApplyZOrder(); + } + } + } + + /// + /// 应用Z-Order排序 + /// + private static void ApplyZOrder() + { + // 按创建时间排序,最新的窗口在最后 + var sortedWindows = _windowStack + .Where(w => IsWindow(w.Handle) && IsWindowVisible(w.Handle) && !IsIconic(w.Handle)) + .OrderBy(w => w.CreatedTime) + .ToList(); + + if (sortedWindows.Count == 0) return; + + // 获取主窗口(第一个注册的窗口) + var mainWindow = sortedWindows.FirstOrDefault(); + if (mainWindow == null) return; + + // 如果主窗口需要置顶且启用了无焦点模式 + if (mainWindow.IsTopmost && mainWindow.IsNoFocusMode) + { + // 检查是否有子窗口在前景 + var foregroundWindow = GetForegroundWindow(); + var hasChildWindowInForeground = false; + + if (foregroundWindow != mainWindow.Handle) + { + var foregroundWindowProcessId = GetWindowThreadProcessId(foregroundWindow, out uint processId); + var currentProcessId = GetCurrentProcessId(); + + if (processId == currentProcessId) + { + // 检查前景窗口是否在我们的窗口列表中 + var foregroundWindowInfo = sortedWindows.FirstOrDefault(w => w.Handle == foregroundWindow); + if (foregroundWindowInfo != null) + { + hasChildWindowInForeground = true; + } + } + } + + if (!hasChildWindowInForeground) + { + // 没有子窗口在前景,主窗口置顶 + SetWindowPos(mainWindow.Handle, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + // 确保主窗口样式正确 + int exStyle = GetWindowLong(mainWindow.Handle, GWL_EXSTYLE); + if ((exStyle & WS_EX_TOPMOST) == 0) + { + SetWindowLong(mainWindow.Handle, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); + } + } + } + + // 处理其他窗口的层级 + for (int i = 1; i < sortedWindows.Count; i++) + { + var windowInfo = sortedWindows[i]; + + // 子窗口应该置顶于主窗口 + if (windowInfo.IsTopmost) + { + SetWindowPos(windowInfo.Handle, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + // 确保窗口样式正确 + int exStyle = GetWindowLong(windowInfo.Handle, GWL_EXSTYLE); + if ((exStyle & WS_EX_TOPMOST) == 0) + { + SetWindowLong(windowInfo.Handle, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); + } + } + } + } + + /// + /// 检查是否有子窗口在前景 + /// + /// 如果有子窗口在前景返回true + public static bool HasChildWindowInForeground() + { + lock (_lockObject) + { + var foregroundWindow = GetForegroundWindow(); + if (foregroundWindow == IntPtr.Zero) return false; + + return _windowStack.Any(w => w.Handle == foregroundWindow); + } + } + + /// + /// 清理无效的窗口记录 + /// + public static void CleanupInvalidWindows() + { + lock (_lockObject) + { + _windowStack.RemoveAll(w => !IsWindow(w.Handle) || !IsWindowVisible(w.Handle)); + } + } + + /// + /// 获取当前注册的窗口数量 + /// + /// 窗口数量 + public static int GetWindowCount() + { + lock (_lockObject) + { + return _windowStack.Count; + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 0a163c09..12d6c8ae 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -978,7 +978,7 @@ IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold" Toggled="ToggleSwitchHighPrecisionLineStraighten_Toggled" /> - + diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index 1fbec0da..54fdb76b 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -684,6 +684,9 @@ namespace Ink_Canvas // 停止置顶维护定时器 StopTopmostMaintenance(); + // 从Z-Order管理器中移除主窗口 + WindowZOrderManager.UnregisterWindow(this); + LogHelper.WriteLogToFile("Ink Canvas closed", LogHelper.LogType.Event); // 检查是否有待安装的更新 @@ -1744,22 +1747,15 @@ namespace Ink_Canvas { try { - var hwnd = new WindowInteropHelper(this).Handle; if (Settings.Advanced.IsAlwaysOnTop) { + // 注册到Z-Order管理器 + WindowZOrderManager.RegisterWindow(this, true, Settings.Advanced.IsNoFocusMode); + // 先设置WPF的Topmost属性 Topmost = true; - // 使用更强的Win32 API调用来确保置顶 - // 1. 设置窗口样式为置顶 - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); - - // 2. 使用SetWindowPos确保窗口在最顶层 - SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); - - // 3. 如果启用了无焦点模式,需要特殊处理 + // 如果启用了无焦点模式,需要特殊处理 if (Settings.Advanced.IsNoFocusMode) { // 启动置顶维护定时器 @@ -1774,15 +1770,9 @@ namespace Ink_Canvas else { // 取消置顶时 - // 1. 先使用Win32 API取消置顶 - SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); - - // 2. 移除置顶窗口样式 - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TOPMOST); - - // 3. 停止置顶维护定时器 + WindowZOrderManager.SetWindowTopmost(this, false); + + // 停止置顶维护定时器 StopTopmostMaintenance(); // 注意:这里不直接设置Topmost,让其他代码根据模式决定 @@ -1851,30 +1841,14 @@ namespace Ink_Canvas return; } - // 检查是否有子窗口打开(模态对话框) - var foregroundWindow = GetForegroundWindow(); - if (foregroundWindow != hwnd) + // 清理无效的窗口记录 + WindowZOrderManager.CleanupInvalidWindows(); + + // 检查是否有子窗口在前景 + if (!WindowZOrderManager.HasChildWindowInForeground()) { - // 检查前景窗口是否是当前应用程序的子窗口 - var foregroundWindowProcessId = GetWindowThreadProcessId(foregroundWindow, out uint processId); - var currentProcessId = GetCurrentProcessId(); - - if (processId == currentProcessId) - { - // 如果有子窗口在前景,暂停置顶维护 - return; - } - - // 如果窗口不在最顶层且没有子窗口,重新设置置顶 - SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); - - // 确保窗口样式正确 - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - if ((exStyle & WS_EX_TOPMOST) == 0) - { - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); - } + // 没有子窗口在前景,重新应用Z-Order + WindowZOrderManager.SetWindowTopmost(this, true); } } catch (Exception ex) diff --git a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs index 96796023..0b78d802 100644 --- a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs +++ b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs @@ -30,6 +30,13 @@ namespace Ink_Canvas var mainWin = (MainWindow)Current.MainWindow; if (mainWin.IsLoaded) { + // 通知Z-Order管理器有系统菜单打开 + // 这会导致主窗口暂时取消置顶,让系统菜单能够正常显示 + if (Ink_Canvas.MainWindow.Settings.Advanced.IsAlwaysOnTop && Ink_Canvas.MainWindow.Settings.Advanced.IsNoFocusMode) + { + WindowZOrderManager.SetWindowTopmost(mainWin, false); + } + // 判斷是否在收納模式中 if (mainWin.isFloatingBarFolded) { @@ -57,6 +64,19 @@ namespace Ink_Canvas } } + private void SysTrayMenu_Closed(object sender, RoutedEventArgs e) + { + var mainWin = (MainWindow)Current.MainWindow; + if (mainWin.IsLoaded) + { + // 菜单关闭后,恢复主窗口的置顶状态 + if (Ink_Canvas.MainWindow.Settings.Advanced.IsAlwaysOnTop && Ink_Canvas.MainWindow.Settings.Advanced.IsNoFocusMode) + { + WindowZOrderManager.SetWindowTopmost(mainWin, true); + } + } + } + private void CloseAppTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e) { var mainWin = (MainWindow)Current.MainWindow; diff --git a/Ink Canvas/Windows/RandWindow.xaml.cs b/Ink Canvas/Windows/RandWindow.xaml.cs index 26810734..be2f2194 100644 --- a/Ink Canvas/Windows/RandWindow.xaml.cs +++ b/Ink Canvas/Windows/RandWindow.xaml.cs @@ -29,6 +29,12 @@ namespace Ink_Canvas // 加载背景 LoadBackground(settings); + + // 注册到Z-Order管理器,确保窗口能够正确置顶 + WindowZOrderManager.RegisterWindow(this, true, false); + + // 添加窗口关闭事件处理 + Closed += RandWindow_Closed; } private void LoadBackground(Settings settings) @@ -75,6 +81,12 @@ namespace Ink_Canvas // 加载背景 LoadBackground(settings); + // 注册到Z-Order管理器,确保窗口能够正确置顶 + WindowZOrderManager.RegisterWindow(this, true, false); + + // 添加窗口关闭事件处理 + Closed += RandWindow_Closed; + new Thread(() => { Thread.Sleep(100); @@ -336,5 +348,14 @@ namespace Ink_Canvas MessageBox.Show("无法调用外部点名:" + ex.Message); } } + + /// + /// 窗口关闭事件处理 + /// + private void RandWindow_Closed(object sender, EventArgs e) + { + // 从Z-Order管理器中移除窗口 + WindowZOrderManager.UnregisterWindow(this); + } } }