Resize borderless window on bottom right corner
Asked Answered
W

4

16

I'd like the user to resize a borderless window on bottom right corner like I can resize the autocomplete window of the combobox control.

I cannot find the properties to configure a form that way.

Maybe someone could help me on the problem.

An image could be found here:

enter image description here

Wean answered 8/10, 2009 at 5:49 Comment(3)
post some code. You can resize by changing the Width and Height of a control .Cambria
Modified question and added link to screenshot because of Andrew Keith's comment: A user should be able to resize the form.Wean
see my solution with a panel : https://mcmap.net/q/206618/-winforms-draw-border-and-move-when-formborderstyle-set-to-noneCidevant
C
15

The proper way to achieve this would be to add a message proc handler (by overriding Form.WndProc for example) to your form and handle the WM_NCHITTEST message. (You can find the C# definition of that message on PInvoke.net) In particular, when you receive the message, calculate if the hit test is for a point in the region you've designated for resize and if it is, return HTBOTTOMRIGHT. The default window proc will do the rest for you, as it will assume that the user has clicked on the bottom right corner of the window border, even though your window has no border.

This aproach requires a teensy bit of Win32 interop, but it'll make your resize look exactly like any other window resize.

The easy way would be to do as @benPearce said and put a panel in the corner and adjust the form size using Width/Height. It's going to work, but the resize is not going to be smooth, especially on Vista and Win7 Basic, where full redraw is disabled on standard move and resize, while is going to attempt redraw on every step.

Update: In both approaches you will have to figure out also how to paint the gripper. You can put a bitmap of the standard gripper, for example. Though, given that your form has no title and border so you are not necessarily stuck with the standard Windows visuals, you might opt in for something snazzier.

Update 2: If you have a control that covers the whole window, it will eat the form mouse messages. You have to somehow clip the place you want to use for resizing out of that control. You have several options to deal with this:

  1. Resize the control to make some space for the resizing grip.
  2. Tweak the control region (throug the Region property) to exclude the resizing grip.
  3. Cover the resizing grip a panel, listen to its MouseEnter message and set the form Capture property to true, which will cause all further mouse messages to go to it. Note: you will have to release the capture once the mouse leaves that region after the resize is finished.

I would recommend to go for option 1 as the simplest. Option 3 is the most complex and would require intimate details on how mouse input works in Windows, so I wouldn't recommend it. Option 2 is a good alternative to option 1, but you'll have to give it a try to see how the ListView control would react to its region being tweaked.

Conan answered 8/10, 2009 at 6:24 Comment(2)
Thank you very much, that works great for emtpy forms. But my form contains a listview that fills the form. No WM_NCHITTEST message is fired.Wean
Anyone coming looking for how to draw the gripper, ControlPaint.DrawSizeGrip is a start.Catrinacatriona
C
35

Here's the code corresponding to Franci's explanations, I was writing it but he answered meanwhile so vote up his explanation which is good if this code suits your needs.

protected override void WndProc(ref Message m) {
    const int wmNcHitTest = 0x84;
    const int htBottomLeft = 16;
    const int htBottomRight = 17;
    if (m.Msg == wmNcHitTest) {
        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;
        if (pt.X >= clientSize.Width - 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16) {
            m.Result = (IntPtr) (IsMirrored ? htBottomLeft : htBottomRight);
            return;
        }
    }
    base.WndProc(ref m);
}

Edit: to write the gripper, you can initialize a new VisualStyleRenderer(VisualStyleElement.Status.Gripper.Normal) and use its PaintBackground() method.

Coonskin answered 8/10, 2009 at 6:29 Comment(1)
More explanation on drawing the gripper is provided at https://mcmap.net/q/206619/-drawing-a-gripper-in-a-borderless-formGyration
R
23

Thanks so much for posting this great sample and explanation. I've added some additions below that others might be interested in. Some of the code here came from other stackoverflow postings, but to be able to see it in one code block might be helpful to others. I wanted to be able to resize the form on ALL borders, not just the lower right corner. I also wanted to be able to drag the form around. Lastly, I wanted a drop-shadow.

//***********************************************************
//This gives us the ability to resize the borderless from any borders instead of just the lower right corner
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)
    {
        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;
        }
    }
    base.WndProc(ref m);
}
//***********************************************************
//***********************************************************
//This gives us the ability to drag the borderless form to a new location
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;

[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();

private void YOURCONTROL_MouseDown(object sender, MouseEventArgs e)
{
    //ctrl-leftclick anywhere on the control to drag the form to a new location 
    if (e.Button == MouseButtons.Left && Control.ModifierKeys == Keys.Control)
    {       
        ReleaseCapture();
        SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
    }  
}
//***********************************************************
//***********************************************************
//This gives us the drop shadow behind the borderless form
private const int CS_DROPSHADOW = 0x20000;
protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ClassStyle |= CS_DROPSHADOW;
        return cp;
    }
}
//***********************************************************
Rossiya answered 20/6, 2013 at 17:29 Comment(0)
C
15

The proper way to achieve this would be to add a message proc handler (by overriding Form.WndProc for example) to your form and handle the WM_NCHITTEST message. (You can find the C# definition of that message on PInvoke.net) In particular, when you receive the message, calculate if the hit test is for a point in the region you've designated for resize and if it is, return HTBOTTOMRIGHT. The default window proc will do the rest for you, as it will assume that the user has clicked on the bottom right corner of the window border, even though your window has no border.

This aproach requires a teensy bit of Win32 interop, but it'll make your resize look exactly like any other window resize.

The easy way would be to do as @benPearce said and put a panel in the corner and adjust the form size using Width/Height. It's going to work, but the resize is not going to be smooth, especially on Vista and Win7 Basic, where full redraw is disabled on standard move and resize, while is going to attempt redraw on every step.

Update: In both approaches you will have to figure out also how to paint the gripper. You can put a bitmap of the standard gripper, for example. Though, given that your form has no title and border so you are not necessarily stuck with the standard Windows visuals, you might opt in for something snazzier.

Update 2: If you have a control that covers the whole window, it will eat the form mouse messages. You have to somehow clip the place you want to use for resizing out of that control. You have several options to deal with this:

  1. Resize the control to make some space for the resizing grip.
  2. Tweak the control region (throug the Region property) to exclude the resizing grip.
  3. Cover the resizing grip a panel, listen to its MouseEnter message and set the form Capture property to true, which will cause all further mouse messages to go to it. Note: you will have to release the capture once the mouse leaves that region after the resize is finished.

I would recommend to go for option 1 as the simplest. Option 3 is the most complex and would require intimate details on how mouse input works in Windows, so I wouldn't recommend it. Option 2 is a good alternative to option 1, but you'll have to give it a try to see how the ListView control would react to its region being tweaked.

Conan answered 8/10, 2009 at 6:24 Comment(2)
Thank you very much, that works great for emtpy forms. But my form contains a listview that fills the form. No WM_NCHITTEST message is fired.Wean
Anyone coming looking for how to draw the gripper, ControlPaint.DrawSizeGrip is a start.Catrinacatriona
S
2

Put a panel or some other control in the corner, using the MouseDown and MouseMove events of the panel, adjust the forms size appropriately.

In MouseDown, i would record the coordinates, then in the MouseMove you can calculate the difference from the original position to adjust the forms size.

Substratosphere answered 8/10, 2009 at 6:4 Comment(2)
I think the last instance of "MouseDown" in the second sentence is supposed to be "MouseMove".Propellant
This took a bit of work but I like the clean event-driven solution better than the other suggestions. ThanksShanghai

© 2022 - 2024 — McMap. All rights reserved.