EDIT: tl;dr go to the first comment.
This question stems from another question of mine Get MouseDown event when mouse goes down on Form border?
In that question I needed to have a form fire an event when the user pushed the left mouse button down on the form border (preparing to drag), which works perfectly. The problem is when the user has finished this action, by letting go of the left mouse button, I would also like to have an event fired.
To do so I produced this code to be placed in a "base form" class that other forms would be derived from. I have removed the FireMouseButton...()
methods for succinctness; they fire custom events.
const int WM_NCLBUTTONUP = 0xA2;
const int WM_NCLBUTTONDWN = 0xA1;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCLBUTTONUP)
FireMouseButtonUp();
if (m.Msg == WM_NCLBUTTONDWN)
FireMouseButtonDown();
base.WndProc(ref m);
}
The problem with this is that the WM_NCLBUTTONUP
message is not sent as I expected it. After reviewing the description for WM_NCLBUTTONUP
I can see why though,
[WM_NCLBUTTONUP is] posted when the user releases the left mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted.
Because the form has captured the mouse while it is being dragged, it will not receive the WM_NCLBUTTONUP
message. (It will if the form is maximized though). This question explains it a little better The curious problem of the missing WM_NCLBUTTONUP message when a window isn't maximised.
The answer to that question is somewhat helpful but causes a lot of confusion for me. In the code below I have a small SSCCE, it implements some code given from the solution to the answer above, checking the WMNCHITTEST
message to see if the mouse has been released;
The idea is that the WM_NCHITTEST
should be sent when the mouse is moving within the form. So once a drag stops this message should be sent with the mouse position as DragStartPoint
in the WndProc message arguments; where DragStartPoint
is recorded when the WM_NCLBUTTONDOWN
message is received.
The issue with this though is that the WM_NCHITTEST
is not always sent after the start of a drag, only when the drag is started on the far sides of the top border (see pic below). The WM_NCLBUTTONDOWN
message is always sent when clicking on the top border (never for sides or bottom). So that is fine, but the WM_NCHITTEST
and as pointed out WM_NCLBUTTONUP
are sent, but only sometimes.
How might I get the WM_NCHITTEST
"test" to work in the code below so that I can be notified once the user has stopped dragging the form? (the "test" being checking the DragStartPoint
in the if statement for WM_NCHITTEST
)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace MouseEventTest
{
public partial class Form1 : Form
{
Random rand = new Random();
public Form1()
{
InitializeComponent();
}
const int WM_NCHITTEST = 0x84;
const int WM_NCLBUTTONUP = 0xA2;
const int WM_NCLBUTTONDWN = 0xA1;
public Point DragStartPoint { get; set; }
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCLBUTTONUP)
{
label1.Text = "Mouse Up On Border";
}
if (m.Msg == WM_NCLBUTTONDWN)
{
label1.Text = "Mouse Down On Border";
Point pos = lParamToPoint(m.LParam);
DragStartPoint = this.PointToClient(pos);
Console.Out.WriteLine("DragStartPoint: " + DragStartPoint);
}
if(m.Msg == WM_NCHITTEST)
{
Point pos = lParamToPoint(m.LParam);
Console.Out.WriteLine("HtTestPnt: " + this.PointToClient(pos));
if (DragStartPoint == this.PointToClient(pos))
{
label1.Text = "Mouse Up HitTest";
}
}
base.WndProc(ref m);
}
private Point lParamToPoint(IntPtr lParamIn)
{
int x = lParamIn.ToInt32() & 0x0000FFFF;
int y = (int)((lParamIn.ToInt32() & 0xFFFF0000) >> 16);
return new Point(x, y);
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(42, 30);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(91, 13);
this.label1.TabIndex = 0;
this.label1.Text = "99999999999999";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(185, 75);
this.Controls.Add(this.label1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
}
}