19

I am creating a small modal form that is used in Winforms application. It is basically a progress bar of sorts. But I would like the user to be able to click anywhere in the form and drag it to move it around on the desktop while it is still being displayed.

How can I implement this behavior?

Joseph Daigle
  • 47,650
  • 10
  • 49
  • 73

5 Answers5

26

Microsoft KB Article 320687 has a detailed answer to this question.

Basically, you override the WndProc method to return HTCAPTION to the WM_NCHITTEST message when the point being tested is in the client area of the form -- which is, in effect, telling Windows to treat the click exactly the same as if it had occured on the caption of the form.

private const int WM_NCHITTEST = 0x84;
private const int HTCLIENT     = 0x1;
private const int HTCAPTION    = 0x2;

protected override void WndProc(ref Message m)
{
    switch(m.Msg)
    {
        case WM_NCHITTEST:
        base.WndProc(ref m);

        if ((int)m.Result == HTCLIENT)
            m.Result = (IntPtr)HTCAPTION;
        return;
    }

    base.WndProc(ref m);
}
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
Timothy Fries
  • 2,161
  • 19
  • 8
15

Here is a way to do it using a P/Invoke.

public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HTCAPTION = 0x2;
[DllImport("User32.dll")]
public static extern bool ReleaseCapture();
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);

void Form_Load(object sender, EventArgs e)
{
   this.MouseDown += new MouseEventHandler(Form_MouseDown);  
}

void Form_MouseDown(object sender, MouseEventArgs e)
{                        
    if (e.Button == MouseButtons.Left)
    {
        ReleaseCapture();
        SendMessage(Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0);
    }
}
FlySwat
  • 172,459
  • 74
  • 246
  • 311
  • You need to have: using System.Runtime.InteropServices; for [DllImport()] to work. – Kamil Szot Sep 10 '09 at 16:15
  • 1
    In my project I used code similar to Timothy Fries's, but i like this one because you can use exactly the same technique to make form dragable by it's controls, like labels. The other technique make form dragable, but if you click on labels, it won't drag. – flamey Oct 23 '09 at 09:29
  • This is also nicer because it allows you to still handle right-click events (for context menus). You can also selectively use left-clicks as said above, or use right/middle-click instead of left-clicks. Overall much more flexible. – coderforlife May 08 '12 at 15:02
  • 1
    These is the perfect piece of code I need. Whats important for this in my use case is I don't have to override the control I want to capture. If I want to drag my form when any specific control is clicked, all I need is the mousedown event, instead of overriding the class. Thanks! – greggorob64 Jul 30 '12 at 18:20
  • 2
    To get this to work when someone clicks a label simply add this additional line of code in the Form Load method (right under the this.MouseDown line). this.label1 += new MouseEventHandler(Form_MouseDown); – coding_is_fun Feb 14 '15 at 02:29
  • To get this to work when someone clicks a label simply add this additional line of code in the Form Load method (right under the this.MouseDown line). this.label1 += new MouseEventHandler(Form_MouseDown); – coding_is_fun Feb 14 '15 at 02:33
5

The following code assumes that the ProgressBarForm form has a ProgressBar control with Dock property set to Fill

public partial class ProgressBarForm : Form
{
    private bool mouseDown;
    private Point lastPos;

    public ProgressBarForm()
    {
        InitializeComponent();
    }

    private void progressBar1_MouseMove(object sender, MouseEventArgs e)
    {
        if (mouseDown)
        {
            int xoffset = MousePosition.X - lastPos.X;
            int yoffset = MousePosition.Y - lastPos.Y;
            Left += xoffset;
            Top += yoffset;
            lastPos = MousePosition;
        }
    }

    private void progressBar1_MouseDown(object sender, MouseEventArgs e)
    {
        mouseDown = true;
        lastPos = MousePosition;
    }

    private void progressBar1_MouseUp(object sender, MouseEventArgs e)
    {
        mouseDown = false;
    }
}
aku
  • 122,288
  • 32
  • 173
  • 203
2

The accepted answer is a cool trick, but it doesn't always work if the Form is covered by a Fill-docked child control like a Panel (or derivates) for example, because this control will eat all most Windows messages.

Here is a simple approach that works also in this case: derive the control in question (use this class instead of the standard one) an handle mouse messages like this:

    private class MyTableLayoutPanel : Panel // or TableLayoutPanel, etc.
    {
        private Point _mouseDown;
        private Point _formLocation;
        private bool _capture;

        // NOTE: we cannot use the WM_NCHITTEST / HTCAPTION trick because the table is in control, not the owning form...
        protected override void OnMouseDown(MouseEventArgs e)
        {
            _capture = true;
            _mouseDown = e.Location;
            _formLocation = ((Form)TopLevelControl).Location;
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            _capture = false;
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (_capture)
            {
                int dx = e.Location.X - _mouseDown.X;
                int dy = e.Location.Y - _mouseDown.Y;
                Point newLocation = new Point(_formLocation.X + dx, _formLocation.Y + dy);
                ((Form)TopLevelControl).Location = newLocation;
                _formLocation = newLocation;
            }
        }
    }
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
0

VC++ 2010 Version (of FlySwat's):

#include <Windows.h>

namespace DragWithoutTitleBar {

    using namespace System;
    using namespace System::Windows::Forms;
    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Data;
    using namespace System::Drawing;

    public ref class Form1 : public System::Windows::Forms::Form
    {
    public:
        Form1(void) { InitializeComponent(); }

    protected:
        ~Form1() { if (components) { delete components; } }

    private:
        System::ComponentModel::Container ^components;
        HWND hWnd;

#pragma region Windows Form Designer generated code
        void InitializeComponent(void)
        {
            this->SuspendLayout();
            this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
            this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
            this->ClientSize = System::Drawing::Size(640, 480);
            this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::None;
            this->Name = L"Form1";
            this->Text = L"Form1";
            this->Load += gcnew EventHandler(this, &Form1::Form1_Load);
            this->MouseDown += gcnew System::Windows::Forms::MouseEventHandler(this, &Form1::Form1_MouseDown);
            this->ResumeLayout(false);

        }
#pragma endregion
    private: System::Void Form1_Load(Object^ sender, EventArgs^  e) {
                    hWnd = static_cast<HWND>(Handle.ToPointer());
                }

    private: System::Void Form1_MouseDown(Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {
                    if (e->Button == System::Windows::Forms::MouseButtons::Left) {
                        ::ReleaseCapture();
                        ::SendMessage(hWnd, /*WM_NCLBUTTONDOWN*/ 0xA1, /*HT_CAPTION*/ 0x2, 0);
                    }
                }

    };
}
Wyllow Wulf
  • 410
  • 4
  • 23