Как разрешить перемещение объекта только по линиям сетки?

Пишу на c# winforms. По кнопке отрисовываю GraphicsPath объект на собственном control (наследуюсь от UserControl), который ещё и сетку рисует 20X20 с шириной и высотой клетки 20x20. Добавил возможность его перемещать мышкой. Такой вопрос: как разрешить перемещение этого объекта только по линиям сетки (шаг = 20)? Допустим объект - прямоугольник или линия с длиной кратной ширине/высоте клетки. Нужен наглядный пример. Ниже код перемещения:

        protected override void OnMouseMove(MouseEventArgs e)
    {
        if (moving)
        {
            var d = new Point(e.X - previousPoint.X, e.Y - previousPoint.Y);
            selectedShape.Move(d);
            previousPoint = e.Location;
            this.Invalidate();
        }
        base.OnMouseMove(e);
    }

, где previousPoint - точка нажатия (получаю в Down) и Move объекта:

            public void Move(Point d)
        {
            Matrix translateMatrix = new Matrix();
            translateMatrix.Translate(d.X, d.Y);
            path_element_.Transform(translateMatrix);
            path_circle_.Transform(translateMatrix);
        }

Ответы (3 шт):

Автор решения: Kromster

Как вариант:

  1. Допустим у вас объект в 20:40, а курсор нажали в 25:44
  2. Запоминаете в каких координатах объект взяли (я обычно запоминаю смещение от курсора, тут это -5:-4)
  3. Перетаскиваете и рассчитываете новое положение объекта, Например курсор стал 14:88, значит объект должен оказаться в 14-5:88-4 - в 9:84
  4. Округляете положение объекта до шага сетки. Самое простое - делить на 20, округлить, умножить на 20. Получите 0:80.
→ Ссылка
Автор решения: Alexander Petrov

Полный пример приложения.
Копируем, запускаем.
Водим мышкой по форме - квадрат перемещается по сетке.

using System.Drawing;
using System.Windows.Forms;

namespace WinForm
{
    public partial class Form1 : Form
    {
        int x;
        int y;
        const int step = 50;

        public Form1()
        {
            //InitializeComponent();

            DoubleBuffered = true;
            Paint += Form1_Paint;
            MouseMove += Form1_MouseMove;
        }
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            ControlPaint.DrawGrid(e.Graphics, e.ClipRectangle, new Size(step, step), Color.White);
            e.Graphics.FillRectangle(Brushes.Green, x, y, 50, 50);
        }
        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            int xNew = e.X / step * step;
            int yNew = e.Y / step * step;

            if (x != xNew || y != yNew)
            {
                x = xNew;
                y = yNew;
                Invalidate();
            }
        }
    }
}
→ Ссылка
Автор решения: Павел Васильев

Если перемещать дискретно мышкой, то тогда добавьте на форму четыре кнопки навигации со стрелками и по ним щёлкайте, но лучше перемещать объект стандартно клавишами WASD и если оставлять копию, то по нажатию пробела space. Так делается в одном из редакторов террейнов в GLScene - Terrain-Node System for creation landscapes по регулярной сетке. В классе главной формы объявляем экземпляры типа: регулярной сетки - Grid2D: TGLXYZGrid; (* шаг приращения Step в XSamplingScale и YSamplingScale пусть равен 20 как у вас, ZSamplingScale 0 ); фриформы меш - Preview_Mesh: TGLFreeForm; ( перемещаемого объекта в окне порта TGLSceneViewer *); и копии объекта для отображения в окне навигации - Pre_MeshCopy: TGLFreeForm;

Затем на событие нажатия клавиши дописываем

  // Moving the preview-node
  if IsKeyDown('a') then
    Preview_Mesh.Position.X := Preview_Mesh.Position.X + 20;
  if IsKeyDown('d') then
    Preview_Mesh.Position.X := Preview_Mesh.Position.X - 20;
  if IsKeyDown('w') then
    Preview_Mesh.Position.Z := Preview_Mesh.Position.Z + 20;
  if IsKeyDown('s') then
    Preview_Mesh.Position.Z := Preview_Mesh.Position.Z - 20;
  ...

и т.д. и т.п. При необходимости копии меш-объекта оставляем в ячейке и перемешаемся дальше или вверх-вниз.

введите сюда описание изображения

→ Ссылка