Drawing lines on a panel - c#

I try to make a graphics.
When I click on my label, I want to draw a line. It works, it draw my line but at the last point there is another line going at the left top corner.. I don't know why.
(It's useless, but it's for another project, I try to understand how works the drawing)
Here's my code :
public partial class Form1 : Form
{
Pen myPen = new Pen(Color.Blue);
Graphics g = null;
int start_x = 0, start_y;
Point[] points = new Point[1000];
int test = 0;
public Form1()
{
InitializeComponent();
start_y = canvas.Height / 2;
points[0] = new Point (start_x,start_y);
myPen.Width = 1;
}
private void drawLine()
{
g.DrawLines(myPen, points);
}
private void incrementation_Click(object sender, EventArgs e)
{
test = test + 1;
incrementation.Text = test.ToString();
if(test == 1)
{
points[1] = new Point(100, start_y);
}
if (test == 2)
{
points[test] = new Point(200, 90),new Point(220, 10);
}
if (test == 3)
{
points[test] = new Point(220, 10);
drawLine();
}
}
private void canvas_Paint(object sender, PaintEventArgs e)
{
g = canvas.CreateGraphics();
}
}

A couple of issues.
You don't assign any values to points after points[3].
Point is a structure and will have a value of [0,0] at all further elements
so your lines go there.. (all 996 of them ;-)
There is more you should change:
Do the drawing in the Paint event or trigger it from there.
Do not store the Paint e.Grahpics object. You can pass it out to use it, but don't try to hold on to it.
After adding or changing the points, write canvas.Invalidate() to trigger the Paint event. This will make your drawing persistent.
To learn about persistent drawing minimize & restore the form!
Also you should use a List<Point> instead of an array. This lets you add Points without having to decide on the number of Points you want to support..
To create a new Point you write something like this:
points.Add(new Point(100, start_y) );
To draw you then use this format in the Paint event::
e.Graphics.DrawLines(myPen, points.toArray());

In the constructor you're filling first point as
points[0] = new Point (start_x,start_y);
At this moment, start_x = 0 (since you're not assigned anything else to it after declaration int start_x = 0).
Then in incrementation_Click you're assigning points[1], points[2] and points[3], but you don't changing anywhere in your code points[0].
So when you calling g.DrawLines - first point will always be (0, canvas.Height / 2)
Aside from this:
You don't need to create graphics explicitly in _Paint event handler since it can accessed as e.Graphics.
It's better to move all paintings into canvas_Paint like:
private void canvas_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLines(myPen, points);
}
and in your _Click handler instead of calling drawLine you should only call canvas.Refresh()

Related

Struggling to get new graphics to appear with each tick of the timer

Sorry if this sounds a bit of a dim question - I'm completely new to this. I'm trying to get a new ellipse to appear on each tick of a timer. So far, I have:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication6
{
public partial class Form1 : Form
{
private Bitmap DrawingArea;
int numberofcircles = 0;
int[] narrary = new int[30];
int newcircle;
Random rnd = new Random();
public Form1()
{
InitializeComponent();
Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
numberofcircles = numberofcircles + 1;
newcircle = (rnd.Next(15) * 6) + 76;
narrary[numberofcircles] = newcircle;
Invalidate();
timer1.Start();
}
private void Form1_Load(object sender, EventArgs e)
{
DrawingArea = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Pen pen = new Pen(Color.Black);
using (var canvas = Graphics.FromImage(DrawingArea))
{ canvas.Clear(Color.Transparent);
canvas.DrawLine(pen, 100, 100, 700, 100);
for (int i = 1; i <= numberofcircles; i++)
{
canvas.DrawEllipse(pen, 180 + (30 * i), narrary[i], 8, 6);
}
}
this.Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Pen pen = new Pen(Color.Red);
for (int i = 1; i <= numberofcircles; i++)
{
canvas.DrawEllipse(pen, 180 + (30 * i), narrary[i], 8, 6);
}
e.Graphics.DrawImage(DrawingArea, 0, 0, DrawingArea.Width, DrawingArea.Height);
}
private void timer1_Tick(object sender, EventArgs e)
{
numberofcircles = numberofcircles + 1;
newcircle = (rnd.Next(15) * 6) + 76;
narrary[numberofcircles] = newcircle;
for (int i = 1; i <= numberofcircles; i++)
{
canvas.DrawEllipse(pen, 180 + (30 * i), narrary[i], 8, 6);
}
Invalidate();
}
}
}
"canvas" and "pen" references are flagging up as errors in the Form1_Paint and timer1_Tick sections ("The name 'canvas' does not exist in the current context"). I'm sure I must be referencing them wrong, but I'm afraid I don't have the basic C# knowledge to be able to sort this out!
I'd be very grateful for any help.
There are a number of concepts it looks like you need explaining.
First, as noted in the comments, you need to pay attention to "scoping". In C# (and most other languages), variables have a well-defined scope that prevent them from being visible except where they are relevant. The variable you're having trouble with are "local variables", and are valid only in the method in which they are declared.
If you want those variables to be visible to other methods, they need to be declared somewhere that they are visible to all methods. For example, you could declare them as instance variables.
But in this case, that would be the wrong thing to do. Another concept you seem to have trouble with is how drawing in a Winforms program works. You should only draw to the screen in the Paint event handler. There are a couple of ways to approach this:
Keep track of the data that is the basis of what you're drawing, and then redraw everything any time the Paint event is raised.
Maintain an off-screen bitmap, drawing new data to it as needed, and then draw this bitmap to the screen when the Paint event is raised.
Either way, you cause the Paint event to be raised by calling Invalidate().
Here is an example of the first approach, based on your original code:
public partial class Form1 : Form
{
const int maxCircleCount = 30;
List<int> circles = new List<int>();
Random rnd = new Random();
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
private void button1_Click(object sender, EventArgs e)
{
// Arguably, you only need to start the timer and can skip these first two lines
circles.Add(rnd.Next(15) * 6 + 76);
Invalidate();
timer1.Start();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Black, 100, 100, 700, 100);
for (int i = 0; i < circles.Count; i++)
{
e.Graphics.DrawEllipse(Pens.Red, 180 + (30 * i), circles[i], 8, 6);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if (circles.Count < maxCircleCount)
{
circles.Add(rnd.Next(15) * 6 + 76);
Invalidate();
}
else
{
timer1.Stop();
}
}
}
The above will draw 30 circles after you click the button, one per timer tick, and then stop the timer. Since in your code example, when the Load event handler is called you don't have any circles yet, it wasn't clear to me what your intent with that code was. So I did not bother to draw any black circles.
Other changes include:
Setting DoubleBuffered to true, to avoid flickering when Invalidate() is called.
Using the stock Pens objects instead of creating them (note that your code, which did create new Pen objects, should have disposed the Pen objects it created…that it did not was also a bug).
Use a List<int> for the circles. This encapsulates the storage and the count in a single object.
Note that an improvement I didn't bother to make would be to consolidate the circles.Add() and Invalidate() calls into a separate method that can be called by any place it needs to be.
Hopefully the above gets you back on track. There are lots of other questions on Stack Overflow discussing the various nuances of how to draw in a Winforms program, whether from raw data or by caching to an off-screen bitmap. You should be able to use those posts to refine your techniques.
See also e.g. Force Form To Redraw? and How do I call paint event?, which include some answers that elaborate on the basic "call Invalidate()" concept.

c# draw lines with dragging

How to draw a line like the windows Paint does, single click for a fixed first point, and the second point (and the line) moves with mouse, another click fixes the line.
int x = 0, y = 0;
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// Create the graphics object
Graphics g = CreateGraphics();
// Create the pen that will draw the line
Pen p = new Pen(Color.Navy);
// Create the pen that will erase the line
Pen erase = new Pen(Color.White);
g.DrawLine(erase, 0, 0, x, y);
// Save the mouse coordinates
x = e.X; y = e.Y;
g.DrawLine(p, 0, 0, x, y);
}
The clicking event part is fine, but with this method above, the erase line is actually white lines, which overlaps on other background image and previously plotted blue lines.
Is there a more manageable way to make it happen? Thanks
Any drawing on the form client area should be implemented in the OnPaint event to avoid any strange effects.
Consider the following code fragment:
Point Latest { get; set; }
List<Point> _points = new List<Point>();
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// Save the mouse coordinates
Latest = new Point(e.X, e.Y);
// Force to invalidate the form client area and immediately redraw itself.
Refresh();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
base.OnPaint(e);
if (_points.Count > 0)
{
var pen = new Pen(Color.Navy);
var pt = _points[0];
for(var i=1; _points.Count > i; i++)
{
var next = _points[i];
g.DrawLine(pen, pt, next);
pt = next;
}
g.DrawLine(pen, pt, Latest);
}
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
Latest = new Point(e.X, e.Y);
_points.Add(Latest);
Refresh();
}
Don't try to erase the lines by drawing on top of them. You'll be better off if you draw to an off-screen buffer and on each draw call paint that bitmap to the control. That way you'll get flicker-free graphics and a clean line that works just the way you want it to.
Take a look at this forum post for a good explanation of how you should use the Graphics class and do drawing in general. There's also a good example program in the end of the post. I suggest you to take a look at that source code after going through the instructions.

Adding element to another element win forms

