当移动到更大的显示时,窗口不能正确调整大小

时间:2023-01-12 12:15:26

My WPF application is exhibiting strange behavior on my two monitor laptop development system. The second monitor has a resolution of 1920 x 1080; the laptop's resolution is 1366 x 768. The laptop is running Windows 8.1 and both displays have their DPI settings set to 100%. When it is plugged in, the second monitor is the primary display. Obviously, when the second monitor is not plugged in, the laptop's display is the primary display.

我的WPF应用程序在我的两个监视器笔记本开发系统上显示了奇怪的行为。第二个显示器的分辨率为1920 x 1080;笔记本电脑的分辨率是1366 x 768。笔记本电脑运行的是windows8.1,两个显示器的DPI设置都设置为100%。当它被插入时,第二个显示器是主显示器。显然,当第二个显示器不插电时,笔记本电脑的显示器就是主显示器。

The application window is always maximized but can be minimized. It cannot be dragged The problem has to do with how the window is displayed when it is moved from one monitor to the other when you plug the second monitor in or unplug it.

应用程序窗口总是最大化,但可以最小化。它不能被拖动问题与窗口如何显示当它从一个监视器移动到另一个当你插入第二个监视器或拔掉它。

When the program is started with the second monitor plugged in, it moves to the laptop's display when it is unplugged. The WPF code handles this change correctly, too. That is, it detects that the original size can't fit on the new monitor so it redraws it to fit. When the second monitor is plugged back in, it moves back to the second monitor and redraws itself at the proper size for that monitor. This is exactly what I want in this scenario. The problem is when the program is started in the other configuration.

当程序启动时,插入第二个监视器,当它不插电时,它会移到笔记本的显示器上。WPF代码也正确地处理了这个更改。也就是说,它会检测到原来的大小无法适应新的监视器,因此它会重新绘制以适应新的监视器。当第二个监视器重新插入时,它将返回到第二个监视器,并按该监视器的适当大小重新绘制自己。在这个场景中,这正是我想要的。问题是程序何时在另一个配置中启动。

When the program is started without the second monitor plugged in, it's drawn at the proper size for the laptop's display. When the second monitor is plugged in with the program running, the window moves to the second monitor, but it is drawn wrong. Since the program is maximized, it has a huge black border surrounding it on three sides with the content displayed in an area the same size it was on the laptop's display.

当程序在没有插入第二个监视器的情况下启动时,它会以合适的大小绘制出来,以供笔记本电脑显示。当第二个监视器插入到正在运行的程序中时,窗口将移动到第二个监视器,但绘制错误。由于程序是最大化的,它有一个巨大的黑色边框围绕着它的三面,内容显示在与笔记本显示相同大小的区域。

Edit: I've just finished some testing and WPF does not seem to handle resolution changes from smaller resolution to higher resolution properly. The window's behavior is identical to what I'm getting when I start the program on the laptop's display & then plug in the second monitor. At least it's consistent.

编辑:我刚刚完成了一些测试,WPF似乎不能正确地处理从小分辨率到高分辨率的分辨率变化。当我在笔记本电脑上启动程序,然后插入第二个显示器时,窗口的行为和我得到的是一样的。至少这是一致的。

I've found that I can get notification of when the second monitor is plugged in, or of screen resolution changes, by handling the SystemEvents.DisplaySettingsChanged event. In my testing, I've found that the when the window moves from the smaller display to the larger one, that the Width, Height, ActualWidth, and ActualHeight are unchanged when the window moves to the larger window. The best I've been able to do is to get the Height & Width properties to values that match the working area of the monitor, but the ActualWidth and ActualHeight properties won't change.

我发现,通过处理系统事件,我可以得到当第二个监视器插入或屏幕分辨率更改时的通知。DisplaySettingsChanged事件。在我的测试中,我发现当窗口从小窗口移动到大窗口时,当窗口移动到大窗口时,宽度、高度、实际宽度和实际高度是不变的。我所能做的最好的事情是让高度和宽度属性与监视器的工作区域匹配,但是实际宽度和实际高度属性不会改变。

How do I force the window to treat my problem case as though it were just a resolution change? Or, how do I force the window to change its ActualWidth and ActualHeight properties to the correct values?

