c8e3bceab2
修复弹出菜单在移动时位置不更新的问题,添加节流机制避免频繁刷新 使用Win32 API强制刷新弹出菜单位置并提升到最顶层 调整浮动工具栏和弹出菜单的样式和位置 移除未使用的字体样式定义
522 lines
19 KiB
C#
522 lines
19 KiB
C#
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;
|
|
|
|
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;
|
|
}
|
|
|
|
public static void ShowWithFadeIn(UIElement element, double duration = 0.15)
|
|
{
|
|
if (element.Visibility == Visibility.Visible) return;
|
|
|
|
if (element == null)
|
|
throw new ArgumentNullException(nameof(element));
|
|
|
|
var sb = new Storyboard();
|
|
|
|
// 渐变动画
|
|
var fadeInAnimation = new DoubleAnimation
|
|
{
|
|
From = 0.5,
|
|
To = 1,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
|
|
|
sb.Children.Add(fadeInAnimation);
|
|
|
|
element.Visibility = Visibility.Visible;
|
|
|
|
sb.Begin((FrameworkElement)element);
|
|
}
|
|
|
|
public static void ShowWithSlideFromBottomAndFade(UIElement element, double duration = 0.15)
|
|
{
|
|
try
|
|
{
|
|
if (element == null)
|
|
throw new ArgumentNullException(nameof(element));
|
|
|
|
if (element.Visibility == Visibility.Visible) return;
|
|
|
|
element.Visibility = Visibility.Visible;
|
|
|
|
var target = ResolveAnimationTarget(element);
|
|
|
|
var sb = new Storyboard();
|
|
|
|
var fadeInAnimation = new DoubleAnimation
|
|
{
|
|
From = 0.5,
|
|
To = 1,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
fadeInAnimation.EasingFunction = new CubicEase();
|
|
|
|
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
|
|
|
var slideAnimation = new DoubleAnimation
|
|
{
|
|
From = 10,
|
|
To = 0,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
|
|
|
slideAnimation.EasingFunction = new CubicEase();
|
|
|
|
sb.Children.Add(fadeInAnimation);
|
|
sb.Children.Add(slideAnimation);
|
|
|
|
target.RenderTransform = new TranslateTransform();
|
|
|
|
sb.Begin((FrameworkElement)target);
|
|
}
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
}
|
|
|
|
public static void ShowWithSlideFromLeftAndFade(UIElement element, double duration = 0.25)
|
|
{
|
|
try
|
|
{
|
|
if (element.Visibility == Visibility.Visible) return;
|
|
|
|
if (element == null)
|
|
throw new ArgumentNullException(nameof(element));
|
|
|
|
var sb = new Storyboard();
|
|
|
|
// 渐变动画
|
|
var fadeInAnimation = new DoubleAnimation
|
|
{
|
|
From = 0.5,
|
|
To = 1,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
|
|
|
// 滑动动画
|
|
var slideAnimation = new DoubleAnimation
|
|
{
|
|
From = element.RenderTransform.Value.OffsetX - 20, // 滑动距离
|
|
To = 0,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
|
|
|
|
sb.Children.Add(fadeInAnimation);
|
|
sb.Children.Add(slideAnimation);
|
|
|
|
element.Visibility = Visibility.Visible;
|
|
element.RenderTransform = new TranslateTransform();
|
|
|
|
sb.Begin((FrameworkElement)element);
|
|
}
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
}
|
|
|
|
public static void ShowWithScaleFromLeft(UIElement element, double duration = 0.2)
|
|
{
|
|
try
|
|
{
|
|
if (element.Visibility == Visibility.Visible) return;
|
|
|
|
if (element == null)
|
|
throw new ArgumentNullException(nameof(element));
|
|
|
|
var sb = new Storyboard();
|
|
|
|
// 水平方向的缩放动画
|
|
var scaleXAnimation = new DoubleAnimation
|
|
{
|
|
From = 0,
|
|
To = 1,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)"));
|
|
|
|
// 垂直方向的缩放动画
|
|
var scaleYAnimation = new DoubleAnimation
|
|
{
|
|
From = 0,
|
|
To = 1,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
scaleYAnimation.EasingFunction = new CubicEase();
|
|
scaleXAnimation.EasingFunction = new CubicEase();
|
|
Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)"));
|
|
|
|
sb.Children.Add(scaleXAnimation);
|
|
sb.Children.Add(scaleYAnimation);
|
|
|
|
element.Visibility = Visibility.Visible;
|
|
element.RenderTransformOrigin = new Point(0, 0.5); // 左侧中心点为基准
|
|
element.RenderTransform = new ScaleTransform(0, 0);
|
|
|
|
sb.Begin((FrameworkElement)element);
|
|
}
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
}
|
|
|
|
public static void ShowWithScaleFromRight(UIElement element, double duration = 0.2)
|
|
{
|
|
try
|
|
{
|
|
if (element.Visibility == Visibility.Visible) return;
|
|
|
|
if (element == null)
|
|
throw new ArgumentNullException(nameof(element));
|
|
|
|
var sb = new Storyboard();
|
|
|
|
// 水平方向的缩放动画
|
|
var scaleXAnimation = new DoubleAnimation
|
|
{
|
|
From = 0,
|
|
To = 1,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)"));
|
|
|
|
// 垂直方向的缩放动画
|
|
var scaleYAnimation = new DoubleAnimation
|
|
{
|
|
From = 0,
|
|
To = 1,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)"));
|
|
|
|
scaleYAnimation.EasingFunction = new CubicEase();
|
|
scaleXAnimation.EasingFunction = new CubicEase();
|
|
|
|
sb.Children.Add(scaleXAnimation);
|
|
sb.Children.Add(scaleYAnimation);
|
|
|
|
element.Visibility = Visibility.Visible;
|
|
element.RenderTransformOrigin = new Point(1, 0.5); // 右侧中心点为基准
|
|
element.RenderTransform = new ScaleTransform(0, 0);
|
|
|
|
sb.Begin((FrameworkElement)element);
|
|
}
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
}
|
|
|
|
public static void HideWithSlideAndFade(UIElement element, double duration = 0.15)
|
|
{
|
|
try
|
|
{
|
|
if (element == null)
|
|
throw new ArgumentNullException(nameof(element));
|
|
|
|
if (element.Visibility == Visibility.Collapsed) return;
|
|
|
|
var target = ResolveAnimationTarget(element);
|
|
|
|
var sb = new Storyboard();
|
|
|
|
var fadeOutAnimation = new DoubleAnimation
|
|
{
|
|
From = 1,
|
|
To = 0,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
fadeOutAnimation.EasingFunction = new CubicEase();
|
|
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
|
|
|
var slideAnimation = new DoubleAnimation
|
|
{
|
|
From = 0,
|
|
To = 10,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
slideAnimation.EasingFunction = new CubicEase();
|
|
|
|
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
|
|
|
sb.Children.Add(fadeOutAnimation);
|
|
sb.Children.Add(slideAnimation);
|
|
|
|
sb.Completed += (s, e) =>
|
|
{
|
|
element.Visibility = Visibility.Collapsed;
|
|
};
|
|
|
|
target.RenderTransform = new TranslateTransform();
|
|
sb.Begin((FrameworkElement)target);
|
|
}
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
}
|
|
|
|
public static void HideWithFadeOut(UIElement element, double duration = 0.15)
|
|
{
|
|
if (element.Visibility == Visibility.Collapsed) return;
|
|
|
|
if (element == null)
|
|
throw new ArgumentNullException(nameof(element));
|
|
|
|
var sb = new Storyboard();
|
|
|
|
var fadeOutAnimation = new DoubleAnimation
|
|
{
|
|
From = 1,
|
|
To = 0,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
|
|
|
sb.Children.Add(fadeOutAnimation);
|
|
|
|
sb.Completed += (s, e) =>
|
|
{
|
|
element.Visibility = Visibility.Collapsed;
|
|
};
|
|
|
|
sb.Begin((FrameworkElement)element);
|
|
}
|
|
|
|
public static void ShowPopupWithSlideAndFade(Popup popup, double duration = 0.15)
|
|
{
|
|
try
|
|
{
|
|
if (popup == null)
|
|
throw new ArgumentNullException(nameof(popup));
|
|
|
|
if (popup.IsOpen) return;
|
|
|
|
var child = popup.Child as FrameworkElement;
|
|
if (child == null)
|
|
{
|
|
popup.IsOpen = true;
|
|
BringPopupToFront(popup);
|
|
return;
|
|
}
|
|
|
|
child.Opacity = 0.5;
|
|
child.RenderTransform = new TranslateTransform(0, 10);
|
|
|
|
popup.IsOpen = true;
|
|
|
|
// 提升 Popup 到最顶层
|
|
BringPopupToFront(popup);
|
|
|
|
var sb = new Storyboard();
|
|
|
|
var fadeInAnimation = new DoubleAnimation
|
|
{
|
|
From = 0.5,
|
|
To = 1,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
fadeInAnimation.EasingFunction = new CubicEase();
|
|
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
|
|
|
var slideAnimation = new DoubleAnimation
|
|
{
|
|
From = 10,
|
|
To = 0,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
slideAnimation.EasingFunction = new CubicEase();
|
|
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
|
|
|
sb.Children.Add(fadeInAnimation);
|
|
sb.Children.Add(slideAnimation);
|
|
|
|
sb.Begin(child);
|
|
}
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
}
|
|
|
|
public static void HidePopupWithSlideAndFade(Popup popup, double duration = 0.15)
|
|
{
|
|
try
|
|
{
|
|
if (popup == null)
|
|
throw new ArgumentNullException(nameof(popup));
|
|
|
|
if (!popup.IsOpen) return;
|
|
|
|
var child = popup.Child as FrameworkElement;
|
|
if (child == null)
|
|
{
|
|
popup.IsOpen = false;
|
|
return;
|
|
}
|
|
|
|
var sb = new Storyboard();
|
|
|
|
var fadeOutAnimation = new DoubleAnimation
|
|
{
|
|
From = 1,
|
|
To = 0,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
fadeOutAnimation.EasingFunction = new CubicEase();
|
|
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
|
|
|
var slideAnimation = new DoubleAnimation
|
|
{
|
|
From = 0,
|
|
To = 10,
|
|
Duration = TimeSpan.FromSeconds(duration)
|
|
};
|
|
slideAnimation.EasingFunction = new CubicEase();
|
|
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
|
|
|
sb.Children.Add(fadeOutAnimation);
|
|
sb.Children.Add(slideAnimation);
|
|
|
|
sb.Completed += (s, e) =>
|
|
{
|
|
popup.IsOpen = false;
|
|
child.Opacity = 1;
|
|
child.RenderTransform = new TranslateTransform();
|
|
};
|
|
|
|
child.RenderTransform = new TranslateTransform();
|
|
sb.Begin(child);
|
|
}
|
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
}
|
|
|
|
}
|
|
}
|