I am adding one panel to another panel it works but circle which is in inspanel not shown. how can i solved this.
I am using below code. It works but not show circle
public static int xTemp = 0;
public static int yTemp = 0;
private void button1_Click(object sender, EventArgs e)
{
Panel insPanel = new Panel();
Random xRandom = new Random();
xTemp= xRandom.Next(20,100);
Random yRandom = new Random();
yTemp = yRandom.Next(20, 100);
insPanel.Location = new Point(xTemp, yTemp);
insPanel.Width=40;
insPanel.Height = 40;
insPanel.Visible = true;
insPanel.BorderStyle = BorderStyle.FixedSingle;
insPanel.Paint += new PaintEventHandler(insPanel_Paint);
panel1.Controls.Add(insPanel);
}
void insPanel_Paint(object sender, PaintEventArgs e)
{
System.Drawing.SolidBrush myBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Red);
System.Drawing.Graphics formGraphics =this.CreateGraphics();
formGraphics.FillEllipse(myBrush, new Rectangle(xTemp,yTemp, 10, 10));
myBrush.Dispose();
formGraphics.Dispose();
}
Main issue: you're trying to draw your circle at wrong coordinates.
xTemp and yTemp are coordinates of insPanel relative to panel1. But when you drawing your circle, you should use coordinates relative to panel you're drawing at - insPanel.
Another issue: there is no need to create and dispose graphics each time you're drawing something on your panel. You can use e.Graphics from Paint eventhandler arguments.
Based on above, your code could look like:
void insPanel_Paint(object sender, PaintEventArgs e)
{
using (var myBrush = new SolidBrush(Color.Red))
{
e.Graphics.FillEllipse(myBrush, new Rectangle(0, 0, 10, 10));
}
}
Also note - since paint event can occur very frequently, it could be a good idea not to create and dispose brush every time, but use brush cached in your class private field.
Use e.Graphics instead of this.CreateGraphics:
System.Drawing.Graphics formGraphics = e.Graphics;
One more issue is you're getting coordinates in range of 20 - 100 (xRandom.Next(20,100)) and your panel dimensions are just 40, 40.

Calling a method from another class inside Panel_Paint does not draw anything

So this should be very simple but I have looked at some similar questions and can't find an answer.
I have a Form1 class and a Resistor class. Inside the Form1 class I have a Panel(I changed the name to Canvas), inside the Canvas_Paint method I am calling the method Draw from the Resistor class but is not drawing anything.
Form1 Class:
public partial class Form1 : Form
{
static float lineWidth = 2.0F;
static float backgroundLineWidth = 2.0F;
static Pen pen = new Pen(Color.Yellow, lineWidth);
static Pen backgroundPen = new Pen(Color.LightGray, backgroundLineWidth);
private bool drawBackground = true;
private List<Resistor> resistors = new List<Resistor>();
public Form1()
{
InitializeComponent();
}
private void Canvas_Paint(object sender, PaintEventArgs e)
{
if (drawBackground)
{
Console.WriteLine("Drawing background...");
Draw_Background(e.Graphics, backgroundPen);
}
if (resistors != null)
{
foreach (Resistor r in resistors)
{
//This does not work.
r.Draw(e.Graphics);
}
}
//The line below draws the line fine.
e.Graphics.DrawLine(pen, 0, 0, 100, 100);
}
private void Draw_Background(Graphics g, Pen pen)
{
for (int i = 0; i < Canvas.Width; i += 10)
{
g.DrawLine(pen, new Point(i, 0), new Point(i, Canvas.Height));
}
for (int j = 0; j < Canvas.Height; j += 10)
{
g.DrawLine(pen, new Point(0, j), new Point(Canvas.Width, j));
}
drawBackground = false;
}
private void AddResistor_Click(object sender, EventArgs e)
{
resistors.Add(new Resistor());
Console.WriteLine("Added a Resistor...");
}
}
Resistor Class:
public class Resistor
{
static private Point startingPoint;
static Pen defaultPen;
private Point[] points;
public Resistor()
{
startingPoint.X = 100;
startingPoint.Y = 100;
defaultPen = new Pen(Color.Yellow, 2.0F);
points = new Point[] {
new Point( 10, 10),
new Point( 10, 100),
new Point(200, 50),
new Point(250, 300)
};
}
public void Draw(Graphics g)
{
//Is this drawing somewhere else?
g.DrawLines(defaultPen, points);
}
}
I have looked at this question which suggests to pass the e.Graphics object in this case to the Draw method in the Resistor class but is not working.
I am new to C# so I would really appreciate any help.
EDIT :
I put the project on github if you want to download and try it out.
EDIT :
So the problem was that after clicking the button the panel Paint method was not being called. The solution was to add Canvas.Invalidate inside the AddResistor_Click method
Run your code in the debugger and put a breakpoint in your event handler and you'll be able to check that your code is actually trying to draw something. If not, then is your event handler ever called? is there anything in your list of resistors? If it is drawing but you don't see anything, then you're not using the correct Graphics context or you're not drawing things in the visible part of your control, or you're drawing over things with subsequent drawing code.
The problem was that when the button was clicked the panel's paint method was not getting called because I tough that the paint method was always getting called. The solution was to add Canvas.Invalidate inside the AddResistor_Click method.
private void AddResistor_Click(object sender, EventArgs e)
{
resistors.Add(new Resistor());
Console.WriteLine("Added a Resistor...");
Canvas.Invalidate();
}