我如何强迫窗口把我的问题当作是一个解决方案的改变?或者,如何强制窗口将其实际宽度和实际高度属性更改为正确的值?

The window descends from a class I wrote called DpiAwareWindow:

这个窗口来自于我编写的名为DpiAwareWindow的类:

public class DpiAwareWindow : Window {

    private const int LOGPIXELSX               = 88;
    private const int LOGPIXELSY               = 90;
    private const int MONITOR_DEFAULTTONEAREST = 0x00000002;
    protected enum MonitorDpiType {
        MDT_Effective_DPI = 0,
        MDT_Angular_DPI   = 1,
        MDT_Raw_DPI       = 2,
        MDT_Default       = MDT_Effective_DPI
    }

    public Point CurrentDpi { get; private set; }

    public bool IsPerMonitorEnabled;

    public Point ScaleFactor { get; private set; }

    protected HwndSource source;

    protected Point systemDpi;

    protected Point WpfDpi { get; set; }

    public DpiAwareWindow()
        : base() {
        // Watch for SystemEvent notifications
        SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;

        // Set up the SourceInitialized event handler
        SourceInitialized += DpiAwareWindow_SourceInitialized;
    }

    ~DpiAwareWindow() {
        // Deregister our SystemEvents handler
        SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
    }

    private void DpiAwareWindow_SourceInitialized( object sender, EventArgs e ) {
        source = (HwndSource) HwndSource.FromVisual( this );
        source.AddHook( WindowProcedureHook );

        // Determine if this application is Per Monitor DPI Aware.
        IsPerMonitorEnabled = GetPerMonitorDPIAware() == ProcessDpiAwareness.Process_Per_Monitor_DPI_Aware;

        // Is the window in per-monitor DPI mode?
        if ( IsPerMonitorEnabled ) {
            // It is.  Calculate the DPI used by the System.
            systemDpi = GetSystemDPI();

            // Calculate the DPI used by WPF.
            WpfDpi = new Point {
                X = 96.0 * source.CompositionTarget.TransformToDevice.M11,
                Y = 96.0 * source.CompositionTarget.TransformToDevice.M22
            };

            // Get the Current DPI of the monitor of the window.
            CurrentDpi = GetDpiForHwnd( source.Handle );

            // Calculate the scale factor used to modify window size, graphics and text.
            ScaleFactor = new Point {
                X = CurrentDpi.X / WpfDpi.X,
                Y = CurrentDpi.Y / WpfDpi.Y
            };

            // Update Width and Height based on the on the current DPI of the monitor
            Width  = Width  * ScaleFactor.X;
            Height = Height * ScaleFactor.Y;

            // Update graphics and text based on the current DPI of the monitor.
            UpdateLayoutTransform( ScaleFactor );
        }
    }

    protected Point GetDpiForHwnd( IntPtr hwnd ) {
        IntPtr monitor = MonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST );

        uint newDpiX = 96;
        uint newDpiY = 96;
        if ( GetDpiForMonitor( monitor, (int) MonitorDpiType.MDT_Effective_DPI, ref newDpiX, ref newDpiY ) != 0 ) {
            return new Point {
                X = 96.0,
                Y = 96.0
            };
        }

