Skip to content

Instantly share code, notes, and snippets.

@MortenChristiansen
Last active February 22, 2024 17:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MortenChristiansen/6463580 to your computer and use it in GitHub Desktop.
Save MortenChristiansen/6463580 to your computer and use it in GitHub Desktop.
A helper class for managing window state when discarding the standard window chrome in WPF.
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Collections.Generic;
namespace WpfUtil
{
/// <summary>
/// Utility class for properly handling windows maximization.
/// </summary>
public static class WindowSizing
{
const int MONITOR_DEFAULTTONEAREST = 0x00000002;
private static Dictionary<Window, Action> _disposeHandlers = new Dictionary<Window, Action>();
#region DLLImports
[DllImport("shell32", CallingConvention = CallingConvention.StdCall)]
public static extern int SHAppBarMessage(int dwMessage, ref APPBARDATA pData);
[DllImport("user32", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32")]
internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
[DllImport("user32")]
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
#endregion
private static MINMAXINFO AdjustWorkingAreaForAutoHide(IntPtr monitorContainingApplication, MINMAXINFO mmi)
{
IntPtr hwnd = FindWindow("Shell_TrayWnd", null);
if (hwnd == null) return mmi;
IntPtr monitorWithTaskbarOnIt = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (!monitorContainingApplication.Equals(monitorWithTaskbarOnIt)) return mmi;
APPBARDATA abd = new APPBARDATA();
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = hwnd;
SHAppBarMessage((int)ABMsg.ABM_GETTASKBARPOS, ref abd);
var uEdge = GetEdge(abd.rc);
var state = (ABState)SHAppBarMessage((int)ABMsg.ABM_GETSTATE, ref abd);
if (state.HasFlag(ABState.ABS_AUTOHIDE))
{
AdjustSizeForAutohide(uEdge, ref mmi);
}
return mmi;
}
private static void AdjustSizeForAutohide(ABEdge uEdge, ref MINMAXINFO mmi)
{
switch (uEdge)
{
case ABEdge.ABE_LEFT:
mmi.ptMaxPosition.x += 2;
mmi.ptMaxTrackSize.x -= 2;
mmi.ptMaxSize.x -= 2;
break;
case ABEdge.ABE_RIGHT:
mmi.ptMaxSize.x -= 2;
mmi.ptMaxTrackSize.x -= 2;
break;
case ABEdge.ABE_TOP:
mmi.ptMaxPosition.y += 2;
mmi.ptMaxTrackSize.y -= 2;
mmi.ptMaxSize.y -= 2;
break;
case ABEdge.ABE_BOTTOM:
mmi.ptMaxSize.y -= 2;
mmi.ptMaxTrackSize.y -= 2;
break;
}
}
private static ABEdge GetEdge(RECT rc)
{
if (rc.top == rc.left && rc.bottom > rc.right)
return ABEdge.ABE_LEFT;
else if (rc.top == rc.left && rc.bottom < rc.right)
return ABEdge.ABE_TOP;
else if (rc.top > rc.left)
return ABEdge.ABE_BOTTOM;
else
return ABEdge.ABE_RIGHT;
}
public static void RegisterSizingEvents(Window window)
{
IntPtr handle = (new WindowInteropHelper(window)).Handle;
var hook = new HwndSourceHook(WindowProc);
var hwnd = HwndSource.FromHwnd(handle);
hwnd.AddHook(hook);
_disposeHandlers.Add(window, () => hwnd.RemoveHook(hook));
}
public static void UnregisterSizingEvents(Window window)
{
_disposeHandlers[window]();
_disposeHandlers.Remove(window);
}
private static IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case 0x0024:
WmGetMinMaxInfo(hwnd, lParam);
handled = true;
break;
}
return (IntPtr)0;
}
private static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
{
MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
IntPtr monitorContainingApplication = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitorContainingApplication != System.IntPtr.Zero)
{
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo(monitorContainingApplication, monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
// If your style introduces an invisible margin, you can offset it by changing this value
var margin = 0;
mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left) - margin;
mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top) - margin;
mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left) + 2 * margin;
mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top) + 2 * margin;
mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x; //maximum drag X size for the window
mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y; //maximum drag Y size for the window
mmi.ptMinTrackSize.x = 800; //minimum drag X size for the window
mmi.ptMinTrackSize.y = 600; //minimum drag Y size for the window
mmi = AdjustWorkingAreaForAutoHide(monitorContainingApplication, mmi); //need to adjust sizing if taskbar is set to autohide
}
Marshal.StructureToPtr(mmi, lParam, true);
}
public enum ABEdge
{
ABE_LEFT = 0,
ABE_TOP = 1,
ABE_RIGHT = 2,
ABE_BOTTOM = 3
}
[Flags]
public enum ABState
{
ABS_AUTOHIDE = 1,
ABS_ALWAYSONTOP = 2,
}
public enum ABMsg
{
ABM_NEW = 0,
ABM_REMOVE = 1,
ABM_QUERYPOS = 2,
ABM_SETPOS = 3,
ABM_GETSTATE = 4,
ABM_GETTASKBARPOS = 5,
ABM_ACTIVATE = 6,
ABM_GETAUTOHIDEBAR = 7,
ABM_SETAUTOHIDEBAR = 8,
ABM_WINDOWPOSCHANGED = 9,
ABM_SETSTATE = 10
}
[StructLayout(LayoutKind.Sequential)]
public struct APPBARDATA
{
public int cbSize;
public IntPtr hWnd;
public int uCallbackMessage;
public int uEdge;
public RECT rc;
public bool lParam;
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MONITORINFO
{
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
public RECT rcMonitor = new RECT();
public RECT rcWork = new RECT();
public int dwFlags = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
public POINT(int x, int y)
{
this.x = x;
this.y = y;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
}
}
@Rabadash8820
Copy link

See also this 2006 MSDN blog post that seems to be one of the first places to mention and solve this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment