How do I detect click on a line in Windows Forms - c#

I have a winforms application
Here is my code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication12
{
public partial class Form1 : Form
{
Graphics gr;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
gr = this.CreateGraphics();
MyLine myline = new MyLine();
myline.P1 = new Point(100, 0);
myline.P2 = new Point(200, 80);
gr.DrawLine(new Pen(Color.Red), myline.P1,myline.P2);
Rectangle r = new Rectangle(0, 0, 50, 50);
gr.DrawRectangle(new Pen(Color.Teal, 5), r);
if (r.Contains(0,25)) MessageBox.Show("within");
}
private void btnClear_Click(object sender, EventArgs e)
{
gr.Clear(this.BackColor);
}
}
}
class MyLine
{
public Point P1 {get; set;}
public Point P2 { get; set; }
}
My problem is this..
I can draw a rectangle, and I can see whether a point is within it.
So I could extend the program to say "yes" when a click on the form is within the rectangle. The Rectangle has a Contains function which is great.
But I want to do the same for Line.
The problem, is that winforms has no Line class. I could write my own Line class, but the problem remains.. how to find whether a click landed on it?
I notice that WPF has such a class How do I recognize a mouse click on a line?
But i'm using winforms.

Using GraphicsPath.IsOutlineVisible method you can determine whether the specified point is under the outline of the path when drawn with the specified Pen. You can set width of the pen.
So you can create a GraphicsPath and then add a line using GraphicsPath.AddLine to the path and check if the path contains the point.
Example:
The below method, checks if the p is on the line with end points p1 and p2 using the specified width.
You can use wider width to increase the tolerance or if the line is wider than 1:
//using System.Drawing;
//using System.Drawing.Drawing2D;
bool IsOnLine(Point p1, Point p2, Point p, int width = 1)
{
using (var path = new GraphicsPath())
{
using (var pen = new Pen(Brushes.Black, width))
{
path.AddLine(p1, p2);
return path.IsOutlineVisible(p, pen);
}
}
}

I implemented a simple Line class to check if a dot fall on the line.
You can capture a mouse position out of Form_Click event
Here's the snippet
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 WindowsFormsApplication
{
public partial class Form1 : Form
{
Line myLine;
int x1 = 10;
int x2 = 40;
int y1 = 0;
int y2 = 30;
public Form1()
{
InitializeComponent();
myLine = new Line() { Start = new Point(x1, y1), Stop = new Point(x2, y2), Epsilon = 10 };
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0));
e.Graphics.DrawLine(pen, x1, y1, x2, y2);
pen.Dispose();
}
private void Form1_Click(object sender, EventArgs e)
{
MouseEventArgs me = (MouseEventArgs)e;
bool contain = myLine.contain(new Point(me.X,me.Y));
}
}
public class Line
{
public Point Start { get; set; }
public Point Stop { get; set; }
public float Epsilon { get; set; }
public bool contain(Point p)
{
// y = mx + c
float m = (Stop.Y - Start.Y) / (Stop.X - Start.X);
float c = Stop.Y - (m * Stop.X);
return p.X >= Math.Min(Start.X, Stop.X)
&& p.X <= Math.Max(Start.X, Stop.X)
&& p.Y >= Math.Min(Start.Y, Stop.Y)
&& p.Y <= Math.Max(Start.Y, Stop.Y)
&& Math.Abs(Math.Abs(p.Y) - Math.Abs((m * p.X) + c)) < epsilon; //with relax rules
//&& (p.Y == (m*p.X)+c); // strict version
}
}
UPDATE
careful of a case where X1 == X2. It will throw exception.

Related

C# draw in panel and shift left the drawn objects

