Winform: fare clic / trascinare in qualsiasi punto del modulo per spostarlo come se fosse cliccato nella didascalia del modulo

Sto creando un piccolo modulo modale che viene utilizzato nell’applicazione Winforms. È fondamentalmente una sorta di barra di progresso. Ma vorrei che l’utente potesse cliccare ovunque nel modulo e trascinarlo per spostarlo sul desktop mentre è ancora in fase di visualizzazione.

Come posso implementare questo comportamento?

L’articolo 320687 della Microsoft KB ha una risposta dettagliata a questa domanda.

Fondamentalmente, si sostituisce il metodo WndProc per restituire HTCAPTION al messaggio WM_NCHITTEST quando il punto sottoposto a test si trova nell’area client del modulo, il che, in effetti, indica a Windows di trattare il clic esattamente come se si fosse verificato in la didascalia del modulo.

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

Ecco un modo per farlo usando un 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); } } 

Il seguente codice presuppone che il modulo ProgressBarForm abbia un controllo ProgressBar con la proprietà Dock impostata su 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; } } 

La risposta accettata è un trucco interessante, ma non sempre funziona se il modulo è coperto da un controllo figlio Fill-docked come un pannello (o derivate), ad esempio, perché questo controllo mangerà tutti i messaggi di Windows.

Ecco un approccio semplice che funziona anche in questo caso: derivate il controllo in questione (usate questa class al posto di quello standard) un messaggio simile al mouse:

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

Versione VC ++ 2010 (di FlySwat):

 #include  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(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); } } }; }