После долгих экспериментов и проб и ошибок я нашел решение. Я переопределял OnResize и соответствовал размеру формы списку в нем (см. Мой комментарий к ответу Джона Сондерса).
Как я уже упоминал в своем вопросе, я заметил, что размер формы уменьшается после отправки 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;
}