        return new Point {
            X = (double) newDpiX,
            Y = (double) newDpiY
        };
    }

    public static ProcessDpiAwareness GetPerMonitorDPIAware() {
        ProcessDpiAwareness awareness = ProcessDpiAwareness.Process_DPI_Unaware;

        try {
            Process curProcess = Process.GetCurrentProcess();
            int result = GetProcessDpiAwareness( curProcess.Handle, ref awareness );
            if ( result != 0 ) {
                throw new Exception( "Unable to read process DPI level" );
            }

        } catch ( DllNotFoundException ) {
            try {
                // We're running on either Vista, Windows 7 or Windows 8.  Return the correct ProcessDpiAwareness value.
                awareness = IsProcessDpiAware() ? ProcessDpiAwareness.Process_System_DPI_Aware : ProcessDpiAwareness.Process_DPI_Unaware;

            } catch ( EntryPointNotFoundException ) { }

        } catch ( EntryPointNotFoundException ) {
            try {
                // We're running on either Vista, Windows 7 or Windows 8.  Return the correct ProcessDpiAwareness value.
                awareness = IsProcessDpiAware() ? ProcessDpiAwareness.Process_System_DPI_Aware : ProcessDpiAwareness.Process_DPI_Unaware;

            } catch ( EntryPointNotFoundException ) { }
        }

        // Return the value in awareness.
        return awareness;
    }

    public static Point GetSystemDPI() {
        IntPtr hDC = GetDC( IntPtr.Zero );
        int newDpiX = GetDeviceCaps( hDC, LOGPIXELSX );
        int newDpiY = GetDeviceCaps( hDC, LOGPIXELSY );
        ReleaseDC( IntPtr.Zero, hDC );

        return new Point {
            X = (double) newDpiX,
            Y = (double) newDpiY
        };
    }

    public void OnDPIChanged() {
        ScaleFactor = new Point {
            X = CurrentDpi.X / WpfDpi.X,
            Y = CurrentDpi.Y / WpfDpi.Y
        };

        UpdateLayoutTransform( ScaleFactor );
    }

    public virtual void SystemEvents_DisplaySettingsChanged( object sender, EventArgs e ) {
        // Get the handle for this window.  Need to worry about a window that has been created by not yet displayed.
        IntPtr handle = source == null ? new HwndSource( new HwndSourceParameters() ).Handle : source.Handle;

        // Get the current DPI for the window we're on.
        CurrentDpi = GetDpiForHwnd( handle );

        // Adjust the scale factor.
        ScaleFactor = new Point {
            X = CurrentDpi.X / WpfDpi.X,
            Y = CurrentDpi.Y / WpfDpi.Y
        };

        // Update the layout transform
        UpdateLayoutTransform( ScaleFactor );
    }

    private void UpdateLayoutTransform( Point scaleFactor ) {
        if ( IsPerMonitorEnabled ) {
            if ( ScaleFactor.X != 1.0 || ScaleFactor.Y != 1.0 ) {
                LayoutTransform = new ScaleTransform( scaleFactor.X, scaleFactor.Y );
            } else {
                LayoutTransform = null;
            }
        }
    }

    public virtual IntPtr WindowProcedureHook( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled ) {
        // Determine which Monitor is displaying the Window
        IntPtr monitor = MonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST );

        // Switch on the message.
        switch ( (WinMessages) msg ) {
            case WinMessages.WM_DPICHANGED:
                // Marshal the value in the lParam into a Rect.
                RECT newDisplayRect = (RECT) Marshal.PtrToStructure( lParam, typeof( RECT ) );

                // Set the Window's position & size.
                Vector ul = source.CompositionTarget.TransformFromDevice.Transform( new Vector( newDisplayRect.left, newDisplayRect.top ) );
                Vector hw = source.CompositionTarget.TransformFromDevice.Transform( new Vector( newDisplayRect.right = newDisplayRect.left, newDisplayRect.bottom - newDisplayRect.top ) );
                Left      = ul.X;
                Top       = ul.Y;
                Width     = hw.X;
                Height    = hw.Y;

                // Remember the current DPI settings.
                Point oldDpi = CurrentDpi;

                // Get the new DPI settings from wParam
                CurrentDpi = new Point {
                    X = (double) ( wParam.ToInt32() >> 16 ),
                    Y = (double) ( wParam.ToInt32() & 0x0000FFFF )
                };

                if ( oldDpi.X != CurrentDpi.X || oldDpi.Y != CurrentDpi.Y ) {
                    OnDPIChanged();
                }

                handled = true;
                return IntPtr.Zero;

            case WinMessages.WM_GETMINMAXINFO:
                // lParam has a pointer to the MINMAXINFO structure.  Marshal it into managed memory.
                MINMAXINFO mmi = (MINMAXINFO) Marshal.PtrToStructure( lParam, typeof( MINMAXINFO ) );
                if ( monitor != IntPtr.Zero ) {
                    MONITORINFO monitorInfo = new MONITORINFO();
                    GetMonitorInfo( monitor, monitorInfo );

                    // Get the Monitor's working area
                    RECT rcWorkArea    = monitorInfo.rcWork;
                    RECT rcMonitorArea = monitorInfo.rcMonitor;

                    // Adjust the maximized size and position to fit the work area of the current monitor
                    mmi.ptMaxPosition.x = Math.Abs( rcWorkArea.left   - rcMonitorArea.left );
                    mmi.ptMaxPosition.y = Math.Abs( rcWorkArea.top    - rcMonitorArea.top );
                    mmi.ptMaxSize     .x = Math.Abs( rcWorkArea.right  - rcWorkArea.left );
                    mmi.ptMaxSize     .y = Math.Abs( rcWorkArea.bottom - rcWorkArea.top );
                }

                // Copy our changes to the mmi object back to the original
                Marshal.StructureToPtr( mmi, lParam, true );
                handled = true;
                return IntPtr.Zero;

            default:
                // Let the WPF code handle all other messages. Return 0.
                return IntPtr.Zero;
        }
    }

    [DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )]
    protected static extern IntPtr GetDC( IntPtr hWnd );

    [DllImport( "gdi32.dll", CallingConvention = CallingConvention.StdCall )]
    protected static extern int GetDeviceCaps( IntPtr hDC, int nIndex );

    [DllImport( "shcore.dll", CallingConvention = CallingConvention.StdCall )]
    protected static extern int GetDpiForMonitor( IntPtr hMonitor, int dpiType, ref uint xDpi, ref uint yDpi );

    [DllImport( "user32" )]
    protected static extern bool GetMonitorInfo( IntPtr hMonitor, MONITORINFO lpmi );

    [DllImport( "shcore.dll", CallingConvention = CallingConvention.StdCall )]
    protected static extern int GetProcessDpiAwareness( IntPtr handle, ref ProcessDpiAwareness awareness );

    [DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )]
    protected static extern bool IsProcessDpiAware();

    [DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )]
    protected static extern IntPtr MonitorFromWindow( IntPtr hwnd, int flag );

    [DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )]
    protected static extern void ReleaseDC( IntPtr hWnd, IntPtr hDC );
}

