Superar el límite de tamaño mínimo de Windows Forms de Windows Forms

En una aplicación que estoy desarrollando, necesito poder hacer un formulario de Windows más pequeño que el límite de altura mínimo impuesto por el sistema operativo (36 px en Vista). Intenté interceptar WM_GETMINMAXINFO y proporcionar mi propia información para anular los límites del OS, pero esto solo funciona para el usuario. Desde el código I puedo establecer la altura en un valor menor que el límite, pero mi cambio solo funciona hasta que WM_WINDOWPOSCHANGED se publique en la cola de mensajes (lo que sucede justo después de que cambie la altura).

Después de mucha experimentación y prueba y error, descubrí una solución. Estaba anulando OnResize y ajustando el tamaño del formulario al ListBox en él (ver mi comentario sobre la respuesta de John Saunders).

Como mencioné en mi pregunta, noté que el tamaño del formulario regresa después de enviar WM_WINDOWPOSCHANGED. La investigación adicional reveló que la regresión de tamaño en realidad comienza cuando se envía WM_WINDOWPOSCHANGING.

WM_WINDOWPOSCHANGING es el mensaje de hermana de WM_WINDOWPOSCHANGED que ocurre antes de que el tamaño de la ventana realmente cambie. No sé por qué, pero por alguna razón WM_WINDOWPOSCHANGING ajusta ciegamente el tamaño del formulario a los límites especificados del sistema operativo (aparentemente no consulta la ventana con WM_GETMINMAXINFO). Por lo tanto, tenía que interceptar WM_WINDOWPOSCHANGING y anularlo con el tamaño que realmente quería.

Esto significa que ya no estoy conformando el tamaño del formulario con OnResize, sino que estoy cumpliendo con el tamaño del formulario cuando recibo WM_WINDOWPOSCHANGING. Esto es incluso mejor que OnResize, porque no hay un parpadeo asociado que se produce cuando se cambia el tamaño y luego se cambia de nuevo cuando el tamaño se ajusta durante OnResize.

Además, es necesario interceptar y anular WM_GETMINMAXINFO; de lo contrario, incluso interceptar WM_WINDOWPOSCHANGING no te servirá de nada.

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 estaba tan cerca!

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

¿El truco para mí? Establecí el tamaño mínimo del formulario en lo que quiera que sea el tamaño real del formulario.

En mi proyecto, eso es todo lo que tengo que hacer para que el formulario sea minúsculo, eso podría ser porque establecer el tamaño mínimo desencadena SetBoundsCore o tal vez estoy haciendo algo más que lo desencadena; en cuyo caso, supongo que tienes que activar de alguna manera SetBoundsCore tú mismo.

Ojalá pudiera dar más de +1 a Zach por eso, es genial y salvó mi tocino. Para lectores futuros, aquí está la traducción VB del código de Zach:

 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  _ 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 

Al jugar con un tamaño mínimo de formulario, noté que el tamaño mínimo de formulario está restringido al tamaño mínimo de formulario del sistema en Form.SetBoundsCore (…). Cuando miro IL desastrosamente, encontré que este método .Net siempre corrige lo que le das (ancho y alto) a SystemInformation.MinimumWindowSize si son más pequeños y el formulario no tiene un elemento primario y su FormBorderStyle es FixedSingle, Fixed3D , FixedDialog o considerable.

La solución más fácil para este problema no es manejar WM_WINDOWPOSCHANGING, sino simplemente configurar FormBorderStyle = System.Windows.Forms.FormBorderStyle.None en el constructor de formularios.

¿Quieres decir, aparte de usar un sistema operativo diferente?

¿Qué tal “No use un formulario”? ¿Qué tan grande es esto que necesitas mostrar? Un pixel? ¿Necesita la funcionalidad completa de Windows Forms?

Ahora, no sé exactamente cómo hacer lo anterior, pero podría ser un comienzo para ti, piensa fuera del cuadro (delimitador).

Seguí la respuesta de Zach y casi resolvió mi problema. Sin embargo, en una configuración de monitor dual, el formulario desapareció cuando se maximizó en la segunda pantalla. Por alguna razón, Windows colocó el formulario fuera de la región visible. Agregar una prueba para la pantalla principal resolvió este problema para mí:

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