I'm drawing rectangles in a panel starting from the left side.
When I reach the right side of the panel I'd like to shift left the rectangles previously drawn to have the space to draw another one, and so on.
Which is the simplest way to do it?
I'm drawing using System.Drawings.Graphics.
I'm using Winforms. The code is:
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;
using System.Runtime.InteropServices;
namespace FFViewer
{
public partial class Form1 : Form
{
[DllImport("shlwapi.dll")]
public static extern int ColorHLSToRGB(int H, int L, int S);
int x=0;
int y=300;
int h = 0;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
panel_Paint();
x = x + 40;
h = h + 40;
if (h >= 240)
h = 0;
}
private void panel_Paint()
{
int val;
Color color;
val = ColorHLSToRGB(h, 128, 240);
Graphics g = panel1.CreateGraphics();
color = ColorTranslator.FromWin32(val);
SolidBrush sb = new SolidBrush( color );
g.FillRectangle(sb, x, y, 40, 100);
}
}
}
So, when I draw the latest rectangle on the right side, I'd like to shift left all the rectangles to leave the space to draw another one on the right side.
P.S. I don't have enough reputation to post images :(
Here's the "old school" way of doing it. This is basically what was done when a continuous graph of a real-time value needed to be displayed AND you didn't want to store the any of the values anywhere. This has severe limitations as it copies from the screen and the drawing will be erased the when window repaints itself. This first example is simply here to demonstrate the process, and is an extension of the way you created the initial blocks:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[DllImport("shlwapi.dll")]
public static extern int ColorHLSToRGB(int H, int L, int S);
int x = 0;
int width = 40;
int y = 300;
int height = 100;
int h = 0;
private void button1_Click(object sender, EventArgs e)
{
if (x + width > panel1.ClientSize.Width) // if drawing the next block would exceed the panel width...
{
// capture what's currently on the screen
Bitmap bmp = new Bitmap(x, height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(panel1.PointToScreen(new Point(0, y)), new Point(0, 0), bmp.Size);
}
// draw it shifted to the left
using (Graphics g = panel1.CreateGraphics())
{
g.DrawImage(bmp, new Point(-width, y));
}
// move x back so the new rectangle will draw where the last one was previously
x = x - width;
}
// draw the new block and increment values
panel_Paint();
x = x + width;
h = h + width;
if (h >= 240)
{
h = 0;
}
}
private void panel_Paint()
{
int val;
Color color;
val = ColorHLSToRGB(h, 128, 240);
color = ColorTranslator.FromWin32(val);
using (Graphics g = panel1.CreateGraphics())
{
using (SolidBrush sb = new SolidBrush(color))
{
g.FillRectangle(sb, x, y, width, height);
}
}
}
}
This could be fixed by creating a Bitmap of the correct size and drawing to that instead. Then you shift everything and draw the new block on the right side. Finally, you'd draw that Bitmap in the Paint() event. So this is doing the same thing as above except we aren't copying from the screen, and the panel will properly redraw itself when requested:
public partial class Form1 : Form
{
[DllImport("shlwapi.dll")]
public static extern int ColorHLSToRGB(int H, int L, int S);
int x = 0;
int width = 40;
int y = 300;
int height = 100;
int h = 0;
Bitmap bmp;
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
panel1.Paint += Panel1_Paint;
}
private void Form1_Load(object sender, EventArgs e)
{
int numBlocks = (int)(panel1.Width / width);
bmp = new Bitmap(numBlocks * width, height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(panel1.BackColor);
}
}
private void Panel1_Paint(object sender, PaintEventArgs e)
{
if (bmp != null)
{
e.Graphics.DrawImage(bmp, new Point(0, y));
}
}
private void button1_Click(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(bmp))
{
if (x + width > bmp.Width) // if drawing the next block would exceed the bmp width...
{
g.DrawImage(bmp, new Point(-width, 0)); // draw ourself shifted to the left
x = x - width;
}
// draw the new block
int val;
Color color;
val = ColorHLSToRGB(h, 128, 240);
color = ColorTranslator.FromWin32(val);
using (SolidBrush sb = new SolidBrush(color))
{
g.FillRectangle(sb, x, 0, width, height);
}
}
x = x + width;
h = h + width;
if (h >= 240)
{
h = 0;
}
panel1.Invalidate(); // force panel1 to redraw itself
}
}
You should not use panel1.CreateGraphics(), but instead handle the Paint event of the panel, otherwise the rectangles might disappear, for instance after a popup appears in front of your form:
panel1.Paint += new PaintEventHandler(panel1_paint);
You'll need to paint all (visible) rectangles in the paint handler; you could keep a List<Rectangle> in your form to store the rectangles you have added:
private List<Rectangle> rectangles = new List<Rectangle>();
...
private void button1_Click(object sender, EventArgs e)
{
rectangles.Add(new Rectangle(x, y, width, height));
panel1.Invalidate(); // cause the paint event to be called
// todo increment x/y
}
Then, in the panel1_Paint handler you can simply draw the rectangles, after having called Graphics.TranslateTransform() to shift the whole drawing area:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.TranslateTransform(-x, 0);
foreach (Rectangle rectangle in rectangles)
{
// paint em
}
e.Graphics.ResetTransform();
}

