Custom Resize Handle in Border-less Form C#

Frank Weindel picture Frank Weindel · Jul 19, 2013 · Viewed 15.7k times · Source

I'm attempting to make border-less forms that pop out of a tool bar. I want the user to be able to grab at the bottom-right corner (a "resize handle") and be able to resize the form, but not be able to resize or reposition the form in any other way.

I've heard that I can intercept the WM_NCHITTEST message sent to the form and set its result to HTBOTTOMRIGHT which will let the operating system handle the re-sizing of the form, just as if it had a sizable frame. The idea I had was to detect if the mouse pointer had entered a box I defined in the corner and if it did then return the HTBOTTOMRIGHT result.

Graphic illustrating the resize handle

This doesn't quite work as I expected it to. I'm able to intercept the message, but it seems the message is only sent when the user positions the mouse cursor on the 1px thick border of the form. That means it works how I want to, if you very precisely position your cursor on the bottom-right edges.

Here is my WndProc override:

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

Am I doing something wrong or is there a better way to do what I'm trying to do?

Much thanks.

Answer

Charles P. picture Charles P. · Oct 29, 2013

I was looking for something similar and Anton's code was a great base. This is what I ended up to have resize work from all sides. I'm unsure a Dictionary was optimal way to store the hitboxes, but I guess it doesn't matter all that much.

And since my form is filled with controls using Fill as Dock parameters, I just had to add a 5px padding to the Form for it to work nicely.

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