Как исправить изменение размера формы без полей с помощью элементов управления на границах формы?

У меня есть winForm без полей, размер которого мне нужно было изменить, и мне удалось это сделать следующим образом:

protected override void WndProc(ref Message m)
    {
        const int wmNcHitTest = 0x84;
        const int htLeft = 10;
        const int htRight = 11;
        const int htTop = 12;
        const int htTopLeft = 13;
        const int htTopRight = 14;
        const int htBottom = 15;
        const int htBottomLeft = 16;
        const int htBottomRight = 17;

        if (m.Msg == wmNcHitTest)
        {
            Console.Write(true + "\n");
            int x = (int)(m.LParam.ToInt64() & 0xFFFF);
            int y = (int)((m.LParam.ToInt64() & 0xFFFF0000) >> 16);
            Point pt = PointToClient(new Point(x, y));
            Size clientSize = ClientSize;
            ///allow resize on the lower right corner
            if (pt.X >= clientSize.Width - 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(IsMirrored ? htBottomLeft : htBottomRight);
                return;
            }
            ///allow resize on the lower left corner
            if (pt.X <= 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(IsMirrored ? htBottomRight : htBottomLeft);
                return;
            }
            ///allow resize on the upper right corner
            if (pt.X <= 16 && pt.Y <= 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(IsMirrored ? htTopRight : htTopLeft);
                return;
            }
            ///allow resize on the upper left corner
            if (pt.X >= clientSize.Width - 16 && pt.Y <= 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(IsMirrored ? htTopLeft : htTopRight);
                return;
            }
            ///allow resize on the top border
            if (pt.Y <= 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(htTop);
                return;
            }
            ///allow resize on the bottom border
            if (pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(htBottom);
                return;
            }
            ///allow resize on the left border
            if (pt.X <= 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(htLeft);
                return;
            }
            ///allow resize on the right border
            if (pt.X >= clientSize.Width - 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(htRight);
                return;
            }
        }
        else
        {
            Console.Write(false + "\n");
        }
        base.WndProc(ref m);
    }

Проблема в том, что на левой и правой границах моей формы есть элементы управления, поэтому переопределение размера, используемое в приведенном выше коде, не работает в тех областях, в которых есть какие-либо элементы управления.

Вот пример:

Проблема с изменением размера

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

Есть ли способ решить эту проблему?


person Joscplan    schedule 11.07.2015    source источник
comment
Одна из идей - удалить кнопку и воссоздать ее в коде. Но не самый чистый метод ...   -  person SteveFerg    schedule 11.07.2015
comment
Вы упускаете суть вопроса @SteveFerg. Во время выполнения метка захватывает сообщения мыши, поэтому пользователь не может изменять размер формы, когда они находятся над ней (меткой), но на краю формы. Форма не получит сообщение проверки попадания в неклиентскую область, так как указатель мыши находится над меткой ...   -  person Idle_Mind    schedule 11.07.2015


Ответы (1)


Проблема здесь в том, что именно элемент управления Label получает уведомления мыши, а не ваша форма без полей. Безусловно, лучший способ решить эту проблему - сделать этикетку прозрачной для мыши. Вы уже знаете, как это сделать, WM_NCHITTEST также позволяет возвращать HTTRANSPARENT. Windows продолжает искать следующего кандидата для уведомления, это будет родительский элемент метки.

Особенно легко сделать для метки, поскольку обычно ее события мыши вообще не используются:

using System;
using System.Windows.Forms;

public class LabelEx : Label {
    protected override void WndProc(ref Message m) {
        const int wmNcHitTest = 0x84;
        const int htTransparent = -1;
        if (!DesignMode && m.Msg == wmNcHitTest) m.Result = new IntPtr(htTransparent);
        else base.WndProc(ref m);
    }
}

Работает для любого класса Control, вы бы хотели быть более избирательным, если бы это была кнопка. Это может быть все, что вам нужно, но все равно довольно неудобно, если у вас есть много разных элементов управления близко к краю. Другой метод, который вы можете использовать, в нативном программировании Windows называется «выделение подклассов». Универсально используется в Winforms для создания классов оболочки .NET для собственных элементов управления Windows. Это тоже хорошо работает, вы можете просмотреть сообщения любого элемента управления и таким образом перехватить WM_NCHITTEST:

    const int edge = 16;

    class MouseFilter : NativeWindow {
        private Form form;
        public MouseFilter(Form form, Control child) {
            this.form = form;
            this.AssignHandle(child.Handle);
        }
        protected override void WndProc(ref Message m) {
            const int wmNcHitTest = 0x84;
            const int htTransparent = -1;

            if (m.Msg == wmNcHitTest) {
                var pos = new Point(m.LParam.ToInt32());
                if (pos.X < this.form.Left + edge ||
                    pos.Y < this.form.Top + edge||
                    pos.X > this.form.Right - edge ||
                    pos.Y > this.form.Bottom - edge) {
                    m.Result = new IntPtr(htTransparent);
                    return;
                }
            }
            base.WndProc(ref m);
        }
    }

И просто создайте экземпляр MouseFilter для каждого элемента управления, который приближается к краю окна:

    protected override void OnLoad(EventArgs e) {
        base.OnLoad(e);
        subClassChildren(this.Controls);
    }

    private void subClassChildren(Control.ControlCollection ctls) {
        foreach (Control ctl in ctls) {
            var rc = this.RectangleToClient(this.RectangleToScreen(ctl.DisplayRectangle));
            if (rc.Left < edge || rc.Right > this.ClientSize.Width - edge ||
                rc.Top < edge || rc.Bottom > this.ClientSize.Height - edge) {
                new MouseFilter(this, ctl);
            }
            subClassChildren(ctl.Controls);
        }
    }
person Hans Passant    schedule 11.07.2015
comment
Быстрый вопрос Ханс: В вашем MouseFilter классе нам действительно нужно перехватывать WM_NCDESTROY и вручную вызывать ReleaseHandle()? Из Примечания он говорит: окно автоматически вызывает этот метод, если оно получает собственное сообщение Win32 WM_NCDESTROY, указывающее, что Windows уничтожила дескриптор. Будет ли этот вызов обработан для нас тогда, когда будет выполнен base.WndProc(ref m);? - person Idle_Mind; 12.07.2015
comment
В обработке по умолчанию есть FUD, NativeWindow.Callback () вызывает ReleaseHandle (false), который не отменяет подкласс окна. Договорились, наверное, лучше здесь. - person Hans Passant; 12.07.2015