Search code examples
c#gridmousecursor-positiononmousemove

Snap to grid mouse locking up


I am working on an application which draws a simple dot grid. I would like the mouse to snap between the points on the grid, eventually to draw lines on the grid.

I have a method which takes in the current mouse location (X,Y) and calculates the nearest grid coordinate.

When I create an event and attempt to move the mouse to the new coordinate the whole system becomes jerky. The mouse doesn't snap smoothly between grid points.

I have copied a code sample below to illustrate what I am attempting to do. Does anyone have any advice they could offer me as to how I can eliminate the jumpiness within the mouse movement?


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 GridTest
{
    public partial class Form1 : Form
    {
        Graphics g;
        const int gridsize = 20;

        public Form1()
        {
            InitializeComponent();
            g = splitContainer1.Panel2.CreateGraphics();
            splitContainer1.Panel2.Invalidate();
        }

        private void splitContainer1_Panel2_Paint(object sender, PaintEventArgs e)
        {
            Drawgrid();
        }

        private void Drawgrid()
        {
            for (int x = 0; x < splitContainer1.Panel2.ClientSize.Width; x += gridsize)
            {
                for (int y = 0; y < splitContainer1.Panel2.ClientSize.Height; y += gridsize)
                { g.DrawLine(Pens.Black, new Point(x, y), new Point(x + 1, y)); }
            }
        }

        private void splitContainer1_Panel2_MouseMove(object sender, MouseEventArgs e)
        {
            Point newPosition = new Point();
            newPosition = RoundToNearest(gridsize, e.Location);
            Cursor.Position = splitContainer1.Panel2.PointToScreen(newPosition);
        }

        private Point RoundToNearest(int nearestRoundValue, Point currentPoint)
        {
            Point newPoint = new Point();
            int lastDigit;

            lastDigit = currentPoint.X % nearestRoundValue;

            if (lastDigit >= (nearestRoundValue/2))
            { newPoint.X = currentPoint.X - lastDigit + nearestRoundValue; }
            else
            { newPoint.X = currentPoint.X - lastDigit; }

            lastDigit = currentPoint.Y % nearestRoundValue;
            if (lastDigit >= (nearestRoundValue / 2))
            { newPoint.Y = currentPoint.Y - lastDigit + nearestRoundValue; }
            else
            { newPoint.Y = currentPoint.Y - lastDigit; }

            return newPoint;
        }
    }
}

Solution

  • I think I understand where you're coming from. You simply need to be some delta away from the original snap point (the left mouse click) before you snap to the new point.

    Here's 50 lines of code illustrating what I mean: (Start a new VB.NET project, add a new module, copy and paste the code, add a reference, to System, System.drawing, and System.Windows.Forms)

    
    Imports System
    Imports System.Drawing
    Imports System.Windows.Forms
    
    Module modSnap
    
        Public Const strApplicationTitle As String = "Snap Demo"
        Public frmSnap As SnapForm
        Public ptSnap, ptStart, ptEnd As Point
    
        Public Class SnapForm
            Inherits Form
            Public Sub New()
                Me.Text = "Snap Demo"
                Me.ClientSize = New Size(800, 600)
                Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedSingle
                Me.MaximizeBox = False
                Me.StartPosition = FormStartPosition.CenterScreen
                Me.DoubleBuffered = True
            End Sub
            Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
                MyBase.OnPaint(e)
                e.Graphics.Clear(Color.Black)
                For row As Integer = 20 To 780 Step 20
                    For col As Integer = 20 To 580 Step 20
                        e.Graphics.DrawEllipse(Pens.Blue, New Rectangle(row - 2, col - 2, 4, 4))
                    Next
                Next
                e.Graphics.DrawLine(Pens.Red, ptStart, ptEnd)
            End Sub
            Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
                MyBase.OnMouseDown(e)
                Dim x As Integer = CInt(e.X / 20) * 20
                Dim y As Integer = CInt(e.Y / 20) * 20
                ptStart = New Point(x, y)
                ptSnap = New Point(x, y)
                Windows.Forms.Cursor.Position = Me.PointToScreen(ptSnap)
            End Sub
            Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
                MyBase.OnMouseMove(e)
                If e.Button = Windows.Forms.MouseButtons.Left Then
                    Dim x As Integer = CInt(e.X / 20) * 20
                    Dim y As Integer = CInt(e.Y / 20) * 20
                    ' must be some delta away from original snap point
                    If (x < ptSnap.X - 15 Or x > ptSnap.X + 15) Or (y < ptSnap.Y - 15 Or y > ptSnap.Y + 15) Then
                        ptSnap = New Point(x, y)
                        ptEnd = New Point(x, y)
                        Me.Invalidate(False)
                        Windows.Forms.Cursor.Position = Me.PointToScreen(ptSnap)
                    End If
                End If
            End Sub
        End Class
    
        Public Sub main()
            Try
                frmSnap = New SnapForm
                Application.Run(frmSnap)
            Catch ex As Exception
                MessageBox.Show(ex.Message, strApplicationTitle, MessageBoxButtons.OK, MessageBoxIcon.Error)
            Finally
                frmSnap.Dispose()
            End Try
        End Sub
    
    End Module