Fábio Antunes
Fábio Antunes

Reputation: 17164

Move control either horizontally or vertically, not combination of both

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 parent control.

However this code allows to move in any direction, I would like to restrict its movement to either the X or Y axis, meaning either the user can move it horizontally or vertically, never a combination of both.

Point lastPosition = Point.Empty;

control.MouseDown += (sender, evt) =>
{
   lastPosition = evt.Location;
};

control.MouseMove += (sender, evt) =>
{
   // This moves the control anywhere.
   // I only want to move in one direction (left<->right  or  top <-> down) never diagonally
   // Not sure how to first find which direction the user wants to move, 
   // nor how to restrict the movement to only one of the directions mentioned
   int x = last.X + movingPiece.Left - mouseDownLocation.X;
   int y = last.Y + movingPiece.Top - mouseDownLocation.Y;
   movingPiece.Left = x;
   movingPiece.Top = y;
};

Thanks

My thanks to Hans Passant and TaW for their answers, from which I extracted the ideas from which I built a small 2D engine, capable of delimiting areas on where a number of controls can be moved about inside a panel container, taking into account collision detection, prevention and control movement direction constraints imposed by a number of custom factors as required. I would like to accept both answers, but since this isn't possible, I accepted the answer who provided the most insight into the matter in question.

Upvotes: 0

Views: 2195

Answers (2)

Hans Passant
Hans Passant

Reputation: 942099

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

Upvotes: 3

TaW
TaW

Reputation: 54453

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.

Upvotes: 3

Related Questions