I am developing Cad application and want to implement snap - when user moves mouse near some object, I set Cursor.Position to the center point of that object. If user moves mouse say 7 pixels in any direction, then Cursor is set free.The way I do it is - I store snaped Cursor position and then under MouseMoveEvent I calculate the distance from stored position to current position. If this position is smaller than defined threshold then I set current cursor position back to stored value. Every time MouseMoveEvent is called that small difference between two cursor positions is added to previously calclated difference, so sooner or later my threshold is reached and Cursor jumps out of snaped position. Code sample:
var x = Cursor.Position.X - storedPosition.X;
pixelsX += x;
int threshold = 7;
if (pixelsX > threshold)
{
Cursor.Position = new System.Drawing.Point(storedPosition.X + 10, storedPosition.Y);
snapReleased = true;
}
The problem with this is that in every MouseMoveEvent mouse is moved very small amount and if threshold is not reached it is set back to stored position which makes Cursor blink(which is very annoying) So my question is - is there a way to detect mouse movement if Cursor is locked in one position?
I would not "snap" the mouse pointer. Do you know the feeling when your mouse is stuck? Depending on your age you may remember roller mice, those with a rubber ball inside. It is horrible.
Instead, I think that the objects you are about to select or are currently moving should react with a snap. For example, when you are about to select an object, when the mouse pointer is closer than the threshold the object gets highlighted. The user can the click the mouse and grab the object.
When moving an object, the object can snap into place when closer than the threshold to another object, guide lines, etc.
The following is a custom panel control that demonstrates an owner drawn cursor that snaps to a grid (snapPoints). The system cursor is hidden/shown on mouse entry/leave. The snap to point distance is controlled by the constant snapLimit.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
namespace WindowsFormsApplication1
{
public class DrawingSurface : Panel
{
private const double snapLimit = 7.0D;
private List<Point> snapPoints = new List<Point>();
private Point cursorPos;
private Point lastDrawnPos;
private bool drawCursor;
public DrawingSurface() : base()
{
this.BorderStyle = BorderStyle.Fixed3D;
this.BackColor = Color.AliceBlue;
this.DoubleBuffered = true;
this.Cursor = Cursors.Cross;
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
System.Windows.Forms.Cursor.Hide();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
System.Windows.Forms.Cursor.Show();
this.drawCursor = false;
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
foreach (Point dot in this.snapPoints)
{
e.Graphics.FillEllipse(Brushes.Red, dot.X - 1, dot.Y - 1, 2, 2);
}
if (drawCursor)
{
Cursor cur = System.Windows.Forms.Cursor.Current;
Point pt = this.cursorPos;
pt.Offset(-cur.HotSpot.X, -cur.HotSpot.Y);
Rectangle target = new Rectangle(pt, cur.Size);
cur.Draw(e.Graphics, target);
this.lastDrawnPos = this.cursorPos;
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
SetCursor(e.Location);
}
private void SetCursor(Point loc)
{
this.cursorPos = loc;
foreach (Point pt in this.snapPoints)
{
double deltaX = loc.X - pt.X;
double deltaY = loc.Y - pt.Y;
double radius = Math.Sqrt((deltaX * deltaX) + (deltaY * deltaY));
if (radius < snapLimit)
{
this.cursorPos = pt;
break;
}
}
if (lastDrawnPos != this.cursorPos)
{
this.drawCursor = true;
this.Invalidate();
}
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
this.snapPoints.Clear();
for (int y = 0; y <= this.ClientRectangle.Height; y += 50)
{
for (int x = 0; x <= this.ClientRectangle.Width; x += 50)
{
this.snapPoints.Add(new Point(x, y));
}
}
}
}
}
Related
I try to creat 2D mario style game. Everything worked until I started make it more 'objective'.Mario doesn't stop on end of window - stops but after while goes across window. If I make method without ref, mario even doesnt stop on while.
Mario class
class Mario: System.Windows.Forms.PictureBox
{
public Mario(int x, int y)
{
Image = Image.FromFile("Mario.png");
Location = new Point(x, y);
Size = new Size(16, 32);
SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
TabIndex = 0;
TabStop = false;
}
public void colision( System.Windows.Forms.Panel s,ref bool l, ref bool r)
{
if (this.Right > s.Right) { r = false; }
if (this.Left < s.Left) { l = false; }
}
}
Main class - Form1
public partial class Form1 : Form
{
bool right=false,left=false;
public Form1()
{
InitializeComponent();
player.Top = screen.Height - player.Height;
}
private void timer1_Tick(object sender, EventArgs e)
{
if(right== true) { player.Left += 1; }
if (left == true) { player.Left -=1; }
player.colision(screen, ref left, ref right);
}
screen is System.Windows.Forms.Panel and player is Mario Type which were initialized in form1.Designer.cs
I deleted irrelevant field and methods.
The location of a control is relative to it's parent - if you place your Mario at (0, 0), it won't appear at the top-left corner of your screen or form, but at the top-left corner of the Panel - its direct parent.
Similarly, the location of that Panel is also relative to its parent.
Let's assume your Panel is positioned in your form at (100, 100) and its size is (400, 300) - this would mean its Left property is 100 and its Right property is 500 - Mario will be out of sight for those 100 pixels.
So your check should be:
if (this.Right > s.Width) { r = false; }
if (this.Left < 0) { l = false; }
You already did the right thing with the vertical placement (by using Height instead of Bottom):
player.Top = screen.Height - player.Height;
My program can draw lines using canvas.Drawline(). How to click line and change this color (select line)?
private List<Point> coordFirst = new List<Point>();
private List<Point> coordLast = new List<Point>();
public Graphics canvas;
private void Form1_Load(object sender, EventArgs e)
{
canvas=panel1.CreateGraphics();
}
Coordinate line stored in coordFirs & coodLast.
Here is a suitable Line class:
class Line
{
public Color LineColor { get; set; }
public float Linewidth { get; set; }
public bool Selected { get; set; }
public Point Start { get; set; }
public Point End { get; set; }
public Line(Color c, float w, Point s, Point e)
{ LineColor = c; Linewidth = w; Start = s; End = e; }
public void Draw(Graphics G)
{ using (Pen pen = new Pen(LineColor, Linewidth)) G.DrawLine(pen, Start, End); }
public bool HitTest(Point Pt)
{
// test if we fall outside of the bounding box:
if ((Pt.X < Start.X && Pt.X < End.X) || (Pt.X > Start.X && Pt.X > End.X) ||
(Pt.Y < Start.Y && Pt.Y < End.Y) || (Pt.Y > Start.Y && Pt.Y > End.Y))
return false;
// now we calculate the distance:
float dy = End.Y - Start.Y;
float dx = End.X - Start.X;
float Z = dy * Pt.X - dx * Pt.Y + Start.Y * End.X - Start.X * End.Y;
float N = dy * dy + dx * dx;
float dist = (float)( Math.Abs(Z) / Math.Sqrt(N));
// done:
return dist < Linewidth / 2f;
}
}
Define a List for the lines, probably at class level:
List<Line> lines = new List<Line>();
Here is how you can initialize it with a few lines:
for (int i = 0; i < 20; i++) lines.Add(new Line(Color.Black, 4f,
new Point(R.Next(panel1.Width), R.Next(panel1.Height)),
new Point(R.Next(panel1.Width), R.Next(panel1.Height))));
Here is the result of clicking on a crossing:
Whenever you add, change or remove a line you need to make the Panel reflect the news by triggering the Paint event:
panel1.Invalidate();
Here is the Paint event of the Panel:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
foreach (Line L in lines) L.Draw(e.Graphics);
}
In the MouseClick event you do the test:
private void panel1_MouseClick(object sender, MouseEventArgs e)
{
foreach(Line L in lines)
L.LineColor = L.HitTest(e.Location) ? Color.Red : Color.Black;
panel1.Invalidate();
}
To avoid flicker don't use the basic Panel class as it isn't doublebuffered. Instead use either a PictureBox or a Label (with AutoSize=false) or a doublebuffered Panel subclass:
class DrawPanel : Panel
{ public DrawPanel () { DoubleBuffered = true; } }
Notes:
There is no such thing as a 'Line' in WinForms, only pixels of various colors. So to select a line you need to store it's two endpoints' coordinates and then find out if you have hit it when clicking.
The above example shows how to do it in math.
Instead one could test each line by drawing it onto a bitmap and test the pixel the mouse has clicked. But drawing those bitmaps would have to do math behind the scenes as well and also allocate space for the bitmaps, so the math will be more efficient..
Yes the Line class looks a little long for such a simple thing a s a line but look how short all the event codes now are! That's because the responsiblities are where they belong!
Also note the the first rule of doing any drawing in WinForms is: Never cache or store a Grahics object. In fact you shouldn't ever use CreateGraphics in the first place, as the Graphics object will never stay in scope and the graphics it produces will not persist (i.e. survive a Minimize-maximize sequence)..
Also note how I pass out the e.Graphics object of the Paint event's parameters to the Line instances so they can draw themselves with a current Graphics object!
To select thinner lines it may help to modify the distance check a little..
The Math was taken directly form Wikipedia.
You can change the color of everything on click. By using click event of particular object.
I give you an example for button. If you click on button then panal’s color will be change. You can modify the code as per your requirement.
private List<Point> coordFirst = new List<Point>();
private List<Point> coordLast = new List<Point>();
public Graphics canvas;
private void Form1_Load(object sender, EventArgs e)
{
canvas = panel1.CreateGraphics();
}
private void panel1_Click(object sender, EventArgs e)
{
panel1.BackColor = Color.Blue;
}
private void nonSelectableButton3_Click(object sender, EventArgs e)
{
panel1.BackColor = Color.BurlyWood;
}
I am trying to make a elipse bounce on a rectangle. I am using a timer to move the elipse in both x and y-direction. My idea was to create an if statement to see if the coordinates of the elipse matches the coordinates of the rectangle.
Here is the code I had written so far:
public partial class Form1 : Form
{
Class1 square = new Class1();
public int before;
public int after;
public int c;
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
after = 590 - (b * b) / 100;
before = 100 + (a * a) / 100;
c = a + b;
Graphics g = e.Graphics;
SolidBrush Brush = new SolidBrush(Color.White);
g.FillEllipse(Brush, a, before, 10, 10);
square.Draw(g);
if (k >= square.y && a >= square.x && a <= square.x + 40)
{
a=c;
before= after;
timer1.Start();
timer2.Stop();
}
else if (k >= square.y + 10)
{
timer2.Stop();
MessageBox.Show("You lost");
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
square.x = e.X;
Cursor.Hide();
}
public int a = 0;
public int b = 0;
public void timer2_Tick(object sender, EventArgs e)
{
a += 1;
Invalidate();
}
private void timer1_Tick(object sender, EventArgs e)
{
b += 1;
Invalidate();
}
}
I know that there are problems. And I have some questions:
Is there a easier way to make the elipse " bounce"?
Is the problem solely with the maths of the curve that the elipse is following?
I know the question may be somewhat undefined or abstract but any help is appriciated. And if you want me to be clearer in some ways, let me know! Thanks
A simple way to make the ellipse bounce of the edges is to check its edge points against the bounds, and then just invert the proper direction. So, something like this should work, if you'll pardon the pseudo-code
loop used to animate the ellipse
before moving, check the position:
if right-most position == right wall
invert x velocity
if left-most position == left wall
invert x velocity
if top-most position == top wall
invert y velocity
if bottom-most position == bottom wall
invert y velocity
move ellipse to next position
This is a pretty simple simulation, but it should give you an idea of how to progress and develop a more sophisticated model. Hope this helps!
I have a program in C# (Windows Forms) which has a rectangle on a Picture Box. They can be drawn at an angle too (rotated). I want to rotate that rectangle using my mouse movements.
I have the code for moving that rectangle
Rectangle areaRect = new Rectangle(100,100, 300, 300);
Bool dragging = false;
Point ptOld = new Point(0, 0);
protected override void OnPaint(PaintEventArgs e)
{
Graphics dcPaint = e.Graphics;
dcPaint.DrawRectangle(rectPen, areaRect);
}
protected override void OnMouseDown(MouseEventArgs e)
{
ptOld = new Point(e.X, e.Y);
dragging = true;
}
protected override void OnMouseMove(MouseEventArgs e)
{
if(dragging = true)
{
Point ptNew = new Point(e.X, e.Y);
int dx = ptNew.X - ptOld.X;
int dy = ptNew.Y - ptOld.Y;
areaRect.Offset(dx, dy); // This one moves the rectangle
ptOld = ptNew;
this.Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
dragging = false;
}
Now My requirement is to rotate this rectangle, Any idea, how that can be achieved.
I think you want to calculate angle between two points on X-axis. If so, try the following code:
const double Rad2Deg = 180.0 / Math.PI;
return Math.Atan2(ptOld.Y - e.Y, e.X - ptOld.X) * Rad2Deg;
Also check out this article on calculating angle between two points
When rotating rectangle with mouse, you define the center of rotation (centerXY), in you case it will be the center of the rectangle maybe.
On mouse down record mouse coordinates, mouse_downXY. These two points define a base line. When moving mouse you'll define another line, formed by current mouse coordinates and the rectangle center.
So you'll need to compute the angle between line (centerXY, mouse_downXY) and (centerXY, current_mouseXY). Computing angle between 2 lines with knowing 3 points coordinates is simple trigonometry, so I won't write code for you :) However this post has the answer.
You can calculate the angle using the difference between the old and the new x mouse coordinate (dx in your example). You can use the RotateTransform method of the Graphics object to rotate the rectangle.
I modified your code to do the rotation in addition to the translation. You can move the rectangle with the left mouse button and you can rotate it using the right mouse button.
Rectangle areaRect = new Rectangle(100, 100, 300, 300);
bool dragging = false;
bool rotating = false;
Point ptOld = new Point(0, 0);
float angle = 0;
protected override void OnPaint(PaintEventArgs e)
{
Graphics dcPaint = e.Graphics;
dcPaint.RotateTransform(angle);
dcPaint.DrawRectangle(Pens.Black, areaRect);
dcPaint.RotateTransform(-angle);
}
protected override void OnMouseDown(MouseEventArgs e)
{
ptOld = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Left)
{
dragging = true;
}
if (e.Button == MouseButtons.Right)
{
rotating = true;
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (dragging == true)
{
Point ptNew = new Point(e.X, e.Y);
int dx = ptNew.X - ptOld.X;
int dy = ptNew.Y - ptOld.Y;
areaRect.Offset(dx, dy); // This one moves the rectangle
ptOld = ptNew;
this.Invalidate();
}
if (rotating == true)
{
Point ptNew = new Point(e.X, e.Y);
int dx = ptNew.X - ptOld.X;
angle = angle + dx / 10f;
ptOld = ptNew;
this.Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
dragging = false;
rotating = false;
}
Right now, the rectangle is rotated around its top left corner. If you apply a translation before the rotation, you can get it to rotate around its middle.
I got code from http://support.microsoft.com/kb/314945 to draw a reversible/rubber band rectangle. I added code to it so that when i leave the left mouse button a rectangle is also created on the image and then i use that for cropping the image.
This works superb. the only problem is that the rubberband rectangle doesnot start or end from where the mouse is... there is very little diference but still it is quite notable. i use the same co-ords to draw the rectangle afterwards which is drawn exactly where my mouse started and where is ended. help would be appreciated.
Here is the code: (Got issue fixed - adding code that others can benefit from it)
I have made it a control and i use it wherever i need it!
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace CroppingControl
{
public partial class CroppingImage : UserControl
{
Rectangle rc = new Rectangle();
Boolean bHaveMouse;
Point ptOriginal = new Point();
Point ptLast = new Point();
Image Pic;
public CroppingImage()
{
InitializeComponent();
pictureBox1.MouseDown += new MouseEventHandler(MyMouseDown);
pictureBox1.MouseUp += new MouseEventHandler(MyMouseUp);
pictureBox1.MouseMove += new MouseEventHandler(MyMouseMove);
bHaveMouse = false;
}
public Image Image
{
set
{
pictureBox1.Image = value;
Pic = value;
}
get
{
return pictureBox1.Image;
}
}
public void MyMouseDown(Object sender, MouseEventArgs e)
{
pictureBox1.Image = Pic;
// Make a note that we "have the mouse".
bHaveMouse = true;
// Store the "starting point" for this rubber-band rectangle.
ptOriginal.X = e.X;
ptOriginal.Y = e.Y;
// Special value lets us know that no previous
// rectangle needs to be erased.
ptLast.X = -1;
ptLast.Y = -1;
}
// Convert and normalize the points and draw the reversible frame.
private void MyDrawReversibleRectangle(Point p1, Point p2)
{
Point px = p1;
Point py = p2;
// Convert the points to screen coordinates.
p1 = PointToScreen(p1);
p2 = PointToScreen(p2);
// Normalize the rectangle.
if (p1.X < p2.X)
{
rc.X = p1.X;
rc.Width = p2.X - p1.X;
}
else
{
rc.X = p2.X;
rc.Width = p1.X - p2.X;
}
if (p1.Y < p2.Y)
{
rc.Y = p1.Y;
rc.Height = p2.Y - p1.Y;
}
else
{
rc.Y = p2.Y;
rc.Height = p1.Y - p2.Y;
}
// Draw the reversible frame.
ControlPaint.DrawReversibleFrame(rc, Color.Black, FrameStyle.Dashed);
rc.X = px.X;
rc.Y = px.Y;
}
// Called when the left mouse button is released.
public void MyMouseUp(Object sender, MouseEventArgs e)
{
// Set internal flag to know we no longer "have the mouse".
bHaveMouse = false;
// If we have drawn previously, draw again in that spot
// to remove the lines.
if (ptLast.X != -1)
{
Point ptCurrent = new Point(e.X, e.Y);
MyDrawReversibleRectangle(ptOriginal, ptLast);
Graphics graphics = pictureBox1.CreateGraphics();
Pen pen = new Pen(Color.Gray, 2);
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.DashDot;
graphics.DrawRectangle(pen, rc);
}
// Set flags to know that there is no "previous" line to reverse.
ptLast.X = -1;
ptLast.Y = -1;
ptOriginal.X = -1;
ptOriginal.Y = -1;
}
// Called when the mouse is moved.
public void MyMouseMove(Object sender, MouseEventArgs e)
{
Point ptCurrent = new Point(e.X, e.Y);
// If we "have the mouse", then we draw our lines.
if (bHaveMouse)
{
// If we have drawn previously, draw again in
// that spot to remove the lines.
if (ptLast.X != -1)
{
MyDrawReversibleRectangle(ptOriginal, ptLast);
}
// Update last point.
ptLast = ptCurrent;
// Draw new lines.
MyDrawReversibleRectangle(ptOriginal, ptCurrent);
}
}
}
}
ControlPaint.DrawReversibleFrame uses screen co-ordinates to draw a rectangle on the screen (i.e. without respect to your application's windows) which is useful when acting upon drag mouse actions as the mouse may move outside of the application window. If you are using these very same co-ordinates raw to paint in your application, then they will be out as the co-ordinates on the control upon which you are painting are with respect to the control's origin (usually its top-left corner).
To use the screen co-ordinates, you first need to convert them into control co-ordinates using the PointToClient() or RectangleToClient() methods on the control upon which you are painting, e.g.
// panel`s OnPaint
Rectangle screenRectangle = ...
Rectangle clientRectangle = panel.RectangleToClient(screenRectangle);
graphics.DrawRectangle(Pens.Red, clientRectangle);