How to handle mouse event

I want to click inside a square and then an "X" should appear, but I'm not sure what to put inside the Form1_MouseDown, Form1_Paint and Form1_MouseUp events. How can I implement this is C#?
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 VTest
{
public partial class Form1 : Form
{
Rectangle rect; // single rect
int sqsize, n;
int margin;
public Form1()
{
n = 3;
margin = 25;
sqsize = 50;
rect = new Rectangle(10, 10, 150, 150);
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
// what goes here?
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
// what goes here?
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
// what goes here?
}
// ...
In your MouseDown event, determining whether the click has occurred within your rectangle is easy:
if (rect.Contains(e.Location))
{
// the user has clicked inside your rectangle
}
Drawing the "X" on the form is also easy:
Graphics g = this.CreateGraphics();
g.DrawString("X", this.Font, SystemBrushes.WindowText,
(float)e.X, (float)e.Y);
However, the "X" in this case will not be persistent, meaning that if you drag another form over your form and then move it away, the "X" will not be there anymore. To draw a persistent "X", create a form-level Point variable like this:
private Point? _Xlocation = null;
Use your MouseDown event to set this variable if the user clicks in your Rectangle:
if (rect.Contains(e.Location))
{
_Xlocation = e.Location;
this.Invalidate(); // this will fire the Paint event
}
Then, in your form's Paint event, draw the "X":
if (_Xlocation != null)
{
e.Graphics.DrawString("X", this.Font, SystemBrushes.WindowText,
(float)e.X, (float)e.Y);
}
else
{
e.Graphics.Clear(this.BackColor);
}
If you want the "X" to then disappear when the user lets go of the mouse button, just put this code in the MouseUp event:
_Xlocation = null;
this.Invalidate();
You can make this as much more complicated as you like. With this code, the "X" will be drawn just below and to the right of wherever you click on the form. If you want the "X" to be centered on the click location, you can use the Graphics object's MeasureString method to determine how high and how wide the "X" will be, and offset the DrawString location accordingly.
You don't need both a mousedown and mouseup event handler.
Pick one to react to, I tend to react to the MouseDown event instead.
But, when you will want to look at the MouseEventArgs properties and you should be able to determine if you are inside the square.
You will probably want to call:
System.Diagnostics.Debug.WriteLine(...)
using the x and y properties in MouseEventArgs, so you can see where the mouse clicks are, and determine when you are in the square.
Once you are there, then you can draw the X.
You may want to write a function to draw an X and test it by having it draw an X at 300,300 so that you can ensure it looks as you want, while you are experimenting with MouseDown.
Update: I like the Rectangle.contains(location) method demonstrated by MusiGenesis.
public partial class formDemo : Form
{
Rectangle rec;
public formDemo() => InitializeComponent();
private void formDemo_Load(object sender, EventArgs e) =>
rec = new Rectangle(150,100,100,100);
private void frmDemo_Paint(object sender, PaintEventArgs e)
{
var p = new Pen(Color.Blue);
var g = e.Graphics;
g.DrawRectangle(p, rec);
}
private void formDemo_MouseMove(object sender, MouseEventArgs e) =>
Cursor = rec.Contains(e.Location) ? Cursors.Cross : Cursors.Default;
private void formDemo_MouseDown(object sender, MouseEventArgs e)
{
if (rec.Contains(e.Location))
{
// Mouse position adjust for window postion and border size.
// You may have to adjust the borders depending your
// Windows theme
int x = MousePosition.X - this.Left - 4;
int y = MousePosition.Y - this.Top - 29;
var g = this.CreateGraphics();
var p = new Pen(Color.Black);
var p1 = new Point(x - 10, y - 10);
var p2 = new Point(x + 10, y + 10);
var p3 = new Point(x - 10, y + 10);
var p4 = new Point(x + 10, y - 10);
g.DrawLines(p, new Point[] { p1, p2 });
g.DrawLines(p, new Point[] { p3, p4 });
}
}
}

Categories