using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace XFEToolBox.WpfCore.Windowing;
///
/// 让无系统标题栏窗口最大化到所在显示器的可用工作区,避免覆盖任务栏或越出视窗。
///
public static class WindowWorkAreaHelper
{
private const int WmGetMinMaxInfo = 0x0024;
private const uint MonitorDefaultToNearest = 0x00000002;
private static readonly ConditionalWeakTable Registrations = new();
///
/// 为窗口安装一次工作区约束。可安全重复调用。
///
public static void Attach(Window window)
{
ArgumentNullException.ThrowIfNull(window);
_ = Registrations.GetValue(window, static target => new HookRegistration(target));
}
private sealed class HookRegistration
{
private readonly Window window;
private HwndSource? source;
public HookRegistration(Window window)
{
this.window = window;
window.SourceInitialized += Window_SourceInitialized;
window.Closed += Window_Closed;
if (PresentationSource.FromVisual(window) is HwndSource existingSource)
AttachSource(existingSource);
}
private void Window_SourceInitialized(object? sender, EventArgs e)
{
if (PresentationSource.FromVisual(window) is HwndSource initializedSource)
AttachSource(initializedSource);
}
private void AttachSource(HwndSource initializedSource)
{
if (ReferenceEquals(source, initializedSource))
return;
source?.RemoveHook(WindowProc);
source = initializedSource;
source.AddHook(WindowProc);
}
private void Window_Closed(object? sender, EventArgs e)
{
window.SourceInitialized -= Window_SourceInitialized;
window.Closed -= Window_Closed;
source?.RemoveHook(WindowProc);
source = null;
}
private IntPtr WindowProc(
IntPtr hwnd,
int message,
IntPtr wParam,
IntPtr lParam,
ref bool handled)
{
if (message != WmGetMinMaxInfo || lParam == IntPtr.Zero)
return IntPtr.Zero;
var monitor = MonitorFromWindow(hwnd, MonitorDefaultToNearest);
if (monitor == IntPtr.Zero)
return IntPtr.Zero;
var monitorInfo = MonitorInfo.Create();
if (!GetMonitorInfo(monitor, ref monitorInfo))
return IntPtr.Zero;
var minMaxInfo = Marshal.PtrToStructure(lParam);
minMaxInfo.MaxPosition = new NativePoint(
monitorInfo.WorkArea.Left - monitorInfo.MonitorArea.Left,
monitorInfo.WorkArea.Top - monitorInfo.MonitorArea.Top);
minMaxInfo.MaxSize = new NativePoint(
monitorInfo.WorkArea.Right - monitorInfo.WorkArea.Left,
monitorInfo.WorkArea.Bottom - monitorInfo.WorkArea.Top);
if (PresentationSource.FromVisual(window)?.CompositionTarget is { } compositionTarget)
{
var transform = compositionTarget.TransformToDevice;
minMaxInfo.MinTrackSize = new NativePoint(
ToDevicePixels(window.MinWidth, transform.M11),
ToDevicePixels(window.MinHeight, transform.M22));
}
Marshal.StructureToPtr(minMaxInfo, lParam, fDeleteOld: false);
handled = true;
return IntPtr.Zero;
}
private static int ToDevicePixels(double value, double scale) =>
double.IsFinite(value) && value > 0
? (int)Math.Ceiling(value * scale)
: 0;
}
[DllImport("user32.dll")]
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetMonitorInfo(IntPtr monitor, ref MonitorInfo monitorInfo);
[StructLayout(LayoutKind.Sequential)]
private struct NativePoint
{
public int X;
public int Y;
public NativePoint(int x, int y)
{
X = x;
Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
private struct MinMaxInfo
{
public NativePoint Reserved;
public NativePoint MaxSize;
public NativePoint MaxPosition;
public NativePoint MinTrackSize;
public NativePoint MaxTrackSize;
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeRect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct MonitorInfo
{
public int Size;
public NativeRect MonitorArea;
public NativeRect WorkArea;
public uint Flags;
public static MonitorInfo Create() => new()
{
Size = Marshal.SizeOf()
};
}
}