public enum SizeMessages {
    SIZE_RESTORED  = 0,
    SIZE_MINIMIZED = 1,
    SIZE_MAXIMIZED = 2,
    SIZE_MAXSHOW   = 3,
    SIZE_MAXHIDE   = 4
}

public enum WinMessages : int {
    WM_DPICHANGED        = 0x02E0,
    WM_GETMINMAXINFO     = 0x0024,
    WM_SIZE              = 0x0005,
    WM_WINDOWPOSCHANGING = 0x0046,
    WM_WINDOWPOSCHANGED  = 0x0047,
}

public enum ProcessDpiAwareness {
    Process_DPI_Unaware           = 0,
    Process_System_DPI_Aware      = 1,
    Process_Per_Monitor_DPI_Aware = 2
}

I don't think that the problem is in this code; I think it's in the WPF Window class. I need to find a way to work around this problem. However, I could be wrong.

我认为问题不在于这个代码;我认为它在WPF窗口类中。我需要找到解决这个问题的方法。然而,我可能错了。

EDIT:

编辑:

I have a test program which contains a normal window that descends from my DpiAwareWindow class. It is exhibiting similar behavior when the screen resolution changes. But, as a test, I changed the code so the window descended from the Window class and I did not see the behavior. So there is something in the DpiAwareWindow code that doesn't work.

我有一个测试程序,它包含一个从我的DpiAwareWindow类派生的普通窗口。当屏幕分辨率改变时,它表现出类似的行为。但是,作为测试,我更改了代码,因此窗口从窗口类派生而来,我没有看到这种行为。DpiAwareWindow代码中有一些东西不能工作。

If it's not too much to ask, could someone with VS 2013 download this WPF Per Monitor DPI Aware sample program, build it & see if it behaves properly when started with a lower screen resolution and then the screen resolution is increased?

如果没有太多的问题,是否有人可以下载这个WPF每个监视DPI感知样本程序,构建它并看看它在开始时是否表现良好,然后屏幕分辨率增加?

Edit 2

编辑2

I've just did some testing and I've found that the problem does not happen if I comment out the entire WinMessages.WM_GETMINMAXINFO case in the WindowProcedureHook method's switch statement. The purpose of this code is to limit the size of a maximized window so it does not obscure the Task Bar.

