fix(UI): 优化弹出菜单位置和层级管理
修复弹出菜单在移动时位置不更新的问题,添加节流机制避免频繁刷新 使用Win32 API强制刷新弹出菜单位置并提升到最顶层 调整浮动工具栏和弹出菜单的样式和位置 移除未使用的字体样式定义
This commit is contained in:
@@ -10,24 +10,6 @@
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<FontFamily x:Key="HarmonyOSFont">./Resources/Fonts/#HarmonyOS Sans SC</FontFamily>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="{StaticResource HarmonyOSFont}"/>
|
||||
</Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="FontFamily" Value="{StaticResource HarmonyOSFont}"/>
|
||||
</Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="FontFamily" Value="{StaticResource HarmonyOSFont}"/>
|
||||
</Style>
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="FontFamily" Value="{StaticResource HarmonyOSFont}"/>
|
||||
</Style>
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="FontFamily" Value="{StaticResource HarmonyOSFont}"/>
|
||||
</Style>
|
||||
<Style TargetType="ui:ScrollViewerEx">
|
||||
<EventSetter Event="PreviewMouseWheel" Handler="ScrollViewer_PreviewMouseWheel"/>
|
||||
</Style>
|
||||
<ContextMenu Opened="SysTrayMenu_Opened" Closed="SysTrayMenu_Closed" x:Shared="false" x:Key="SysTrayMenu" Padding="6" ui:ThemeManager.RequestedTheme="Light">
|
||||
<MenuItem IsCheckable="True" IsChecked="False" Checked="HideICCMainWindowTrayIconMenuItem_Checked" Unchecked="HideICCMainWindowTrayIconMenuItem_UnChecked" Name="HideICCMainWindowTrayIconMenuItem">
|
||||
<MenuItem.Header>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2363,8 +2363,8 @@
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
|
||||
<Grid Name="FloatingbarUIForInkReplay">
|
||||
<Viewbox Name="ViewboxFloatingBar" Margin="100,5,0,0" Cursor="Arrow"
|
||||
HorizontalAlignment="Left" Height="58" VerticalAlignment="Top" Width="1200"
|
||||
<Viewbox Name="ViewboxFloatingBar" Margin="150,250,0,0" Cursor="Arrow"
|
||||
HorizontalAlignment="Left" Height="58" VerticalAlignment="Top"
|
||||
UseLayoutRounding="True" SnapsToDevicePixels="True"
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<Viewbox.LayoutTransform>
|
||||
@@ -2990,24 +2990,24 @@
|
||||
AllowsTransparency="True"
|
||||
StaysOpen="True"
|
||||
IsOpen="False">
|
||||
<Border CornerRadius="6" Background="#F4F4F5"
|
||||
BorderBrush="#3b82f6" BorderThickness="1">
|
||||
<Border CornerRadius="8" Background="#F4F4F5"
|
||||
BorderBrush="#3b82f6" BorderThickness="2">
|
||||
<ikw:SimpleStackPanel Margin="-1">
|
||||
<Grid Margin="8,8,8,5">
|
||||
<Grid Margin="8,7,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" FontSize="12"
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" FontSize="10"
|
||||
Text="{i18n:I18n Key=Tools_MoreFeaturesTitle}"
|
||||
Foreground="#2563eb" FontWeight="Bold"/>
|
||||
<ui:FontIcon Grid.Column="1" Icon="{x:Static ui:SegoeFluentIcons.ChromeCloseContrast}"
|
||||
Foreground="#DC2626" FontSize="12" VerticalAlignment="Center"
|
||||
Foreground="#DC2626" FontSize="8" VerticalAlignment="Center"
|
||||
MouseDown="Border_MouseDown"
|
||||
MouseUp="CloseBordertools_MouseUp" />
|
||||
</Grid>
|
||||
<Border Margin="8,0,8,8" BorderBrush="#D4D4D8" Background="#fafafa" BorderThickness="1"
|
||||
CornerRadius="6">
|
||||
<Border Margin="6,0,6,6" BorderBrush="#D4D4D8" Background="#fafafa" BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<ikw:SimpleStackPanel Margin="2" Spacing="1">
|
||||
<ikw:SimpleStackPanel.Resources>
|
||||
<Style TargetType="Label" BasedOn="{StaticResource AutoFitToolPopupLabel8}" />
|
||||
|
||||
@@ -112,9 +112,10 @@ namespace Ink_Canvas
|
||||
(popupSize, targetSize, offset) => new[]
|
||||
{
|
||||
new CustomPopupPlacement(
|
||||
new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height),
|
||||
new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height - 8),
|
||||
PopupPrimaryAxis.Vertical)
|
||||
};
|
||||
|
||||
BlackboardLeftSide.Visibility = Visibility.Collapsed;
|
||||
BlackboardCenterSide.Visibility = Visibility.Collapsed;
|
||||
BlackboardRightSide.Visibility = Visibility.Collapsed;
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
@@ -205,6 +206,7 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
private Point pointPPT = new Point(-1, -1);
|
||||
private DispatcherTimer _floatingBarScreenFollowTimer;
|
||||
private DispatcherTimer _popupRefreshTimer; // Popup 位置刷新节流定时器
|
||||
private string _lastFloatingBarScreenDeviceName;
|
||||
private string _lastCanvasScreenDeviceName;
|
||||
private bool _isRebuildingCanvasForScreen;
|
||||
@@ -227,6 +229,95 @@ namespace Ink_Canvas
|
||||
pointPPT = new Point(xPos, yPos);
|
||||
else
|
||||
pointDesktop = new Point(xPos, yPos);
|
||||
|
||||
// 刷新 Popup 菜单位置(带节流)
|
||||
RefreshPopupPositionThrottled();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新 Popup 菜单位置(带节流,避免频繁调用)
|
||||
/// </summary>
|
||||
private void RefreshPopupPositionThrottled()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果没有打开的 Popup,直接返回
|
||||
if (!BorderTools.IsOpen && !BoardBorderToolsPopup.IsOpen) return;
|
||||
|
||||
// 如果定时器不存在,创建一个(16ms ≈ 60fps)
|
||||
if (_popupRefreshTimer == null)
|
||||
{
|
||||
_popupRefreshTimer = new DispatcherTimer();
|
||||
_popupRefreshTimer.Interval = TimeSpan.FromMilliseconds(16);
|
||||
_popupRefreshTimer.Tick += (s, e) =>
|
||||
{
|
||||
_popupRefreshTimer?.Stop();
|
||||
RefreshPopupPositionInternal();
|
||||
};
|
||||
}
|
||||
|
||||
// 重启定时器(如果已经在运行,会重新计时)
|
||||
_popupRefreshTimer.Stop();
|
||||
_popupRefreshTimer.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RefreshPopupPositionThrottled error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实际执行 Popup 位置刷新(强力版本)
|
||||
/// </summary>
|
||||
private void RefreshPopupPositionInternal()
|
||||
{
|
||||
try
|
||||
{
|
||||
RefreshPopupForcefully(BorderTools);
|
||||
RefreshPopupForcefully(BoardBorderToolsPopup);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RefreshPopupPositionInternal error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制刷新单个 Popup 的位置
|
||||
/// 使用多种策略确保位置更新生效
|
||||
/// </summary>
|
||||
private void RefreshPopupForcefully(Popup popup)
|
||||
{
|
||||
if (popup == null || !popup.IsOpen || popup.PlacementTarget == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 策略1:强制失效测量和布局
|
||||
popup.InvalidateMeasure();
|
||||
popup.InvalidateArrange();
|
||||
|
||||
// 策略2:修改 Offset 触发重新定位
|
||||
var originalHorizontalOffset = popup.HorizontalOffset;
|
||||
var originalVerticalOffset = popup.VerticalOffset;
|
||||
|
||||
popup.HorizontalOffset = originalHorizontalOffset + 0.001;
|
||||
popup.VerticalOffset = originalVerticalOffset + 0.001;
|
||||
|
||||
popup.HorizontalOffset = originalHorizontalOffset;
|
||||
popup.VerticalOffset = originalVerticalOffset;
|
||||
|
||||
// 策略3:强制更新布局
|
||||
popup.UpdateLayout();
|
||||
|
||||
// 策略4:使用 Win32 API 强制刷新窗口位置(终极方案)
|
||||
AnimationsHelper.ForceRefreshPopupPosition(popup);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[PopupRefresh] Forcefully refreshed popup position");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[PopupRefresh] Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user