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

xn1cxnb4  于 2023-03-31  发布在  Windows
关注(0)|答案(8)|浏览(149)

在我正在开发的一个应用程序中,我需要能够使windows窗体小于操作系统规定的最小高度限制(Vista中为36px).我曾尝试拦截WM_GETMINMAXINFO并提供自己的信息来覆盖OS限制,但这只对用户有效.从代码中我可以将高度设置为小于限制的值但我的更改只在WM_WINDOWPOSCHANGED被发送到消息队列之前有效(这发生在我更改高度之后)。

y53ybaqx

y53ybaqx1#

经过大量的实验和反复试验,我发现了一个解决方案。我重写了OnResize,并使表单的大小符合其中的ListBox(请参阅我对John Saunders回答的评论)。
正如我在我的问题中提到的,我注意到在发送WM_WINDOWPOSCHANGED后,窗体的大小会退化。进一步的调查显示,大小退化实际上是在发送WM_WINDOWPOSCHANGING时开始的。
WM_WINDOWPOSCHANGING是WM_WINDOWPOSCHANGED的姐妹消息,它在窗口大小实际改变之前发生。我不知道为什么,但是由于某种原因,WM_WINDOWPOSCHANGING盲目地使窗体的大小符合OS指定的限制(显然它没有用WM_GETMINMAXINFO查询窗口)。因此,我需要拦截WM_WINDOWPOSCHANGING并用我真正想要的大小覆盖它。
这意味着我不再使用OnResize来确定窗体的大小,而是在接收WM_WINDOWPOSCHANGING时确定窗体的大小。这甚至比OnResize更好,因为在OnResize期间,当更改大小时不会出现相关的 Flink ,然后在更改大小时再次更改。
另外,有必要拦截并覆盖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;
}
yuvru6vn

yuvru6vn2#

阿列克谢是如此接近!

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

我把窗体的最小大小设置为我想要的窗体的实际大小。
在我的项目中,这就是我所要做的,以使窗体变小,这可能是因为设置最小大小触发了SetBoundsCore,也可能是我正在做的其他事情触发了它;在这种情况下,我猜你必须 * 以某种方式 * 自己触发SetBoundsCore。

vzgqcmou

vzgqcmou3#

当使用最小窗体大小时,我注意到,最小窗体大小被限制为Form.SetBoundsCore(...)中的系统最小窗体大小。当我查看IL反汇编时,我发现,如果SystemInformation.MinimumWindowSize更小,并且窗体没有父窗体,并且其FormBorderStyle是FixedSingle,Fixed3D,FixedDialog或Sizable,则此.Net方法总是更正您给予的内容(宽度和高度)。
解决这个问题最简单的方法不是处理WM_WINDOWPOSCHANGING,而是在表单构造函数中设置FormBorderStyle = System.Windows.Forms.FormBorderStyle.None

u7up0aaq

u7up0aaq4#

我希望我能给予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
mefy6pfw

mefy6pfw5#

你的意思是,除了使用不同的操作系统?
“不使用窗体”怎么样?你需要显示多大的东西?一个像素?它需要完整的Windows窗体功能吗?
现在,我不知道如何做到这一点,但它可能是一个开始,为您-认为以外的(边界)框。

nue99wik

nue99wik6#

我按照Zach的回答,它几乎解决了我的问题。然而,在双显示器设置中,当窗体在第二个屏幕上最大化时,窗体消失了。由于某种原因,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);
        }
    }
}
jjhzyzn0

jjhzyzn07#

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

base.WndProc(ref m)

在向WndProc函数添加钩子时,请使用此函数。

7tofc5zh

7tofc5zh8#

我正在使用C# / WinForms,今天我遇到了这个限制。
经过一些测试,我发现设置MinimumSize = new Size(1, 1);将使用户能够缩小窗口超出系统限制,所以没有必要处理WM_GETMINMAXINFO消息-事实上,设置MinimumSize后,WM_GETMINMAXINFO将返回ptMinTrackSize中的设置值,只是默认情况下MinimumSize设置为(0, 0),这会导致返回系统默认值。
尽管将MinimumSize设置为(1, 1)允许用户进一步缩小窗口(即使将FormBorderStyle设置为Sizable (default)),但当我尝试在代码中设置大小时,它仍然受到系统默认值的限制。我引用Alexey的回答:
当我查看IL反汇编时,我发现,这个.Net方法总是纠正你给予它的东西(宽度和高度)到SystemInformation.MinimumWindowSize,如果它们更小,并且窗体没有父窗体,它的FormBorderStyle是FixedSingle,Fixed 3D,FixedDialog或Sizable。
所以......似乎试图删除这个不必要的约束将是非常困难的。然而,我的程序已经有一个处理WM_WINDOWPOSCHANGING的例程,所以我想出了一个简单的解决方案:

***(重要)**首先设置MinimumSize = new Size(1, 1);,使WM_GETMINMAXINFO返回(1,1),而不是系统默认值,表单只需要设置一次。

  • 当收到一个特定大小(幻数)的WM_WINDOWPOSCHANGING消息时,将大小设置为所需的大小。这个“幻数大小”不应该太大或太小,我使用的是SystemInformation.MinimumWindowSize
private Size DesiredSize { get; set; } = Size.Empty;

    private void SetSizeBelowSystemMinimum(Size size)
    {
        DesiredSize = size;
        Size = SystemInformation.MinimumWindowSize;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct WINDOWPOS
    {
        public IntPtr hwnd;
        public IntPtr hwndInsertAfter;
        public int x, y, cx, cy;
        public uint flags;
    }

    protected override void WndProc(ref Message m)
    {
        const int WM_WINDOWPOSCHANGING = 0x0046;

        switch (m.Msg) {
            case WM_WINDOWPOSCHANGING:
                var w = (WINDOWPOS)Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS));
                //Console.WriteLine($"WM_WINDOWPOSCHANGING: x{w.x} y{w.y} w{w.cx} h{w.cy}");

                if (DesiredSize != Size.Empty && w.cx == SystemInformation.MinimumWindowSize.Width && w.cy == SystemInformation.MinimumWindowSize.Height) {
                    w.cx = DesiredSize.Width;
                    w.cy = DesiredSize.Height;
                    Marshal.StructureToPtr(w, m.LParam, true);
                    DesiredSize = Size.Empty;
                }
                break;
        }

        base.WndProc(ref m);
    }

相关问题