克服操作系统强加的Windows窗体最小大小限制

时间:2009-06-14 07:18:09

标签: .net winforms minimum-size

在我正在开发的应用程序中,我需要能够使窗口形式小于操作系统施加的最小高度限制(Vista中为36 px)。我试图拦截WM_GETMINMAXINFO并提供我自己的信息来覆盖操作系统限制,但这仅适用于用户。从代码中我可以将高度设置为小于限制的值,但是我的更改只有在WM_WINDOWPOSCHANGED发布到消息队列之后才会起作用(这在我更改高度之后发生)。

7 个答案:

答案 0 :(得分:14)

经过多次实验和反复试验,我发现了一个解决方案。我重写了OnResize,并将表单的大小与其中的ListBox一致(请参阅我对John Saunders的评论)。

正如我在我的问题中提到的,我注意到在发送WM_WINDOWPOSCHANGED之后,表单的大小会下降。进一步调查显示,大小回归实际上是在发送WM_WINDOWPOSCHANGING时开始的。

WM_WINDOWPOSCHANGING是WM_WINDOWPOSCHANGED的姐妹消息,它在窗口大小实际发生变化之前发生。我不知道为什么,但由于某种原因,WM_WINDOWPOSCHANGING盲目地将窗体的大小与OS指定的限制一致(显然它不会使用WM_GETMINMAXINFO查询窗口)。因此,我需要拦截WM_WINDOWPOSCHANGING并用我真正想要的大小覆盖它。

这意味着我不再使用OnResize来符合表单的大小,而是在接收WM_WINDOWPOSCHANGING时符合表单大小。这甚至比OnResize更好,因为在更改大小时没有相关的闪烁,然后在OnResize期间符合大小时再次更改。

此外,有必要拦截并覆盖WM_GETMINMAXINFO,否则即使拦截WM_WINDOWPOSCHANGING也没有用。

using System.Runtime.InteropServices;

private const int WM_WINDOWPOSCHANGING = 0x0046;
private const int WM_GETMINMAXINFO = 0x0024;

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_WINDOWPOSCHANGING)
    {
        WindowPos windowPos = (WindowPos)m.GetLParam(typeof(WindowPos));

        // Make changes to windowPos

        // Then marshal the changes back to the message
        Marshal.StructureToPtr(windowPos, m.LParam, true);
    }

    base.WndProc(ref m);

    // Make changes to WM_GETMINMAXINFO after it has been handled by the underlying
    // WndProc, so we only need to repopulate the minimum size constraints
    if (m.Msg == WM_GETMINMAXINFO)
    {
        MinMaxInfo minMaxInfo = (MinMaxInfo)m.GetLParam(typeof(MinMaxInfo));
        minMaxInfo.ptMinTrackSize.x = this.MinimumSize.Width;
        minMaxInfo.ptMinTrackSize.y = this.MinimumSize.Height;
        Marshal.StructureToPtr(minMaxInfo, m.LParam, true);
   }
}

struct WindowPos
{
     public IntPtr hwnd;
     public IntPtr hwndInsertAfter;
     public int x;
     public int y;
     public int width;
     public int height;
     public uint flags;
}

struct POINT
{
    public int x;
    public int y;
}

struct MinMaxInfo
{
    public POINT ptReserved;
    public POINT ptMaxSize;
    public POINT ptMaxPosition;
    public POINT ptMinTrackSize;
    public POINT ptMaxTrackSize;
}

答案 1 :(得分:4)

Alexey非常接近!

    protected override void SetBoundsCore(int x,int y,int width, int height,BoundsSpecified specified)
    {
        base.SetBoundsCore(x, y, this.MinimumSize.Width, this.MinimumSize.Height, specified);
    }

我的诀窍。我将Form的最小大小设置为我想要Form的实际大小。

在我的项目中,我需要做的就是让Form变得很小,这可能是因为设置最小大小会触发SetBoundsCore,或者我正在做其他触发它的事情;在这种情况下,我猜你必须以某种方式自己触发SetBoundsCore。

答案 2 :(得分:1)

我希望我可以为Zach提供超过+1的奖励,这很棒并且保存了我的培根。 对于未来的读者,这里是Zach代码的VB翻译:

Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports System.Drawing

