Pondidum Pondidum - 11 months ago 55
Vb.net Question

Programatically start moving a form

I am trying to make a form move (using the titlebar) from a button click.

I thought this would be simple using SendMessage:

Const WM_LBUTTONDOWN As Integer = &H201

Button1.Capture = False
Cursor.Position = Me.Location + New Size(50, 8)

SendMessage(Me.Handle, WM_LBUTTONDOWN, CType(1, IntPtr), IntPtr.Zero)

However, although this sends the message if the cursor is in the forms client area, it does not seem to send it to the forms titlebar (the form captures the event somehow, despite the cursor being on the titlebar not in the client area).

I have tried the above code in both mousedown and click events on the button, moving the cursor and then pressing on the button1.

Any suggestions?

Answer Source

You would need WM_NCLBUTTONDOWN (and pass HTCAPTION as wParam). I'm still not entirely sure this would accomplish what you're trying to do, though.

Typically, the way to allow the user to move your form when clicking somewhere other than the title bar is to process the WM_NCHITTEST message and return HTCAPTION when the cursor is over the area from which you'd like to initiate moving. However, if this area is occupied by a child control, you also have to process WM_NCHITTEST from the child control and return HTTRANSPARENT.

Incidentally, an easier—if slightly less correct—way to accomplish this is to do as Mehrdad Afshari suggested, and just set the form's Location property. You commented to him that "it needs to move on the mouse move", and that's exactly what you can and should do.

class MyForm : Form{
    Point downAt;

        Label lbl      = new Label();
        lbl.AutoSize   = true;
        lbl.BackColor  = Color.Blue;
        lbl.ForeColor  = Color.White;
        lbl.Location   = new Point(50, 50);
        lbl.Text       = "Drag me to move this form.";
        lbl.Parent     = this;
        lbl.MouseDown += (s, e)=>downAt = e.Location;
        lbl.MouseMove += (s, e)=>{if(lbl.Capture) Location += (Size)e.Location - (Size)downAt;};

The problem with this approach is that it bypasses Windows' code for moving a top-level window. This means that if the user has not selected the "Show window contents while dragging" option in the Display Properties dialog, this will effectively ignore that setting (it won't show a drag outline). There may be other drawbacks that I haven't thought of as well.

On the whole, though, this is a simple, easy way to accomplish this that is a fully .NET solution which doesn't rely on any platform invoke (so it should be portable to Mono on Unix).

Oops. I just realized that I gave you C# example code, but your code seems to be VB.NET. I guess what you would need would be:

Sub New()
    Dim lbl As New Label
    lbl.AutoSize  = True
    lbl.BackColor = Color.Blue
    lbl.ForeColor = Color.White
    lbl.Location  = New Point(50, 50)
    lbl.Text      = "Drag me to move this form."
    lbl.Parent    = Me
    AddHandler lbl.MouseDown, Function(ByVal s As Object, ByVal e As MouseEventArgs)
        Me.downAt = e.Location
    End Function
    AddHandler lbl.MouseMove, Function(ByVal s As Object, ByVal e As MouseEventArgs)
        If lbl.Capture Then
            Me.Location = Me.Location + DirectCast(e.Location, Size) - DirectCast(Me.downAt, Size)
        End If
    End Function
End Sub

This may not be the most succinct way to express this in VB.NET. I used Reflector to help me translate it.