I was here burning the midnight oil around some lines of code trying to solve a problem.
The code bellow will setup the control to be able to move anywhere within its p
You cannot make this reliable as intended. The common solution for this UI problem is to provide the user with an assist that he can turn on and off as desired. The Shift key is the common choice for that. Allow free movement when it is turned off, but snap in the dominant direction when it is held down.
You use the Control.ModifierKeys property to check if the key is down in your MouseMove event handler. You need the KeyDown and KeyUp event handlers so you can see the Shift key being pressed and released. Significant changes are required to properly follow the mouse position, it is not necessarily hovering over the control anymore when you hold down the Shift key. Enough moving parts to encapsulate this in a helper class:
class ControlMover {
private Control control;
private Point downPos;
private Point startPos;
enum Constrains { None, Hor, Ver };
private Constrains constraint;
public ControlMover(Control ctl) {
control = ctl;
startPos = control.Location;
downPos = Cursor.Position;
control.Capture = true;
control.MouseMove += control_MouseMove;
control.MouseUp += control_MouseUp;
control.MouseCaptureChanged += control_MouseCaptureChanged;
control.KeyDown += control_KeyDown;
control.KeyUp += control_KeyUp;
}
void handleKey(Keys key, bool down) {
Console.WriteLine((int)key);
if (key == Keys.Escape) {
control.Capture = false;
control.Location = startPos;
}
else if ((key & Keys.KeyCode) == Keys.ShiftKey) {
if (!down) constraint = Constrains.None;
else if (constraint == Constrains.None) {
var curPos = Cursor.Position;
if (Math.Abs(curPos.X - downPos.X) >= Math.Abs(curPos.Y - downPos.Y))
constraint = Constrains.Hor;
else constraint = Constrains.Ver;
}
moveControl();
}
}
void control_MouseCaptureChanged(object sender, EventArgs e) {
// This ends it
if (control.Capture) return;
control.MouseMove -= control_MouseMove;
control.MouseUp -= control_MouseUp;
control.MouseCaptureChanged -= control_MouseCaptureChanged;
control.KeyDown -= control_KeyDown;
control.KeyUp -= control_KeyUp;
}
private void moveControl() {
var curPos = Cursor.Position;
if (constraint == Constrains.Hor) curPos.Y = downPos.Y;
if (constraint == Constrains.Ver) curPos.X = downPos.X;
curPos = control.Parent.PointToClient(curPos);
// Keep it inside the parent
curPos.X = Math.Max(0, curPos.X);
curPos.Y = Math.Max(0, curPos.Y);
curPos.X = Math.Min(control.Parent.ClientSize.Width - control.Width, curPos.X);
curPos.Y = Math.Min(control.Parent.ClientSize.Height - control.Height, curPos.Y);
control.Location = curPos;
}
void control_MouseUp(object sender, MouseEventArgs e) { control.Capture = false; }
void control_MouseMove(object sender, MouseEventArgs e) { moveControl(); }
void control_KeyDown(object sender, KeyEventArgs e) { handleKey(e.KeyData, true); }
void control_KeyUp(object sender, KeyEventArgs e) { handleKey(e.KeyData, false); }
}
Sample usage:
private void button1_MouseDown(object sender, MouseEventArgs e) {
new ControlMover(button1);
}
Here is an example. I add a scripted small Panel
'piece' to a larger Panel
'board'.
I check for a minimum delta, so that a shaky hand won't start the movement..
One flag tracks the movement, another one the direction, with '0' being 'not yet' decided.
bool pieceMoving = false;
byte pieceDirection = 0;
Point startPosition = Point.Empty;
private void AddPieceButton_Click(object sender, EventArgs e)
{
Panel newPiece = new Panel();
newPiece.Size = new Size(16, 16);
newPiece.BackColor = Color.Blue;
pan_board.Controls.Add(newPiece);
newPiece.MouseDown += (sender2, evt) =>
{ pieceMoving = true; pieceDirection = 0; startPosition = evt.Location; };
newPiece.MouseUp += (sender2, evt) =>
{ pieceMoving = false; pieceDirection = 0;};
newPiece.MouseMove += (sender2, evt) =>
{
int delta = 0;
if (!pieceMoving) return;
if (pieceDirection == 0)
{
int deltaX = Math.Abs(startPosition.X - evt.X);
int deltaY = Math.Abs(startPosition.Y - evt.Y);
delta = deltaX + deltaY;
if (deltaX == deltaY) return;
if (delta < 6) return; // some minimum movement value
if (deltaX > deltaY) pieceDirection = 1; else pieceDirection = 2;
}
// else if (delta == 0) { pieceDirection = 0; return; } // if you like!
Panel piece = (Panel) sender2;
if (pieceDirection == 1) piece.Left += evt.X; else piece.Top += evt.Y;
};
Since I have put the code in a Button
click, I named the sender 'sender2' and I use it to allow for the same code being used for many pieces.