Public Class MyForm

    ' Ghastly hack to allow the form to be narrower than the widows-imposed limit (about 132 in WIndows 7)
    ' Thanks to http://stackoverflow.com/questions/992352/overcome-os-imposed-windows-form-minimum-size-limit

    Private Const WM_WINDOWPOSCHANGING As Integer = &H46
    Private Const WM_GETMINMAXINFO As Integer = &H24
    Protected Overrides Sub WndProc(ByRef m As Message)
        If m.Msg = WM_WINDOWPOSCHANGING Then
            Dim windowPos As WindowPos = CType(m.GetLParam(GetType(WindowPos)), WindowPos)

            ' Make changes to windowPos

            ' Then marshal the changes back to the message
            Marshal.StructureToPtr(windowPos, m.LParam, True)
        End If

        MyBase.WndProc(m)

        ' Make changes to WM_GETMINMAXINFO after it has been handled by the underlying
        ' WndProc, so we only need to repopulate the minimum size constraints
        If m.Msg = WM_GETMINMAXINFO Then
            Dim minMaxInfo As MINMAXINFO = DirectCast(m.GetLParam(GetType(MINMAXINFO)), MINMAXINFO)
            minMaxInfo.ptMinTrackSize.X = Me.MinimumSize.Width
            minMaxInfo.ptMinTrackSize.Y = Me.MinimumSize.Height
            Marshal.StructureToPtr(minMaxInfo, m.LParam, True)
        End If
    End Sub

    Private Structure WindowPos
        Public hwnd As IntPtr
        Public hwndInsertAfter As IntPtr
        Public x As Integer
        Public y As Integer
        Public width As Integer
        Public height As Integer
        Public flags As UInteger
    End Structure
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure MINMAXINFO
        Dim ptReserved As Point
        Dim ptMaxSize As Point
        Dim ptMaxPosition As Point
        Dim ptMinTrackSize As Point
        Dim ptMaxTrackSize As Point
    End Structure

    .... rest of the form

End Class

答案 3 :(得分:1)

当玩最小的表单大小时,我注意到,最小的表单大小仅限于Form.SetBoundsCore(...)中的系统最小表单大小。当我以反汇编方式查看IL时,我发现,如果它们更小并且表单没有父级且其FormBorderStyle是FixedSingle,Fixed3D,那么这个.Net方法总是会纠正你给它的内容(宽度和高度)(SystemInformation.MinimumWindowSize)。 ,FixedDialog或Sizable。

解决此问题的最简单方法是不处理WM_WINDOWPOSCHANGING,而只是在表单构造函数中设置 FormBorderStyle = System.Windows.Forms.FormBorderStyle.None

答案 4 :(得分:0)

你的意思是,除了使用不同的操作系统外?

“不要使用表格”怎么样?你需要展示这件事有多大?一个像素?它是否需要完整的Windows窗体功能?

现在,我并不完全知道如何做到这一点,但它可能是一个开始 - 在(边界)框之外思考。

答案 5 :(得分:0)

我按照扎克的回答,几乎解决了我的问题。但是,在双显示器设置中,当在第二个屏幕上最大化时,表单消失了。出于某种原因,Windows将表单定位在可见区域之外。为主屏幕添加测试为我解决了这个问题:

if (m.Msg == (int)CWinApi.Messages.WM_GETMINMAXINFO)
{
    if (this.FormBorderStyle == System.Windows.Forms.FormBorderStyle.None)
    {
        Screen screen = Screen.FromControl(this);

        if (screen.Primary)
        {
            CWinApi.MINMAXINFO minMaxInfo = (CWinApi.MINMAXINFO)m.GetLParam(typeof(CWinApi.MINMAXINFO));

            minMaxInfo.ptMaxSize.x = screen.WorkingArea.Size.Width;
            minMaxInfo.ptMaxSize.y = screen.WorkingArea.Size.Height;
            minMaxInfo.ptMaxPosition.x = screen.WorkingArea.X;
            minMaxInfo.ptMaxPosition.y = screen.WorkingArea.Y;

            System.Runtime.InteropServices.Marshal.StructureToPtr(minMaxInfo, m.LParam, true);
        }
    }
}

答案 6 :(得分:0)

有没有人有这个的 WPF 版本? 我无法让它在我的窗口上工作,似乎没有办法调用

base.WndProc(ref m)

向 WndProc 函数添加钩子时的函数。