SetWindowPlacement won't correct placement for WPF tool windows

Related searches

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.

You can pass your proposed window rectangle to MonitorFromRect() with the MONITOR_DEFAULTTONEAREST flag. This will return an HMONITOR representing the monitor the window most intersects (is on) - or if the window is completely off-screen, the nearest monitor to the proposed coords.

You can then call GetMonitorInfo() to find the monitor's display and workspace rectangles, and bounds-check your proposed window coords to make sure the window is completely on-screen before you show it.

Find a target window and minimize, maximize, or restore it in C# , won't find it. Next the code gets the target window's current placement. Before calling GetWindowPlacement (or SetWindowPlacement), the� The GetWindowPlacement, FindWindow, and SetWindowPlacement functions from user32.dll are used to manage the window operations. The FindWindow function is used to retrieve the handle of the window whose class name and window name match the specified string. This function won't search for the child window (FindWindowEx is used for the child window).

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;
}

Set Window Action - (Minimize / Maximize), This function won't search for the child window ( FindWindowEx is used bool SetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT� SetWindowPlacement(target_hwnd, ref placement); } The code uses the FindWindowByCaption API function to find the target window. Note that you must enter the target window’s caption exactly correctly or FindWindowByCaption won’t find it. Next the code gets the target window’s current placement.

I was able to come up with a solution (based on RandomEngy's code) that extends the Window class with a KeepInsideNearestMonitor() method that ensure WPF Window placement within the currently visible bounds. It comes without Win32 and works with regard to the calling factor that you can setup in Windows 10 via Right Click on Desktop>Display Settings.

public static class WindowExtension
{
    [StructLayout(LayoutKind.Sequential)]
    internal 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;
        }
    }

    internal static void KeepInsideNearestMonitor(this Window floatingWindow)
    {
        RECT normalPosition = new RECT();
        normalPosition.Left = (int)floatingWindow.FloatingLeft;
        normalPosition.Top = (int)floatingWindow.FloatingTop;
        normalPosition.Bottom = normalPosition.Top + (int)floatingWindow.FloatingHeight;
        normalPosition.Right = normalPosition.Left + (int)floatingWindow.FloatingWidth;

        // Are we using only one monitor?
        if (SystemParameters.PrimaryScreenWidth == SystemParameters.VirtualScreenWidth &&
            SystemParameters.PrimaryScreenHeight == SystemParameters.VirtualScreenHeight)
        {
            RECT primaryscreen = new RECT(0,0, (int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight);

            if (!RectanglesIntersect(normalPosition, primaryscreen))
            {
                normalPosition = PlaceOnScreen(primaryscreen, normalPosition);

                floatingWindow.FloatingLeft = normalPosition.Left;
                floatingWindow.FloatingTop = normalPosition.Top;
                floatingWindow.FloatingHeight = normalPosition.Bottom - normalPosition.Top;
                floatingWindow.FloatingWidth = normalPosition.Right - normalPosition.Left;
            }

            return;
        }
        else
        {
            RECT primaryscreen = new RECT(0, 0, (int)SystemParameters.VirtualScreenWidth, (int)SystemParameters.VirtualScreenHeight);

            if (!RectanglesIntersect(normalPosition, primaryscreen))
            {
                normalPosition = PlaceOnScreen(primaryscreen, normalPosition);

                floatingWindow.FloatingLeft = normalPosition.Left;
                floatingWindow.FloatingTop = normalPosition.Top;
                floatingWindow.FloatingHeight = normalPosition.Bottom - normalPosition.Top;
                floatingWindow.FloatingWidth = normalPosition.Right - normalPosition.Left;
            }

            return;
        }
    }

    /// <summary>
    /// Determine whether <paramref name="a"/> and <paramref name="b"/>
    /// have an intersection or not.
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// Determine the place where <paramref name="windowRect"/> should be placed
    /// inside the <paramref name="monitorRect"/>.
    /// </summary>
    /// <param name="monitorRect"></param>
    /// <param name="windowRect"></param>
    /// <returns></returns>
    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;
    }
}

Use SetWindowPlacement in COM ATL Projects - MSDN, I am wrapping this function like this to call SetWindowPlacement from The point is that a scripting user won't necessarily call your object's� This partly solves the problem, but it still means that if you resize a window, maximize it, and then close it, you won't get the benefit of storing the previous size information. Unfortunately, this is one of the more glaring omissions in the Windows Forms toolkit.

Get WindowPlacement - Visual Studio, close it, you won't get the benefit of storing the previous size information. private static extern bool SetWindowPlacement(IntPtr handle,� The system automatically sets the size and position of a maximized window to the system-defined defaults for a maximized window. To override these defaults, an application can either call the SetWindowPlacement function or process the WM_GETMINMAXINFO message that is received by a window when the system is about to maximize the window.

Odd Set/GetWindowPlacement problem, SetWindowPlacement(hWnd, &wip); } } This code appears to run Again, the problem is not that it doesn't restore the window, it won't let me restore the window� Here are the examples of the python api mcplatform.win32gui.GetWindowPlacement taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.

You took it too literally, it doesn't exist. The point is that a scripting user won't necessarily call your object's functions in the manner shown by your earlier posts. WINAPI. WinAPI_SetWindowPlacement AutoItX3. WinGetHandle ("[CLASS:Notepad]"), NULL, 1, NULL, NULL, NULL, NULL, 0, 0, 1280, 365 WINAPI. WinAPI_SetWindowPlacement AutoItX3.

Comments
  • There are pretty explicit warnings in the MSDN article for WINDOWPLACEMENT about the difference between workspace and screen coordinates and how tool windows are different. Nothing to look at so I can only assume you are getting it wrong somehow. Don't keep your code a secret.
  • The difference between workspace and screen coordinates isn't the issue here. I've attached the code but the heart of the issue is calling SetPlacement on a tool window with a normal placement RECT that's outside the normal screen area.
  • Actually you don't need explicit XML serialization: you can store an instance of WINDOWPLACEMENT in settings as is. By the way, WIndows 7 as of now seems to adjust the position if it's outside the visible area for now. Otherwise, thank you for sharing your code!
  • @Vlad The settings UI has weird behavior when you try to just save it as-is. Also in my case I don't save it with the built-in settings; I am storing in a sqlite database. Also when you saw it adjust the position of the window outside the viewable area, were you using a regular window or a tool window?
  • @RandomEngy: I didn't notice any weird behavior with VS 2019's settings. The database is of course a different story.