我刚刚做了一些测试,发现如果我注释掉整个winmessage,问题不会发生。windowprocedure durehook方法的开关语句中的WM_GETMINMAXINFO用例。这段代码的目的是限制最大化窗口的大小,这样就不会模糊任务栏。

This code was added to keep a maximized window from obscuring the task bar. There seems to be some kind of interaction between what it returns and whatever logic is running in WPF when the screen resolution changes.

添加此代码是为了防止最大化窗口模糊任务栏。当屏幕分辨率改变时,它返回的内容和WPF中运行的逻辑之间似乎存在某种交互。

2 个解决方案

#1


5  

I've finally resolved this problem. It turns out that what I needed to do is change one line in the switch statement in the WindowProcedureHook method:

我终于解决了这个问题。原来我需要做的是在window过程rehook方法的switch语句中更改一行:

        case WinMessages.WM_GETMINMAXINFO:
            // lParam has a pointer to the MINMAXINFO structure.  Marshal it into managed memory.
            MINMAXINFO mmi = (MINMAXINFO) Marshal.PtrToStructure( lParam, typeof( MINMAXINFO ) );
            if ( monitor != IntPtr.Zero ) {
                MONITORINFO monitorInfo = new MONITORINFO();
                GetMonitorInfo( monitor, monitorInfo );

                // Get the Monitor's working area
                RECT rcWorkArea    = monitorInfo.rcWork;
                RECT rcMonitorArea = monitorInfo.rcMonitor;

                // Adjust the maximized size and position to fit the work area of the current monitor
                mmi.ptMaxPosition.x = Math.Abs( rcWorkArea.left   - rcMonitorArea.left );
                mmi.ptMaxPosition.y = Math.Abs( rcWorkArea.top    - rcMonitorArea.top );
                mmi.ptMaxSize     .x = Math.Abs( rcWorkArea.right  - rcWorkArea.left );
                mmi.ptMaxSize     .y = Math.Abs( rcWorkArea.bottom - rcWorkArea.top );
            }

            // Copy our changes to the mmi object back to the original
            Marshal.StructureToPtr( mmi, lParam, true );
            handled = false; // This line used to set handled to true
            return IntPtr.Zero;

With this change, the code that's normally executed in WPF when the WM_GETMINMAXINFO message is received still runs, but it uses the change to the MINMAXINFO object made by the code in order to do its work. With this change, the WPF window handles the resolution changes properly.

通过这个更改,WPF中通常在接收WM_GETMINMAXINFO消息时执行的代码仍然运行,但是它使用代码对MINMAXINFO对象的更改来完成它的工作。通过此更改,WPF窗口将正确地处理解析更改。

EDIT

编辑

And it turns out that the code no longer needs to look specifically for a screen resolution or installed monitor change. That is, the SystemEvent.DisplaySettingsChanged event handler is no longer needed.

事实证明,代码不再需要特别查找屏幕分辨率或安装的监视器更改。也就是说,SystemEvent。不再需要DisplaySettingsChanged事件处理程序。

#2


0  

Turns out its not a complicated fix. The MinTrackSize point (bounds) needs to be set to the working area dimensions of the secondary monitor.

结果证明这不是一个复杂的解决方案。需要将MinTrackSize点(界限)设置为次要监视器的工作区维度。

private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{
    MINMAXINFO mmi = (MINMAXINFO)System.Runtime.InteropServices.Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

    /*  0x0001        // center rect to monitor 
        0x0000        // clip rect to monitor 
        0x0002        // use monitor work area 
        0x0000        // use monitor entire area */

    int MONITOR_DEFAULTTONEAREST = 0x00000002;
    System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

    if (monitor != System.IntPtr.Zero)
    {
        MONITORINFO monitorInfo = new MONITORINFO();
        GetMonitorInfo(monitor, monitorInfo);
        RECT rcWorkArea = monitorInfo.rcWork;
        RECT rcMonitorArea = monitorInfo.rcMonitor;

        // set the maximize size of the application
        mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
        mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
        mmi.ptMaxSize.x     = Math.Abs(rcWorkArea.right - rcWorkArea.left);
        mmi.ptMaxSize.y     = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);

        // reset the bounds of the application to the monitor working dimensions
        mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x;
        mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y;
    }

    System.Runtime.InteropServices.Marshal.StructureToPtr(mmi, lParam, true);
}

