Tag Archives: Full-Screen

Fixing full-screen WPF windows

2011-12-13 – Note – if you got here trying to find out how to create a full-screen WPF window – you need to set WindowState=”Maximized” and WindowStyle=”None”. This post is about a workaround for an issue that will sometimes arise when doing this.

A while back when I was working on a full-screen WPF application – I found a bug when sometimes, especially after you have changed your screen resolution – the application would show up with a visible bright border, which looks pretty ugly when the UI is mostly black and the display has a black frame as most displays seem to these days. I have found two related bugs reported, but only vague description of a workaround and no fix.

  1. https://connect.microsoft.com/VisualStudio/feedback/details/363288/maximised-wpf-window-not-covering-full-screen?wa=wsignin1.0
  2. https://connect.microsoft.com/VisualStudio/feedback/details/540394/maximized-window-does-not-cover-working-area-after-screen-setup-change?wa=wsignin1.0

I have managed to get some hints for how to solve it to a reusable copy&paste fix I am now using in various places and I thought I would share it:

1. Hook up the handler for the SourceInitialized event in your maximized window’s XAML or code behind and define it as below:

public MainWindow()
{
    InitializeComponent();
    this.SourceInitialized += mainWindow_SourceInitialized;
}

private void mainWindow_SourceInitialized(object sender, EventArgs e)
{
    IntPtr handle = (new WindowInteropHelper(this)).Handle;

    // Use WindowProc as the callback method
    // to process all native window messages.
    HwndSource.FromHwnd(handle)
        .AddHook(NativeMethods.MaximizedSizeFixWindowProc);
}

2. Add the NativeMethods class to your project.

using System;
using System.Runtime.InteropServices;

/// <summary>
/// Native Windows API methods and interfaces.
/// </summary>
internal static class NativeMethods
{
    #region Constants
    // The WM_GETMINMAXINFO message is sent to a window when the size or
    // position of the window is about to change.
    // An application can use this message to override the window's
    // default maximized size and position, or its default minimum or
    // maximum tracking size.
    private const int WM_GETMINMAXINFO = 0x0024;

    // Constants used with MonitorFromWindow()
    // Returns NULL.
    private const int MONITOR_DEFAULTTONULL = 0;

    // Returns a handle to the primary display monitor.
    private const int MONITOR_DEFAULTTOPRIMARY = 1;

    // Returns a handle to the display monitor that is nearest to the window.
    private const int MONITOR_DEFAULTTONEAREST = 2;
    #endregion

    #region Structs
    /// <summary>
    /// Native Windows API-compatible POINT struct
    /// </summary>
    [Serializable, StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int X;
        public int Y;
    }
    /// <summary>
    /// The RECT structure defines the coordinates of the upper-left
    /// and lower-right corners of a rectangle.
    /// </summary>
    /// <see cref="http://msdn.microsoft.com/en-us/library/dd162897%28VS.85%29.aspx"/>
    /// <remarks>
    /// By convention, the right and bottom edges of the rectangle
    /// are normally considered exclusive.
    /// In other words, the pixel whose coordinates are ( right, bottom )
    /// lies immediately outside of the the rectangle.
    /// For example, when RECT is passed to the FillRect function, the rectangle
    /// is filled up to, but not including,
    /// the right column and bottom row of pixels. This structure is identical
    /// to the RECTL structure.
    /// </remarks>
    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        /// <summary>
        /// The x-coordinate of the upper-left corner of the rectangle.
        /// </summary>
        public int Left;

        /// <summary>
        /// The y-coordinate of the upper-left corner of the rectangle.
        /// </summary>
        public int Top;

        /// <summary>
        /// The x-coordinate of the lower-right corner of the rectangle.
        /// </summary>
        public int Right;

