XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEToolBox

【WPF】XFE工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using Forms = System.Windows.Forms;

namespace XFEToolBox.Client.Views.Windows;

public partial class TrayMenuWindow : Window
{
    private readonly Action showHome;
    private readonly Action showPalette;
    private readonly Action exit;

    public TrayMenuWindow(Action showHome, Action showPalette, Action exit)
    {
        InitializeComponent();
        this.showHome = showHome;
        this.showPalette = showPalette;
        this.exit = exit;
    }

    public void ShowAtCursor()
    {
        // 先把尚未显示的 HWND 移到鼠标所在显示器,确保 WPF 按目标屏幕 DPI
        // 完成首次布局,避免从主屏移动后被 Windows 进行位图拉伸。
        PositionNearCursor();
        if (!IsVisible) Show();
        // DPI 切换可能改变原生窗口尺寸,再按实际尺寸校正一次边缘位置。
        PositionNearCursor();
        Activate();
    }

    private void OpenHomeButton_Click(object sender, RoutedEventArgs e) => Run(showHome);
    private void OpenPaletteButton_Click(object sender, RoutedEventArgs e) => Run(showPalette);
    private void ExitButton_Click(object sender, RoutedEventArgs e) => Run(exit);
    private void Window_Deactivated(object? sender, EventArgs e) => Hide();
    private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key != Key.Escape) return;
        Hide();
        e.Handled = true;
    }

    private void Run(Action action)
    {
        Hide();
        action();
    }

    private void PositionNearCursor()
    {
        var cursor = Forms.Control.MousePosition;
        var area = Forms.Screen.FromPoint(cursor).WorkingArea;
        var handle = new WindowInteropHelper(this).EnsureHandle();
        if (!GetWindowRect(handle, out var bounds)) return;
        var width = bounds.Right - bounds.Left;
        var height = bounds.Bottom - bounds.Top;
        var left = Math.Clamp(cursor.X - width + 18, area.Left, area.Right - width);
        var preferredTop = cursor.Y - height - 8;
        var top = preferredTop >= area.Top ? preferredTop : Math.Min(cursor.Y + 8, area.Bottom - height);
        SetWindowPos(handle, new IntPtr(-1), left, top, 0, 0, 0x0001 | 0x0010);
    }

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetWindowRect(IntPtr windowHandle, out NativeRect rectangle);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetWindowPos(
        IntPtr windowHandle, IntPtr insertAfter, int x, int y, int width, int height, uint flags);

    [StructLayout(LayoutKind.Sequential)]
    private struct NativeRect
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
}