在我正在开发的应用程序中,我需要能够使窗口窗体小于操作系统施加的最小高度限制(Vista 中为 36 像素)。我尝试拦截 WM_GETMINMAXINFO 并提供我自己的信息来覆盖操作系统限制,但这仅适用于用户。从代码中,我可以将高度设置为小于限制的值,但我的更改仅在 WM_WINDOWPOSCHANGED 发布到消息队列之前有效(这发生在我更改高度之后)。
7 回答
经过大量的实验和反复试验,我找到了一个解决方案。我正在覆盖 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;
}
阿列克谢离得太近了!
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 。
我希望我能为此给 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
当使用最小表单大小时,我注意到,最小表单大小被限制为 Form.SetBoundsCore(...) 中的系统最小表单大小。当我查看 IL disassemly 时,我发现这个 .Net 方法总是更正你给它的(宽度和高度)到 SystemInformation.MinimumWindowSize 如果它们更小并且表单没有父级并且它的 FormBorderStyle 是 FixedSingle,Fixed3D , FixedDialog 或 Sizable。
这个问题最简单的解决方案是不处理 WM_WINDOWPOSCHANGING,而只是在表单构造函数中设置FormBorderStyle = System.Windows.Forms.FormBorderStyle.None。
你的意思是,除了使用不同的操作系统?
“不要使用表格”怎么样?你需要展示的这个东西有多大?一个像素?它是否需要完整的 Windows 窗体功能?
现在,我不完全知道如何执行上述操作,但这对您来说可能是一个开始 - 在(边界)框之外思考。
I followed Zach's answer and it almost solved my problem. However, in a dual monitor setup, the form disappeared when it was maximized on the second screen. For some reason Windows positioned the form outside the visible region. Adding a test for the primary screen solved this problem for me:
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);
}
}
}
Has does anyone have a WPF version of this? I could not get it to work on my window, there doesnt seems a way to call the
base.WndProc(ref m)
function when adding a hook to the WndProc function.