Настраиваемая ручка изменения размера в форме без границ C #

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

Я слышал, что могу перехватить WM_NCHITTEST сообщение, отправленное в форму, и установить его результат равным HTBOTTOMRIGHT, что позволит операционной системе обработать изменение размера формы, как если бы у нее был значительный фрейм. Моя идея заключалась в том, чтобы определить, вошел ли указатель мыши в поле, которое я определил в углу, и, если это так, вернуть результат HTBOTTOMRIGHT.

Рисунок, иллюстрирующий маркер изменения размера

Это не совсем так, как я ожидал. Я могу перехватить сообщение, но кажется, что сообщение отправляется только тогда, когда пользователь помещает курсор мыши на границу формы толщиной 1 пиксель. Это означает, что он работает так, как я хочу, если вы очень точно поместите курсор на нижний правый край.

Вот мое WndProc переопределение:

protected override void WndProc(ref Message m)
{
    const UInt32 WM_NCHITTEST = 0x0084;
    const UInt32 HTBOTTOMRIGHT = 17;
    const int RESIZE_HANDLE_SIZE = 40;
    bool handled = false;
    if (m.Msg == WM_NCHITTEST)
    {
        Size formSize = this.Size;
        Point screenPoint = new Point(m.LParam.ToInt32());
        Point clientPoint = this.PointToClient(screenPoint);
        Rectangle hitBox = new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE);
        if (hitBox.Contains(clientPoint))
        {
            m.Result = (IntPtr)HTBOTTOMRIGHT;
            handled = true;
        }
    }

    if (!handled)
        base.WndProc(ref m);
}

Я что-то делаю не так или есть лучший способ сделать то, что я пытаюсь сделать?

Большое спасибо.


person Frank Weindel    schedule 19.07.2013    source источник
comment
Думаю, это было бы полезно stackoverflow.com/questions/2575216 / и codeproject.com/Articles/24005/   -  person Anton Semenov    schedule 19.07.2013
comment
Думаю ли я об этом, или вы не могли бы просто изменить FormBorderStyle на Sizeable всякий раз, когда ваш пользователь наводит курсор на ваш пользовательский хитбокс, и FixedSingle всякий раз, когда он его покидает? Обрабатывайте события MouseEnter и MouseLeave из класса Rectangle.   -  person B L    schedule 19.07.2013
comment
@AntonSemenov: Второе решение обходит обработчик операционной системы. Первый соответствует тому, что я хочу, но не работает. Я не получаю сообщение WM_NCHITTEST, за исключением самого края окна.   -  person Frank Weindel    schedule 19.07.2013
comment
@glace: Это приведет к тому, что граница окна заметно изменится, чего я действительно не хочу.   -  person Frank Weindel    schedule 19.07.2013
comment
@ Фрэнк Вайндель, я понимаю. Я использовал настройки в вашем примере, и это не оказало заметного влияния на внешний вид окон, я полагаю, у вас есть другие настройки.   -  person B L    schedule 19.07.2013


Ответы (4)


Я искал что-то подобное, и код Антона был отличной базой. Это то, что я закончил, чтобы изменить размер работы со всех сторон. Я не уверен, что Dictionary был оптимальным способом хранения хитбоксов, но думаю, это не имеет большого значения.

А поскольку моя форма заполнена элементами управления с использованием параметров «Залить как док», мне просто нужно было добавить отступ в 5 пикселей к Form, чтобы он работал нормально.