c# Custom control click event

I've made a custom control and I want to add a click event so when the user clicks anywhere on the control it will return the position of where the user has clicked on the control. For example if the user clicks in the middle of the bar it would essentially return me enough information for me to calculate 50% and that the user has clicked in the middle.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace CustomRangeBar
{
public partial class RangeBar : UserControl
{
public RangeBar()
{
InitializeComponent();
label1.ForeColor = Color.Black;
this.ForeColor = SystemColors.Highlight; // set the default color the rangeBar
this.Click += new EventHandler(RangeBar_Click);
}
protected float percent = 0.0f; // Protected because we don't want this to be accessed from the outside
// Create a Value property for the rangeBar
public float Value
{
get
{
return percent;
}
set
{
// Maintain the value between 0 and 100
if (value < 0) value = 0;
else if (value > 100) value = 100;
percent = value;
label1.Text = value.ToString();
//redraw the rangeBar every time the value changes
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush b = new SolidBrush(this.ForeColor); //create brush that will draw the background of the range bar
// create a linear gradient that will be drawn over the background. FromArgb means you can use the Alpha value which is the transparency
LinearGradientBrush lb = new LinearGradientBrush(new Rectangle(0, 0, this.Width, this.Height), Color.FromArgb(255, Color.White), Color.FromArgb(50, Color.White), LinearGradientMode.Vertical);
// calculate how much has the rangeBar to be filled for 'x' %
int width = (int)((percent / 100) * this.Width);
e.Graphics.FillRectangle(b, 0, 0, width, this.Height);
e.Graphics.FillRectangle(lb, 0, 0, width, this.Height);
b.Dispose(); lb.Dispose();
}
private void RangeBar_SizeChanged(object sender, EventArgs e)
{
// maintain the label in the center of the rangeBar
label1.Location = new Point(this.Width / 2 - 21 / 2 - 4, this.Height / 2 - 15 / 2);
}
}
}
public void RangeBar_Click(object obj, EventArgs ea)
{
// This get executed if the pictureBox gets clicked
label1.text = "Increment 1";
}
OnClick is not a good function to override or subscribe to because it does not tell you the position where the click happened which is what you are looking for.
What you want is OnMouseClick which includes the x,y of the click point.
protected override void OnMouseClick(MouseEventArgs e)
{
int x = e.X;
int y = e.Y;
//Do your calculation here.
}

how to allow g.drawstring in c# to be adjusted after it is placed in picturebox

I am trying to use a picturebox and pick out 2 points(eyes in a photo). I would pick out the two points with g.DrawString where I will draw 2 different 'x'. The problem now is I am stuck as what if the user has placed the 'x' on a wrong position and I would like to adjust it. Is there any codes which will allow the g.DrawString 'x' to be able to be moved?
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 Camera
{
public partial class CamDisplay : Form
{
public CamDisplay()
{
InitializeComponent();
this.pictureBox1.ImageLocation = #"C:\center90\center90(1).jpg";
}
bool MousedClicked = true;
bool MouseClicked2 = true;
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
base.OnMouseClick(e);
if (MousedClicked == true)
{
txtXaxis.Text = e.X.ToString();
txtYaxis.Text = e.Y.ToString();
MousedClicked = false;
using (Graphics g = Graphics.FromHwnd(pictureBox1.Handle))
{
using (Font myFont = new Font("Calibri", 8))
{
g.DrawString("X", myFont, Brushes.Red, new PointF(e.X, e.Y));
}
}
}
else if (MouseClicked2 == true)
{
txtRXaxis.Text = e.X.ToString();
txtRYaxis.Text = e.Y.ToString();
MouseClicked2 = false;
using (Graphics g = Graphics.FromHwnd(pictureBox1.Handle))
{
using (Font myFont = new Font("Calibri", 8))
{
g.DrawString("X", myFont, Brushes.Red, new PointF(e.X, e.Y));
}
}
}
else
{
MousedClicked = false;
MouseClicked2 = false;
}
}
private void CamDisplay_MouseEnter(object sender, EventArgs e)
{
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox1.Cursor = Cursors.Hand;
}
}
}
Your question is unclear. As I understand your problem you have two options:
If you are using your Graphics object to directly manipulate the BMP (ie. you DrawString directly to the bitmap) there is no way to "undo" a previous operation. You could instead keep a local copy of the original bitmap in memory, duplicate it to a second bitmap and perform the operations only on the copy, which is the one you show in your form. When you need to "undo" you just go back to the original copy.
Do not manipulate the bitmap directly, but instead perform your drawing operations in the OnPaint() event of the PictureBox. In that way you only draw to the screen, leaving the original Bmp intact. See example here: http://msdn.microsoft.com/en-us/library/b818z6z6%28v=vs.110%29.aspx

