views:

1019

answers:

5

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;
        }
    }
}
+2  A: 

Your mouse keeps snapping to the same point if you try to move it -- because it's still closest to that point... If you move the mouse left, move the cursor to the point to the left of the current one instead of recalculating at the current. Apply for the 3 other directions...

I wouldn't recommend this behaviour though, it will cause a lot of irritation. Snap your controls to the grid, not the mouse.

Vincent Van Den Berghe
A: 

Vincent,

Thank you for your reply. The problem is that I want to use the mouse to draw lines on the grid, but only starting/ending at the gridpoints so I really don' thave any controls to snap to the grid.

Thanks, Scott Vercuski

Scott Vercuski
+4  A: 

Don't modify the cursor position. You don't need to.

Instead, draw as if it was snapped to the grid. When the user clicks somewhere, just draw the line from the nearest grid points.

For instance, if the user clicks on (197,198), but you know that the nearest point actually is (200,200), just draw a line to (200,200) instead of (197,198).

And please, don't mess with the actual cursor position.


I don't know if there's some way to hide the mouse cursor. If there is, you can hide it and draw it yourself, without modifying the real position.

DonkeyMaster
A: 

I agree with ruijoel, do not mess with the cursor position. It is better to have a cross or a ring that is drawn at the snap point to show the user which point is the one that will be snapped to at a click event.

For this to work well you may want to look at xor-drawing so that item is erased once you move to a new snap point.

Kalle
+1  A: 

Hi Scott,

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