Преодолеть ограничение на размер формы Windows для Windows

В приложении, которое я разрабатываю, мне нужно, чтобы форма окна была меньше минимального предела высоты, установленного операционной системой (36 пикселей в Vista). Я попытался перехватить WM_GETMINMAXINFO и предоставить свою собственную информацию для переопределения ограничений ОС, но это работает только для пользователя. Из кода я могу установить высоту на значение, меньшее, чем предел, но мое изменение работает только до тех пор, пока WM_WINDOWPOSCHANGED не будет опубликован в очереди сообщений (что происходит сразу после того, как я изменяю высоту).

Ответы на вопрос(6)

Как насчет "Не используйте форму"? Насколько велика эта вещь, которую вам нужно показать? Пиксель? Требуется ли полная функциональность Windows Forms?

Теперь я точно не знаю, как это сделать, но это может быть началом для вас - подумайте за пределами (ограничивающей) рамки.

 Zach Johnson15 июн. 2009 г., 02:18
Моя форма содержит ListBox (который содержит один или несколько элементов), и я пытаюсь привести форму в соответствие с размером ListBox. Когда ListBox содержит только один элемент, высота формы должна быть только 16 пикселей плюс высота границ (которая варьируется). В Vista общая высота обычно составляет около 32 пикселей, поэтому минимальная высота Vista в 36 пикселей оставляет дополнительные пробелы в нижней части формы (что выглядит некрасиво). В XP ситуация такая же.

что минимальный размер формы ограничен системным минимальным размером формы в Form.SetBoundsCore (...). Когда я разбираю IL, я обнаружил, что этот метод .Net всегда корректирует то, что вы ему даете (ширина и высота), в SystemInformation.MinimumWindowSize, если они меньше, а форма не имеет родителя, а ее FormBorderStyle - FixedSingle, Fixed3D , FixedDialog или Sizable.

Самым простым решением этой проблемы является не обработка WM_WINDOWPOSCHANGING, а просто установкаFormBorderStyle = System.Windows.Forms.FormBorderStyle.None в конструкторе формы.

 Zach Johnson12 нояб. 2011 г., 20:23
Это будет хорошо работать, если вы не хотите, чтобы граница была в вашей форме, но если вы хотите сохранить границу, но иметь немного меньшее окно (например, окно с предложением intellisense, содержащее только 1 элемент), тогда потребуется другой маршрут.

и он почти решил мою проблему. Однако в настройке с двумя мониторами форма исчезла, когда ее развернули на втором экране. По какой-то причине 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);
        }
    }
}

это здорово и спасло мой бекон. Для будущих читателей, вот перевод 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
Решение Вопроса

делял 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;
}
 Maurice Flanagan21 дек. 2011 г., 01:16
Отлично, я должен вам копию WindowTabs, если вам интересно!
 wenqiang04 янв. 2011 г., 10:56
Зак, твой подход работает как шарм, и он мне очень помогает. Спасибо!
 interDist21 окт. 2012 г., 23:20
Чтобы код работал полностью, в первомif где написано «Внести изменения в windowPos», следует добавить следующее:windowPos.cx = this.Width; windowPos.cy = this.Height; и, кажется, этого будет достаточно. Спасибо Зак!
 Wout07 авг. 2013 г., 17:01
Работает как шарм, большое спасибо! Мне пришлось добавить атрибут SecuritySafeCritical, чтобы не получить исключение MethodAccessException в Marshal.StructureToPtr (.NET 4.0): [SecuritySafeCritical] // В противном случае будет получено исключение MethodAccessException.
 Zach Johnson21 дек. 2011 г., 06:12
@MauriceFlanagan Конечно, пришлите мне DM в твиттере (я @zachoverflow) или отправьте мне электронное письмо на zach на zachjohnson.net.

Алексей был так близко!

    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 самостоятельно.

Ваш ответ на вопрос