fix(UI): 优化弹出菜单位置和层级管理

修复弹出菜单在移动时位置不更新的问题,添加节流机制避免频繁刷新
使用Win32 API强制刷新弹出菜单位置并提升到最顶层
调整浮动工具栏和弹出菜单的样式和位置
移除未使用的字体样式定义
This commit is contained in:
PrefacedCorg
2026-05-02 17:06:08 +08:00
parent f825211987
commit c8e3bceab2
5 changed files with 235 additions and 28 deletions
+133
View File
@@ -1,6 +1,8 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
@@ -8,6 +10,133 @@ namespace Ink_Canvas.Helpers
{
internal class AnimationsHelper
{
#region Win32 API - Popup
[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 bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private const uint GW_HWNDPREV = 3;
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private static readonly IntPtr HWND_TOP = new IntPtr(0);
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOACTIVATE = 0x0010;
private const uint SWP_SHOWWINDOW = 0x0040;
/// <summary>
/// 强制刷新 Popup 的实际窗口位置(终极方案)
/// 通过 Win32 API 直接操作窗口句柄
/// </summary>
public static void ForceRefreshPopupPosition(Popup popup)
{
if (popup?.Child == null || !popup.IsOpen) return;
try
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
var source = PresentationSource.FromVisual(popup.Child) as HwndSource;
if (source?.Handle == null) return;
var hwnd = source.Handle;
// 获取当前窗口位置
if (GetWindowRect(hwnd, out RECT rect))
{
// 使用相同的参数调用 SetWindowPos,但加上 SWP_SHOWWINDOW
// 这会强制窗口管理器重新评估并更新窗口位置
SetWindowPos(
hwnd,
HWND_TOP,
rect.Left, rect.Top,
rect.Right - rect.Left,
rect.Bottom - rect.Top,
SWP_NOACTIVATE | SWP_SHOWWINDOW);
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] Force refreshed position: ({rect.Left}, {rect.Top})");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] ForceRefreshPopupPosition failed: {ex.Message}");
}
}), System.Windows.Threading.DispatcherPriority.Render);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] ForceRefreshPopupPosition error: {ex.Message}");
}
}
/// <summary>
/// 将 Popup 窗口提升到最顶层,确保不被其他控件遮挡
/// 采用多重策略确保置顶生效
/// </summary>
private static void BringPopupToFront(Popup popup)
{
try
{
if (popup?.Child == null) return;
Action bringToTopAction = () =>
{
try
{
var source = PresentationSource.FromVisual(popup.Child) as HwndSource;
if (source?.Handle == null) return;
var hwnd = source.Handle;
// 策略1:直接设置为 TOPMOST(最高优先级)
SetWindowPos(hwnd, HWND_TOPMOST,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] Set TOPMOST for popup");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] BringPopupToFront failed: {ex.Message}");
}
};
// 立即执行第一次
Application.Current.Dispatcher.BeginInvoke(bringToTopAction,
System.Windows.Threading.DispatcherPriority.Render);
// 延迟 50ms 后再次执行(确保在其他窗口操作之后)
Application.Current.Dispatcher.BeginInvoke(bringToTopAction,
System.Windows.Threading.DispatcherPriority.Normal);
// 延迟 100ms 后第三次执行(最终确认)
Application.Current.Dispatcher.BeginInvoke(bringToTopAction,
System.Windows.Threading.DispatcherPriority.Background);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] BringPopupToFront error: {ex.Message}");
}
}
#endregion
private static UIElement ResolveAnimationTarget(UIElement element)
{
return element;
@@ -296,6 +425,7 @@ namespace Ink_Canvas.Helpers
if (child == null)
{
popup.IsOpen = true;
BringPopupToFront(popup);
return;
}
@@ -304,6 +434,9 @@ namespace Ink_Canvas.Helpers
popup.IsOpen = true;
// 提升 Popup 到最顶层
BringPopupToFront(popup);
var sb = new Storyboard();
var fadeInAnimation = new DoubleAnimation