protected override void WndProc(ref Message m)
{
    const UInt32 WM_NCHITTEST = 0x0084;
    const UInt32 WM_MOUSEMOVE = 0x0200;

    const UInt32 HTLEFT = 10;
    const UInt32 HTRIGHT = 11;
    const UInt32 HTBOTTOMRIGHT = 17;
    const UInt32 HTBOTTOM = 15;
    const UInt32 HTBOTTOMLEFT = 16;
    const UInt32 HTTOP = 12;
    const UInt32 HTTOPLEFT = 13;
    const UInt32 HTTOPRIGHT = 14;

    const int RESIZE_HANDLE_SIZE = 10;
    bool handled = false;
    if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
    {
        Size formSize = this.Size;
        Point screenPoint = new Point(m.LParam.ToInt32());
        Point clientPoint = this.PointToClient(screenPoint);

        Dictionary<UInt32, Rectangle> boxes = new Dictionary<UInt32, Rectangle>() {
            {HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
            {HTBOTTOM, new Rectangle(RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
            {HTBOTTOMRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
            {HTRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE)},
            {HTTOPRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
            {HTTOP, new Rectangle(RESIZE_HANDLE_SIZE, 0, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
            {HTTOPLEFT, new Rectangle(0, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
            {HTLEFT, new Rectangle(0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE) }
        };

        foreach (KeyValuePair<UInt32, Rectangle> hitBox in boxes)
        {
            if (hitBox.Value.Contains(clientPoint))
            {
                m.Result = (IntPtr) hitBox.Key;
                handled = true;
                break;
            }
        }
    }

    if (!handled)
        base.WndProc(ref m);
}
person Charles P.    schedule 29.10.2013
comment
Я не могу проверить, что это работает, потому что отказался от этого подхода, но я помню, что мои формы были заполнены панелями. Добавление заполнения формы может иметь значение. - person Frank Weindel; 30.10.2013
comment
Я проверил. Оно работает. Не забудьте сделать отступы. - person DCOPTimDowd; 14.11.2018

просто небольшая модификация вашего кода. Я добавил WM_MOUSEMOVE обработку сообщений:

    protected override void WndProc(ref Message m)
    {
        const UInt32 WM_NCHITTEST = 0x0084;
        const UInt32 WM_MOUSEMOVE = 0x0200;
        const UInt32 HTBOTTOMRIGHT = 17;
        const int RESIZE_HANDLE_SIZE = 10;
        bool handled = false;
        if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE )
        {
            Size formSize = this.Size;
            Point screenPoint = new Point(m.LParam.ToInt32());
            Point clientPoint = this.PointToClient(screenPoint);
            Rectangle hitBox = new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE);
            if (hitBox.Contains(clientPoint))
            {
                m.Result = (IntPtr)HTBOTTOMRIGHT;
                handled = true;
            }
        }

        if (!handled)
            base.WndProc(ref m);
    }

кстати, вы можете нарисовать захват размера окна системы с помощью ControlPaint.DrawSizeGrip Method http://msdn.microsoft.com/en-us/library/2e1yx2sa.aspx.

person Anton Semenov    schedule 19.07.2013

Антон Семенов, я не понял ваш код.

В любом случае, у меня была проблема с первым кодом Charles P,
когда я разворачиваю окно, а затем пытаюсь изменить его размер - его размер изменяется.
после этого я не мог ' t снова установите его до нормального размера и не увеличивайте его снова с помощью обычной кнопки максимума.

для решения этой проблемы я добавил условие внутри цикла foreach внизу:

    protected override void WndProc(ref Message m)
    {
        const UInt32 WM_NCHITTEST = 0x0084;
        const UInt32 WM_MOUSEMOVE = 0x0200;

        const UInt32 HTLEFT = 10;
        const UInt32 HTRIGHT = 11;
        const UInt32 HTBOTTOMRIGHT = 17;
        const UInt32 HTBOTTOM = 15;
        const UInt32 HTBOTTOMLEFT = 16;
        const UInt32 HTTOP = 12;
        const UInt32 HTTOPLEFT = 13;
        const UInt32 HTTOPRIGHT = 14;

        const int RESIZE_HANDLE_SIZE = 10;
        bool handled = false;
        if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
        {
            Size formSize = this.Size;
            Point screenPoint = new Point(m.LParam.ToInt32());
            Point clientPoint = this.PointToClient(screenPoint);

            Dictionary<UInt32, Rectangle> boxes = new Dictionary<UInt32, Rectangle>() {
        {HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
        {HTBOTTOM, new Rectangle(RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
        {HTBOTTOMRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
        {HTRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE)},
        {HTTOPRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
        {HTTOP, new Rectangle(RESIZE_HANDLE_SIZE, 0, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
        {HTTOPLEFT, new Rectangle(0, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
        {HTLEFT, new Rectangle(0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE) }
            };

            foreach (KeyValuePair<UInt32, Rectangle> hitBox in boxes)
            {
                if (this.WindowState != FormWindowState.Maximized 
                    && hitBox.Value.Contains(clientPoint))
                    {
                        m.Result = (IntPtr)hitBox.Key;
                        handled = true;
                        break;
                    }
            }
        }

        if (!handled)
            base.WndProc(ref m);
    }
person snir    schedule 08.07.2014

На основе решения Charles P. были внесены некоторые изменения в него, надеюсь, это поможет и другим :) Небольшие проверки и улучшения, чтобы не объявлять дополнительные переменные каждый раз, когда вызывается сообщение Windows. Также проверяет, не закрашивается ли якорь захвата, когда состояние окон развернуто. Я хотел создать из него настраиваемый элемент управления, но, к сожалению, в итоге заполнил форму этим кодом.

Конструктор или в файле дизайнера:

this.DoubleBuffered = true;
this.ResizeRedraw   = true;

Переопределение функций Windows:

    const uint WM_NCHITTEST = 0x0084, WM_MOUSEMOVE = 0x0200,
                 HTLEFT = 10, HTRIGHT = 11, HTBOTTOMRIGHT = 17,
                 HTBOTTOM = 15, HTBOTTOMLEFT = 16, HTTOP = 12,
                 HTTOPLEFT = 13, HTTOPRIGHT = 14;
    Size formSize;
    Point screenPoint;
    Point clientPoint;
    Dictionary<uint, Rectangle> boxes;
    const int RHS = 10; // RESIZE_HANDLE_SIZE
    bool handled;

    protected override void WndProc(ref Message m)
    {
        if (this.WindowState == FormWindowState.Maximized)
        {
            base.WndProc(ref m);
            return;
        }

        handled  = false;
        if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
        {
            formSize    = this.Size;
            screenPoint = new Point(m.LParam.ToInt32());
            clientPoint = this.PointToClient(screenPoint);

            boxes = new Dictionary<uint, Rectangle>() {
                {HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RHS, RHS, RHS)},
                {HTBOTTOM, new Rectangle(RHS, formSize.Height - RHS, formSize.Width - 2*RHS, RHS)},
                {HTBOTTOMRIGHT, new Rectangle(formSize.Width - RHS, formSize.Height - RHS, RHS, RHS)},
                {HTRIGHT, new Rectangle(formSize.Width - RHS, RHS, RHS, formSize.Height - 2*RHS)},
                {HTTOPRIGHT, new Rectangle(formSize.Width - RHS, 0, RHS, RHS) },
                {HTTOP, new Rectangle(RHS, 0, formSize.Width - 2*RHS, RHS) },
                {HTTOPLEFT, new Rectangle(0, 0, RHS, RHS) },
                {HTLEFT, new Rectangle(0, RHS, RHS, formSize.Height - 2*RHS) }
            };

            foreach (var hitBox in boxes)
            {
                if (hitBox.Value.Contains(clientPoint))
                {
                    m.Result = (IntPtr)hitBox.Key;
                    handled  = true;
                    break;
                }
            }
        }

        if (!handled)
            base.WndProc(ref m);
    }


    protected override void OnPaint(PaintEventArgs e)
    {
        if (this.WindowState != FormWindowState.Maximized)
        {
            ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor,
                this.ClientSize.Width - 16, this.ClientSize.Height - 16, 16, 16);
        }

        base.OnPaint(e);
    }
person Community    schedule 14.04.2017