[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct MINMAXINFO
{
    public POINT ptReserved;
    public POINT ptMaxSize;
    public POINT ptMaxPosition;
    public POINT ptMinTrackSize;
    public POINT ptMaxTrackSize;
};

#1


5  

I've finally resolved this problem. It turns out that what I needed to do is change one line in the switch statement in the WindowProcedureHook method:

我终于解决了这个问题。原来我需要做的是在window过程rehook方法的switch语句中更改一行:

        case WinMessages.WM_GETMINMAXINFO:
            // lParam has a pointer to the MINMAXINFO structure.  Marshal it into managed memory.
            MINMAXINFO mmi = (MINMAXINFO) Marshal.PtrToStructure( lParam, typeof( MINMAXINFO ) );
            if ( monitor != IntPtr.Zero ) {
                MONITORINFO monitorInfo = new MONITORINFO();
                GetMonitorInfo( monitor, monitorInfo );

                // Get the Monitor's working area
                RECT rcWorkArea    = monitorInfo.rcWork;
                RECT rcMonitorArea = monitorInfo.rcMonitor;

                // Adjust the maximized size and position to fit the work area of the current monitor
                mmi.ptMaxPosition.x = Math.Abs( rcWorkArea.left   - rcMonitorArea.left );
                mmi.ptMaxPosition.y = Math.Abs( rcWorkArea.top    - rcMonitorArea.top );
                mmi.ptMaxSize     .x = Math.Abs( rcWorkArea.right  - rcWorkArea.left );
                mmi.ptMaxSize     .y = Math.Abs( rcWorkArea.bottom - rcWorkArea.top );
            }

            // Copy our changes to the mmi object back to the original
            Marshal.StructureToPtr( mmi, lParam, true );
            handled = false; // This line used to set handled to true
            return IntPtr.Zero;

With this change, the code that's normally executed in WPF when the WM_GETMINMAXINFO message is received still runs, but it uses the change to the MINMAXINFO object made by the code in order to do its work. With this change, the WPF window handles the resolution changes properly.

通过这个更改,WPF中通常在接收WM_GETMINMAXINFO消息时执行的代码仍然运行,但是它使用代码对MINMAXINFO对象的更改来完成它的工作。通过此更改,WPF窗口将正确地处理解析更改。

EDIT

编辑

And it turns out that the code no longer needs to look specifically for a screen resolution or installed monitor change. That is, the SystemEvent.DisplaySettingsChanged event handler is no longer needed.

事实证明,代码不再需要特别查找屏幕分辨率或安装的监视器更改。也就是说,SystemEvent。不再需要DisplaySettingsChanged事件处理程序。

#2


0  

Turns out its not a complicated fix. The MinTrackSize point (bounds) needs to be set to the working area dimensions of the secondary monitor.

结果证明这不是一个复杂的解决方案。需要将MinTrackSize点(界限)设置为次要监视器的工作区维度。

private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{
    MINMAXINFO mmi = (MINMAXINFO)System.Runtime.InteropServices.Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

    /*  0x0001        // center rect to monitor 
        0x0000        // clip rect to monitor 
        0x0002        // use monitor work area 
        0x0000        // use monitor entire area */

    int MONITOR_DEFAULTTONEAREST = 0x00000002;
    System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

    if (monitor != System.IntPtr.Zero)
    {
        MONITORINFO monitorInfo = new MONITORINFO();
        GetMonitorInfo(monitor, monitorInfo);
        RECT rcWorkArea = monitorInfo.rcWork;
        RECT rcMonitorArea = monitorInfo.rcMonitor;

        // set the maximize size of the application
        mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
        mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
        mmi.ptMaxSize.x     = Math.Abs(rcWorkArea.right - rcWorkArea.left);
        mmi.ptMaxSize.y     = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);

        // reset the bounds of the application to the monitor working dimensions
        mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x;
        mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y;
    }

    System.Runtime.InteropServices.Marshal.StructureToPtr(mmi, lParam, true);
}

[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct MINMAXINFO
{
    public POINT ptReserved;
    public POINT ptMaxSize;
    public POINT ptMaxPosition;
    public POINT ptMinTrackSize;
    public POINT ptMaxTrackSize;
};