How to draw using trackbar?

well how do you draw in C# using variables?
ive managed to draw some shapes but only when i hardcode in the lengths. i need to draw shapes using a trackbar to get the lengths.
public abstract class Shape
{
//private String shape;
private int length;
}
public virtual void setLength(int newLength)
{
this.length = newLength;
}
public virtual int getLength()
{
return length;
}
//public String getShape()
//{
// return shape;
//}
//abstract public double getLength(float length);
abstract public float getPerimeter(int length);
abstract public float getArea(int length);
only showing square class but this project also includes triangle and square.
using System;
using System.Drawing;
public class Square : Shape
{
private float perimeter, area;
public override float getPerimeter(int length)
{
perimeter = length*4;
return perimeter;
}
public override float getArea(int length)
{
area = length*length;
return area;
}
}
this is the class with all my event handlers
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 shapes
{
//private System.Windows.Forms.TrackBar trackBar1;
public partial class Form1 : Form
{
private Shape shape;
private int length = 0;
private int shapeL = 0;
public Form1()
{
InitializeComponent();
}
private void panel2_Paint(object sender, PaintEventArgs e)
{
}
private void menuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
label3.Text = "Length Slider: " + trackBar1.Value;
textBox1.Text = shape.getPerimeter(shape.getLength()).ToString("0.00");
textBox2.Text = shape.getArea(shape.getLength()).ToString("0.00");
textBox1.Refresh();
textBox2.Refresh();
length = trackBar1.Value;
shape.setLength(length);
}
private void onCircleClick(object sender, EventArgs e)
{
shape = new Circle();
//length = trackBar1.Value;
length = shape.getLength();
this.Refresh();
using (Graphics g = this.panel1.CreateGraphics())
{
Pen pen = new Pen(Color.Black, 2);
Graphics formGraphics;
formGraphics = this.panel1.CreateGraphics();
formGraphics.DrawEllipse(pen, 50, 50, length, length);
//g.DrawEllipse(pen, 100, 100, length, length);
}
}
private void onSquareClick(object sender, EventArgs e)
{
shape = new Square();
length = trackBar1.Value;
using (Graphics g = this.panel1.CreateGraphics())
{
Pen pen = new Pen(Color.Black, 2);
g.DrawRectangle(pen, 50, 50, length, length);
System.Windows.Forms.MessageBox.Show("lenght is: " + length);
}
}
private void onTriangleClick(object sender, EventArgs e)
{
shape = new Triangle();
length = trackBar1.Value;
using (Graphics g = this.panel1.CreateGraphics())
{
SolidBrush blueBrush = new SolidBrush(Color.Blue);
// Create points that define polygon.
Point point1 = new Point(50, 50);
Point point2 = new Point(50, 100);
Point point3 = new Point(100, 50);
Point[] curvePoints = { point1, point2, point3};
// Draw polygon to screen.
g.FillPolygon(blueBrush, curvePoints);
}
}
private void shapeToolStripMenuItem_Click(object sender, EventArgs e)
{
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Pen pen = new Pen(Color.Black, 2);
Graphics g = pe.Graphics;
g = this.CreateGraphics();
g.DrawRectangle(pen, 50, 50, length, length);
}
private void OnPaint(object sender, PaintEventArgs e)
{
}
}
}
yes its very messy, as you can see ive tried various things.
whats the difference between the panel1_paint and onPaint?
as you can see im not too sure how to use eventhandlers, the onCircleClick is basically a menu item button but how do i activate a different eveenthandler(panel1_Paint) from another eventhandler(onCircleClick)?
do graphics need to drawn in a *_paint/OnPaint method? ive gotten mine to draw in just the normal panel.
next is whats the best course of action to get the trackbar value to the shape object and back again to the method? yes the data is being saved (i think) when i use displayMessage(shape.getLength) it displays the length and is usually one off.
whats the equilent to repaint() in java for c#? ive tried this.Refresh(); but it doesnt work itll draw the shape then makes it disappear.
am i writing my setters/getters properly? or should i use
public int X
{
get {return x;}
set {x = value;}
}
in java, graphics will draw on any panel, in c# does it need to be in a specific container?
this is very simple, lets say that you want to draw on panel2.
all you have to do is to write this inside your private void panel2_Paint(object sender, PaintEventArgs e) body.
{
e.Graphics.Clear(panel1.BackgroundColor);
int length = trackBar1.Value;
Pen pen = new Pen(Color.Black, 2);
e.Graphics.DrawRectangle(pen, 50, 50, length, length);
}
and whenever you want to refresh the drawing, you can call either panel2.Refresh() or panel2.Invalidate(). both would do the job.
note that if you did this, panel2 won't get cleared after drawing the shape as it happened with you before.
note also that the panel would flicker as you change the trackbar value. i know how to handle this, but i don't want to complicate the solution for now.

