【问题标题】:Overcome OS Imposed Windows Form Minimum Size Limit克服操作系统强加的 Windows 窗体最小大小限制
【发布时间】:2010-11-02 19:16:43
【问题描述】:

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

【问题讨论】:

    标签: .net winforms minimum-size


    【解决方案1】:

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

    base.WndProc(ref m)
    

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

    【讨论】:

      【解决方案2】:

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

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

      WM_WINDOWPOSCHANGING 是 WM_WINDOWPOSCHANGED 的姊妹消息,它发生在窗口大小实际改变之前。我不知道为什么,但由于某种原因,WM_WINDOWPOSCHANGING 盲目地将表单的大小符合操作系统指定的限制(显然它不会使用 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;
      }
      

      【讨论】:

      • Zach,你的方法很有魅力,对我帮助很大。谢谢!
      • 太好了,如果你有兴趣,我欠你一份 WindowTabs!
      • @MauriceFlanagan 当然,在 twitter 上给我发私信(我是@zachoverflow),或者在 zachjohnson.net 上给我发一封电子邮件。
      • 为了让代码充分发挥作用,在第一个 if 中写着“对 windowPos 进行更改”,应添加以下内容:windowPos.cx = this.Width; windowPos.cy = this.Height;,这似乎就足够了。谢谢扎克!
      • 像魅力一样工作,非常感谢!我确实必须添加一个 SecuritySafeCritical 属性才能在 Marshal.StructureToPtr 处不获取 MethodAccessException(.NET 4.0):[SecuritySafeCritical] // 否则将获取 MethodAccessException。
      【解决方案3】:

      我按照 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);
              }
          }
      }
      

      【讨论】:

        【解决方案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);
            }
        

        为我做了诀窍。我将表单的最小大小设置为我想要表单的实际大小。

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

        【讨论】:

          【解决方案5】:

          当使用最小表单大小时,我注意到,最小表单大小被限制为 Form.SetBoundsCore(...) 中的系统最小表单大小。当我查看 IL disassemly 时,我发现这个 .Net 方法总是更正你给它的(宽度和高度)到 SystemInformation.MinimumWindowSize 如果它们更小并且表单没有父级并且它的 FormBorderStyle 是 FixedSingle,Fixed3D 、FixedDialog 或 Sizable。

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

          【讨论】:

          • 如果您不想在表单上设置边框,但如果您想保留边框但窗口稍小一些(想想智能感知建议窗口中只有 1 个项目),这会很好用,则需要另一条路线。
          【解决方案6】:

          我希望我能为此给 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
          

          【讨论】:

            【解决方案7】:

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

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

            现在,我不完全知道如何执行上述操作,但这对您来说可能是一个开始 - 在(边界)框之外思考。

            【讨论】:

            • 我的表单包含一个列表框(其中包含一个或多个项目),我正在尝试使表单符合列表框的大小。当 ListBox 只包含一项时,表单的高度只需 16 px 加上边框的高度(会有所不同)。在 Vista 中,总高度通常约为 32 px,因此 Vista 的 36 px 最小高度会在表单底部留下额外的空白(看起来很难看)。在 XP 中情况是一样的。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-07-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-12-05
            • 1970-01-01
            • 2019-11-01
            相关资源
            最近更新 更多