在我正在开发的应用程序中,我需要能够使 Windows 窗体小于操作系统强制规定的最小高度限制(在 Vista 中为 36 像素)。我尝试拦截 WM_GETMINMAXINFO 并提供自己的信息来覆盖操作系统的限制,但这只适用于用户。从代码中,我可以将高度设置为小于限制的值,但我的更改仅在 WM_WINDOWPOSCHANGED 被发送到消息队列之前生效(这发生在我更改高度后不久)。
在我正在开发的应用程序中,我需要能够使 Windows 窗体小于操作系统强制规定的最小高度限制(在 Vista 中为 36 像素)。我尝试拦截 WM_GETMINMAXINFO 并提供自己的信息来覆盖操作系统的限制,但这只适用于用户。从代码中,我可以将高度设置为小于限制的值,但我的更改仅在 WM_WINDOWPOSCHANGED 被发送到消息队列之前生效(这发生在我更改高度后不久)。
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;
}
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。
当我尝试最小化窗体大小时,我注意到,Form.SetBoundsCore(...) 中的最小窗体大小被限制为系统最小窗体大小。通过查看IL反汇编,我发现,如果宽度和高度比系统最小窗口大小要小,并且该窗体没有父级,并且其FormBorderStyle为FixedSingle、Fixed3D、FixedDialog或Sizable,那么这个.Net方法会自动将它们更正为SystemInformation.MinimumWindowSize。
解决此问题的最简单方法不是处理 WM_WINDOWPOSCHANGING,而是在窗体构造函数中设置 FormBorderStyle = System.Windows.Forms.FormBorderStyle.None。
我真希望能给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 https://dev59.com/Y0fSa4cB1Zd3GeqPBv_-
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
我按照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);
}
}
}
我正在使用C# / WinForms,今天遇到了这个限制。
经过一些测试,我发现设置MinimumSize = new Size(1, 1);
将使用户能够缩小窗口超出系统限制,因此不必处理WM_GETMINMAXINFO
消息 - 实际上,在设置MinimumSize
之后,WM_GETMINMAXINFO
将在ptMinTrackSize
中返回设置的值,只是默认情况下MinimumSize
设置为(0, 0)
,导致返回系统默认值。
即使将MinimumSize
设置为(1, 1)
允许用户进一步缩小窗口(即使将FormBorderStyle
设置为Sizable(默认)
),但当我尝试在代码中设置大小时,仍受到系统默认值的限制。我引用了Alexey的答案中的这部分内容:
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);
}
base.WndProc(ref m)
在向 WndProc 函数添加钩子时使用的函数。
你的意思是,除了使用不同的操作系统之外?
那么“不使用表单”怎么样?你需要显示多大的东西?一个像素吗?它需要完整的Windows Forms功能吗?
现在,我不确定如何实现上述内容,但这可能是你的一个起点——跳出(边界)框架。
windowPos.cx = this.Width; windowPos.cy = this.Height;
看起来这就足够了。谢谢 Zach! - interDist[SecuritySafeCritical] //否则会出现MethodAccessException。
- Wout