How to print rectangle with exact size in c#?

I am new to Dot Net, I want to print a rectangle with 20mm width and 8mm height exactly if I measure with scale. I also want to print text exactly in the middle of rectangle.Can anyone suggest me how can I achieve this?
I am really sorry for not being clear earlier. I have tried using "PageUnits" its working fine. However, I have problem with the margins.
I am able to print correct margins(8.8mm left and 22mm Top) if I am using the printer "HP LaserJet P2035n". If print using "Canon iR2020 PCL5e" I am getting incorrect margins(8.1mm left and 8.0mm Top) where I should get 8.8mm left and 22mm top margins. Can someone explain me where I am doing wrong.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Printing;
namespace ConsoleApplication6
{
class DrawShape
{
public static void DrawRec()
{
PrintDocument doc = new PrintDocument();
doc.PrintPage += doc_PrintPage;
doc.Print();
}
static void doc_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
PageSettings PageSet = new PageSettings();
float MarginX = PageSet.PrintableArea.X;
float MarginY = PageSet.PrintableArea.Y;
float x = (float)(8.8-((MarginX/100)*25.4));
float y = (float)(22-((MarginY/100)*25.4));
g.PageUnit = GraphicsUnit.Millimeter;
g.DrawRectangle(Pens.Black, x, y, 20, 8);
}
}
}
You may want to start with this:
private void button1_Click(object sender, EventArgs e)
{
using (Graphics formGraphics = this.CreateGraphics())
{
formGraphics.PageUnit = GraphicsUnit.Millimeter;
formGraphics.DrawRectangle(Pens.Blue, 0, 0, 20, 80);
}
}
You can then using DrawString on the Graphics object to draw text inside your rectangle.

Categories

Resources