        /// <summary>
        /// The y-coordinate of the lower-right corner of the rectangle.
        /// </summary>
        public int Bottom;
    }

    /// <summary>
    /// The MINMAXINFO structure contains information about a window's
    /// maximized size and position and its minimum and maximum tracking size.
    /// <seealso cref="http://msdn.microsoft.com/en-us/library/ms632605%28VS.85%29.aspx"/>
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct MINMAXINFO
    {
        /// <summary>
        /// Reserved; do not use.
        /// </summary>
        public POINT Reserved;

        /// <summary>
        /// Specifies the maximized width (POINT.x)
        /// and the maximized height (POINT.y) of the window.
        /// For top-level windows, this value
        /// is based on the width of the primary monitor.
        /// </summary>
        public POINT MaxSize;

        /// <summary>
        /// Specifies the position of the left side
        /// of the maximized window (POINT.x)
        /// and the position of the top
        /// of the maximized window (POINT.y).
        /// For top-level windows, this value is based
        /// on the position of the primary monitor.
        /// </summary>
        public POINT MaxPosition;

        /// <summary>
        /// Specifies the minimum tracking width (POINT.x)
        /// and the minimum tracking height (POINT.y) of the window.
        /// This value can be obtained programmatically
        /// from the system metrics SM_CXMINTRACK and SM_CYMINTRACK.
        /// </summary>
        public POINT MinTrackSize;

        /// <summary>
        /// Specifies the maximum tracking width (POINT.x)
        /// and the maximum tracking height (POINT.y) of the window.
        /// This value is based on the size of the virtual screen
        /// and can be obtained programmatically
        /// from the system metrics SM_CXMAXTRACK and SM_CYMAXTRACK.
        /// </summary>
        public POINT MaxTrackSize;
    }

    /// <summary>
    /// The WINDOWINFO structure contains window information.
    /// <seealso cref="http://msdn.microsoft.com/en-us/library/ms632610%28VS.85%29.aspx"/>
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct WINDOWINFO
    {
        /// <summary>
        /// The size of the structure, in bytes.
        /// The caller must set this to sizeof(WINDOWINFO).
        /// </summary>
        public uint Size;

        /// <summary>
        /// Pointer to a RECT structure
        /// that specifies the coordinates of the window.
        /// </summary>
        public RECT Window;

        /// <summary>
        /// Pointer to a RECT structure
        /// that specifies the coordinates of the client area.
        /// </summary>
        public RECT Client;

        /// <summary>
        /// The window styles. For a table of window styles,
        /// <see cref="http://msdn.microsoft.com/en-us/library/ms632680%28VS.85%29.aspx">
        /// CreateWindowEx
        /// </see>.
        /// </summary>
        public uint Style;

        /// <summary>
        /// The extended window styles. For a table of extended window styles,
        /// see CreateWindowEx.
        /// </summary>
        public uint ExStyle;

        /// <summary>
        /// The window status. If this member is WS_ACTIVECAPTION,
        /// the window is active. Otherwise, this member is zero.
        /// </summary>
        public uint WindowStatus;

        /// <summary>
        /// The width of the window border, in pixels.
        /// </summary>
        public uint WindowBordersWidth;

        /// <summary>
        /// The height of the window border, in pixels.
        /// </summary>
        public uint WindowBordersHeight;

        /// <summary>
        /// The window class atom (see
        /// <see cref="http://msdn.microsoft.com/en-us/library/ms633586%28VS.85%29.aspx">
        /// RegisterClass
        /// </see>).
        /// </summary>
        public ushort WindowType;

        /// <summary>
        /// The Windows version of the application that created the window.
        /// </summary>
        public ushort CreatorVersion;
    }

    /// <summary>
    /// The MONITORINFO structure contains information about a display monitor.
    /// The GetMonitorInfo function stores information in a MONITORINFO structure.
    /// <seealso cref="http://msdn.microsoft.com/en-us/library/dd145065%28VS.85%29.aspx"/>
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct MONITORINFO
    {
        /// <summary>
        /// The size, in bytes, of the structure. Set this member
        /// to sizeof(MONITORINFO) (40) before calling the GetMonitorInfo function.
        /// Doing so lets the function determine
        /// the type of structure you are passing to it.
        /// </summary>
        public int Size;

        /// <summary>
        /// A RECT structure that specifies the display monitor rectangle,
        /// expressed in virtual-screen coordinates.
        /// Note that if the monitor is not the primary display monitor,
        /// some of the rectangle's coordinates may be negative values.
        /// </summary>
        public RECT Monitor;

        /// <summary>
        /// A RECT structure that specifies the work area rectangle
        /// of the display monitor that can be used by applications,
        /// expressed in virtual-screen coordinates.
        /// Windows uses this rectangle to maximize an application on the monitor.
        /// The rest of the area in rcMonitor contains system windows
        /// such as the task bar and side bars.
        /// Note that if the monitor is not the primary display monitor,
        /// some of the rectangle's coordinates may be negative values.
        /// </summary>
        public RECT WorkArea;

        /// <summary>
        /// The attributes of the display monitor.
        ///
        /// This member can be the following value:
        /// 1 : MONITORINFOF_PRIMARY
        /// </summary>
        public uint Flags;
    }
    #endregion

    #region Imported methods
    /// <summary>
    /// The GetWindowInfo function retrieves information about the specified window.
    /// <seealso cref="http://msdn.microsoft.com/en-us/library/ms633516%28VS.85%29.aspx"/>
    /// </summary>
    /// <param name="hwnd">The window handle.</param>
    /// <param name="pwi">The reference to WINDOWINFO structure.</param>
    /// <returns>true on success</returns>
    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool GetWindowInfo(IntPtr hwnd, ref WINDOWINFO pwi);

    /// <summary>
    /// The MonitorFromWindow function retrieves a handle to the display monitor
    /// that has the largest area of intersection with the bounding rectangle
    /// of a specified window.
    /// <seealso cref="http://msdn.microsoft.com/en-us/library/dd145064%28VS.85%29.aspx"/>
    /// </summary>
    /// <param name="hwnd">The window handle.</param>
    /// <param name="dwFlags">Determines the function's return value
    /// if the window does not intersect any display monitor.</param>
    /// <returns>
    /// Monitor HMONITOR handle on success or based on dwFlags for failure
    /// </returns>
    [DllImport("user32.dll")]
    private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);

    /// <summary>
    /// The GetMonitorInfo function retrieves information about a display monitor
    /// <seealso cref="http://msdn.microsoft.com/en-us/library/dd144901%28VS.85%29.aspx"/>
    /// </summary>
    /// <param name="hMonitor">A handle to the display monitor of interest.</param>
    /// <param name="lpmi">
    /// A pointer to a MONITORINFO structure that receives information
    /// about the specified display monitor.
    /// </param>
    /// <returns></returns>
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
    #endregion

    /// <summary>
    /// Window procedure callback.
    /// Hooked to a WPF maximized window works around a WPF bug:
    /// https://connect.microsoft.com/VisualStudio/feedback/details/363288/maximised-wpf-window-not-covering-full-screen?wa=wsignin1.0#tabs
    /// possibly also:
    /// https://connect.microsoft.com/VisualStudio/feedback/details/540394/maximized-window-does-not-cover-working-area-after-screen-setup-change?wa=wsignin1.0
    /// </summary>
    /// <param name="hwnd">The window handle.</param>
    /// <param name="msg">The window message.</param>
    /// <param name="wParam">The wParam (word parameter).</param>
    /// <param name="lParam">The lParam (long parameter).</param>
    /// <param name="handled">
    /// if set to <c>true</c> - the message is handled
    /// and should not be processed by other callbacks.
    /// </param>
    /// <returns></returns>
    internal static IntPtr MaximizedSizeFixWindowProc(
        IntPtr hwnd,
        int msg,
        IntPtr wParam,
        IntPtr lParam,
        ref bool handled)
    {
        switch (msg)
        {
            case WM_GETMINMAXINFO:
                // Handle the message and mark it as handled,
                // so other callbacks do not touch it
                WmGetMinMaxInfo(hwnd, lParam);
                handled = true;
                break;
        }
        return (IntPtr)0;
    }

    /// <summary>
    /// Creates and populates the MINMAXINFO structure for a maximized window.
    /// Puts the structure into memory address given by lParam.
    /// Only used to process a WM_GETMINMAXINFO message.
    /// </summary>
    /// <param name="hwnd">The window handle.</param>
    /// <param name="lParam">The lParam.</param>
    private static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
    {
        // Get the MINMAXINFO structure from memory location given by lParam
        MINMAXINFO mmi =
            (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

        // Get the monitor that overlaps the window or the nearest
        IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
        if (monitor != IntPtr.Zero)
        {
            // Get monitor information
            MONITORINFO monitorInfo = new MONITORINFO();
            monitorInfo.Size = Marshal.SizeOf(typeof(MONITORINFO));
            GetMonitorInfo(monitor, ref monitorInfo);

            // The display monitor rectangle.
            // If the monitor is not the primary display monitor,
            // some of the rectangle's coordinates may be negative values
            RECT rcMonitorArea = monitorInfo.Monitor;

            // Get window information
            WINDOWINFO windowInfo = new WINDOWINFO();
            windowInfo.Size = (UInt32)(Marshal.SizeOf(typeof(WINDOWINFO)));
            GetWindowInfo(hwnd, ref windowInfo);
            int borderWidth = (int)windowInfo.WindowBordersWidth;
            int borderHeight = (int)windowInfo.WindowBordersHeight;

            // Set the dimensions of the window in maximized state
            mmi.MaxPosition.X = -borderWidth;
            mmi.MaxPosition.Y = -borderHeight;
            mmi.MaxSize.X =
                rcMonitorArea.Right - rcMonitorArea.Left + 2 * borderWidth;
            mmi.MaxSize.Y =
                rcMonitorArea.Bottom - rcMonitorArea.Top + 2 * borderHeight;

            // Set minimum and maximum size
            // to the size of the window in maximized state
            mmi.MinTrackSize.X = mmi.MaxSize.X;
            mmi.MinTrackSize.Y = mmi.MaxSize.Y;
            mmi.MaxTrackSize.X = mmi.MaxSize.X;
            mmi.MaxTrackSize.Y = mmi.MaxSize.Y;
        }

        // Copy the structure to memory location specified by lParam.
        // This concludes processing of WM_GETMINMAXINFO.
        Marshal.StructureToPtr(mmi, lParam, true);
    }
}

(Source code at dropbox.)

Advertisements
Tagged , , ,