I'm p-invoking into SetWindowPlacement in my WPF app to save and restore the window location. This works great but the advertised capacity to make sure a window is never completely hidden doesn't seem to function when the window is a tool window rather than a standard window. You call SetWindowPlacement with negative Left and Right placements and it will happily open it off-screen with no way of getting it back on.
Is there a way I can make SetWindowPlacement correct the placement for these tool windows (for missing monitors and such)?
Failing that, is there a good manual way to do it? For reference, the code:
// RECT structure required by WINDOWPLACEMENT structure
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left, int top, int right, int bottom)
{
this.Left = left;
this.Top = top;
this.Right = right;
this.Bottom = bottom;
}
}
// POINT structure required by WINDOWPLACEMENT structure
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
// WINDOWPLACEMENT stores the position, size, and state of a window
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
public int length;
public int flags;
public int showCmd;
public POINT minPosition;
public POINT maxPosition;
public RECT normalPosition;
}
public static class WindowPlacement
{
private static Encoding encoding = new UTF8Encoding();
private static XmlSerializer serializer = new XmlSerializer(typeof(WINDOWPLACEMENT));
[DllImport("user32.dll")]
private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
public static void SetPlacement(IntPtr windowHandle, string placementXml)
{
if (string.IsNullOrEmpty(placementXml))
{
return;
}
WINDOWPLACEMENT placement;
byte[] xmlBytes = encoding.GetBytes(placementXml);
try
{
using (MemoryStream memoryStream = new MemoryStream(xmlBytes))
{
placement = (WINDOWPLACEMENT)serializer.Deserialize(memoryStream);
}
placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
placement.flags = 0;
placement.showCmd = (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd);
SetWindowPlacement(windowHandle, ref placement);
}
catch (InvalidOperationException)
{
// Parsing placement XML failed. Fail silently.
}
}
public static string GetPlacement(IntPtr windowHandle)
{
WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
GetWindowPlacement(windowHandle, out placement);
using (MemoryStream memoryStream = new MemoryStream())
{
using (XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8))
{
serializer.Serialize(xmlTextWriter, placement);
byte[] xmlBytes = memoryStream.ToArray();
return encoding.GetString(xmlBytes);
}
}
}
}
Calling SetPlacement with Top: 200, Bottom: 600, Left: -1000, Right: -300.
From Jonathan's answer I came up with this code to rescue the window manually:
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
public int cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}
...
[DllImport("user32.dll")]
private static extern IntPtr MonitorFromRect([In] ref RECT lprc, uint dwFlags);
[DllImport("user32.dll")]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
private const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
...
IntPtr closestMonitorPtr = MonitorFromRect(ref placement.normalPosition, MONITOR_DEFAULTTONEAREST);
MONITORINFO closestMonitorInfo = new MONITORINFO();
closestMonitorInfo.cbSize = Marshal.SizeOf(typeof (MONITORINFO));
bool getInfoSucceeded = GetMonitorInfo(closestMonitorPtr, ref closestMonitorInfo);
if (getInfoSucceeded && !RectanglesIntersect(placement.normalPosition, closestMonitorInfo.rcMonitor))
{
placement.normalPosition = PlaceOnScreen(closestMonitorInfo.rcMonitor, placement.normalPosition);
}
...
private static bool RectanglesIntersect(RECT a, RECT b)
{
if (a.Left > b.Right || a.Right < b.Left)
{
return false;
}
if (a.Top > b.Bottom || a.Bottom < b.Top)
{
return false;
}
return true;
}
private static RECT PlaceOnScreen(RECT monitorRect, RECT windowRect)
{
int monitorWidth = monitorRect.Right - monitorRect.Left;
int monitorHeight = monitorRect.Bottom - monitorRect.Top;
if (windowRect.Right < monitorRect.Left)
{
// Off left side
int width = windowRect.Right - windowRect.Left;
if (width > monitorWidth)
{
width = monitorWidth;
}
windowRect.Left = monitorRect.Left;
windowRect.Right = windowRect.Left + width;
}
else if (windowRect.Left > monitorRect.Right)
{
// Off right side
int width = windowRect.Right - windowRect.Left;
if (width > monitorWidth)
{
width = monitorWidth;
}
windowRect.Right = monitorRect.Right;
windowRect.Left = windowRect.Right - width;
}
if (windowRect.Bottom < monitorRect.Top)
{
// Off top
int height = windowRect.Bottom - windowRect.Top;
if (height > monitorHeight)
{
height = monitorHeight;
}
windowRect.Top = monitorRect.Top;
windowRect.Bottom = windowRect.Top + height;
}
else if (windowRect.Top > monitorRect.Bottom)
{
// Off bottom
int height = windowRect.Bottom - windowRect.Top;
if (height > monitorHeight)
{
height = monitorHeight;
}
windowRect.Bottom = monitorRect.Bottom;
windowRect.Top = windowRect.Bottom - height;
}
return windowRect;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With