19
votes

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?

5

5 Answers

26
votes

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

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

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;
    }
}
2
votes

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;
            }
        }
    }
0
votes

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

    };
}