How to fix borderless form resize with controls on borders of the form?

4

I have a borderless winForm which I needed to resize, and I managed to do it this way:

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

The problem is that there are controls on the left and right borders of my form, so the resize override used on the code above doesn't work on those areas in which there are controls of any kind.

Here is an example:

Resize problem

On the image above you can see that the label inside the marked area is on the left border of my form and it won't let me resize it.

Is there a way to solve this issue?

c#
window
window-resize
borderless
asked on Stack Overflow Jul 11, 2015 by Joscplan

1 Answer

6

The problem here is that it is the Label control that gets the mouse notifications, not your borderless form. By far the best way to solve this problem is making the label transparent to the mouse. You already know how to do that, WM_NCHITTEST also permits returning HTTRANSPARENT. Windows keeps looking for the next candidate for the notification, it will be the label's Parent.

Especially easy to do for a label since you don't normally have any use for its mouse events at all:

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

Works for any Control class, you'd want to be more selective if it were a button. Might be all you need, still pretty awkward however if you have a lot of different kind of controls close to the edge. Another technique you can use is called "sub-classing" in native Windows programming. Universally used in Winforms to create wrapper .NET classes for native Windows controls. It works well here too, you can have a peek at the messages of any control and intercept WM_NCHITTEST that way:

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

And just create a MouseFilter instance for every control that gets close to the window edge:

    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);
        }
    }
answered on Stack Overflow Jul 11, 2015 by Hans Passant • edited Jul 12, 2017 by Hans Passant

User contributions licensed under CC BY-SA 3.0